From 985541e999df25d3d9b6357d87c720b956854cce Mon Sep 17 00:00:00 2001 From: Stanislav Ravas Date: Wed, 23 Aug 2023 14:18:46 +0200 Subject: [PATCH 1/2] Serializer: Factor-out slice writing to SerializerBackend trait impl This allows implementing different writing backends. --- src/ser/mod.rs | 42 ++++++++++---------------------- src/ser/ser_backend.rs | 55 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 29 deletions(-) create mode 100644 src/ser/ser_backend.rs diff --git a/src/ser/mod.rs b/src/ser/mod.rs index 0674aa15..30b1062c 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -11,11 +11,13 @@ use heapless::{String, Vec}; use self::map::SerializeMap; use self::seq::SerializeSeq; +use self::ser_backend::{SerializerBackend, SliceSerializer}; use self::struct_::{SerializeStruct, SerializeStructVariant}; mod map; mod seq; mod struct_; +mod ser_backend; /// Serialization result pub type Result = ::core::result::Result; @@ -49,50 +51,31 @@ impl fmt::Display for Error { } } + /// A structure that serializes Rust values as JSON into a buffer. pub struct Serializer<'a> { - buf: &'a mut [u8], - current_length: usize, + backend: &'a mut dyn SerializerBackend } impl<'a> Serializer<'a> { /// Create a new `Serializer` - pub fn new(buf: &'a mut [u8]) -> Self { - Serializer { - buf, - current_length: 0, + pub fn new(backend: &'a mut dyn SerializerBackend) -> Self { + Self { + backend } } /// Return the current amount of serialized data in the buffer pub fn end(&self) -> usize { - self.current_length + self.backend.end() } fn push(&mut self, c: u8) -> Result<()> { - if self.current_length < self.buf.len() { - unsafe { self.push_unchecked(c) }; - Ok(()) - } else { - Err(Error::BufferFull) - } - } - - unsafe fn push_unchecked(&mut self, c: u8) { - self.buf[self.current_length] = c; - self.current_length += 1; + self.backend.push(c) } fn extend_from_slice(&mut self, other: &[u8]) -> Result<()> { - if self.current_length + other.len() > self.buf.len() { - // won't fit in the buf; don't modify anything and return an error - Err(Error::BufferFull) - } else { - for c in other { - unsafe { self.push_unchecked(*c) }; - } - Ok(()) - } + self.backend.extend_from_slice(other) } } @@ -469,9 +452,10 @@ pub fn to_slice(value: &T, buf: &mut [u8]) -> Result where T: ser::Serialize + ?Sized, { - let mut ser = Serializer::new(buf); + let mut backend = SliceSerializer::new(buf); + let mut ser = Serializer::new(&mut backend); value.serialize(&mut ser)?; - Ok(ser.current_length) + Ok(backend.current_length) } impl ser::Error for Error { diff --git a/src/ser/ser_backend.rs b/src/ser/ser_backend.rs new file mode 100644 index 00000000..5cecbed9 --- /dev/null +++ b/src/ser/ser_backend.rs @@ -0,0 +1,55 @@ +use super::{Result, Error}; + +pub trait SerializerBackend { + /// Return the current amount of serialized data in the buffer + fn end(&self) -> usize; + fn push(&mut self, c: u8) -> Result<()>; + fn extend_from_slice(&mut self, other: &[u8]) -> Result<()>; +} + +pub struct SliceSerializer<'a> { + buf: &'a mut [u8], + pub(crate) current_length: usize, +} + +impl<'a> SliceSerializer<'a> { + /// Create a new `Serializer` + pub fn new(buf: &'a mut [u8]) -> Self { + Self { + buf, + current_length: 0, + } + } + + unsafe fn push_unchecked(&mut self, c: u8) { + self.buf[self.current_length] = c; + self.current_length += 1; + } +} + +impl<'a> SerializerBackend for SliceSerializer<'a> { + fn end(&self) -> usize { + self.current_length + } + + fn push(&mut self, c: u8) -> Result<()> { + if self.current_length < self.buf.len() { + unsafe { self.push_unchecked(c) }; + Ok(()) + } else { + Err(Error::BufferFull) + } + } + + fn extend_from_slice(&mut self, other: &[u8]) -> Result<()> { + if self.current_length + other.len() > self.buf.len() { + // won't fit in the buf; don't modify anything and return an error + Err(Error::BufferFull) + } else { + for c in other { + unsafe { self.push_unchecked(*c) }; + } + Ok(()) + } + } +} From bb199dc4cda32a9202151373d6f8047b83042e2f Mon Sep 17 00:00:00 2001 From: Stanislav Ravas Date: Wed, 23 Aug 2023 15:17:46 +0200 Subject: [PATCH 2/2] ser: Add to_writer function to write to impl of embedded_io::Write --- Cargo.toml | 6 ++++++ src/lib.rs | 3 +++ src/ser/mod.rs | 44 ++++++++++++++++++++++++++++++++++++++ src/ser/tests/my_writer.rs | 32 +++++++++++++++++++++++++++ src/ser/write_backend.rs | 35 ++++++++++++++++++++++++++++++ 5 files changed, 120 insertions(+) create mode 100644 src/ser/tests/my_writer.rs create mode 100644 src/ser/write_backend.rs diff --git a/Cargo.toml b/Cargo.toml index a17db23f..6879c84f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,10 +28,16 @@ version = "0.3.5" default-features = false optional = true +[dependencies.embedded-io] +default-features = false +version = "0.6" +optional = true + [dev-dependencies] serde_derive = "1.0.100" [features] default = ["heapless"] +embedded-io = ["dep:embedded-io"] custom-error-messages = ["heapless"] std = ["serde/std"] diff --git a/src/lib.rs b/src/lib.rs index 7f2de3a3..4b9a6da6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,6 +70,9 @@ pub use self::de::{from_slice, from_str}; pub use self::ser::to_slice; #[cfg(feature = "heapless")] pub use self::ser::{to_string, to_vec}; +#[doc(inline)] +#[cfg(feature = "embedded-io")] +pub use self::ser::to_writer; #[cfg(feature = "heapless")] pub use heapless; diff --git a/src/ser/mod.rs b/src/ser/mod.rs index 30b1062c..a65ab07c 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -19,6 +19,9 @@ mod seq; mod struct_; mod ser_backend; +#[cfg(feature = "embedded-io")] +mod write_backend; + /// Serialization result pub type Result = ::core::result::Result; @@ -29,6 +32,9 @@ pub type Result = ::core::result::Result; pub enum Error { /// Buffer is full BufferFull, + /// Some IO error occurred + #[cfg(feature = "embedded-io")] + IOError } impl From<()> for Error { @@ -458,6 +464,19 @@ where Ok(backend.current_length) } +/// Serializes the given data structure as a JSON byte vector into the provided writer +#[cfg(feature = "embedded-io")] +pub fn to_writer(value: &T, writer: &mut W) -> Result<()> +where + T: ser::Serialize + ?Sized, + W: embedded_io::Write +{ + let mut backend = write_backend::WriteSerializer::new(writer); + let mut ser = Serializer::new(&mut backend); + value.serialize(&mut ser)?; + Ok(()) +} + impl ser::Error for Error { fn custom(_msg: T) -> Self where @@ -832,4 +851,29 @@ mod tests { let sd3 = SimpleDecimal(22222.777777); assert_eq!(&*crate::to_string::<_, N>(&sd3).unwrap(), r#"22222.78"#); } + + #[cfg(feature = "embedded-io")] + mod my_writer; + + #[cfg(feature = "embedded-io")] + #[test] + fn to_writer() { + #[derive(Serialize)] + struct Dog<'a> { + name: &'a str, + age: u8 + } + + let dog = Dog { name: "Punto", age: 10 }; + let json1 = crate::to_string::<_, 128>(&dog).unwrap(); + + let mut my_writer = my_writer::MyWriter { buffer: [0; 128], pos: 0, fail: false }; + crate::to_writer(&dog, &mut my_writer).unwrap(); + let json2 = &my_writer.buffer[..my_writer.pos]; + + assert_eq!( + json1.as_bytes(), + json2 + ); + } } diff --git a/src/ser/tests/my_writer.rs b/src/ser/tests/my_writer.rs new file mode 100644 index 00000000..f2e89b0b --- /dev/null +++ b/src/ser/tests/my_writer.rs @@ -0,0 +1,32 @@ +pub struct MyWriter { + pub buffer: [u8; 128], + pub pos: usize, + pub fail: bool +} + +#[derive(Debug)] +pub struct MyWriterError { } + +impl embedded_io::Error for MyWriterError { + fn kind(&self) -> embedded_io::ErrorKind { + embedded_io::ErrorKind::OutOfMemory + } +} + +impl embedded_io::ErrorType for MyWriter { + type Error = MyWriterError; +} + +impl embedded_io::Write for MyWriter { + fn write(&mut self, buf: &[u8]) -> Result { + let av = self.buffer.len() - self.pos; + let wr = core::cmp::min(av, buf.len()); + self.buffer[self.pos..(self.pos + wr)].copy_from_slice(&buf[..wr]); + self.pos = self.pos + wr; + Ok(wr) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} diff --git a/src/ser/write_backend.rs b/src/ser/write_backend.rs new file mode 100644 index 00000000..431eb64b --- /dev/null +++ b/src/ser/write_backend.rs @@ -0,0 +1,35 @@ +use super::{Result, Error, ser_backend::SerializerBackend}; +use embedded_io::{Write, self}; + +pub struct WriteSerializer<'a, W: Write> { + writer: &'a mut W, + current_length: usize, +} + +impl<'a, W: Write> WriteSerializer<'a, W> { + /// Create a new `Serializer` + pub fn new(writer: &'a mut W) -> Self { + Self { + writer, + current_length: 0 + } + } +} + +impl<'a, W: Write> SerializerBackend for WriteSerializer<'a, W> { + fn end(&self) -> usize { + self.current_length + } + + fn push(&mut self, c: u8) -> Result<()> { + self.writer.write_all(&[c; 1]).map_err(|_err| Error::IOError)?; + self.current_length = self.current_length + 1; + Ok(()) + } + + fn extend_from_slice(&mut self, other: &[u8]) -> Result<()> { + self.writer.write_all(other).map_err(|_err| Error::IOError)?; + self.current_length = self.current_length + other.len(); + Ok(()) + } +}