1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
#![no_std]
#![doc = include_str!("../README.md")]

mod array;
mod iter;
mod option;

pub use array::Array;
pub use iter::MiniconfIter;
pub use miniconf_derive::Miniconf;
pub use option::Option;

#[cfg(feature = "mqtt-client")]
mod mqtt_client;

#[cfg(feature = "mqtt-client")]
pub use mqtt_client::MqttClient;

// Re-exports
pub use heapless;
pub use serde;
pub use serde_json_core;

#[cfg(feature = "mqtt-client")]
pub use minimq;

#[cfg(feature = "mqtt-client")]
pub use minimq::embedded_time;

#[doc(hidden)]
pub use serde::{
    de::{Deserialize, DeserializeOwned},
    ser::Serialize,
};

/// Errors that can occur when using the [Miniconf] API.
#[non_exhaustive]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Error {
    /// The provided path wasn't found in the structure.
    ///
    /// Double check the provided path to verify that it's valid.
    PathNotFound,

    /// The provided path was valid, but there was trailing data at the end.
    ///
    /// Check the end of the path and remove any excess characters.
    PathTooLong,

    /// The provided path was valid, but did not specify a value fully.
    ///
    /// Double check the ending and add the remainder of the path.
    PathTooShort,

    /// The value provided for configuration could not be deserialized into the proper type.
    ///
    /// Check that the serialized data is valid JSON and of the correct type.
    Deserialization(serde_json_core::de::Error),

    /// The value provided could not be serialized.
    ///
    /// Check that the buffer had sufficient space.
    Serialization(serde_json_core::ser::Error),

    /// When indexing into an array, the index provided was out of bounds.
    ///
    /// Check array indices to ensure that bounds for all paths are respected.
    BadIndex,

    /// The path does not exist at runtime.
    ///
    /// This is the case if a deferred [core::option::Option] or [Option]
    /// is `None` at runtime. `PathAbsent` takes precedence over `PathNotFound`
    /// if the path is simultaneously masked by a `Option::None` at runtime but
    /// would still be non-existent if it weren't.
    PathAbsent,
}

/// Errors that occur during iteration over topic paths.
#[non_exhaustive]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum IterError {
    /// The provided state vector is not long enough.
    PathDepth,

    /// The provided topic length is not long enough.
    PathLength,
}

impl From<Error> for u8 {
    fn from(err: Error) -> u8 {
        match err {
            Error::PathNotFound => 1,
            Error::PathTooLong => 2,
            Error::PathTooShort => 3,
            Error::Deserialization(_) => 5,
            Error::BadIndex => 6,
            Error::Serialization(_) => 7,
            Error::PathAbsent => 8,
        }
    }
}

impl From<serde_json_core::de::Error> for Error {
    fn from(err: serde_json_core::de::Error) -> Error {
        Error::Deserialization(err)
    }
}

impl From<serde_json_core::ser::Error> for Error {
    fn from(err: serde_json_core::ser::Error) -> Error {
        Error::Serialization(err)
    }
}

/// Metadata about a [Miniconf] namespace.
#[non_exhaustive]
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
pub struct Metadata {
    /// The maximum length of a path.
    pub max_length: usize,

    /// The maximum path depth.
    pub max_depth: usize,

    /// The number of paths.
    pub count: usize,
}

/// Helper trait for [core::iter::Peekable].
pub trait Peekable: core::iter::Iterator {
    fn peek(&mut self) -> core::option::Option<&Self::Item>;
}

impl<I: core::iter::Iterator> Peekable for core::iter::Peekable<I> {
    fn peek(&mut self) -> core::option::Option<&Self::Item> {
        core::iter::Peekable::peek(self)
    }
}

/// Trait exposing serialization/deserialization of elements by path.
pub trait Miniconf {
    /// Update an element by path.
    ///
    /// # Args
    /// * `path` - The path to the element with '/' as the separator.
    /// * `data` - The serialized data making up the content.
    ///
    /// # Returns
    /// The number of bytes consumed from `data` or an [Error].
    fn set(&mut self, path: &str, data: &[u8]) -> Result<usize, Error> {
        self.set_path(&mut path.split('/').peekable(), data)
    }

