Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for writing to embedded_io::Write #80

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
86 changes: 57 additions & 29 deletions src/ser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@ 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;

#[cfg(feature = "embedded-io")]
mod write_backend;

/// Serialization result
pub type Result<T> = ::core::result::Result<T, Error>;
Expand All @@ -27,6 +32,9 @@ pub type Result<T> = ::core::result::Result<T, Error>;
pub enum Error {
/// Buffer is full
BufferFull,
/// Some IO error occurred
#[cfg(feature = "embedded-io")]
IOError
}

impl From<()> for Error {
Expand All @@ -49,50 +57,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)
}
}

Expand Down Expand Up @@ -469,9 +458,23 @@ pub fn to_slice<T>(value: &T, buf: &mut [u8]) -> Result<usize>
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(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<T, W>(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(ser.current_length)
Ok(())
}

impl ser::Error for Error {
Expand Down Expand Up @@ -848,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
);
}
}
55 changes: 55 additions & 0 deletions src/ser/ser_backend.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
}
}
32 changes: 32 additions & 0 deletions src/ser/tests/my_writer.rs
Original file line number Diff line number Diff line change
@@ -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<usize, Self::Error> {
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(())
}
}
35 changes: 35 additions & 0 deletions src/ser/write_backend.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
}
Loading