diff --git a/firmware/src/json/error.rs b/firmware/src/json/error.rs new file mode 100644 index 0000000..4defc57 --- /dev/null +++ b/firmware/src/json/error.rs @@ -0,0 +1,42 @@ +use super::value::TryFromValueError; +use core::fmt; + +/// JSON reader/writer error +#[derive(Debug, PartialEq)] +pub enum Error { + Io(E), + Eof, + Unexpected(char), + NumberTooLarge, + InvalidType, +} + +impl From for Error { + fn from(err: E) -> Self { + Self::Io(err) + } +} + +impl From for Error { + fn from(_err: TryFromValueError) -> Self { + Self::InvalidType + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Io(err) => write!(f, "I/O error: {err}"), + Self::Eof => write!(f, "Premature EOF"), + Self::Unexpected(ch) => write!(f, "Unexpected `{ch}`"), + Self::NumberTooLarge => write!(f, "Number too large"), + Self::InvalidType => write!(f, "Invalid type"), + } + } +} + +impl Error { + pub fn unexpected(ch: u8) -> Self { + Self::Unexpected(char::from(ch)) + } +} diff --git a/firmware/src/json/mod.rs b/firmware/src/json/mod.rs new file mode 100644 index 0000000..8e0610f --- /dev/null +++ b/firmware/src/json/mod.rs @@ -0,0 +1,13 @@ +#![allow(unused_imports)] + +mod error; +pub use self::error::Error; + +mod reader; +pub use self::reader::{FromJson, Reader}; + +mod value; +pub use self::value::{TryFromValueError, Value}; + +mod writer; +pub use self::writer::{ObjectWriter, ToJson, Writer}; diff --git a/firmware/src/json.rs b/firmware/src/json/reader.rs similarity index 51% rename from firmware/src/json.rs rename to firmware/src/json/reader.rs index 5c4ad43..e1e9dec 100644 --- a/firmware/src/json.rs +++ b/firmware/src/json/reader.rs @@ -1,157 +1,10 @@ +use super::error::Error; +use super::value::Value; use alloc::boxed::Box; -use alloc::string::{String, ToString}; +use alloc::string::String; use alloc::vec::Vec; -use core::fmt; use core::iter::Extend; -use embedded_io_async::{BufRead, Write}; - -/// JSON value conversion error -#[derive(Debug)] -pub struct TryFromValueError; - -impl fmt::Display for TryFromValueError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "JSON value conversion error") - } -} - -/// JSON value -#[derive(Debug, Clone, PartialEq)] -pub enum Value { - Null, - Boolean(bool), - Number(f64), - String(String), - Array(Vec), - Object(Vec<(String, Value)>), -} - -impl From<()> for Value { - fn from(_value: ()) -> Self { - Self::Null - } -} - -impl From for Value { - fn from(value: bool) -> Self { - Self::Boolean(value) - } -} - -impl From for Value { - fn from(value: f64) -> Self { - Self::Number(value) - } -} - -impl From<&str> for Value { - fn from(value: &str) -> Self { - Self::String(value.to_string()) - } -} - -impl From for Value { - fn from(value: String) -> Self { - Self::String(value) - } -} - -impl From<&[Value]> for Value { - fn from(value: &[Value]) -> Self { - Self::Array(value.into()) - } -} - -impl From> for Value { - fn from(value: Vec) -> Self { - Self::Array(value) - } -} - -impl From<&[(String, Value)]> for Value { - fn from(value: &[(String, Value)]) -> Self { - Self::Object(value.into()) - } -} - -impl From> for Value { - fn from(value: Vec<(String, Value)>) -> Self { - Self::Object(value) - } -} - -impl TryFrom for bool { - type Error = TryFromValueError; - - fn try_from(value: Value) -> Result { - match value { - Value::Boolean(b) => Ok(b), - _ => Err(TryFromValueError), - } - } -} - -impl TryFrom for f64 { - type Error = TryFromValueError; - - fn try_from(value: Value) -> Result { - match value { - Value::Number(n) => Ok(n), - _ => Err(TryFromValueError), - } - } -} - -impl TryFrom for String { - type Error = TryFromValueError; - - fn try_from(value: Value) -> Result { - match value { - Value::String(s) => Ok(s), - _ => Err(TryFromValueError), - } - } -} - -/// JSON reader/writer error -#[derive(Debug, PartialEq)] -pub enum Error { - Io(E), - Eof, - Unexpected(char), - NumberTooLarge, - InvalidType, -} - -impl From for Error { - fn from(err: E) -> Self { - Self::Io(err) - } -} - -impl From for Error { - fn from(_err: TryFromValueError) -> Self { - Self::InvalidType - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Io(err) => write!(f, "I/O error: {err}"), - Self::Eof => write!(f, "Premature EOF"), - Self::Unexpected(ch) => write!(f, "Unexpected `{ch}`"), - Self::NumberTooLarge => write!(f, "Number too large"), - Self::InvalidType => write!(f, "Invalid type"), - } - } -} - -impl Error { - fn unexpected(ch: u8) -> Self { - Self::Unexpected(char::from(ch)) - } -} +use embedded_io_async::BufRead; /// Asynchronous streaming JSON reader /// @@ -541,352 +394,9 @@ impl FromJson for Value { } } -/// Asynchronous streaming JSON writer -/// -/// This JSON writer writes to a wrapped asynchronous byte writer and creates JSON without storing -/// any JSON in memory. -#[derive(Debug)] -pub struct Writer { - writer: W, -} - -impl Writer { - /// Create JSON writer - pub fn new(writer: W) -> Self { - Self { writer } - } - - /// Returns a reference to the inner writer wrapped by this writer - #[allow(dead_code)] - pub fn get_ref(&self) -> &W { - &self.writer - } - - /// Returns a mutable reference to the inner writer wrapped by this writer - #[allow(dead_code)] - pub fn get_mut(&mut self) -> &mut W { - &mut self.writer - } - - /// Consumes the writer, returning its inner writer - #[allow(dead_code)] - pub fn into_inner(self) -> W { - self.writer - } - - /// Write type to JSON - /// Uses the type's `ToJson` implementation to write JSON to this reader. - pub async fn write(&mut self, value: T) -> Result<(), Error> { - value.to_json(self).await - } - - /// Write any JSON value - pub async fn write_any(&mut self, value: &Value) -> Result<(), Error> { - match value { - Value::Object(object) => Box::pin(self.write(object)).await, - Value::Array(array) => Box::pin(self.write(array)).await, - Value::String(string) => self.write(string).await, - Value::Number(number) => self.write(*number).await, - Value::Boolean(boolean) => self.write(*boolean).await, - Value::Null => self.write_null().await, - } - } - - /// Write JSON object - pub async fn write_object(&mut self) -> Result, Error> { - ObjectWriter::new(self).await - } - - /// Write JSON array - pub async fn write_array<'a, T, I>(&mut self, iter: I) -> Result<(), Error> - where - T: ToJson + 'a, - I: IntoIterator, - { - self.writer.write_all(b"[").await?; - for (i, elem) in iter.into_iter().enumerate() { - if i > 0 { - self.writer.write_all(b", ").await?; - } - self.write(elem).await?; - } - self.writer.write_all(b"]").await?; - Ok(()) - } - - /// Write JSON string - pub async fn write_string(&mut self, value: &str) -> Result<(), Error> { - self.writer.write_all(b"\"").await?; - // OPTIMIZE: Writing each char separately to a writer is quite inefficient - for ch in value.escape_default() { - self.writer.write_all(&[ch as u8]).await?; - } - self.writer.write_all(b"\"").await?; - Ok(()) - } - - /// Write JSON number - pub async fn write_number(&mut self, value: f64) -> Result<(), Error> { - let buf = value.to_string(); - self.writer.write_all(buf.as_bytes()).await?; - Ok(()) - } - - /// Write JSON integer - pub async fn write_integer(&mut self, value: i64) -> Result<(), Error> { - let buf = value.to_string(); - self.writer.write_all(buf.as_bytes()).await?; - Ok(()) - } - - /// Write JSON boolean - pub async fn write_boolean(&mut self, value: bool) -> Result<(), Error> { - self.writer - .write_all(if value { b"true" } else { b"false" }) - .await?; - Ok(()) - } - - /// Write JSON null - pub async fn write_null(&mut self) -> Result<(), Error> { - self.writer.write_all(b"null").await?; - Ok(()) - } -} - -/// JSON object writer -pub struct ObjectWriter<'w, W: Write> { - json: &'w mut Writer, - has_fields: bool, -} - -impl<'w, W: Write> ObjectWriter<'w, W> { - /// Start object - pub async fn new(json: &'w mut Writer) -> Result> { - json.writer.write_all(b"{").await?; - Ok(Self { - json, - has_fields: false, - }) - } - - /// Write object field - pub async fn field( - &mut self, - key: &str, - value: T, - ) -> Result<&mut Self, Error> { - if self.has_fields { - self.json.writer.write_all(b", ").await?; - } - self.json.write_string(key).await?; - self.json.writer.write_all(b": ").await?; - self.json.write(value).await?; - self.has_fields = true; - Ok(self) - } - - /// Write object fields from iterable collections - pub async fn fields_from<'a, K, V, I>(&mut self, iter: I) -> Result<&mut Self, Error> - where - K: AsRef + 'a, - V: ToJson + 'a, - I: IntoIterator, - { - for (key, value) in iter { - self.field(key.as_ref(), value).await?; - } - Ok(self) - } - - /// Finish object - pub async fn finish(&mut self) -> Result<(), Error> { - self.json.writer.write_all(b"}").await?; - Ok(()) - } -} - -/// Serialize to streaming JSON -pub trait ToJson { - /// Serialize this type using the given JSON writer - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error>; -} - -impl ToJson for () { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - writer.write_null().await - } -} - -impl ToJson for bool { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - writer.write_boolean(*self).await - } -} - -impl ToJson for u8 { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - writer.write_integer(i64::from(*self)).await - } -} - -impl ToJson for u16 { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - writer.write_integer(i64::from(*self)).await - } -} - -impl ToJson for u32 { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - writer.write_integer(i64::from(*self)).await - } -} - -impl ToJson for u64 { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - writer - .write_integer(i64::try_from(*self).map_err(|_e| Error::NumberTooLarge)?) - .await - } -} - -impl ToJson for i8 { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - writer.write_integer(i64::from(*self)).await - } -} - -impl ToJson for i16 { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - writer.write_integer(i64::from(*self)).await - } -} - -impl ToJson for i32 { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - writer.write_integer(i64::from(*self)).await - } -} - -impl ToJson for i64 { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - writer.write_integer(*self).await - } -} - -impl ToJson for f32 { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - #[allow(clippy::cast_lossless)] - writer.write_number(*self as f64).await - } -} - -impl ToJson for f64 { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - writer.write_number(*self).await - } -} - -impl ToJson for str { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - writer.write_string(self).await - } -} - -impl ToJson for String { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - writer.write_string(self).await - } -} - -impl ToJson for [T] { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - writer.write_array(self).await - } -} - -impl ToJson for Vec { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - writer.write_array(self).await - } -} - -impl ToJson for [(&str, T)] { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - writer - .write_object() - .await? - .fields_from(self) - .await? - .finish() - .await - } -} - -impl ToJson for [(String, T)] { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - writer - .write_object() - .await? - .fields_from(self) - .await? - .finish() - .await - } -} - -impl ToJson for Vec<(&str, T)> { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - writer - .write_object() - .await? - .fields_from(self) - .await? - .finish() - .await - } -} - -impl ToJson for Vec<(String, T)> { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - writer - .write_object() - .await? - .fields_from(self) - .await? - .finish() - .await - } -} - -impl ToJson for Value { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - writer.write_any(self).await - } -} - -impl ToJson for &T { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - (**self).to_json(writer).await - } -} - -impl ToJson for &mut T { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - (**self).to_json(writer).await - } -} - -impl ToJson for Box { - async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { - (**self).to_json(writer).await - } -} - #[cfg(test)] mod tests { use super::*; - use alloc::collections::{LinkedList, VecDeque}; use alloc::vec; fn reader(s: &str) -> Reader<&[u8]> { @@ -1046,159 +556,4 @@ mod tests { assert_read_eq!("1234", read_null, Err(Error::Unexpected('1'))); assert_read_eq!("\"null\"", read_null, Err(Error::Unexpected('"'))); } - - fn writer() -> Writer> { - Writer::new(Vec::new()) - } - - macro_rules! assert_write_eq { - ($method:ident, $($value:expr)?, $json:expr) => {{ - let mut writer = writer(); - let res = writer.$method($($value)?).await; - let json = String::from_utf8(writer.into_inner()).unwrap(); - assert_eq!(res.map(|()| json.as_str()), $json) - }}; - } - - #[async_std::test] - async fn write() { - #[derive(Debug, Default)] - struct Test { - foo: String, - bar: f64, - baz: bool, - } - - impl ToJson for Test { - async fn to_json( - &self, - writer: &mut Writer, - ) -> Result<(), Error> { - writer - .write_object() - .await? - .field("foo", &self.foo) - .await? - .field("bar", self.bar) - .await? - .field("baz", self.baz) - .await? - .finish() - .await - } - } - - assert_write_eq!( - write, - Test { - foo: "hi".into(), - bar: 42.0, - baz: true, - }, - Ok(r#"{"foo": "hi", "bar": 42, "baz": true}"#) - ); - } - - #[async_std::test] - async fn write_any() { - assert_write_eq!(write_any, &Value::Null, Ok("null")); - assert_write_eq!(write_any, &Value::Boolean(false), Ok("false")); - assert_write_eq!(write_any, &Value::Number(123.456), Ok("123.456")); - assert_write_eq!(write_any, &Value::String("hello".into()), Ok("\"hello\"")); - assert_write_eq!( - write_any, - &Value::Array(vec![ - Value::Number(1.0), - Value::Number(2.0), - Value::Number(3.0), - Value::Number(4.0) - ]), - Ok("[1, 2, 3, 4]") - ); - assert_write_eq!( - write_any, - &Value::Object(vec![ - ("foo".into(), Value::String("hi".into())), - ("bar".into(), Value::Number(42.0)), - ("baz".into(), Value::Boolean(true)), - ]), - Ok(r#"{"foo": "hi", "bar": 42, "baz": true}"#) - ); - } - - #[async_std::test] - async fn write_object() { - let mut writer = writer(); - let res = (&mut writer) - .write_object() - .await - .unwrap() - .field("foo", "hi") - .await - .unwrap() - .field("bar", 42) - .await - .unwrap() - .field("baz", true) - .await - .unwrap() - .finish() - .await; - let json = String::from_utf8(writer.into_inner()).unwrap(); - assert_eq!( - res.map(|()| json.as_str()), - Ok(r#"{"foo": "hi", "bar": 42, "baz": true}"#) - ); - } - - #[async_std::test] - async fn write_array() { - assert_write_eq!(write_array, [1, 2, 3, 4], Ok("[1, 2, 3, 4]")); - assert_write_eq!(write_array, &[1, 2, 3, 4], Ok("[1, 2, 3, 4]")); - assert_write_eq!(write_array, vec![1, 2, 3, 4], Ok("[1, 2, 3, 4]")); - assert_write_eq!( - write_array, - LinkedList::from([1, 2, 3, 4]), - Ok("[1, 2, 3, 4]") - ); - assert_write_eq!( - write_array, - VecDeque::from([1, 2, 3, 4]), - Ok("[1, 2, 3, 4]") - ); - } - - #[async_std::test] - async fn write_string() { - assert_write_eq!(write_string, "", Ok("\"\"")); - assert_write_eq!(write_string, "hello", Ok("\"hello\"")); - assert_write_eq!(write_string, "hello \"world\"", Ok(r#""hello \"world\"""#)); - } - - #[async_std::test] - async fn write_number() { - assert_write_eq!(write_number, 0.0, Ok("0")); - assert_write_eq!(write_number, 123.0, Ok("123")); - assert_write_eq!(write_number, -234.0, Ok("-234")); - assert_write_eq!(write_number, 123.456, Ok("123.456")); - assert_write_eq!(write_number, -234.567, Ok("-234.567")); - } - - #[async_std::test] - async fn write_integer() { - assert_write_eq!(write_integer, 0, Ok("0")); - assert_write_eq!(write_integer, 123, Ok("123")); - assert_write_eq!(write_integer, -234, Ok("-234")); - } - - #[async_std::test] - async fn write_boolean() { - assert_write_eq!(write_boolean, false, Ok("false")); - assert_write_eq!(write_boolean, true, Ok("true")); - } - - #[async_std::test] - async fn write_null() { - assert_write_eq!(write_null, , Ok("null")); - } } diff --git a/firmware/src/json/value.rs b/firmware/src/json/value.rs new file mode 100644 index 0000000..2e5e650 --- /dev/null +++ b/firmware/src/json/value.rs @@ -0,0 +1,111 @@ +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use core::fmt; + +/// JSON value conversion error +#[derive(Debug)] +pub struct TryFromValueError; + +impl fmt::Display for TryFromValueError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "JSON value conversion error") + } +} + +/// JSON value +#[derive(Debug, Clone, PartialEq)] +pub enum Value { + Null, + Boolean(bool), + Number(f64), + String(String), + Array(Vec), + Object(Vec<(String, Value)>), +} + +impl From<()> for Value { + fn from(_value: ()) -> Self { + Self::Null + } +} + +impl From for Value { + fn from(value: bool) -> Self { + Self::Boolean(value) + } +} + +impl From for Value { + fn from(value: f64) -> Self { + Self::Number(value) + } +} + +impl From<&str> for Value { + fn from(value: &str) -> Self { + Self::String(value.to_string()) + } +} + +impl From for Value { + fn from(value: String) -> Self { + Self::String(value) + } +} + +impl From<&[Value]> for Value { + fn from(value: &[Value]) -> Self { + Self::Array(value.into()) + } +} + +impl From> for Value { + fn from(value: Vec) -> Self { + Self::Array(value) + } +} + +impl From<&[(String, Value)]> for Value { + fn from(value: &[(String, Value)]) -> Self { + Self::Object(value.into()) + } +} + +impl From> for Value { + fn from(value: Vec<(String, Value)>) -> Self { + Self::Object(value) + } +} + +impl TryFrom for bool { + type Error = TryFromValueError; + + fn try_from(value: Value) -> Result { + match value { + Value::Boolean(b) => Ok(b), + _ => Err(TryFromValueError), + } + } +} + +impl TryFrom for f64 { + type Error = TryFromValueError; + + fn try_from(value: Value) -> Result { + match value { + Value::Number(n) => Ok(n), + _ => Err(TryFromValueError), + } + } +} + +impl TryFrom for String { + type Error = TryFromValueError; + + fn try_from(value: Value) -> Result { + match value { + Value::String(s) => Ok(s), + _ => Err(TryFromValueError), + } + } +} diff --git a/firmware/src/json/writer.rs b/firmware/src/json/writer.rs new file mode 100644 index 0000000..a683b29 --- /dev/null +++ b/firmware/src/json/writer.rs @@ -0,0 +1,511 @@ +use super::error::Error; +use super::value::Value; +use alloc::boxed::Box; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use embedded_io_async::Write; + +/// Asynchronous streaming JSON writer +/// +/// This JSON writer writes to a wrapped asynchronous byte writer and creates JSON without storing +/// any JSON in memory. +#[derive(Debug)] +pub struct Writer { + writer: W, +} + +impl Writer { + /// Create JSON writer + pub fn new(writer: W) -> Self { + Self { writer } + } + + /// Returns a reference to the inner writer wrapped by this writer + #[allow(dead_code)] + pub fn get_ref(&self) -> &W { + &self.writer + } + + /// Returns a mutable reference to the inner writer wrapped by this writer + #[allow(dead_code)] + pub fn get_mut(&mut self) -> &mut W { + &mut self.writer + } + + /// Consumes the writer, returning its inner writer + #[allow(dead_code)] + pub fn into_inner(self) -> W { + self.writer + } + + /// Write type to JSON + /// Uses the type's `ToJson` implementation to write JSON to this reader. + pub async fn write(&mut self, value: T) -> Result<(), Error> { + value.to_json(self).await + } + + /// Write any JSON value + pub async fn write_any(&mut self, value: &Value) -> Result<(), Error> { + match value { + Value::Object(object) => Box::pin(self.write(object)).await, + Value::Array(array) => Box::pin(self.write(array)).await, + Value::String(string) => self.write(string).await, + Value::Number(number) => self.write(*number).await, + Value::Boolean(boolean) => self.write(*boolean).await, + Value::Null => self.write_null().await, + } + } + + /// Write JSON object + pub async fn write_object(&mut self) -> Result, Error> { + ObjectWriter::new(self).await + } + + /// Write JSON array + pub async fn write_array<'a, T, I>(&mut self, iter: I) -> Result<(), Error> + where + T: ToJson + 'a, + I: IntoIterator, + { + self.writer.write_all(b"[").await?; + for (i, elem) in iter.into_iter().enumerate() { + if i > 0 { + self.writer.write_all(b", ").await?; + } + self.write(elem).await?; + } + self.writer.write_all(b"]").await?; + Ok(()) + } + + /// Write JSON string + pub async fn write_string(&mut self, value: &str) -> Result<(), Error> { + self.writer.write_all(b"\"").await?; + // OPTIMIZE: Writing each char separately to a writer is quite inefficient + for ch in value.escape_default() { + self.writer.write_all(&[ch as u8]).await?; + } + self.writer.write_all(b"\"").await?; + Ok(()) + } + + /// Write JSON number + pub async fn write_number(&mut self, value: f64) -> Result<(), Error> { + let buf = value.to_string(); + self.writer.write_all(buf.as_bytes()).await?; + Ok(()) + } + + /// Write JSON integer + pub async fn write_integer(&mut self, value: i64) -> Result<(), Error> { + let buf = value.to_string(); + self.writer.write_all(buf.as_bytes()).await?; + Ok(()) + } + + /// Write JSON boolean + pub async fn write_boolean(&mut self, value: bool) -> Result<(), Error> { + self.writer + .write_all(if value { b"true" } else { b"false" }) + .await?; + Ok(()) + } + + /// Write JSON null + pub async fn write_null(&mut self) -> Result<(), Error> { + self.writer.write_all(b"null").await?; + Ok(()) + } +} + +/// JSON object writer +#[allow(clippy::module_name_repetitions)] +pub struct ObjectWriter<'w, W: Write> { + json: &'w mut Writer, + has_fields: bool, +} + +impl<'w, W: Write> ObjectWriter<'w, W> { + /// Start object + pub async fn new(json: &'w mut Writer) -> Result> { + json.writer.write_all(b"{").await?; + Ok(Self { + json, + has_fields: false, + }) + } + + /// Write object field + pub async fn field( + &mut self, + key: &str, + value: T, + ) -> Result<&mut Self, Error> { + if self.has_fields { + self.json.writer.write_all(b", ").await?; + } + self.json.write_string(key).await?; + self.json.writer.write_all(b": ").await?; + self.json.write(value).await?; + self.has_fields = true; + Ok(self) + } + + /// Write object fields from iterable collections + pub async fn fields_from<'a, K, V, I>(&mut self, iter: I) -> Result<&mut Self, Error> + where + K: AsRef + 'a, + V: ToJson + 'a, + I: IntoIterator, + { + for (key, value) in iter { + self.field(key.as_ref(), value).await?; + } + Ok(self) + } + + /// Finish object + pub async fn finish(&mut self) -> Result<(), Error> { + self.json.writer.write_all(b"}").await?; + Ok(()) + } +} + +/// Serialize to streaming JSON +pub trait ToJson { + /// Serialize this type using the given JSON writer + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error>; +} + +impl ToJson for () { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + writer.write_null().await + } +} + +impl ToJson for bool { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + writer.write_boolean(*self).await + } +} + +impl ToJson for u8 { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + writer.write_integer(i64::from(*self)).await + } +} + +impl ToJson for u16 { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + writer.write_integer(i64::from(*self)).await + } +} + +impl ToJson for u32 { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + writer.write_integer(i64::from(*self)).await + } +} + +impl ToJson for u64 { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + writer + .write_integer(i64::try_from(*self).map_err(|_e| Error::NumberTooLarge)?) + .await + } +} + +impl ToJson for i8 { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + writer.write_integer(i64::from(*self)).await + } +} + +impl ToJson for i16 { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + writer.write_integer(i64::from(*self)).await + } +} + +impl ToJson for i32 { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + writer.write_integer(i64::from(*self)).await + } +} + +impl ToJson for i64 { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + writer.write_integer(*self).await + } +} + +impl ToJson for f32 { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + #[allow(clippy::cast_lossless)] + writer.write_number(*self as f64).await + } +} + +impl ToJson for f64 { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + writer.write_number(*self).await + } +} + +impl ToJson for str { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + writer.write_string(self).await + } +} + +impl ToJson for String { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + writer.write_string(self).await + } +} + +impl ToJson for [T] { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + writer.write_array(self).await + } +} + +impl ToJson for Vec { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + writer.write_array(self).await + } +} + +impl ToJson for [(&str, T)] { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + writer + .write_object() + .await? + .fields_from(self) + .await? + .finish() + .await + } +} + +impl ToJson for [(String, T)] { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + writer + .write_object() + .await? + .fields_from(self) + .await? + .finish() + .await + } +} + +impl ToJson for Vec<(&str, T)> { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + writer + .write_object() + .await? + .fields_from(self) + .await? + .finish() + .await + } +} + +impl ToJson for Vec<(String, T)> { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + writer + .write_object() + .await? + .fields_from(self) + .await? + .finish() + .await + } +} + +impl ToJson for Value { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + writer.write_any(self).await + } +} + +impl ToJson for &T { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + (**self).to_json(writer).await + } +} + +impl ToJson for &mut T { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + (**self).to_json(writer).await + } +} + +impl ToJson for Box { + async fn to_json(&self, writer: &mut Writer) -> Result<(), Error> { + (**self).to_json(writer).await + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloc::collections::{LinkedList, VecDeque}; + use alloc::vec; + + fn writer() -> Writer> { + Writer::new(Vec::new()) + } + + macro_rules! assert_write_eq { + ($method:ident, $($value:expr)?, $json:expr) => {{ + let mut writer = writer(); + let res = writer.$method($($value)?).await; + let json = String::from_utf8(writer.into_inner()).unwrap(); + assert_eq!(res.map(|()| json.as_str()), $json) + }}; + } + + #[async_std::test] + async fn write() { + #[derive(Debug, Default)] + struct Test { + foo: String, + bar: f64, + baz: bool, + } + + impl ToJson for Test { + async fn to_json( + &self, + writer: &mut Writer, + ) -> Result<(), Error> { + writer + .write_object() + .await? + .field("foo", &self.foo) + .await? + .field("bar", self.bar) + .await? + .field("baz", self.baz) + .await? + .finish() + .await + } + } + + assert_write_eq!( + write, + Test { + foo: "hi".into(), + bar: 42.0, + baz: true, + }, + Ok(r#"{"foo": "hi", "bar": 42, "baz": true}"#) + ); + } + + #[async_std::test] + async fn write_any() { + assert_write_eq!(write_any, &Value::Null, Ok("null")); + assert_write_eq!(write_any, &Value::Boolean(false), Ok("false")); + assert_write_eq!(write_any, &Value::Number(123.456), Ok("123.456")); + assert_write_eq!(write_any, &Value::String("hello".into()), Ok("\"hello\"")); + assert_write_eq!( + write_any, + &Value::Array(vec![ + Value::Number(1.0), + Value::Number(2.0), + Value::Number(3.0), + Value::Number(4.0) + ]), + Ok("[1, 2, 3, 4]") + ); + assert_write_eq!( + write_any, + &Value::Object(vec![ + ("foo".into(), Value::String("hi".into())), + ("bar".into(), Value::Number(42.0)), + ("baz".into(), Value::Boolean(true)), + ]), + Ok(r#"{"foo": "hi", "bar": 42, "baz": true}"#) + ); + } + + #[async_std::test] + async fn write_object() { + let mut writer = writer(); + let res = (&mut writer) + .write_object() + .await + .unwrap() + .field("foo", "hi") + .await + .unwrap() + .field("bar", 42) + .await + .unwrap() + .field("baz", true) + .await + .unwrap() + .finish() + .await; + let json = String::from_utf8(writer.into_inner()).unwrap(); + assert_eq!( + res.map(|()| json.as_str()), + Ok(r#"{"foo": "hi", "bar": 42, "baz": true}"#) + ); + } + + #[async_std::test] + async fn write_array() { + assert_write_eq!(write_array, [1, 2, 3, 4], Ok("[1, 2, 3, 4]")); + assert_write_eq!(write_array, &[1, 2, 3, 4], Ok("[1, 2, 3, 4]")); + assert_write_eq!(write_array, vec![1, 2, 3, 4], Ok("[1, 2, 3, 4]")); + assert_write_eq!( + write_array, + LinkedList::from([1, 2, 3, 4]), + Ok("[1, 2, 3, 4]") + ); + assert_write_eq!( + write_array, + VecDeque::from([1, 2, 3, 4]), + Ok("[1, 2, 3, 4]") + ); + } + + #[async_std::test] + async fn write_string() { + assert_write_eq!(write_string, "", Ok("\"\"")); + assert_write_eq!(write_string, "hello", Ok("\"hello\"")); + assert_write_eq!(write_string, "hello \"world\"", Ok(r#""hello \"world\"""#)); + } + + #[async_std::test] + async fn write_number() { + assert_write_eq!(write_number, 0.0, Ok("0")); + assert_write_eq!(write_number, 123.0, Ok("123")); + assert_write_eq!(write_number, -234.0, Ok("-234")); + assert_write_eq!(write_number, 123.456, Ok("123.456")); + assert_write_eq!(write_number, -234.567, Ok("-234.567")); + } + + #[async_std::test] + async fn write_integer() { + assert_write_eq!(write_integer, 0, Ok("0")); + assert_write_eq!(write_integer, 123, Ok("123")); + assert_write_eq!(write_integer, -234, Ok("-234")); + } + + #[async_std::test] + async fn write_boolean() { + assert_write_eq!(write_boolean, false, Ok("false")); + assert_write_eq!(write_boolean, true, Ok("true")); + } + + #[async_std::test] + async fn write_null() { + assert_write_eq!(write_null, , Ok("null")); + } +}