    /// Retrieve a serialized value by path.
    ///
    /// # Args
    /// * `path` - The path to the element with '/' as the separator.
    /// * `data` - The buffer to serialize the data into.
    ///
    /// # Returns
    /// The number of bytes used in the `data` buffer or an [Error].
    fn get(&self, path: &str, data: &mut [u8]) -> Result<usize, Error> {
        self.get_path(&mut path.split('/').peekable(), data)
    }

    /// Create an iterator of all possible paths.
    ///
    /// This is a depth-first walk.
    /// The iterator will walk all paths, even those that may be absent at run-time (see [Option]).
    /// The iterator has an exact and trusted [Iterator::size_hint].
    ///
    /// # Template Arguments
    /// * `L`  - The maximum depth of the path, i.e. number of separators plus 1.
    /// * `TS` - The maximum length of the path in bytes.
    ///
    /// # Returns
    /// A [MiniconfIter] of paths or an [IterError] if `L` or `TS` are insufficient.
    fn iter_paths<const L: usize, const TS: usize>(
    ) -> Result<iter::MiniconfIter<Self, L, TS>, IterError> {
        let meta = Self::metadata();

        if TS < meta.max_length {
            return Err(IterError::PathLength);
        }

        if L < meta.max_depth {
            return Err(IterError::PathDepth);
        }

        Ok(Self::unchecked_iter_paths(Some(meta.count)))
    }

    /// Create an iterator of all possible paths.
    ///
    /// This is a depth-first walk.
    /// It will return all paths, even those that may be absent at run-time.
    ///
    /// # Note
    /// This does not check that the path size or state vector are large enough. If they are not,
    /// panics may be generated internally by the library.
    ///
    /// # Args
    /// * `count`: Optional iterator length if known.
    ///
    /// # Template Arguments
    /// * `L`  - The maximum depth of the path, i.e. number of separators plus 1.
    /// * `TS` - The maximum length of the path in bytes.
    fn unchecked_iter_paths<const L: usize, const TS: usize>(
        count: core::option::Option<usize>,
    ) -> iter::MiniconfIter<Self, L, TS> {
        iter::MiniconfIter::new(count)
    }

    /// Deserialize an element by path.
    ///
    /// # Args
    /// * `path_parts`: A `Peekable` `Iterator` identifying the element.
    /// * `value`: A slice containing the data to be deserialized.
    ///
    /// # Returns
    /// The number of bytes consumed from `value` or an `Error`.
    fn set_path<'a, P: Peekable<Item = &'a str>>(
        &mut self,
        path_parts: &'a mut P,
        value: &[u8],
    ) -> Result<usize, Error>;

    /// Serialize an element by path.
    ///
    /// # Args
    /// * `path_parts`: A `Peekable` `Iterator` identifying the element.
    /// * `value`: A slice for the value to be serialized into.
    ///
    /// # Returns
    /// The number of bytes written to `value` or an `Error`.
    fn get_path<'a, P: Peekable<Item = &'a str>>(
        &self,
        path_parts: &'a mut P,
        value: &mut [u8],
    ) -> Result<usize, Error>;

    /// Get the next path in the namespace.
    ///
    /// This is usually not called directly but through a [MiniconfIter] returned by [Miniconf::iter_paths].
    ///
    /// # Args
    /// * `state`: A state array indicating the path to be retrieved.
    ///   A zeroed vector indicates the first path. The vector is advanced
    ///   such that the next element will be retrieved when called again.
    ///   The array needs to be at least as long as the maximum path depth.
    /// * `path`: A string to write the path into.
    ///
    /// # Returns
    /// A `bool` indicating a valid path was written to `path` from the given `state`.
    /// If `false`, `path` is invalid and there are no more paths within `self` at and
    /// beyond `state`.
    /// May return `IterError` indicating insufficient `state` or `path` size.
    fn next_path<const TS: usize>(
        state: &mut [usize],
        path: &mut heapless::String<TS>,
    ) -> Result<bool, IterError>;

    /// Get metadata about the paths in the namespace.
    fn metadata() -> Metadata;
}