~tdeo/serde_bare

5 2

[PATCH serde_bare] Add support for using serde_bare in no_std + alloc crates

Antoine van Gelder <antoine@ockam.io>
Details
Message ID
<CAOHqsxX93fa4j35hvmvkuN8REE+Df6p3V-DUeVLUP4sHs_-OAg@mail.gmail.com>
DKIM signature
missing
Download raw message
This patch makes it possible to use serde_bare in environments that
don't provide the rust std library.

e.g. embedded targets, web.

This patch does require that the target include the alloc library and
provide a suitable allocator.

Example usage is as follows:

   [features]
   default = ["std"]
   std = ["serde_bare/std"]
   alloc = ["serde_bare/alloc"]

   [dependencies]
   serde_bare = { version = "x.x", default-features = false }

The project can then be compiled for std with:

   cargo build

And for no_std as:

   cargo build --no-default-features --features="alloc"

---
Cargo.toml    | 11 +++++++++--
src/compat.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++
src/de.rs     | 16 +++++++++++-----
src/error.rs  | 12 +++++++-----
src/lib.rs    | 26 ++++++++++++++++++++------
src/ser.rs    |  6 +++++-
6 files changed, 96 insertions(+), 19 deletions(-)
create mode 100644 src/compat.rs

diff --git a/Cargo.toml b/Cargo.toml
index 448f839..78e1f47 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,7 @@
[package]
name = "serde_bare"
version = "0.4.0"
+resolver = "2"
authors = ["Tadeo Kondrak <me@tadeo.ca>"]
license = "MIT OR Apache-2.0"
edition = "2018"
@@ -10,12 +11,18 @@ keywords = ["serde", "bare"]
categories = ["encoding"]
exclude = ["benches/go-reference"]

+[features]
+default = [ "std" ]
+std = ["serde/std"]
+alloc = ["core2/alloc", "core2/nightly", "serde/alloc", "serde/derive"]
+
[dependencies]
-serde = "1.0"
+serde =  { version = "1.0", default-features = false, optional = true }
+core2 = { version = "0.3.2", default-features = false, optional = true }

[dev-dependencies]
serde_derive = "1.0"
-serde_bytes = "0.11"
+serde_bytes = { version = "0.11", default-features = false, features
= [ "alloc" ] }
criterion = "0.3"

