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> {
34 inner: Mutex<RefCell<Option<T>>>,
35 #[cfg(feature = "async")]
37 notify: crate::asynchronous::Notify,
38}
39
40impl<T> ConfigCell<T> {
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> ConfigCell<T> {
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> {
87 crate::Changes::new(self)
88 }
89
90 #[cfg(feature = "async")]
91 pub(crate) fn notify(&self) -> &crate::asynchronous::Notify {
92 &self.notify
93 }
94}
95
96impl<T: Clone + DeserializeOwned + Validate> ConfigCell<T> {
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 .map(|(value, _consumed)| value)
132 .map_err(|error| {
133 let kind = match error {
137 serde_json_core::de::Error::InvalidType
141 | serde_json_core::de::Error::CustomError => ErrorKind::Type,
142 _ => ErrorKind::Parse,
143 };
144
145 Error::new(kind, "the document is not a configuration of this shape")
146 }),
147
148 #[allow(unreachable_patterns)]
149 _ => Err(Error::new(
150 ErrorKind::Unsupported,
151 "the format's feature is not enabled in this build",
152 )),
153 }
154}
155
156impl<T> Default for ConfigCell<T> {
157 fn default() -> Self {
158 Self::new()
159 }
160}
161
162impl<T> core::fmt::Debug for ConfigCell<T> {
163 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
164 f.debug_struct("ConfigCell").finish_non_exhaustive()
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use serde::Deserialize;
173
174 use super::*;
175
176 #[derive(Debug, Clone, PartialEq, Deserialize)]
177 struct Settings {
178 interval_ms: u32,
179 verbose: bool,
180 }
181
182 impl Validate for Settings {
183 fn validate(&self) -> Result<(), &'static str> {
184 if self.interval_ms == 0 {
185 return Err("interval_ms of zero would spin");
186 }
187
188 Ok(())
189 }
190 }
191
192 #[test]
193 fn an_empty_cell_answers_nothing() {
194 let cell: ConfigCell<Settings> = ConfigCell::new();
195
196 assert!(!cell.is_set());
197 assert!(cell.get().is_none());
198 }
199
200 #[test]
201 fn a_stored_value_comes_back() {
202 let cell = ConfigCell::new();
203
204 cell.store(Settings {
205 interval_ms: 1000,
206 verbose: false,
207 });
208
209 assert_eq!(cell.get().unwrap().interval_ms, 1000);
210 }
211
212 #[cfg(feature = "json")]
213 #[test]
214 fn a_document_replaces_the_configuration() {
215 let cell = ConfigCell::new();
216
217 cell.store(Settings {
218 interval_ms: 1000,
219 verbose: false,
220 });
221
222 cell.apply(br#"{"interval_ms": 250, "verbose": true}"#, Format::Json)
223 .expect("the document fits");
224
225 assert_eq!(
226 cell.get().unwrap(),
227 Settings {
228 interval_ms: 250,
229 verbose: true,
230 }
231 );
232 }
233
234 #[cfg(feature = "json")]
235 #[test]
236 fn a_document_that_does_not_parse_leaves_the_previous_one_serving() {
237 let cell = ConfigCell::new();
238
239 cell.store(Settings {
240 interval_ms: 1000,
241 verbose: false,
242 });
243
244 let error = cell
245 .apply(b"{not json", Format::Json)
246 .expect_err("that is not a document");
247
248 assert_eq!(error.kind(), ErrorKind::Parse);
249 assert_eq!(
250 cell.get().unwrap().interval_ms,
251 1000,
252 "a bad document must not take the device's configuration with it"
253 );
254 }
255
256 #[cfg(feature = "json")]
257 #[test]
258 fn a_document_that_fails_validation_is_refused_whole() {
259 let cell = ConfigCell::new();
260
261 cell.store(Settings {
262 interval_ms: 1000,
263 verbose: false,
264 });
265
266 let error = cell
267 .apply(br#"{"interval_ms": 0, "verbose": true}"#, Format::Json)
268 .expect_err("zero would spin");
269
270 assert_eq!(error.kind(), ErrorKind::Invalid);
271 assert_eq!(error.message(), "interval_ms of zero would spin");
272 assert!(
273 !cell.get().unwrap().verbose,
274 "not even the fields that were fine"
275 );
276 }
277
278 #[cfg(feature = "json")]
279 #[test]
280 fn a_document_missing_a_field_is_a_type_error_not_a_parse_error() {
281 let cell: ConfigCell<Settings> = ConfigCell::new();
282
283 let error = cell
284 .apply(br#"{"interval_ms": 250}"#, Format::Json)
285 .expect_err("`verbose` is missing");
286
287 assert_eq!(error.kind(), ErrorKind::Type);
291 }
292
293 #[cfg(feature = "std")]
296 #[test]
297 fn debug_never_prints_the_configuration() {
298 extern crate std;
299
300 let cell = ConfigCell::new();
301
302 cell.store(Settings {
303 interval_ms: 1234,
304 verbose: true,
305 });
306
307 assert!(!std::format!("{cell:?}").contains("1234"));
308 }
309}