Crate miniconf

source ·
Expand description

Miniconf

crates.io docs QUARTIQ Matrix Chat Continuous Integration

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-client Enable the MQTT client feature. See the example in MqttClient.
  • json-core Enable the JsonCoreSlash implementation of serializing from and into json slices (using serde_json_core).

The mqtt-client and json-core features are enabled by default.

Re-exports

Structs

Enums

  • Errors that can occur when using the Miniconf API. A usize member 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 Result up 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.