[[bench]]
diff --git a/src/compat.rs b/src/compat.rs
new file mode 100644
index 0000000..fb6fe07
--- /dev/null
+++ b/src/compat.rs
@@ -0,0 +1,44 @@
+/// A facade around the various collections and primitives needed
+/// to support "std" and "no_std + alloc" targets.
+
+// std::boxed
+pub mod boxed {
+    #[cfg(feature = "std")]
+    pub use std::boxed::Box;
+    #[cfg(all(not(feature = "std"), feature = "alloc"))]
+    pub use alloc::boxed::Box;
+}
+
+// std::error::Error trait
+pub mod error {
+    #[cfg(feature = "std")]
+    pub use std::error::Error;
+    #[cfg(not(feature = "std"))]
+    pub trait Error: core::fmt::Debug + core::fmt::Display {
+        fn source(&self) -> Option<&(dyn Error + 'static)> {
+            None
+        }
+    }
+}
+
+// std::io
+#[cfg(feature = "std")]
+pub use std::io;
+#[cfg(not(feature = "std"))]
+pub use core2::io;
+
+// std::string
+pub mod string {
+    #[cfg(feature = "std")]
+    pub use std::string::{String, ToString};
+    #[cfg(all(not(feature = "std"), feature = "alloc"))]
+    pub use alloc::string::{String, ToString};
+}
+
+// std::vec
+pub mod vec {
+    #[cfg(feature = "std")]
+    pub use std::vec::Vec;
+    #[cfg(all(not(feature = "std"), feature = "alloc"))]
+    pub use alloc::vec::Vec;
+}
diff --git a/src/de.rs b/src/de.rs
index 8063944..bb1f28a 100644
--- a/src/de.rs
+++ b/src/de.rs
@@ -1,23 +1,28 @@
use crate::{error::Error, Uint};
use serde::de;
-use std::{
+use core::{
    convert::TryInto,
    i16, i32, i64, i8,
-    io::{Cursor, Read},
    str, u16, u32, u64, u8,
};
+use crate::compat::{
+    io::{self, Cursor, Read},
+    string::{String, ToString},
+    vec::Vec,
+};

/// Try and return a Vec<u8> of `len` bytes from a Reader
+#[cfg(any(feature = "std", feature = "alloc"))]
#[inline]
-fn read_bytes<R: Read>(reader: R, len: usize) -> Result<Vec<u8>,
std::io::Error> {
+fn read_bytes<R: Read>(reader: R, len: usize) -> Result<Vec<u8>, io::Error> {
    // Allocate at most 4096 bytes to start with. Growing a Vec is
fairly efficient once you get out
    // of the region of the first few hundred bytes.
    let capacity = len.min(4096);
    let mut buffer = Vec::with_capacity(capacity);
    let read = reader.take(len as u64).read_to_end(&mut buffer)?;
    if read < len {
-        Err(std::io::Error::new(
-            std::io::ErrorKind::UnexpectedEof,
+        Err(io::Error::new(
+            io::ErrorKind::UnexpectedEof,
            "Unexpected EOF reading number of bytes expected in field prefix",
        ))
    } else {
@@ -655,6 +660,7 @@ mod test {

    #[test]
    fn test_slice() {
+        use crate::compat::boxed::Box;
        assert_eq!(
            &[0u8; 4][..],
            &*from_slice::<Box<[u8]>>(&[4, 0, 0, 0, 0]).unwrap()
diff --git a/src/error.rs b/src/error.rs
index 28937cc..a1762e3 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -1,10 +1,12 @@
use serde::{de, ser};
-use std::{
-    fmt::{self, Display},
+use core::fmt::{self, Debug, Display};
+use crate::compat::{
+    error,
    io,
+    string::{String, ToString}
};

-pub type Result<T> = std::result::Result<T, Error>;
+pub type Result<T> = core::result::Result<T, Error>;

#[derive(Debug)]
pub enum Error {
@@ -36,7 +38,7 @@ impl Display for Error {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Error::Message(msg) => formatter.write_str(msg),
-            Error::Io(e) => e.fmt(formatter),
+            Error::Io(e) => Debug::fmt(&e, formatter),
            Error::AnyUnsupported => formatter.write_str("BARE does
not support any"),
            Error::InvalidUtf8 => formatter.write_str("invalid utf-8
in string"),
            Error::InvalidChar => formatter.write_str("invalid
unicode codepoint in char"),
@@ -46,4 +48,4 @@ impl Display for Error {
    }
}

-impl std::error::Error for Error {}
+impl error::Error for Error {}
diff --git a/src/lib.rs b/src/lib.rs
index b8acc21..f7e5492 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -81,6 +81,18 @@
//! Serialized as a `uint` followed by the variant data.
//! The container name and variant name are ignored.

+#![cfg_attr(not(feature = "std"), no_std)]
+
+#[cfg(not(feature = "std"))]
+#[macro_use]
+extern crate core;
+
+#[cfg(feature = "alloc")]
+#[allow(unused_imports)]
+#[macro_use]
+extern crate alloc;
+
+mod compat;
pub mod de;
pub mod error;
pub mod ser;
@@ -102,7 +114,7 @@ impl Default for Uint {
}

impl serde::ser::Serialize for Uint {
-    fn serialize<S>(&self, serializer: S) ->
std::result::Result<S::Ok, S::Error>
+    fn serialize<S>(&self, serializer: S) ->
core::result::Result<S::Ok, S::Error>
    where
        S: serde::ser::Serializer,
    {
@@ -128,11 +140,11 @@ impl serde::ser::Serialize for Uint {
}

impl<'de> serde::de::Deserialize<'de> for Uint {
-    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
+    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
    where
        D: serde::de::Deserializer<'de>,
    {
-        use std::fmt;
+        use core::fmt;

        struct UintVisitor;
        impl<'de> serde::de::Visitor<'de> for UintVisitor {
@@ -142,7 +154,7 @@ impl<'de> serde::de::Deserialize<'de> for Uint {
                write!(formatter, "a BARE encoded variable-length integer")
            }

-            fn visit_seq<A>(self, mut seq: A) ->
std::result::Result<Self::Value, A::Error>
+            fn visit_seq<A>(self, mut seq: A) ->
core::result::Result<Self::Value, A::Error>
            where
                A: serde::de::SeqAccess<'de>,
            {
@@ -189,7 +201,7 @@ impl Default for Int {
}

impl serde::ser::Serialize for Int {
-    fn serialize<S>(&self, serializer: S) ->
std::result::Result<S::Ok, S::Error>
+    fn serialize<S>(&self, serializer: S) ->
core::result::Result<S::Ok, S::Error>
    where
        S: serde::ser::Serializer,
    {
@@ -203,7 +215,7 @@ impl serde::ser::Serialize for Int {
}

impl<'de> serde::de::Deserialize<'de> for Int {
-    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
+    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
    where
        D: serde::de::Deserializer<'de>,
    {
@@ -229,6 +241,7 @@ mod test {
            (i64::MAX, &[254, 255, 255, 255, 255, 255, 255, 255, 255, 1]),
        ];
        for &(n, bytes) in CASES {
+            #[cfg(feature = "std")]
            println!("testing {}", n);
            let int = Int(n);
            let got_bytes = to_vec(&int).unwrap();
@@ -247,6 +260,7 @@ mod test {
            (u64::MAX, &[255, 255, 255, 255, 255, 255, 255, 255, 255, 1]),
        ];
        for &(n, bytes) in CASES {
+            #[cfg(feature = "std")]
            println!("testing {}", n);
            let int = Uint(n);
            let got_bytes = to_vec(&int).unwrap();
diff --git a/src/ser.rs b/src/ser.rs
index cf385a0..f920f87 100644
--- a/src/ser.rs
+++ b/src/ser.rs
@@ -1,6 +1,9 @@
use crate::{error::Error, Uint};
use serde::{ser, Serialize};
-use std::io::Write;
+use crate::compat::{
+    io::Write,
+    vec::Vec
+};

pub struct Serializer<W> {
    writer: W,
@@ -419,6 +422,7 @@ where
mod test {
    #[test]
    fn test_unbounded_sequence() {
+        use crate::compat::vec::Vec;
        use serde::Serializer;
        let seq = [1, 2, 3];
        let vec = Vec::<u8>::new();
-- 
2.30.1 (Apple Git-130)
Details
Message ID
<CELLWZ8MXFQ0.H8PNZ51XDU4D@mikochi>
In-Reply-To
<CAOHqsxX93fa4j35hvmvkuN8REE+Df6p3V-DUeVLUP4sHs_-OAg@mail.gmail.com> (view parent)
DKIM signature
missing
Download raw message
On Tue Sep 28, 2021 at 8:13 AM MDT, Antoine van Gelder wrote:
> This patch makes it possible to use serde_bare in environments that
> don't provide the rust std library.
>
> e.g. embedded targets, web.
>
> This patch does require that the target include the alloc library and
> provide a suitable allocator.

Hi, thanks for your patch!
Unfortunately it looks like the code section been wrapped to a column
limit, so it's not possible to apply it.

I think, rather than trying to expose the same API for std and alloc
through core2 which imitates std::io, we could use a similar approach as
serde_json does, where we define our own sealed and stripped-down Read
and maybe Write traits, and use that as the input to the Deserializer.

This allows us to avoid having std::io types in the public API except
for adapter types gated by a feature.

This would mean that `from_reader` and `from_writer` wouldn't initially
be available in `alloc` mode, only `from_slice` and `to_vec`.

Is that enough for your use-case?

(Sorry for the double email, forgot to copy the list.)
Antoine van Gelder <antoine@ockam.io>
Details
Message ID
<CAOHqsxWJqzXvhYFyzwSnCg-YDPVqxqz+mBgNjCqJUi1ZNWVqzQ@mail.gmail.com>
In-Reply-To
<CELLWZ8MXFQ0.H8PNZ51XDU4D@mikochi> (view parent)
DKIM signature
missing
Download raw message
> Hi, thanks for your patch!
> Unfortunately it looks like the code section been wrapped to a column
> limit, so it's not possible to apply it.

Oh dear. My email address is unfortunately reliant on one of those
non-compliant mail clients mentioned in the FAQ!

> I think, rather than trying to expose the same API for std and alloc
> through core2 which imitates std::io, we could use a similar approach as
> serde_json does, where we define our own sealed and stripped-down Read
> and maybe Write traits, and use that as the input to the Deserializer.
>
> This allows us to avoid having std::io types in the public API except
> for adapter types gated by a feature.

This sounds most reasonable.

> This would mean that `from_reader` and `from_writer` wouldn't initially
> be available in `alloc` mode, only `from_slice` and `to_vec`.
>
> Is that enough for your use-case?

Yup, this would do  the trick. I'm not currently using the
reader/writer conversions.
Details
Message ID
<CELPAMD8VO44.3NNICQUIC0BU7@mikochi>
In-Reply-To
<CAOHqsxWJqzXvhYFyzwSnCg-YDPVqxqz+mBgNjCqJUi1ZNWVqzQ@mail.gmail.com> (view parent)
DKIM signature
missing
Download raw message
On Tue Sep 28, 2021 at 9:51 AM MDT, Antoine van Gelder wrote:
> > Hi, thanks for your patch!
> > Unfortunately it looks like the code section been wrapped to a column
> > limit, so it's not possible to apply it.
>
> Oh dear. My email address is unfortunately reliant on one of those
> non-compliant mail clients mentioned in the FAQ!
>
> > I think, rather than trying to expose the same API for std and alloc
> > through core2 which imitates std::io, we could use a similar approach as
> > serde_json does, where we define our own sealed and stripped-down Read
> > and maybe Write traits, and use that as the input to the Deserializer.
> >
> > This allows us to avoid having std::io types in the public API except
> > for adapter types gated by a feature.
>
> This sounds most reasonable.
>
> > This would mean that `from_reader` and `from_writer` wouldn't initially
> > be available in `alloc` mode, only `from_slice` and `to_vec`.
> >
> > Is that enough for your use-case?
>
> Yup, this would do the trick. I'm not currently using the
> reader/writer conversions.

I've implemented this and pushed it to the git repository. Please give
it a try and let me know if everything seems correct.

If it works for you, I'll release it as 0.5.0 soon.
Antoine van Gelder <antoine@ockam.io>
Details
Message ID
<CAOHqsxUs4UDVxB6encZnqSdHYw-AByrDSq5H_YeMpfyoBBQAdw@mail.gmail.com>
In-Reply-To
<CELPAMD8VO44.3NNICQUIC0BU7@mikochi> (view parent)
DKIM signature
missing
Download raw message
> I've implemented this and pushed it to the git repository. Please give
> it a try and let me know if everything seems correct.

Wow, thank you.

> If it works for you, I'll release it as 0.5.0 soon.

Looking good on my side, all tests are green!

(Apologies for the double post!)
Details
Message ID
<CELR8L8ZRWCY.3RSGT3S4SMKQO@mikochi>
In-Reply-To
<CAOHqsxUs4UDVxB6encZnqSdHYw-AByrDSq5H_YeMpfyoBBQAdw@mail.gmail.com> (view parent)
DKIM signature
missing
Download raw message
On Tue Sep 28, 2021 at 12:29 PM MDT, Antoine van Gelder wrote:
> Looking good on my side, all tests are green!
Thanks for testing, published!
Reply to thread Export thread (mbox)