dynamic_config_embedded/
cell.rs1use core::cell::RefCell;
4
5use critical_section::Mutex;
6use serde::de::DeserializeOwned;
7
8use crate::error::{Error, ErrorKind};
9use crate::{Format, Validate};
10
11pub struct ConfigCell<T, const WAITERS: usize = { crate::DEFAULT_WAITERS }> {
34 inner: Mutex<RefCell<Option<T>>>,
35 #[cfg(feature = "async")]
37 notify: crate::asynchronous::Notify<WAITERS>,
38}
39
40impl<T, const WAITERS: usize> ConfigCell<T, WAITERS> {
41 #[must_use]
43 pub const fn new() -> Self {
44 Self {
45 inner: Mutex::new(RefCell::new(None)),
46 #[cfg(feature = "async")]
47 notify: crate::asynchronous::Notify::new(),
48 }
49 }
50}
51
52impl<T: Clone, const WAITERS: usize> ConfigCell<T, WAITERS> {
53 pub fn store(&self, value: T) {
58 critical_section::with(|token| {
59 self.inner.borrow(token).replace(Some(value));
60 });
61
62 #[cfg(feature = "async")]
63 self.notify.bump();
64 }
65
66 #[must_use]
72 pub fn get(&self) -> Option<T> {
73 critical_section::with(|token| self.inner.borrow(token).borrow().clone())
74 }
75
76 #[must_use]
78 pub fn is_set(&self) -> bool {
79 critical_section::with(|token| self.inner.borrow(token).borrow().is_some())
80 }
81
82 #[cfg(feature = "async")]
84 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
85 #[must_use]
86 pub fn changes(&'static self) -> crate::Changes<T, WAITERS> {
87 crate::Changes::new(self)
88 }
89
90 #[cfg(feature = "async")]
91 pub(crate) fn notify(&self) -> &crate::asynchronous::Notify<WAITERS> {
92 &self.notify
93 }
94}
95
96impl<T: Clone + DeserializeOwned + Validate, const WAITERS: usize> ConfigCell<T, WAITERS> {
97 pub fn apply(&self, document: &[u8], format: Format) -> Result<(), Error> {
109 let value = parse::<T>(document, format)?;
110
111 value
112 .validate()
113 .map_err(|message| Error::new(ErrorKind::Invalid, message))?;
114
115 self.store(value);
116
117 Ok(())
118 }
119}
120
121fn parse<T: DeserializeOwned>(document: &[u8], format: Format) -> Result<T, Error> {
123 #[cfg(not(feature = "json"))]
126 let _ = (document, format);
127
128 match format {
129 #[cfg(feature = "json")]
130 Format::Json => serde_json_core::from_slice::<T>(document)
131 .and_then(|(value, consumed)| {
132 if consumed == document.len() {
139 Ok(value)
140 } else {
141 Err(serde_json_core::de::Error::TrailingCharacters)
142 }
143 })
144 .map_err(|error| {
145 let kind = match error {
149 serde_json_core::de::Error::InvalidType
153 | serde_json_core::de::Error::CustomError => ErrorKind::Type,
154 _ => ErrorKind::Parse,
155 };
156
157 Error::new(kind, "the document is not a configuration of this shape")
158 }),
159
160 #[allow(unreachable_patterns)]
161 _ => Err(Error::new(
162 ErrorKind::Unsupported,
163 "the format's feature is not enabled in this build",
164 )),
165 }
166}
167
168impl<T, const WAITERS: usize> Default for ConfigCell<T, WAITERS> {
169 fn default() -> Self {
170 Self::new()
171 }
172}
173
174impl<T> core::fmt::Debug for ConfigCell<T> {
175 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
176 f.debug_struct("ConfigCell").finish_non_exhaustive()
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use serde::Deserialize;
185
186 use super::*;
187
188 #[derive(Debug, Clone, PartialEq, Deserialize)]
189 struct Settings {
190 interval_ms: u32,
191 verbose: bool,
192 }
193
194 impl Validate for Settings {
195 fn validate(&self) -> Result<(), &'static str> {
196 if self.interval_ms == 0 {
197 return Err("interval_ms of zero would spin");
198 }
199
200 Ok(())
201 }
202 }
203
204 #[test]
205 fn an_empty_cell_answers_nothing() {
206 let cell: ConfigCell<Settings> = ConfigCell::new();
207
208 assert!(!cell.is_set());
209 assert!(cell.get().is_none());
210 }
211
212 #[test]
213 fn a_stored_value_comes_back() {
214 let cell: ConfigCell<Settings> = ConfigCell::new();
215
216 cell.store(Settings {
217 interval_ms: 1000,
218 verbose: false,
219 });
220
221 assert_eq!(cell.get().unwrap().interval_ms, 1000);
222 }
223
224 #[cfg(feature = "json")]
225 #[test]
226 fn a_document_replaces_the_configuration() {
227 let cell: ConfigCell<Settings> = ConfigCell::new();
228
229 cell.store(Settings {
230 interval_ms: 1000,
231 verbose: false,
232 });
233
234 cell.apply(br#"{"interval_ms": 250, "verbose": true}"#, Format::Json)
235 .expect("the document fits");
236
237 assert_eq!(
238 cell.get().unwrap(),
239 Settings {
240 interval_ms: 250,
241 verbose: true,
242 }
243 );
244 }
245
246 #[cfg(feature = "json")]
247 #[test]
248 fn a_document_that_does_not_parse_leaves_the_previous_one_serving() {
249 let cell: ConfigCell<Settings> = ConfigCell::new();
250
251 cell.store(Settings {
252 interval_ms: 1000,
253 verbose: false,
254 });
255
256 let error = cell
257 .apply(b"{not json", Format::Json)
258 .expect_err("that is not a document");
259
260 assert_eq!(error.kind(), ErrorKind::Parse);
261 assert_eq!(
262 cell.get().unwrap().interval_ms,
263 1000,
264 "a bad document must not take the device's configuration with it"
265 );
266 }
267
268 #[cfg(feature = "json")]
269 #[test]
270 fn a_document_that_fails_validation_is_refused_whole() {
271 let cell: ConfigCell<Settings> = ConfigCell::new();
272
273 cell.store(Settings {
274 interval_ms: 1000,
275 verbose: false,
276 });
277
278 let error = cell
279 .apply(br#"{"interval_ms": 0, "verbose": true}"#, Format::Json)
280 .expect_err("zero would spin");
281
282 assert_eq!(error.kind(), ErrorKind::Invalid);
283 assert_eq!(error.message(), "interval_ms of zero would spin");
284 assert!(
285 !cell.get().unwrap().verbose,
286 "not even the fields that were fine"
287 );
288 }
289
290 #[cfg(feature = "json")]
291 #[test]
292 fn a_document_missing_a_field_is_a_type_error_not_a_parse_error() {
293 let cell: ConfigCell<Settings> = ConfigCell::new();
294
295 let error = cell
296 .apply(br#"{"interval_ms": 250}"#, Format::Json)
297 .expect_err("`verbose` is missing");
298
299 assert_eq!(error.kind(), ErrorKind::Type);
303 }
304
305 #[cfg(feature = "std")]
308 #[test]
309 fn debug_never_prints_the_configuration() {
310 extern crate std;
311
312 let cell: ConfigCell<Settings> = ConfigCell::new();
313
314 cell.store(Settings {
315 interval_ms: 1234,
316 verbose: true,
317 });
318
319 assert!(!std::format!("{cell:?}").contains("1234"));
320 }
321}