diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 060e2d55..d741ca67 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,9 +126,9 @@ jobs: - stable - 1.65.0 # keep in sync with manifest MSRV args: - - --all-features + - "" - --no-default-features - - --no-default-features --features json + - --no-default-features --features json-core steps: - uses: actions/checkout@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 327b14f5..f7455889 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,7 +33,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [breaking] `iter_paths`/`MiniconfIter` is now generic over the type to write the path into. Downstream crates should replace `iter_paths::()` with `iter_paths::>()`. -* The `mqtt-client` feature has been renamed into `mqtt` with backward compatibility. * [breaking] Re-exports of `heapless` and `serde-json-core` have been removed as they are not needed to work with the public API. diff --git a/Cargo.toml b/Cargo.toml index 4305483b..5e4a11fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,17 +17,20 @@ serde = { version = "1.0.120", features = ["derive"], default-features = false } miniconf_derive = { path = "miniconf_derive" , version = "0.6" } itoa = "1.0.4" -serde-json-core = { git = "https://github.com/rust-embedded-community/serde-json-core.git" , optional = true} -log = {version = "0.4", optional = true} -heapless = { version = "0.7", features = ["serde"], optional=true } +serde-json-core = { git = "https://github.com/rust-embedded-community/serde-json-core.git" , optional = true } +serde_json = { version = "1.0", optional = true } +tinyvec = { version = "1.6", optional = true } +log = {version = "0.4", optional = true } +heapless = { version = "0.7", optional=true } minimq = { version = "0.7", optional = true } smlang = { version = "0.6", optional = true } [features] -default = ["mqtt"] -json = ["dep:serde-json-core", "dep:heapless"] -mqtt = ["dep:minimq", "dep:smlang", "dep:log", "json"] -mqtt-client = ["mqtt"] # backwards compat +default = ["mqtt-client"] +json-core = ["dep:serde-json-core"] +json = ["dep:serde_json", "dep:tinyvec", "std"] +std = [] +mqtt-client = ["json-core", "dep:minimq", "dep:smlang", "dep:log", "dep:heapless"] [dev-dependencies] machine = "0.3" @@ -43,8 +46,8 @@ branch = "feature/0.7" [[example]] name = "mqtt" -required-features = ["mqtt"] +required-features = ["mqtt-client"] [[example]] name = "readback" -required-features = ["json"] +required-features = ["json-core"] diff --git a/README.md b/README.md index 80ad5185..5a5b3968 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,10 @@ Deferred (non-atomic) access to inner elements of some types is not yet supporte * Tuple structs (other than [Option], [Array]) ## Features -* `mqtt` Enable the MQTT client feature. See the example in [MqttClient]. -* `json` Enable the [SerDe] implementation for the [JsonCoreSlash] style. +* `mqtt-client` Enable the MQTT client feature. See the example in [MqttClient]. +* `json-core` Enable the [SerDe] implementation for the [JsonCoreSlash] style + (using `serde_json_core`). +* `json` Enable the [SerDe] implementation for the [JsonSlash] style (using + `serde_json`). -Both features are enabled by default. +The `mqtt-client` and `json-core` features are enabled by default. diff --git a/src/json.rs b/src/json.rs index 9fff12cc..7a646d56 100644 --- a/src/json.rs +++ b/src/json.rs @@ -1,46 +1,31 @@ use crate::{Error, Miniconf, SerDe}; -use serde_json_core::{de, ser}; /// Marker struct for the "JSON and `/`" [SerDe] specification. /// -/// Access items with `'/'` as path separator and JSON (from `serde-json-core`) +/// Access items with `'/'` as path separator and JSON (from `serde-json`) /// as serialization/deserialization payload format. -pub struct JsonCoreSlash; +pub struct JsonSlash; -impl SerDe for T +impl SerDe for T where T: Miniconf, { const SEPARATOR: char = '/'; - type DeError = de::Error; - type SerError = ser::Error; + type DeError = serde_json::Error; + type SerError = serde_json::Error; fn set(&mut self, path: &str, data: &[u8]) -> Result> { - let mut de = de::Deserializer::new(data); + let mut de = serde_json::Deserializer::from_slice(data); self.set_path(&mut path.split(Self::SEPARATOR).skip(1), &mut de)?; - de.end().map_err(Error::PostDeserialization) + de.end() + .map_err(Error::PostDeserialization) + .map(|_| data.len()) } fn get(&self, path: &str, data: &mut [u8]) -> Result> { - let mut ser = ser::Serializer::new(data); + let mut buf = std::io::Cursor::new(data); + let mut ser = serde_json::Serializer::new(&mut buf); self.get_path(&mut path.split(Self::SEPARATOR).skip(1), &mut ser)?; - Ok(ser.end()) - } -} - -// These allow unifying serde error information to make writing examples -// and tests easier. Doing this conversion is optional. -// #[cfg(any(test, doctest))] -impl From> for Error { - fn from(value: Error) -> Self { - match value { - Error::BadIndex => Self::BadIndex, - Error::PathAbsent => Self::PathAbsent, - Error::PathNotFound => Self::PathNotFound, - Error::PathTooLong => Self::PathTooLong, - Error::PathTooShort => Self::PathTooShort, - Error::PostDeserialization(_) => Error::PostDeserialization(de::Error::CustomError), - Error::SerDe(_) => Self::SerDe(de::Error::CustomError), - } + Ok(buf.position() as _) } } diff --git a/src/json_core.rs b/src/json_core.rs new file mode 100644 index 00000000..9fff12cc --- /dev/null +++ b/src/json_core.rs @@ -0,0 +1,46 @@ +use crate::{Error, Miniconf, SerDe}; +use serde_json_core::{de, ser}; + +/// Marker struct for the "JSON and `/`" [SerDe] specification. +/// +/// Access items with `'/'` as path separator and JSON (from `serde-json-core`) +/// as serialization/deserialization payload format. +pub struct JsonCoreSlash; + +impl SerDe for T +where + T: Miniconf, +{ + const SEPARATOR: char = '/'; + type DeError = de::Error; + type SerError = ser::Error; + + fn set(&mut self, path: &str, data: &[u8]) -> Result> { + let mut de = de::Deserializer::new(data); + self.set_path(&mut path.split(Self::SEPARATOR).skip(1), &mut de)?; + de.end().map_err(Error::PostDeserialization) + } + + fn get(&self, path: &str, data: &mut [u8]) -> Result> { + let mut ser = ser::Serializer::new(data); + self.get_path(&mut path.split(Self::SEPARATOR).skip(1), &mut ser)?; + Ok(ser.end()) + } +} + +// These allow unifying serde error information to make writing examples +// and tests easier. Doing this conversion is optional. +// #[cfg(any(test, doctest))] +impl From> for Error { + fn from(value: Error) -> Self { + match value { + Error::BadIndex => Self::BadIndex, + Error::PathAbsent => Self::PathAbsent, + Error::PathNotFound => Self::PathNotFound, + Error::PathTooLong => Self::PathTooLong, + Error::PathTooShort => Self::PathTooShort, + Error::PostDeserialization(_) => Error::PostDeserialization(de::Error::CustomError), + Error::SerDe(_) => Self::SerDe(de::Error::CustomError), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 6ffc80e8..c981a0d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ -#![cfg_attr(not(any(test, doctest)), no_std)] -#![cfg_attr(feature = "json", doc = include_str!("../README.md"))] +#![cfg_attr(not(any(test, doctest, feature = "std")), no_std)] +#![cfg_attr(feature = "json-core", doc = include_str!("../README.md"))] use core::fmt::Write; @@ -11,16 +11,21 @@ pub use iter::*; mod option; pub use option::*; +#[cfg(feature = "json-core")] +mod json_core; +#[cfg(feature = "json-core")] +pub use json_core::*; + #[cfg(feature = "json")] mod json; #[cfg(feature = "json")] pub use json::*; -#[cfg(feature = "mqtt")] +#[cfg(feature = "mqtt-client")] pub use minimq; // re-export -#[cfg(feature = "mqtt")] +#[cfg(feature = "mqtt-client")] mod mqtt_client; -#[cfg(feature = "mqtt")] +#[cfg(feature = "mqtt-client")] pub use mqtt_client::*; pub use serde; // re-export diff --git a/src/mqtt_client/mod.rs b/src/mqtt_client/mod.rs index a3304c69..4fcd2f77 100644 --- a/src/mqtt_client/mod.rs +++ b/src/mqtt_client/mod.rs @@ -1,12 +1,12 @@ use crate::{JsonCoreSlash, Miniconf, SerDe}; use core::fmt::Write; +use heapless::{String, Vec}; use minimq::{ embedded_nal::{IpAddr, TcpClientStack}, embedded_time, types::{SubscriptionOptions, TopicFilter}, Publication, QoS, Retain, }; -use serde_json_core::heapless::{String, Vec}; // The maximum topic length of any settings path. const MAX_TOPIC_LENGTH: usize = 128; diff --git a/tests/arrays.rs b/tests/arrays.rs index db0d4f55..9609b97a 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -1,4 +1,4 @@ -#![cfg(feature = "json")] +#![cfg(feature = "json-core")] use miniconf::{Array, Error, Miniconf, SerDe}; use serde::Deserialize; diff --git a/tests/enums.rs b/tests/enums.rs index 2de9842a..61670b46 100644 --- a/tests/enums.rs +++ b/tests/enums.rs @@ -1,4 +1,4 @@ -#![cfg(feature = "json")] +#![cfg(feature = "json-core")] use miniconf::{Miniconf, SerDe}; use serde::{Deserialize, Serialize}; diff --git a/tests/generics.rs b/tests/generics.rs index 61a4188c..2b2d6a07 100644 --- a/tests/generics.rs +++ b/tests/generics.rs @@ -1,4 +1,4 @@ -#![cfg(feature = "json")] +#![cfg(feature = "json-core")] use miniconf::{Miniconf, SerDe}; use serde::{Deserialize, Serialize}; diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 1fbf9f2b..bcdf8765 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -1,4 +1,4 @@ -#![cfg(features = "mqtt")] +#![cfg(features = "mqtt-client")] use machine::*; use miniconf::{ diff --git a/tests/iter.rs b/tests/iter.rs index 8ef76af7..7b771f60 100644 --- a/tests/iter.rs +++ b/tests/iter.rs @@ -1,4 +1,4 @@ -#![cfg(feature = "json")] +#![cfg(feature = "json-core")] use miniconf::{Miniconf, SerDe}; diff --git a/tests/json.rs b/tests/json.rs new file mode 100644 index 00000000..65fddef1 --- /dev/null +++ b/tests/json.rs @@ -0,0 +1,20 @@ +#![cfg(feature = "json")] + +use miniconf::{Miniconf, SerDe}; + +#[test] +fn struct_with_string() { + #[derive(Miniconf, Default)] + struct Settings { + string: String, + } + + let mut s = Settings::default(); + + let mut buf = [0u8; 256]; + let len = s.get("/string", &mut buf).unwrap(); + assert_eq!(&buf[..len], b"\"\""); + + s.set("/string", br#""test""#).unwrap(); + assert_eq!(s.string, "test"); +} diff --git a/tests/option.rs b/tests/option.rs index 3dbda8d2..7332b15d 100644 --- a/tests/option.rs +++ b/tests/option.rs @@ -1,4 +1,4 @@ -#![cfg(feature = "json")] +#![cfg(feature = "json-core")] use miniconf::{Error, Miniconf, SerDe}; diff --git a/tests/republish.rs b/tests/republish.rs index 5d54340d..ca5f9297 100644 --- a/tests/republish.rs +++ b/tests/republish.rs @@ -1,4 +1,4 @@ -#![cfg(features = "mqtt")] +#![cfg(features = "mqtt-client")] use miniconf::{ minimq::{self, types::TopicFilter}, diff --git a/tests/structs.rs b/tests/structs.rs index 5601866d..5c3fe7ea 100644 --- a/tests/structs.rs +++ b/tests/structs.rs @@ -1,4 +1,4 @@ -#![cfg(feature = "json")] +#![cfg(feature = "json-core")] use miniconf::{Miniconf, SerDe}; use serde::{Deserialize, Serialize}; @@ -78,23 +78,6 @@ fn recursive_struct() { assert_eq!(metadata.count, 3); } -#[test] -fn struct_with_string() { - #[derive(Miniconf, Default)] - struct Settings { - string: heapless::String<10>, - } - - let mut s = Settings::default(); - - let mut buf = [0u8; 256]; - let len = s.get("/string", &mut buf).unwrap(); - assert_eq!(&buf[..len], b"\"\""); - - s.set("/string", br#""test""#).unwrap(); - assert_eq!(s.string, "test"); -} - #[test] fn empty_struct() { #[derive(Miniconf, Default)] diff --git a/tests/validation_failure.rs b/tests/validation_failure.rs index 8e32f129..05ebc1fa 100644 --- a/tests/validation_failure.rs +++ b/tests/validation_failure.rs @@ -1,4 +1,4 @@ -#![cfg(features = "mqtt")] +#![cfg(features = "mqtt-client")] use miniconf::{minimq, Miniconf}; use serde::Deserialize;