Expand description
Miniconf
Miniconf enables lightweight (no_std) partial serialization (retrieval) and deserialization
(updates, modification) within a tree by key. The tree is backed by
structs/arrays/Options of serializable types.
Miniconf can be used as a very simple and flexible backend for run-time settings management in embedded devices
over any transport. It was originally designed to work with JSON (serde_json_core)
payloads over MQTT (minimq) and provides a comlete MQTT settings management
client and a Python reference implementation to ineract with it.
Miniconf is completely generic over the serde::Serializer/serde::Deserializer backend and the path hierarchy separator.
Example
use miniconf::{Error, Miniconf, JsonCoreSlash};
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Copy, Clone, Default)]
enum Either {
#[default]
Bad,
Good,
}
#[derive(Deserialize, Serialize, Copy, Clone, Default, Miniconf)]
struct Inner {
a: i32,
b: i32,
}
#[derive(Miniconf, Default)]
struct Settings {
// Atomic updtes by field name
foo: bool,
enum_: Either,
struct_: Inner,
array: [i32; 2],
option: Option<i32>,
// Exposing elements of containers
// ... by field name
#[miniconf(defer)]
struct_defer: Inner,
// ... or by index
#[miniconf(defer)]
array_defer: [i32; 2],
// ... or deferring to two levels (index and then inner field name)
#[miniconf(defer(2))]
array_miniconf: [Inner; 2],
// Hiding paths by setting the Option to `None` at runtime
#[miniconf(defer)]
option_defer: Option<i32>,
// Hiding a path and deferring to the inner
#[miniconf(defer(2))]
option_miniconf: Option<Inner>,
// Hiding elements of an Array of Miniconf items
#[miniconf(defer(3))]
array_option_miniconf: [Option<Inner>; 2],
}
let mut settings = Settings::default();
let mut buf = [0; 64];
// Atomic updates by field name
settings.set_json("/foo", b"true")?;
assert_eq!(settings.foo, true);
settings.set_json("/enum_", br#""Good""#)?;
settings.set_json("/struct_", br#"{"a": 3, "b": 3}"#)?;
settings.set_json("/array", b"[6, 6]")?;
settings.set_json("/option", b"12")?;
settings.set_json("/option", b"null")?;
// Deep access by field name in a struct
settings.set_json("/struct_defer/a", b"4")?;
// ... or by index in an array
settings.set_json("/array_defer/0", b"7")?;
// ... or by index and then struct field name
settings.set_json("/array_miniconf/1/b", b"11")?;
// If a deferred Option is `None` it is hidden at runtime and can't be accessed
settings.option_defer = None;
assert_eq!(settings.set_json("/option_defer", b"13"), Err(Error::Absent(1)));
settings.option_defer = Some(0);
settings.set_json("/option_defer", b"13")?;
settings.option_miniconf = Some(Inner::default()).into();
settings.set_json("/option_miniconf/a", b"14")?;
settings.array_option_miniconf[1] = Some(Inner::default()).into();
settings.set_json("/array_option_miniconf/1/a", b"15")?;
// Serializing elements by path
let len = settings.get_json("/struct_", &mut buf)?;
assert_eq!(&buf[..len], br#"{"a":3,"b":3}"#);
// Iterating over all paths
for path in Settings::iter_paths::<String>("/") {
let path = path.unwrap();
// Serializing each
match settings.get_json(&path, &mut buf) {
Ok(len) => {
// Deserialize again
settings.set_json(&path, &buf[..len])?;
}
// Some settings are still `None` and thus their paths are expected to be absent
Err(Error::Absent(_)) => {}
e => {
e?;
}
}
}
MQTT
There is an MQTT-based client that implements settings management over the MQTT protocol with JSON payloads. A Python reference library is provided that interfaces with it.
# Discover the complete unique prefix of an application listening to messages
# under the topic `quartiq/application/12345` and set its `foo` setting to `true`.
python -m miniconf -d quartiq/application/+ foo=true
Derive macro
For structs Miniconf offers a Miniconf derive macro.
The macro implements the Miniconf trait that exposes access to serialized field values through their path.
All types supported by either serde (and the serde::Serializer/serde::Deserializer backend) or Miniconf
can be used as fields.
Structs, arrays, and Options can then be cascaded to construct more complex trees.
When using the derive macro, the behavior and tree recursion depth can be configured for each
struct field using the #[miniconf(defer(Y))] attribute.
See also the Miniconf trait documentation for details.
Keys and paths
Lookup into the tree is done using an iterator over Key items. usize indices or &str names are supported.
Path iteration is supported with arbitrary separator between names.
Formats
Miniconf is generic over the serde backend/payload format and the path hierarchy separator.
Currently support for / as the path hierarchy separator and JSON (serde_json_core) is implemented
through the JsonCoreSlash super trait.
Transport
Miniconf is designed to be protocol-agnostic. Any means that can receive key-value input from some external source can be used to modify values by path.
Limitations
Deferred/deep/non-atomic access to inner elements of some types is not yet supported, e.g. enums
other than core::option::Option. These are still however usable in their atomic serde form as leaves.
Features
mqtt-clientEnable the MQTT client feature. See the example in MqttClient.json-coreEnable the JsonCoreSlash implementation of serializing from and into json slices (usingserde_json_core).
The mqtt-client and json-core features are enabled by default.
Re-exports
Structs
- Metadata about a Miniconf namespace.
- MQTT settings interface.
- An iterator over the paths in a Miniconf namespace.
- Struct to indicate a short indices slice or a too small iterator state.
Enums
- Errors that can occur when using the Miniconf API. A
usizemember indicates the depth where the error occurred. The depth is the number of names or indices consumed. It is also the number of separators in a path or the length of an indices slice.
Traits
- Pass the
Resultup one hierarchy level. - Miniconf with “JSON and
/”. - Capability to convert a key into an node index.
- Serialization/deserialization of nodes by keys/paths and traversal.
Derive Macros
- Derive the Miniconf trait for custom types.