diff --git a/.config/hakari.toml b/.config/hakari.toml index 3b6d9d8822..9991cd92b0 100644 --- a/.config/hakari.toml +++ b/.config/hakari.toml @@ -33,6 +33,7 @@ workspace-members = [ "compute_api", "consumption_metrics", "desim", + "json", "metrics", "pageserver_api", "postgres_backend", diff --git a/Cargo.lock b/Cargo.lock index 0d4dc10149..237defaec3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3489,6 +3489,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json" +version = "0.1.0" +dependencies = [ + "futures", + "itoa", + "ryu", +] + [[package]] name = "json-structural-diff" version = "0.2.0" @@ -8693,8 +8702,10 @@ dependencies = [ "fail", "form_urlencoded", "futures-channel", + "futures-core", "futures-executor", "futures-io", + "futures-sink", "futures-util", "generic-array", "getrandom 0.2.11", diff --git a/Cargo.toml b/Cargo.toml index 68016a08a9..840e3c6036 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ members = [ "libs/walproposer", "libs/wal_decoder", "libs/postgres_initdb", + "libs/proxy/json", "libs/proxy/postgres-protocol2", "libs/proxy/postgres-types2", "libs/proxy/tokio-postgres2", diff --git a/libs/proxy/json/Cargo.toml b/libs/proxy/json/Cargo.toml new file mode 100644 index 0000000000..2f163c141d --- /dev/null +++ b/libs/proxy/json/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "json" +version = "0.1.0" +edition.workspace = true +license.workspace = true + +[dependencies] +ryu = "1" +itoa = "1" + +[dev-dependencies] +futures = "0.3" diff --git a/libs/proxy/json/src/lib.rs b/libs/proxy/json/src/lib.rs new file mode 100644 index 0000000000..a8b2e6b509 --- /dev/null +++ b/libs/proxy/json/src/lib.rs @@ -0,0 +1,412 @@ +//! A JSON serialization lib, designed for more flexibility than `serde_json` offers. +//! +//! Features: +//! +//! ## Dynamic construction +//! +//! Sometimes you have dynamic values you want to serialize, that are not already in a serde-aware model like a struct or a Vec etc. +//! To achieve this with serde, you need to implement a lot of different traits on a lot of different new-types. +//! Because of this, it's often easier to give-in and pull all the data into a serde-aware model (`serde_json::Value` or some intermediate struct), +//! but that is often not very efficient. +//! +//! This crate allows full control over the JSON encoding without needing to implement any extra traits. Just call the +//! relevant functions, and it will guarantee a correctly encoded JSON value. +//! +//! ## Async construction +//! +//! Similar to the above, sometimes the values arrive asynchronously. Often collecting those values in memory +//! is more expensive than writing them as JSON, since the overheads of `Vec` and `String` is much higher, however +//! there are exceptions. +//! +//! Serializing to JSON all in one go is also more CPU intensive and can cause lag spikes, +//! whereas serializing values incrementally spreads out the CPU load and reduces lag. +//! +//! ## Examples +//! +//! To represent the following JSON as a compact string +//! +//! ```json +//! { +//! "results": { +//! "rows": [ +//! { +//! "id": 1, +//! "value": null +//! }, +//! { +//! "id": 2, +//! "value": "hello" +//! } +//! ] +//! } +//! } +//! ``` +//! +//! We can use the following code: +//! +//! ``` +//! // create the outer object +//! let s = json::value_to_string!(|v| json::value_as_object!(|v| { +//! // create an entry with key "results" and start an object value associated with it. +//! let results = v.key("results"); +//! json::value_as_object!(|results| { +//! // create an entry with key "rows" and start an list value associated with it. +//! let rows = results.key("rows"); +//! json::value_as_list!(|rows| { +//! // create a list entry and start an object value associated with it. +//! let row = rows.entry(); +//! json::value_as_object!(|row| { +//! // add entry "id": 1 +//! row.entry("id", 1); +//! // add entry "value": null +//! row.entry("value", json::Null); +//! }); +//! +//! // create a list entry and start an object value associated with it. +//! let row = rows.entry(); +//! json::value_as_object!(|row| { +//! // add entry "id": 2 +//! row.entry("id", 2); +//! // add entry "value": "hello" +//! row.entry("value", "hello"); +//! }); +//! }); +//! }); +//! })); +//! +//! assert_eq!(s, r#"{"results":{"rows":[{"id":1,"value":null},{"id":2,"value":"hello"}]}}"#); +//! ``` + +mod macros; +mod str; +mod value; + +pub use value::{Null, ValueEncoder}; + +#[must_use] +/// Serialize a single json value. +pub struct ValueSer<'buf> { + buf: &'buf mut Vec, + start: usize, +} + +impl<'buf> ValueSer<'buf> { + /// Create a new json value serializer. + pub fn new(buf: &'buf mut Vec) -> Self { + Self { buf, start: 0 } + } + + /// Borrow the underlying buffer + pub fn as_buffer(&self) -> &[u8] { + self.buf + } + + #[inline] + pub fn value(self, e: impl ValueEncoder) { + e.encode(self); + } + + /// Write raw bytes to the buf. This must be already JSON encoded. + #[inline] + pub fn write_raw_json(self, data: &[u8]) { + self.buf.extend_from_slice(data); + self.finish(); + } + + /// Start a new object serializer. + #[inline] + pub fn object(self) -> ObjectSer<'buf> { + ObjectSer::new(self) + } + + /// Start a new list serializer. + #[inline] + pub fn list(self) -> ListSer<'buf> { + ListSer::new(self) + } + + /// Finish the value ser. + #[inline] + fn finish(self) { + // don't trigger the drop handler which triggers a rollback. + // this won't cause memory leaks because `ValueSet` owns no allocations. + std::mem::forget(self); + } +} + +impl Drop for ValueSer<'_> { + fn drop(&mut self) { + self.buf.truncate(self.start); + } +} + +#[must_use] +/// Serialize a json object. +pub struct ObjectSer<'buf> { + value: ValueSer<'buf>, + start: usize, +} + +impl<'buf> ObjectSer<'buf> { + /// Start a new object serializer. + #[inline] + pub fn new(value: ValueSer<'buf>) -> Self { + value.buf.push(b'{'); + let start = value.buf.len(); + Self { value, start } + } + + /// Borrow the underlying buffer + pub fn as_buffer(&self) -> &[u8] { + self.value.as_buffer() + } + + /// Start a new object entry with the given string key, returning a [`ValueSer`] for the associated value. + #[inline] + pub fn key(&mut self, key: impl KeyEncoder) -> ValueSer<'_> { + key.write_key(self) + } + + /// Write an entry (key-value pair) to the object. + #[inline] + pub fn entry(&mut self, key: impl KeyEncoder, val: impl ValueEncoder) { + self.key(key).value(val); + } + + #[inline] + fn entry_inner(&mut self, f: impl FnOnce(&mut Vec)) -> ValueSer<'_> { + // track before the separator so we the value is rolled back it also removes the separator. + let start = self.value.buf.len(); + + // push separator if necessary + if self.value.buf.len() > self.start { + self.value.buf.push(b','); + } + // push key + f(self.value.buf); + // push value separator + self.value.buf.push(b':'); + + // return value writer. + ValueSer { + buf: self.value.buf, + start, + } + } + + /// Reset the buffer back to before this object was started. + #[inline] + pub fn rollback(self) -> ValueSer<'buf> { + // Do not fully reset the value, only reset it to before the `{`. + // This ensures any `,` before this value are not clobbered. + self.value.buf.truncate(self.start - 1); + self.value + } + + /// Finish the object ser. + #[inline] + pub fn finish(self) { + self.value.buf.push(b'}'); + self.value.finish(); + } +} + +pub trait KeyEncoder { + fn write_key<'a>(self, obj: &'a mut ObjectSer) -> ValueSer<'a>; +} + +#[must_use] +/// Serialize a json object. +pub struct ListSer<'buf> { + value: ValueSer<'buf>, + start: usize, +} + +impl<'buf> ListSer<'buf> { + /// Start a new list serializer. + #[inline] + pub fn new(value: ValueSer<'buf>) -> Self { + value.buf.push(b'['); + let start = value.buf.len(); + Self { value, start } + } + + /// Borrow the underlying buffer + pub fn as_buffer(&self) -> &[u8] { + self.value.as_buffer() + } + + /// Write an value to the list. + #[inline] + pub fn push(&mut self, val: impl ValueEncoder) { + self.entry().value(val); + } + + /// Start a new value entry in this list. + #[inline] + pub fn entry(&mut self) -> ValueSer<'_> { + // track before the separator so we the value is rolled back it also removes the separator. + let start = self.value.buf.len(); + + // push separator if necessary + if self.value.buf.len() > self.start { + self.value.buf.push(b','); + } + + // return value writer. + ValueSer { + buf: self.value.buf, + start, + } + } + + /// Reset the buffer back to before this object was started. + #[inline] + pub fn rollback(self) -> ValueSer<'buf> { + // Do not fully reset the value, only reset it to before the `[`. + // This ensures any `,` before this value are not clobbered. + self.value.buf.truncate(self.start - 1); + self.value + } + + /// Finish the object ser. + #[inline] + pub fn finish(self) { + self.value.buf.push(b']'); + self.value.finish(); + } +} + +#[cfg(test)] +mod tests { + use crate::{Null, ValueSer}; + + #[test] + fn object() { + let mut buf = vec![]; + let mut object = ValueSer::new(&mut buf).object(); + object.entry("foo", "bar"); + object.entry("baz", Null); + object.finish(); + + assert_eq!(buf, br#"{"foo":"bar","baz":null}"#); + } + + #[test] + fn list() { + let mut buf = vec![]; + let mut list = ValueSer::new(&mut buf).list(); + list.entry().value("bar"); + list.entry().value(Null); + list.finish(); + + assert_eq!(buf, br#"["bar",null]"#); + } + + #[test] + fn object_macro() { + let res = crate::value_to_string!(|obj| { + crate::value_as_object!(|obj| { + obj.entry("foo", "bar"); + obj.entry("baz", Null); + }) + }); + + assert_eq!(res, r#"{"foo":"bar","baz":null}"#); + } + + #[test] + fn list_macro() { + let res = crate::value_to_string!(|list| { + crate::value_as_list!(|list| { + list.entry().value("bar"); + list.entry().value(Null); + }) + }); + + assert_eq!(res, r#"["bar",null]"#); + } + + #[test] + fn rollback_on_drop() { + let res = crate::value_to_string!(|list| { + crate::value_as_list!(|list| { + list.entry().value("bar"); + + 'cancel: { + let nested_list = list.entry(); + crate::value_as_list!(|nested_list| { + nested_list.entry().value(1); + + assert_eq!(nested_list.as_buffer(), br#"["bar",[1"#); + if true { + break 'cancel; + } + }) + } + + assert_eq!(list.as_buffer(), br#"["bar""#); + + list.entry().value(Null); + }) + }); + + assert_eq!(res, r#"["bar",null]"#); + } + + #[test] + fn rollback_object() { + let res = crate::value_to_string!(|obj| { + crate::value_as_object!(|obj| { + let entry = obj.key("1"); + entry.value(1_i32); + + let entry = obj.key("2"); + let entry = { + let mut nested_obj = entry.object(); + nested_obj.entry("foo", "bar"); + nested_obj.rollback() + }; + + entry.value(2_i32); + }) + }); + + assert_eq!(res, r#"{"1":1,"2":2}"#); + } + + #[test] + fn rollback_list() { + let res = crate::value_to_string!(|list| { + crate::value_as_list!(|list| { + let entry = list.entry(); + entry.value(1_i32); + + let entry = list.entry(); + let entry = { + let mut nested_list = entry.list(); + nested_list.push("foo"); + nested_list.rollback() + }; + + entry.value(2_i32); + }) + }); + + assert_eq!(res, r#"[1,2]"#); + } + + #[test] + fn string_escaping() { + let mut buf = vec![]; + let mut object = ValueSer::new(&mut buf).object(); + + let key = "hello"; + let value = "\n world"; + + object.entry(format_args!("{key:?}"), value); + object.finish(); + + assert_eq!(buf, br#"{"\"hello\"":"\n world"}"#); + } +} diff --git a/libs/proxy/json/src/macros.rs b/libs/proxy/json/src/macros.rs new file mode 100644 index 0000000000..d3b5cfed10 --- /dev/null +++ b/libs/proxy/json/src/macros.rs @@ -0,0 +1,86 @@ +//! # Examples +//! +//! ``` +//! use futures::{StreamExt, TryStream, TryStreamExt}; +//! +//! async fn stream_to_json_list(mut s: S) -> Result +//! where +//! S: TryStream + Unpin, +//! T: json::ValueEncoder +//! { +//! Ok(json::value_to_string!(|val| json::value_as_list!(|val| { +//! // note how we can use `.await` and `?` in here. +//! while let Some(value) = s.try_next().await? { +//! val.push(value); +//! } +//! }))) +//! } +//! +//! let stream = futures::stream::iter([1, 2, 3]).map(Ok::); +//! let json_string = futures::executor::block_on(stream_to_json_list(stream)).unwrap(); +//! assert_eq!(json_string, "[1,2,3]"); +//! ``` + +/// A helper to create a new JSON vec. +/// +/// Implemented as a macro to preserve all control flow. +#[macro_export] +macro_rules! value_to_vec { + (|$val:ident| $body:expr) => {{ + let mut buf = vec![]; + let $val = $crate::ValueSer::new(&mut buf); + let _: () = $body; + buf + }}; +} + +/// A helper to create a new JSON string. +/// +/// Implemented as a macro to preserve all control flow. +#[macro_export] +macro_rules! value_to_string { + (|$val:ident| $body:expr) => {{ + ::std::string::String::from_utf8($crate::value_to_vec!(|$val| $body)) + .expect("json should be valid utf8") + }}; +} + +/// A helper that ensures the [`ObjectSer::finish`](crate::ObjectSer::finish) method is called on completion. +/// +/// Consumes `$val` and assigns it as an [`ObjectSer`](crate::ObjectSer) serializer. +/// The serializer is only 'finished' if the body completes. +/// The serializer is rolled back if `break`/`return` escapes the body. +/// +/// Implemented as a macro to preserve all control flow. +#[macro_export] +macro_rules! value_as_object { + (|$val:ident| $body:expr) => {{ + let mut obj = $crate::ObjectSer::new($val); + + let $val = &mut obj; + let res = $body; + + obj.finish(); + res + }}; +} + +/// A helper that ensures the [`ListSer::finish`](crate::ListSer::finish) method is called on completion. +/// +/// Consumes `$val` and assigns it as an [`ListSer`](crate::ListSer) serializer. +/// The serializer is only 'finished' if the body completes. +/// The serializer is rolled back if `break`/`return` escapes the body. +/// +/// Implemented as a macro to preserve all control flow. +#[macro_export] +macro_rules! value_as_list { + (|$val:ident| $body:expr) => {{ + let mut list = $crate::ListSer::new($val); + + let $val = &mut list; + let res = $body; + + list.finish(); + res + }}; +} diff --git a/libs/proxy/json/src/str.rs b/libs/proxy/json/src/str.rs new file mode 100644 index 0000000000..b092fd50ec --- /dev/null +++ b/libs/proxy/json/src/str.rs @@ -0,0 +1,166 @@ +//! Helpers for serializing escaped strings. +//! +//! ## License +//! +//! +//! +//! Licensed by David Tolnay under MIT or Apache-2.0. +//! +//! With modifications by Conrad Ludgate on behalf of Databricks. + +use std::fmt::{self, Write}; + +/// Represents a character escape code in a type-safe manner. +pub enum CharEscape { + /// An escaped quote `"` + Quote, + /// An escaped reverse solidus `\` + ReverseSolidus, + // /// An escaped solidus `/` + // Solidus, + /// An escaped backspace character (usually escaped as `\b`) + Backspace, + /// An escaped form feed character (usually escaped as `\f`) + FormFeed, + /// An escaped line feed character (usually escaped as `\n`) + LineFeed, + /// An escaped carriage return character (usually escaped as `\r`) + CarriageReturn, + /// An escaped tab character (usually escaped as `\t`) + Tab, + /// An escaped ASCII plane control character (usually escaped as + /// `\u00XX` where `XX` are two hex characters) + AsciiControl(u8), +} + +impl CharEscape { + #[inline] + fn from_escape_table(escape: u8, byte: u8) -> CharEscape { + match escape { + self::BB => CharEscape::Backspace, + self::TT => CharEscape::Tab, + self::NN => CharEscape::LineFeed, + self::FF => CharEscape::FormFeed, + self::RR => CharEscape::CarriageReturn, + self::QU => CharEscape::Quote, + self::BS => CharEscape::ReverseSolidus, + self::UU => CharEscape::AsciiControl(byte), + _ => unreachable!(), + } + } +} + +pub(crate) fn format_escaped_str(writer: &mut Vec, value: &str) { + writer.reserve(2 + value.len()); + + writer.push(b'"'); + + let rest = format_escaped_str_contents(writer, value); + writer.extend_from_slice(rest); + + writer.push(b'"'); +} + +pub(crate) fn format_escaped_fmt(writer: &mut Vec, args: fmt::Arguments) { + writer.push(b'"'); + + Collect { buf: writer } + .write_fmt(args) + .expect("formatting should not error"); + + writer.push(b'"'); +} + +struct Collect<'buf> { + buf: &'buf mut Vec, +} + +impl fmt::Write for Collect<'_> { + fn write_str(&mut self, s: &str) -> fmt::Result { + let last = format_escaped_str_contents(self.buf, s); + self.buf.extend(last); + Ok(()) + } +} + +// writes any escape sequences, and returns the suffix still needed to be written. +fn format_escaped_str_contents<'a>(writer: &mut Vec, value: &'a str) -> &'a [u8] { + let bytes = value.as_bytes(); + + let mut start = 0; + + for (i, &byte) in bytes.iter().enumerate() { + let escape = ESCAPE[byte as usize]; + if escape == 0 { + continue; + } + + writer.extend_from_slice(&bytes[start..i]); + + let char_escape = CharEscape::from_escape_table(escape, byte); + write_char_escape(writer, char_escape); + + start = i + 1; + } + + &bytes[start..] +} + +const BB: u8 = b'b'; // \x08 +const TT: u8 = b't'; // \x09 +const NN: u8 = b'n'; // \x0A +const FF: u8 = b'f'; // \x0C +const RR: u8 = b'r'; // \x0D +const QU: u8 = b'"'; // \x22 +const BS: u8 = b'\\'; // \x5C +const UU: u8 = b'u'; // \x00...\x1F except the ones above +const __: u8 = 0; + +// Lookup table of escape sequences. A value of b'x' at index i means that byte +// i is escaped as "\x" in JSON. A value of 0 means that byte i is not escaped. +static ESCAPE: [u8; 256] = [ + // 1 2 3 4 5 6 7 8 9 A B C D E F + UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, // 0 + UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, // 1 + __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4 + __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F +]; + +fn write_char_escape(writer: &mut Vec, char_escape: CharEscape) { + let s = match char_escape { + CharEscape::Quote => b"\\\"", + CharEscape::ReverseSolidus => b"\\\\", + // CharEscape::Solidus => b"\\/", + CharEscape::Backspace => b"\\b", + CharEscape::FormFeed => b"\\f", + CharEscape::LineFeed => b"\\n", + CharEscape::CarriageReturn => b"\\r", + CharEscape::Tab => b"\\t", + CharEscape::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.extend_from_slice(bytes); + } + }; + + writer.extend_from_slice(s); +} diff --git a/libs/proxy/json/src/value.rs b/libs/proxy/json/src/value.rs new file mode 100644 index 0000000000..705af9603e --- /dev/null +++ b/libs/proxy/json/src/value.rs @@ -0,0 +1,168 @@ +use core::fmt; +use std::collections::{BTreeMap, HashMap}; + +use crate::str::{format_escaped_fmt, format_escaped_str}; +use crate::{KeyEncoder, ObjectSer, ValueSer, value_as_list, value_as_object}; + +/// Write a value to the underlying json representation. +pub trait ValueEncoder { + fn encode(self, v: ValueSer<'_>); +} + +pub(crate) fn write_int(x: impl itoa::Integer, b: &mut Vec) { + b.extend_from_slice(itoa::Buffer::new().format(x).as_bytes()); +} + +pub(crate) fn write_float(x: impl ryu::Float, b: &mut Vec) { + b.extend_from_slice(ryu::Buffer::new().format(x).as_bytes()); +} + +impl ValueEncoder for &T { + #[inline] + fn encode(self, v: ValueSer<'_>) { + T::encode(*self, v); + } +} + +impl ValueEncoder for &str { + #[inline] + fn encode(self, v: ValueSer<'_>) { + format_escaped_str(v.buf, self); + v.finish(); + } +} + +impl ValueEncoder for fmt::Arguments<'_> { + #[inline] + fn encode(self, v: ValueSer<'_>) { + if let Some(s) = self.as_str() { + format_escaped_str(v.buf, s); + } else { + format_escaped_fmt(v.buf, self); + } + v.finish(); + } +} + +macro_rules! int { + [$($t:ty),*] => { + $( + impl ValueEncoder for $t { + #[inline] + fn encode(self, v: ValueSer<'_>) { + write_int(self, v.buf); + v.finish(); + } + } + )* + }; +} + +int![u8, u16, u32, u64, usize, u128]; +int![i8, i16, i32, i64, isize, i128]; + +macro_rules! float { + [$($t:ty),*] => { + $( + impl ValueEncoder for $t { + #[inline] + fn encode(self, v: ValueSer<'_>) { + write_float(self, v.buf); + v.finish(); + } + } + )* + }; +} + +float![f32, f64]; + +impl ValueEncoder for bool { + #[inline] + fn encode(self, v: ValueSer<'_>) { + v.write_raw_json(if self { b"true" } else { b"false" }); + } +} + +impl ValueEncoder for Option { + #[inline] + fn encode(self, v: ValueSer<'_>) { + match self { + Some(value) => value.encode(v), + None => Null.encode(v), + } + } +} + +impl KeyEncoder for &str { + #[inline] + fn write_key<'a>(self, obj: &'a mut ObjectSer) -> ValueSer<'a> { + let obj = &mut *obj; + obj.entry_inner(|b| format_escaped_str(b, self)) + } +} + +impl KeyEncoder for fmt::Arguments<'_> { + #[inline] + fn write_key<'a>(self, obj: &'a mut ObjectSer) -> ValueSer<'a> { + if let Some(key) = self.as_str() { + obj.entry_inner(|b| format_escaped_str(b, key)) + } else { + obj.entry_inner(|b| format_escaped_fmt(b, self)) + } + } +} + +/// Represents the JSON null value. +pub struct Null; + +impl ValueEncoder for Null { + #[inline] + fn encode(self, v: ValueSer<'_>) { + v.write_raw_json(b"null"); + } +} + +impl ValueEncoder for Vec { + #[inline] + fn encode(self, v: ValueSer<'_>) { + value_as_list!(|v| { + for t in self { + v.entry().value(t); + } + }); + } +} + +impl ValueEncoder for &[T] { + #[inline] + fn encode(self, v: ValueSer<'_>) { + value_as_list!(|v| { + for t in self { + v.entry().value(t); + } + }); + } +} + +impl ValueEncoder for HashMap { + #[inline] + fn encode(self, o: ValueSer<'_>) { + value_as_object!(|o| { + for (k, v) in self { + o.entry(k, v); + } + }); + } +} + +impl ValueEncoder for BTreeMap { + #[inline] + fn encode(self, o: ValueSer<'_>) { + value_as_object!(|o| { + for (k, v) in self { + o.entry(k, v); + } + }); + } +} diff --git a/workspace_hack/Cargo.toml b/workspace_hack/Cargo.toml index fb10e27d2a..fc01deb92d 100644 --- a/workspace_hack/Cargo.toml +++ b/workspace_hack/Cargo.toml @@ -40,8 +40,10 @@ env_logger = { version = "0.11" } fail = { version = "0.5", default-features = false, features = ["failpoints"] } form_urlencoded = { version = "1" } futures-channel = { version = "0.3", features = ["sink"] } +futures-core = { version = "0.3" } futures-executor = { version = "0.3" } futures-io = { version = "0.3" } +futures-sink = { version = "0.3" } futures-util = { version = "0.3", features = ["channel", "io", "sink"] } generic-array = { version = "0.14", default-features = false, features = ["more_lengths", "zeroize"] } getrandom = { version = "0.2", default-features = false, features = ["std"] }