Skip to main content

tango/server/
attr.rs

1//! Implementation of attribute instantiations.
2
3use std::time::{Duration, Instant, SystemTime};
4use std::sync::{Arc, Mutex};
5use std::cmp::Ordering;
6use std::borrow::Cow;
7
8use tracing::warn;
9
10use crate::dev::{AttrConfig, AttrDeltaConfig, AttrInfo, AttrMemorized, AttrMode, AttrNumber, AttrQuality, AttrRangeConfig, AttrRead, 
11    AttrValueEvent, Named, RecordedResult, DevSource, DisplayLevel};
12use crate::value::{AttrDataKind, DevEnum, Spectrum, Value, ValueFormat};
13use crate::error::{reason, Error, ErrorKind, Result};
14use crate::buf::Element;
15
16use super::dev::{Access, Dev, ModelRef, ReadCtx};
17use super::def::{AttrDef, ReadFn, WriteFn};
18use super::history::History;
19use super::polling::PolledKind;
20use super::Model;
21
22
23/// An attribute of a single device, together with the last value written to it, which
24/// Tango keeps aside from the model, and its configuration on that device. Its definition
25/// is shared the way that of a [`Command`](super::Command) is.
26pub struct Attr<M: Model> {
27    /// The definition, shared with the class when the class defines it.
28    def: Arc<AttrDef<M>>,
29    /// What that attribute is on its device, which is how it is read, see [`AttrRole`].
30    role: AttrRole,
31    /// The last value written to that attribute, which every read of it gives back
32    /// alongside the read value, so that a client always knows what the device was asked
33    /// for as well as what it does.
34    /// 
35    /// Behind a lock of its own rather than in the `DevMutable` of the device: it is
36    /// updated by a write, which holds the model, and the interface store is the one lock
37    /// taken *under* the model, never around it.
38    w_value: Mutex<Option<Value>>,
39    /// When that attribute was last written, which the delta alarm gives the device time
40    /// from to reach the value written, see [`AttrDeltaConfig`]. A set point the device
41    /// gives itself, or restores, is not a write and gives it none.
42    w_time: Mutex<Option<Instant>>,
43    /// The readings recorded of that attribute, which a read from the cache is answered
44    /// with, see [`Self::push_history`].
45    history: Mutex<History<AttrRead<Value>>>,
46    /// The configuration of that attribute on its device, see [`AttrSetup`], behind a lock
47    /// of its own for the same reason as the write value.
48    setup: Mutex<AttrSetup>,
49}
50
51/// What an attribute is on its device, which is how it is read: through the read function
52/// of its definition, or derived by the device itself for the two every device has, which
53/// a command of the same name reads as well.
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub(super) enum AttrRole {
56    /// An attribute of the interface of the device, read through its definition.
57    Interface,
58    /// The State, derived through the state function of the class, see [`Dev::model_state`].
59    State,
60    /// The Status, derived through the status function of the class, see
61    /// [`Dev::model_status`].
62    Status,
63}
64
65/// How an attribute is configured on its device: the configuration of its definition,
66/// which the database overrides once it is asked, see [`Attr::load_config`].
67struct AttrSetup {
68    /// The inner attribute config that is actually being returned when getting attribute
69    /// configuration.
70    config: AttrConfig,
71    /// What the database holds of the attribute that could not be applied, which every
72    /// read and write of it fails with until the device is initialized again: an attribute
73    /// whose bounds or units are not the ones it was configured with is one a client must
74    /// not trust.
75    error: Option<Error>,
76    /// Whether the database was asked for that configuration.
77    loaded: bool,
78    /// The memorized value the database holds for an attribute that writes it again, which
79    /// is written once the model is built, see [`Attr::restore`].
80    restore: Option<Value>,
81    /// Why the memorized value could not be restored, which every read fails with until a
82    /// write gives the attribute a value again: a client must not take for the set point a
83    /// value the device was never given back.
84    mem_error: Option<Error>,
85}
86
87impl<M: Model> Attr<M> {
88
89    /// A new attribute of the interface of a device, from its definition, which nothing was
90    /// written to yet.
91    pub(super) fn new(def: Arc<AttrDef<M>>) -> Self {
92        Self::new_with_role(def, AttrRole::Interface)
93    }
94
95    /// A new attribute of a device that is that role on it, see [`Self::new`].
96    pub(super) fn new_with_role(def: Arc<AttrDef<M>>, role: AttrRole) -> Self {
97        
98        let setup = AttrSetup { 
99            config: def.config.clone(), 
100            error: None, 
101            loaded: false, 
102            restore: None, 
103            mem_error: None,
104        };
105
106        Self { 
107            def, 
108            role,
109            w_value: Mutex::new(None), 
110            w_time: Mutex::new(None),
111            setup: Mutex::new(setup),
112            history: Mutex::new(History::new()),
113        }
114
115    }
116
117    /// The name of that attribute, as its definition gave it.
118    #[inline]
119    pub fn name(&self) -> &str {
120        &self.def.name
121    }
122
123    /// The configuration of that attribute on its device.
124    pub fn config(&self) -> AttrConfig {
125        self.setup.lock().unwrap().config.clone()
126    }
127
128    /// What that attribute is on its device, see [`AttrRole`].
129    #[inline]
130    pub(super) fn role(&self) -> AttrRole {
131        self.role
132    }
133
134    /// The kind of data that attribute holds, as its definition gave it, which one with
135    /// no handler yet does not know.
136    pub(super) fn data_kind(&self) -> Result<AttrDataKind> {
137        self.def.kind.ok_or_else(|| Error::new(ErrorKind::Internal)
138            .with_description(format!("Attribute {:?} does not advertise the kind of its data", self.name())))
139    }
140
141    /// The display level that attribute is shown at, as its definition gave it.
142    #[inline]
143    pub(super) fn display_level(&self) -> DisplayLevel {
144        self.def.display_level
145    }
146
147    /// The period the definition of that attribute asks it to be polled at, which the
148    /// database has the last word on, see [`AttrDef::polled`].
149    #[inline]
150    pub(super) fn poll_period(&self) -> Option<Duration> {
151        self.def.poll_period
152    }
153
154    /// Whether that attribute can be polled, which is whether it has a value to read: a
155    /// write-only attribute has none to record, see [`Self::push_history`].
156    #[inline]
157    pub(super) fn is_pollable(&self) -> bool {
158        self.role != AttrRole::Interface || self.def.read.is_some()
159    }
160
161    /// Whether the database was asked for the configuration of that attribute, which is
162    /// done for the whole device at once, see [`Dev::init`].
163    pub(super) fn is_loaded(&self) -> bool {
164        self.setup.lock().unwrap().loaded
165    }
166
167    /// Whether that attribute has any alarm configured: a bound of its alarm or warning
168    /// range, or a delta, which is what a device in alarm looks at, see
169    /// [`Dev::set_auto_alarm_states`].
170    pub(super) fn has_alarms(&self) -> bool {
171        let config = &self.setup.lock().unwrap().config;
172        config.alarm.is_set() || config.warning.is_set() || config.delta.is_some()
173    }
174
175    /// Configure that attribute from what the database holds of it: the properties it holds
176    /// for the attribute at the level of the class and then of the device, the device
177    /// overriding the class, and both the definition.
178    /// 
179    /// A level the database could not be asked for, and a property that cannot be applied,
180    /// leave what came before them in place and are kept as the error of the attribute, see
181    /// [`AttrSetup::error`].
182    /// 
183    /// The memorized value of the attribute, which only the device holds, is taken here
184    /// too the first time: it is the set point right away for an attribute that only
185    /// restores it, and is kept to be written for one that writes it again, see
186    /// [`Self::restore`]. Configuring it again, once a client changed its configuration,
187    /// leaves its set point alone.
188    pub(super) fn load_config(&self, dev: &Dev<M>, class: PropsResult<'_>, device: PropsResult<'_>) {
189
190        let mut loader = ConfigLoader::new(&self.def.config, self.def.kind);
191        loader.apply_level("class", class);
192        loader.apply_level("device", device);
193
194        let (config, mut error) = loader.finish();
195        if let Some(incoherence) = incoherence(&config) {
196            error = Some(match error {
197                Some(error) => error.with_append_description(incoherence),
198                None => config_err(incoherence),
199            });
200        }
201
202        if let Some(error) = &error {
203            warn!(dev = %dev.name(), attr = %self.name(), err = %error, "Failed to configure the attribute from the database");
204        }
205
206        let mut setup = self.setup.lock().unwrap();
207        if setup.loaded {
208            setup.config = config;
209            setup.error = error;
210            return;
211        }
212
213        let mut restore = None;
214        let mut mem_error = None;
215
216        // Check if the device attribute properties have a __value property...
217        let mem_value = device.ok()
218            .and_then(|props| props
219                .iter()
220                .find(|prop| prop.name.value().eq_ignore_ascii_case(MEM_VALUE)));
221
222        if let (Some(mode), Some(mem_value)) = (self.def.memorized, mem_value) {
223            match parse_mem_value(self.def.kind.as_ref(), &mem_value.value) {
224                Ok(None) => {}
225                Ok(Some(value)) => match mode {
226                    AttrMemorized::RestoreSetPoint => *self.w_value.lock().unwrap() = Some(value),
227                    AttrMemorized::RestoreAndWrite => restore = Some(value),
228                }
229                Err(err) => {
230                    warn!(dev = %dev.name(), attr = %self.name(), %err, "Failed to parse the memorized value");
231                    mem_error = Some(Error::new(ErrorKind::Reason(reason::API_AttrConfig))
232                        .with_description(format!("The memorized value cannot be parsed: {err}")));
233                }
234            }
235        }
236
237        *setup = AttrSetup { 
238            config, 
239            error, 
240            loaded: true, 
241            restore, 
242            mem_error,
243        };
244
245    }
246
247    /// Change the configuration of that attribute to the requested one, given what the
248    /// database holds for its class, giving back what that does to what the database holds
249    /// for it on its device.
250    /// 
251    /// The requested configuration is the whole configuration the attribute is to have, which
252    /// a client gets by reading the one it has and changing what it means to, so every field
253    /// is one of:
254    /// 
255    /// - what the attribute has already, which is left alone;
256    /// - a value, stored for the device, unless it is what the device inherits, in which case
257    ///   the device has nothing of its own to store and it is deleted;
258    /// - no value, which brings the field back to what the device inherits, deleting what it
259    ///   has of its own.
260    /// 
261    /// No value therefore never means *no value* when the device inherits one, which is the
262    /// one thing cppTango does that this does not: forcing the library default over the
263    /// defaults of the class.
264    /// 
265    /// The requested configuration is refused, nothing being changed, if it gives a value
266    /// to a property the kind of the attribute does not support, see
267    /// [`ConfigProp::supported_by`]. The configuration the attribute ends up with is refused
268    /// too if it is incoherent, see [`incoherence`], or if it leaves out the set point of a memorized
269    /// attribute, which its next initialization would then fail to restore. Once changed, the
270    /// error of the attribute is what could not be applied of its class, see
271    /// [`AttrSetup::error`].
272    pub(super) fn change_config(&self, class: &[Named<Spectrum<str>>], requested: &AttrConfig) -> std::result::Result<ConfigChanges, String> {
273
274        // What the device inherits, the definition which the class overrides, which is what
275        // the configuration it ends up with starts from: every field is the inherited one
276        // unless the device has one of its own.
277        let mut config = ConfigLoader::new(&self.def.config, self.def.kind);
278        config.apply_level("class", Ok(class));
279        let (mut config, class_error) = config.finish();
280
281        if let Some(prop) = unsupported_prop(requested, self.def.kind) {
282            return Err(format!("its property {:?} is {}", prop.name(), unsupported(self.def.kind)));
283        }
284
285        let w_value = self.def.memorized.and_then(|_| self.w_value.lock().unwrap().clone());
286
287        let mut setup = self.setup.lock().unwrap();
288        let mut changes = ConfigChanges::default();
289
290        for prop in ConfigProp::ALL {
291
292            let target = prop_value(requested, prop);
293            let now = prop_value(&setup.config, prop);
294            let inherited = prop_value(&config, prop);
295
296            if target.is_unset() || target == inherited {
297                // The device has something of its own only if what it has is not inherited.
298                if now != inherited {
299                    changes.del.push(prop);
300                }
301            } else {
302                if target != now {
303                    changes.put.push((prop, target.to_values()));
304                }
305                copy_prop(&mut config, requested, prop);
306            }
307
308        }
309
310        if let Some(incoherence) = incoherence(&config) {
311            return Err(incoherence);
312        }
313
314        if let Some(w_value) = &w_value
315        && any_number(w_value, |number| outside(&config.range, number)) {
316            return Err("its memorized value would be outside of its range".to_string());
317        }
318
319        // The configuration is the requested one over the inherited one, so what could not
320        // be applied of the class is all that is left wrong with it.
321        setup.config = config;
322        setup.error = class_error;
323        Ok(changes)
324
325    }
326
327    /// Write the memorized value the database held for that attribute, if it writes it
328    /// again and its device has just been initialized into that model, see
329    /// [`AttrMemorized::RestoreAndWrite`]. The value is written as a client would write
330    /// it, but is not stored again, being the one the database already holds.
331    /// 
332    /// An attribute whose configuration could not be applied is not written: its bounds
333    /// are not the ones it was configured with.
334    pub(super) async fn restore(&self, dev: &Dev<M>, model: &mut M) {
335
336        let Some(value) = self.setup.lock().unwrap().restore.take() else {
337            return;
338        };
339
340        if self.check_config(dev, false).is_err() {
341            return;
342        }
343
344        if let Err(err) = self.write_inner(dev, &mut ModelRef::Exclusive(model), value).await {
345            warn!(dev = %dev.name(), attr = %self.name(), %err, "Failed to write the memorized value back");
346            self.setup.lock().unwrap().mem_error = Some(Error::new(err.kind())
347                .with_description("The memorized value cannot be written back")
348                .with_parent(err));
349        }
350
351    }
352
353    /// Fail if the configuration of that attribute could not be applied, see
354    /// [`AttrSetup::error`], and, when asked to, if its memorized value could not be
355    /// restored, see [`AttrSetup::mem_error`], which only fails a read: a write is what
356    /// gives the attribute a set point again.
357    fn check_config(&self, dev: &Dev<M>, check_mem: bool) -> Result<()> {
358        
359        let setup = self.setup.lock().unwrap();
360        
361        if let Some(error) = &setup.error {
362            return Err(Error::new(ErrorKind::Reason(reason::API_AttrConfig))
363                .with_description(format!("Attribute {:?} of device {:?} has a wrong \
364                    configuration in the database", self.name(), dev.name()))
365                .with_parent(error.clone()));
366        }
367
368        if check_mem && let Some(error) = &setup.mem_error {
369            return Err(Error::new(ErrorKind::Reason(reason::API_MemAttFailedDuringInit))
370                .with_description(format!("Attribute {:?} of device {:?} has no set point \
371                    and has to be written again", self.name(), dev.name()))
372                .with_parent(error.clone()));
373        }
374
375        Ok(())
376
377    }
378
379    /// Whether that attribute is memorized, see [`AttrMemorized`].
380    #[inline]
381    pub fn is_memorized(&self) -> bool {
382        self.def.memorized.is_some()
383    }
384
385    /// Give that attribute a new set point without writing it: the write function is not
386    /// called, and the value is what every read gives back as the write value from then
387    /// on, and what the database remembers of a memorized attribute.
388    /// 
389    /// This is for a device whose set point changes by itself, a motor stopped short of its
390    /// target for instance, so that a client and the next initialization find the value
391    /// the device actually works towards. An attribute that cannot be written has no set
392    /// point to give, see [`Self::check_writable`].
393    pub(super) async fn set_w_value(&self, dev: &Dev<M>, value: Value) -> Result<()> {
394        self.check_writable(dev)?;
395        *self.w_value.lock().unwrap() = Some(value.clone());
396        self.setup.lock().unwrap().mem_error = None;
397        self.memorize(dev, &value).await
398    }
399
400    /// Store that value as the memorized value of that attribute, if it is memorized.
401    async fn memorize(&self, dev: &Dev<M>, value: &Value) -> Result<()> {
402        
403        if self.def.memorized.is_none() {
404            return Ok(());
405        }
406
407        let mem_value = mem_value_str(value)
408            .map_err(|err| Error::new(ErrorKind::Reason(reason::API_AttrConfig))
409                .with_description(format!("Attribute {:?} of device {:?} cannot be memorized, {err}", 
410                    self.name(), 
411                    dev.name())))?;
412
413        dev.server()
414            .database()
415            .set_dev_attr_prop_async(dev.name(), (self.name(), (MEM_VALUE, mem_value)))
416            .await
417
418    }
419
420    /// The access reading that attribute needs to the model, which for the State and the
421    /// Status is the one deriving them needs, see [`Dev::state_access`].
422    pub(super) fn read_access(&self, dev: &Dev<M>, ctx: &ReadCtx<M>) -> Access {
423        match (self.role, &self.def.read) {
424            (AttrRole::State, _) => 
425                dev.state_access(ctx),
426            (AttrRole::Status, _) => 
427                dev.status_access(ctx),
428            (AttrRole::Interface, Some(ReadFn::ExclusiveSync(_) | ReadFn::ExclusiveAsync(_))) => 
429                Access::Exclusive,
430            (AttrRole::Interface, _) => 
431                Access::Shared,
432        }
433    }
434
435    /// The access writing that attribute needs to the model.
436    pub(super) fn write_access(&self) -> Access {
437        match &self.def.write {
438            Some(WriteFn::ExclusiveSync(_) | WriteFn::ExclusiveAsync(_)) => Access::Exclusive,
439            _ => Access::Shared,
440        }
441    }
442
443    /// Whether reading that attribute may block, its read function being synchronous, see
444    /// [`crate::pool::run`], or deriving the State or the Status, see
445    /// [`Dev::state_may_block`].
446    pub(super) fn read_may_block(&self, dev: &Dev<M>, ctx: &ReadCtx<M>) -> bool {
447        match (self.role, &self.def.read) {
448            (AttrRole::State, _) => 
449                dev.state_may_block(ctx),
450            (AttrRole::Status, _) => 
451                dev.status_may_block(ctx),
452            (AttrRole::Interface, read) => 
453                matches!(read, Some(ReadFn::SharedSync(_) | ReadFn::ExclusiveSync(_))),
454        }
455    }
456
457    /// Whether writing that attribute may block, see [`Self::read_may_block`].
458    pub(super) fn write_may_block(&self) -> bool {
459        matches!(&self.def.write, Some(WriteFn::SharedSync(_) | WriteFn::ExclusiveSync(_)))
460    }
461
462    /// What a client is told of that attribute: what it may read and change of its
463    /// configuration, and what its definition advertises of its data.
464    /// 
465    /// A definition advertising neither a kind nor a format, which only one built out of a
466    /// raw handler and given neither can be, has nothing to tell a client of its data and
467    /// is answered with an error rather than with a guess.
468    /// 
469    /// An attribute whose configuration could not be applied is described all the same,
470    /// with what could be, so that a client can still see what it is configured with.
471    /// 
472    /// TODO: [`AttrMode::ReadWithWrite`] cannot be derived from the handlers, the write
473    /// attribute it is paired with having to be named; it needs its own definition.
474    pub fn info(&self) -> Result<AttrInfo> {
475
476        let mode = match (self.role, &self.def.read, &self.def.write) {
477            // The State and the Status have no handler, the device deriving them itself.
478            (AttrRole::State | AttrRole::Status, _, _) => AttrMode::Read,
479            (AttrRole::Interface, Some(_), Some(_)) => AttrMode::ReadWrite,
480            (AttrRole::Interface, Some(_), None) => AttrMode::Read,
481            (AttrRole::Interface, None, Some(_)) => AttrMode::Write,
482            // An attribute that has no handler yet is advertised as anything a handler
483            // could make it, every read and write of it failing, see [`Self::no_handler_err`].
484            (AttrRole::Interface, None, None) => AttrMode::ReadWrite,
485        };
486
487        Ok(AttrInfo {
488            config: self.config(),
489            mode,
490            data_kind: self.data_kind()?,
491            data_format: self.def.format
492                .ok_or_else(|| Error::new(ErrorKind::Internal)
493                    .with_description(format!("Attribute {:?} does not advertise the \
494                        format of its data", self.name())))?,
495            display_level: self.def.display_level,
496            memorized: self.def.memorized,
497        })
498
499    }
500
501    /// The error of reading or writing that attribute when it has no handler at all, see
502    /// [`Self::info`].
503    fn no_handler_err(&self, dev: &Dev<M>) -> Error {
504        Error::not_implemented()
505            .with_description(format!("Attribute {:?} of device {:?} has no handler defined",
506                self.name(), dev.name()))
507    }
508
509    /// The error writing that attribute fails with when it cannot be written, see
510    /// [`Self::check_writable`].
511    fn not_writable_err(&self, dev: &Dev<M>) -> Error {
512        Error::new(ErrorKind::AttrNotWritable)
513            .with_description(format!("Attribute {:?} of device {:?} is not writable", self.name(), dev.name()))
514    }
515
516    /// Refuse a subscription to the events of that kind of that attribute if nothing ever
517    /// sends them, and a push of one the device did not declare: the change and archive
518    /// events are only sent once the device declares it pushes them, see
519    /// [`AttrDef::push_change_event`], and the periodic ones only while the attribute is
520    /// polled, see [`super::polling`]. The user and alarm events, which only a device
521    /// pushes, are always allowed, as cppTango allows them.
522    ///
523    /// TODO: The change and archive events of a polled attribute, which the polling sends
524    /// once their criteria are checked.
525    pub(super) fn check_push_event(&self, dev: &Dev<M>, event: AttrValueEvent) -> Result<()> {
526
527        let pushed = match event {
528            AttrValueEvent::Change => self.def.pushed.change.is_some(),
529            AttrValueEvent::Archive => self.def.pushed.archive.is_some(),
530            AttrValueEvent::Periodic => dev.server().poller()
531                .is_some_and(|poller| poller.period(dev.name(), PolledKind::Attr, self.name()).is_some()),
532            AttrValueEvent::User | 
533            AttrValueEvent::Alarm => true,
534        };
535
536        if pushed {
537            return Ok(());
538        }
539
540        Err(Error::new(ErrorKind::Reason(reason::API_AttributePollingNotStarted))
541            .with_description(format!("Attribute {:?} of device {:?} is not polled and its \
542                {event:?} events are not pushed by the device", self.name(), dev.name())))
543
544    }
545
546    /// Check that the device announces this attribute ready to be read, see
547    /// [`AttrDef::push_data_ready_event`], which both an announcement and a subscription to
548    /// it need.
549    pub(super) fn check_data_ready(&self, dev: &Dev<M>) -> Result<()> {
550        if self.def.pushed.data_ready {
551            return Ok(());
552        }
553        Err(Error::new(ErrorKind::Reason(reason::API_AttributeNotDataReadyEnabled))
554            .with_description(format!("Attribute {:?} of device {:?} is not announced ready \
555                to be read by the device", self.name(), dev.name())))
556    }
557
558    /// The alarm that reading is in: the one the read function gave it, or else the one
559    /// the configuration of that attribute puts it in, outside of the alarm range, outside
560    /// of the warning range, or too far from the value written for longer than the device
561    /// is given to reach it, see [`AttrDeltaConfig`].
562    /// 
563    /// A quality the read function gave is never overridden, the device always having the
564    /// last word on the quality of its own readings, and it counts the way cppTango counts
565    /// it: an ALARM or a WARNING it gave is an alarm all the same.
566    fn alarm(&self, read: &AttrRead<Value>) -> Option<AttrAlarm> {
567
568        let value = match (read.quality, &read.value) {
569            (AttrQuality::Alarm | AttrQuality::Warning, _) => return Some(AttrAlarm::Given(read.quality)),
570            (AttrQuality::Valid, Some(value)) => value,
571            _ => return None,
572        };
573
574        let (alarm, warning, delta) = {
575            let config = &self.setup.lock().unwrap().config;
576            (config.alarm, config.warning, config.delta)
577        };
578
579        if any_number(value, |number| outside(&alarm, number)) {
580            return Some(AttrAlarm::Alarm);
581        }
582
583        if any_number(value, |number| outside(&warning, number)) {
584            return Some(AttrAlarm::Warning);
585        }
586
587        if let (Some(delta), Some(w_value)) = (delta, &read.w_value) {
588            let written = *self.w_time.lock().unwrap();
589            let settled = written.is_some_and(|time| time.elapsed() >= delta.time.unwrap_or_default());
590            if settled && drifted(value, w_value, delta.value) {
591                return Some(AttrAlarm::Delta);
592            }
593        }
594
595        None
596
597    }
598
599    /// Read that attribute, see [`Self::read`], giving back along with the reading the
600    /// alarm its configuration puts it in, if any, see [`Self::alarm`].
601    pub(super) async fn read_with_alarm(&self, dev: &Dev<M>, model: &mut ModelRef<'_, M>, ctx: &mut ReadCtx<M>, source: DevSource) -> Result<(AttrRead<Value>, Option<AttrAlarm>)> {
602        let mut read = self.read_inner(dev, model, ctx, source).await?;
603        let alarm = self.alarm(&read);
604        if let Some(alarm) = alarm {
605            read.quality = alarm.quality();
606        }
607        Ok((read, alarm))
608    }
609
610    /// Read that attribute on the model, borrowed with the access [`Self::read_access`]
611    /// asked for, its quality being the one its configuration gives it, see
612    /// [`Self::alarm`]. The request reading it is what the State and the Status are
613    /// derived along with, see [`ReadCtx`].
614    pub(super) async fn read(&self, dev: &Dev<M>, model: &mut ModelRef<'_, M>, ctx: &mut ReadCtx<M>, source: DevSource) -> Result<AttrRead<Value>> {
615        self.read_with_alarm(dev, model, ctx, source).await.map(|(read, _)| read)
616    }
617
618    /// Read that attribute as its device gives it, see [`Self::read`].
619    async fn read_inner(&self, dev: &Dev<M>, model: &mut ModelRef<'_, M>, ctx: &mut ReadCtx<M>, source: DevSource) -> Result<AttrRead<Value>> {
620
621        self.check_config(dev, true)?;
622
623        // A read from the cache is answered with the latest reading recorded, its write
624        // value merged when it was, and only a read from the cache alone fails without
625        // one, the other reading the device instead.
626        if let DevSource::CacheDev | DevSource::Cache = source {
627
628            let history = self.history.lock().unwrap();
629            if let Some(read) = history.latest() {
630                return read.clone();
631            }
632
633            if let DevSource::Cache = source {
634                if history.is_empty() {
635                    return Err(Error::new(ErrorKind::AttrNotPolled)
636                        .with_description(format!("Attribute {:?} of device {:?} has no reading \
637                            recorded to be read from the cache", 
638                            self.name(), dev.name())));
639                } else {
640                    return Err(Error::new(ErrorKind::ValueOutdated)
641                        .with_description(format!("The latest reading recorded of attribute {:?} \
642                            of device {:?} is outdated", 
643                            self.name(), dev.name())));
644                }
645            }
646
647        }
648
649        // Boxed since deriving them reads the attributes that may put the device in ALARM,
650        // through here as well.
651        match self.role {
652            AttrRole::State => return Ok(AttrRead::new(Box::pin(dev.model_state(model, ctx)).await?.into())),
653            AttrRole::Status => return Ok(AttrRead::new(Box::pin(dev.model_status(model, ctx)).await?.into())),
654            AttrRole::Interface => {}
655        }
656
657        let mut read = match &self.def.read {
658            Some(ReadFn::SharedSync(func)) => func(model.shared(), dev)?,
659            Some(ReadFn::ExclusiveSync(func)) => func(model.exclusive()?, dev)?,
660            Some(ReadFn::SharedAsync(func)) => func(model.shared(), dev).await?,
661            Some(ReadFn::ExclusiveAsync(func)) => func(model.exclusive()?, dev).await?,
662            None if self.def.write.is_none() => return Err(self.no_handler_err(dev)),
663            // A write-only attribute has nothing to read but what was written to it, which
664            // is what Tango gives for one: the set point back, and no read value, rather
665            // than an error.
666            None => AttrRead {
667                value: None,
668                w_value: None,
669                quality: AttrQuality::Valid,
670                time: SystemTime::now(),
671            },
672        };
673
674        self.merge_w_value(&mut read);
675        Ok(read)
676
677    }
678
679    /// Fail if that reading is not one a read of that attribute gives, which is what a
680    /// device pushes as an event or records in the history: it has a read value, so an
681    /// attribute that cannot be read has none to push, and the State and the Status are
682    /// always of a valid quality, their commands answering with the value alone.
683    pub(super) fn check_pushed(&self, read: &AttrRead<Value>) -> Result<()> {
684
685        if read.value.is_none() {
686            return Err(Error::new(ErrorKind::InvalidData)
687                .with_description(format!("Attribute {:?} cannot be pushed a reading with no read value", 
688                    self.name())));
689        }
690
691        if self.role != AttrRole::Interface 
692        && read.quality != AttrQuality::Valid {
693            return Err(Error::new(ErrorKind::InvalidData)
694                .with_description(format!("Attribute {:?} cannot be pushed a reading of {:?} quality, only a valid one", 
695                    self.name(), read.quality)));
696        }
697
698        Ok(())
699
700    }
701
702    /// Give a reading that has no write value the last one that was written, and keep the
703    /// write value of one that has it, so that the readings that follow give that one back.
704    /// Either way what a client gets, read or pushed, does not depend on the model having
705    /// kept the value it was written.
706    pub(super) fn merge_w_value(&self, read: &mut AttrRead<Value>) {
707        match &read.w_value {
708            None => read.w_value = self.w_value.lock().unwrap().clone(),
709            Some(w_value) => *self.w_value.lock().unwrap() = Some(w_value.clone()),
710        }
711    }
712
713    /// Record that reading in the history of that attribute, the oldest one being dropped
714    /// when the history is full, see [`Self::set_history_depth`]. A reading that failed
715    /// is recorded as well, as the error it failed with.
716    /// 
717    /// The latest reading is what a read from the cache is answered with, for that long
718    /// when given a validity, after which the device is read again, or a read from the
719    /// cache alone fails as outdated; without one, it answers until the next is recorded.
720    /// 
721    /// The reading must have a read value, so an attribute that cannot be read records none,
722    /// and be of a valid quality for the State and the Status, their commands answering
723    /// with the value alone. The write value is merged into it as for an event, see
724    /// [`Dev::push_attr_event`].
725    pub fn push_history(&self, mut read: Result<AttrRead<Value>>, valid_for: Option<Duration>) -> Result<()> {
726
727        if let Ok(read) = &mut read {
728            self.check_pushed(read)?;
729            self.merge_w_value(read);
730        }
731
732        let time = match &read {
733            Ok(read) => read.time,
734            Err(_) => SystemTime::now(),
735        };
736
737        self.history.lock().unwrap().push(time, read, valid_for);
738        Ok(())
739
740    }
741
742    /// The latest readings recorded in the history of that attribute, oldest first and at
743    /// most `max` of them, see [`Self::push_history`].
744    pub fn history(&self, max: usize) -> Vec<RecordedResult<AttrRead<Value>>> {
745        self.history.lock().unwrap().entries(max)
746    }
747
748    /// Forget every reading recorded in the history of that attribute, so that a read from
749    /// the cache finds none.
750    pub fn clear_history(&self) {
751        self.history.lock().unwrap().clear();
752    }
753
754    /// How many readings the history of that attribute keeps at most.
755    pub fn history_depth(&self) -> usize {
756        self.history.lock().unwrap().depth()
757    }
758
759    /// Set how many readings the history of that attribute keeps at most, the oldest ones
760    /// beyond that being dropped now. A depth of zero records nothing.
761    pub fn set_history_depth(&self, depth: usize) {
762        self.history.lock().unwrap().set_depth(depth);
763    }
764
765    /// Write that attribute on the model, borrowed with the access [`Self::write_access`]
766    /// asked for, keep the value that was written and store it if the attribute is
767    /// memorized.
768    /// 
769    /// A value the device accepted but the database could not store fails the write all
770    /// the same, the way cppTango does: it is the set point of the device, but not the one
771    /// its next initialization finds.
772    /// 
773    /// An attribute that cannot be written is refused before anything else, see
774    /// [`Self::check_writable`]: the State and the Status have no configuration a write
775    /// would have to be checked against.
776    pub(super) async fn write(&self, dev: &Dev<M>, model: &mut ModelRef<'_, M>, value: Value) -> Result<()> {
777        self.check_writable(dev)?;
778        self.check_config(dev, false)?;
779        self.write_inner(dev, model, value.clone()).await?;
780        self.memorize(dev, &value).await
781    }
782
783    /// Fail if that attribute cannot be written: the State and the Status, which are what
784    /// the device says of itself, and an attribute that has no write function, failing with
785    /// [`Self::no_handler_err`] when it has no handler at all.
786    fn check_writable(&self, dev: &Dev<M>) -> Result<()> {
787        match (self.role, &self.def.read, &self.def.write) {
788            (AttrRole::State | AttrRole::Status, _, _) => 
789                Err(self.not_writable_err(dev)),
790            (AttrRole::Interface, _, Some(_)) => 
791                Ok(()),
792            (AttrRole::Interface, None, None) => 
793                Err(self.no_handler_err(dev)),
794            (AttrRole::Interface, Some(_), None) => 
795                Err(self.not_writable_err(dev)),
796        }
797    }
798
799    /// Write that attribute on the model and keep the value that was written, see
800    /// [`Self::write`].
801    async fn write_inner(&self, dev: &Dev<M>, model: &mut ModelRef<'_, M>, value: Value) -> Result<()> {
802
803        self.check_limits(dev, &value)?;
804
805        // The handler and the server share the written value, the server keeping it for
806        // the reads.
807        match &self.def.write {
808            Some(WriteFn::SharedSync(func)) => func(model.shared(), dev, value.clone())?,
809            Some(WriteFn::ExclusiveSync(func)) => func(model.exclusive()?, dev, value.clone())?,
810            Some(WriteFn::SharedAsync(func)) => func(model.shared(), dev, value.clone()).await?,
811            Some(WriteFn::ExclusiveAsync(func)) => func(model.exclusive()?, dev, value.clone()).await?,
812            None if self.def.read.is_none() => return Err(self.no_handler_err(dev)),
813            None => return Err(self.not_writable_err(dev)),
814        }
815
816        // Only a write the device accepted is remembered: a write value a client reads
817        // back is one the device is actually working towards, which also gives back a set
818        // point to an attribute whose memorized value could not be restored.
819        *self.w_value.lock().unwrap() = Some(value);
820        *self.w_time.lock().unwrap() = Some(Instant::now());
821        self.setup.lock().unwrap().mem_error = None;
822        Ok(())
823
824    }
825
826    /// Fail if a number of that value is outside the range of values of that attribute,
827    /// which is the range a value written to it has to be in, see [`AttrConfig::range`].
828    /// A value that is not a number has no range to be outside of.
829    fn check_limits(&self, dev: &Dev<M>, value: &Value) -> Result<()> {
830
831        let range = self.setup.lock().unwrap().config.range;
832        if !any_number(value, |number| outside(&range, number)) {
833            return Ok(());
834        }
835
836        let bound = |bound: Option<AttrNumber>| bound.map_or_else(|| "-".to_string(), |bound| bound.to_string());
837        Err(Error::new(ErrorKind::Reason(reason::API_WAttrOutsideLimit))
838            .with_description(format!("Value written to attribute {:?} of device {:?} is \
839                outside of its range [{}, {}]", self.name(), dev.name(), bound(range.min), bound(range.max))))
840        
841    }
842
843}
844
845/// The alarm the configuration of an attribute puts a reading of it in, see
846/// [`Attr::alarm`].
847#[derive(Debug, Clone, Copy, PartialEq, Eq)]
848pub enum AttrAlarm {
849    /// Outside of the alarm range.
850    Alarm,
851    /// Outside of the warning range.
852    Warning,
853    /// Too far from the value written.
854    Delta,
855    /// If the reading of a device already gave a Alarm/Warning quality on the attribute.
856    Given(AttrQuality),
857}
858
859impl AttrAlarm {
860
861    /// The quality of a reading in that alarm.
862    pub fn quality(self) -> AttrQuality {
863        match self {
864            Self::Alarm | Self::Delta => AttrQuality::Alarm,
865            Self::Warning => AttrQuality::Warning,
866            Self::Given(quality) => quality,
867        }
868    }
869
870    /// Why an attribute of that name is in that alarm, as the status of its device says.
871    pub fn describe(self, name: &str) -> String {
872        match self {
873            Self::Alarm => format!("Alarm: the attribute {name} is outside of its alarm range"),
874            Self::Warning => format!("Warning: the attribute {name} is outside of its warning range"),
875            Self::Delta => format!("Alarm: the attribute {name} is too far from the value written to it"),
876            Self::Given(AttrQuality::Warning) => format!("Warning: the attribute {name} is in WARNING"),
877            Self::Given(_) => format!("Alarm: the attribute {name} is in ALARM"),
878        }
879    }
880
881}
882
883// =========================== //
884//           Limits            //
885// =========================== //
886
887/// Whether that predicate holds for any number of that value, a value of a kind that is
888/// not a number having none: every element of an array is one of its numbers.
889fn any_number(value: &Value, mut pred: impl FnMut(AttrNumber) -> bool) -> bool {
890
891    macro_rules! any {
892        ($format:expr) => {
893            match $format {
894                ValueFormat::Scalar(data) => data.iter().any(|number| pred(number.into())),
895                ValueFormat::Spectrum(data) => data.iter().any(|number| pred(number.into())),
896                ValueFormat::Image(data) => data.iter().any(|number| pred(number.into())),
897            }
898        };
899    }
900
901    match value {
902        Value::Octet(format) => any!(format),
903        Value::UShort(format) => any!(format),
904        Value::Short(format) => any!(format),
905        Value::ULong(format) => any!(format),
906        Value::Long(format) => any!(format),
907        Value::ULong64(format) => any!(format),
908        Value::Long64(format) => any!(format),
909        Value::Float(format) => any!(format),
910        Value::Double(format) => any!(format),
911        _ => false,
912    }
913
914}
915
916/// Whether that number is outside of that range, both bounds being part of it, a number
917/// that cannot be compared with a bound, a not-a-number, being outside of any.
918fn outside(range: &AttrRangeConfig, number: AttrNumber) -> bool {
919    let below = range.min.is_some_and(|min| number.partial_cmp(&min).is_none_or(Ordering::is_lt));
920    let above = range.max.is_some_and(|max| number.partial_cmp(&max).is_none_or(Ordering::is_gt));
921    below || above
922}
923
924/// Whether a number of the read value is further than that from the number of the value
925/// written at the same place, the two being compared element by element.
926fn drifted(read: &Value, written: &Value, delta: AttrNumber) -> bool {
927
928    let mut written_numbers = Vec::new();
929    any_number(written, |number| {
930        written_numbers.push(number.as_f64());
931        false
932    });
933    
934    let delta = delta.as_f64();
935    let mut written_numbers = written_numbers.into_iter();
936    any_number(read, |number| written_numbers.next()
937        .is_some_and(|written| (number.as_f64() - written).abs() > delta))
938    
939}
940
941// =========================== //
942//   Database configuration    //
943// =========================== //
944
945/// What the database holds for an attribute at one level, or why it could not be asked.
946pub type PropsResult<'a> = std::result::Result<&'a [Named<Spectrum<str>>], &'a Error>;
947
948// The strings standing for a property that has no value, which bring the field back to no
949// value at all whatever the levels below gave it, see `NOT_SPECIFIED` in the CORBA module.
950const NOT_SPECIFIED: &str = "Not specified";
951const NOT_A_NUMBER: &str = "NaN";
952
953/// The configuration of an attribute being built out of the properties the database holds
954/// for it, starting from the one of its definition.
955struct ConfigLoader {
956    config: AttrConfig,
957    /// The data kind of that attribute, which its limits are read as, and which some
958    /// properties are not supported by, see [`ConfigProp::supported_by`].
959    kind: Option<AttrDataKind>,
960    /// The two halves of the delta alarm, which are two properties that each level may give
961    /// one of, and which only make an alarm together, see [`Self::finish`].
962    delta_value: Option<AttrNumber>,
963    delta_time: Option<Duration>,
964    /// Every property that could not be applied, described.
965    errors: Vec<String>,
966    /// The failure of the first level the database could not be asked for, which the
967    /// error of the configuration is caused by.
968    cause: Option<Error>,
969}
970
971impl ConfigLoader {
972
973    fn new(config: &AttrConfig, kind: Option<AttrDataKind>) -> Self {
974        Self {
975            config: config.clone(),
976            kind,
977            delta_value: config.delta.map(|delta| delta.value),
978            delta_time: config.delta.and_then(|delta| delta.time),
979            errors: Vec::new(),
980            cause: None,
981        }
982    }
983
984    /// Apply every property the database holds at that level, named after it in the errors.
985    fn apply_level(&mut self, level: &str, props: PropsResult<'_>) {
986        match props {
987            Ok(props) => {
988                for prop in props {
989                    if let Err(err) = self.apply(prop.name.value(), &prop.value) {
990                        self.errors.push(format!("{level} property {:?}: {err}", prop.name.value()));
991                    }
992                }
993            }
994            Err(err) => {
995                self.errors.push(format!("{level} properties cannot be read from the database"));
996                self.cause.get_or_insert_with(|| err.clone());
997            }
998        }
999    }
1000
1001    /// Apply the property of that name. A property that is not one of the configuration is
1002    /// left alone: the database holds more of an attribute than its configuration, the
1003    /// value of a memorized one for instance.
1004    fn apply(&mut self, name: &str, values: &Spectrum<str>) -> std::result::Result<(), String> {
1005        match ConfigProp::from_name(name) {
1006            Some(prop) => self.apply_prop(prop, values),
1007            None => Ok(()),
1008        }
1009    }
1010
1011    /// Apply that property, see [`Self::apply`]. A property the kind of the attribute does
1012    /// not support is refused unless it has no value.
1013    fn apply_prop(&mut self, prop: ConfigProp, values: &Spectrum<str>) -> std::result::Result<(), String> {
1014
1015        if !prop.supported_by(self.kind) {
1016            return match values.iter().all(|value| is_unset(value.trim())) {
1017                true => Ok(()),
1018                false => Err(unsupported(self.kind)),
1019            };
1020        }
1021        
1022        let config = &mut self.config;
1023        let limits = limit_kind(self.kind);
1024        
1025        match prop {
1026            ConfigProp::Description => config.doc = text(values),
1027            ConfigProp::Label => config.label = text(values),
1028            ConfigProp::Unit => config.unit = text(values),
1029            ConfigProp::StandardUnit => config.standard_unit = number(values)?,
1030            ConfigProp::DisplayUnit => config.display_unit = text(values),
1031            ConfigProp::Format => config.display_format = text(values),
1032            ConfigProp::MinValue => config.range.min = limit(values, limits)?,
1033            ConfigProp::MaxValue => config.range.max = limit(values, limits)?,
1034            ConfigProp::MinAlarm => config.alarm.min = limit(values, limits)?,
1035            ConfigProp::MaxAlarm => config.alarm.max = limit(values, limits)?,
1036            ConfigProp::MinWarning => config.warning.min = limit(values, limits)?,
1037            ConfigProp::MaxWarning => config.warning.max = limit(values, limits)?,
1038            ConfigProp::DeltaVal => self.delta_value = limit(values, limits)?,
1039            // A delta time of zero is how cppTango says there is none.
1040            ConfigProp::DeltaT => self.delta_time = duration(values)?.filter(|time| !time.is_zero()),
1041            ConfigProp::RelChange => config.change_event.rel_change = change(values)?,
1042            ConfigProp::AbsChange => config.change_event.abs_change = change(values)?,
1043            ConfigProp::EventPeriod => config.periodic_event.period = duration(values)?,
1044            ConfigProp::ArchiveRelChange => config.archive_event.rel_change = change(values)?,
1045            ConfigProp::ArchiveAbsChange => config.archive_event.abs_change = change(values)?,
1046            ConfigProp::ArchivePeriod => config.archive_event.period = duration(values)?,
1047            ConfigProp::EnumLabels => config.enum_labels = labels(values),
1048        }
1049
1050        Ok(())
1051
1052    }
1053
1054    /// The configuration that was built, and the error of what could not be applied, one
1055    /// line of its description per property.
1056    fn finish(self) -> (AttrConfig, Option<Error>) {
1057        let mut config = self.config;
1058        config.delta = self.delta_value.map(|value| AttrDeltaConfig { value, time: self.delta_time });
1059        let error = (!self.errors.is_empty()).then(|| {
1060            let error = config_err(self.errors.join("\n"));
1061            match self.cause {
1062                Some(cause) => error.with_parent(cause),
1063                None => error,
1064            }
1065        });
1066        (config, error)
1067    }
1068
1069}
1070
1071/// The value of one property of a configuration, borrowed from it, which is what two
1072/// configurations are compared by, property by property, with nothing written out: two
1073/// values that parse the same are the same value, however they were written.
1074#[derive(Debug, Clone, Copy, PartialEq)]
1075enum ConfigPropValue<'a> {
1076    Text(Option<&'a str>),
1077    Number(Option<f64>),
1078    Limit(Option<AttrNumber>),
1079    Duration(Option<Duration>),
1080    Labels(&'a [Cow<'static, str>]),
1081}
1082
1083impl ConfigPropValue<'_> {
1084
1085    /// Whether that property has no value.
1086    fn is_unset(self) -> bool {
1087        match self {
1088            Self::Text(text) => text.is_none(),
1089            Self::Number(number) => number.is_none(),
1090            Self::Limit(limit) => limit.is_none(),
1091            Self::Duration(duration) => duration.is_none(),
1092            Self::Labels(labels) => labels.is_empty(),
1093        }
1094    }
1095
1096    /// The values that property is stored as, a property with no value being stored as
1097    /// [`NOT_SPECIFIED`], a text being one value per line and a duration a number of
1098    /// milliseconds.
1099    fn to_values(self) -> Vec<String> {
1100        if self.is_unset() {
1101            return vec![NOT_SPECIFIED.to_string()];
1102        }
1103        match self {
1104            Self::Text(text) => text.unwrap_or_default().split('\n').map(str::to_string).collect(),
1105            Self::Number(number) => number.iter().map(f64::to_string).collect(),
1106            Self::Limit(limit) => limit.iter().map(AttrNumber::to_string).collect(),
1107            Self::Duration(duration) => duration.iter().map(|duration| (duration.as_secs_f64() * 1000.0).to_string()).collect(),
1108            Self::Labels(labels) => labels.iter().map(|label| label.to_string()).collect(),
1109        }
1110    }
1111
1112}
1113
1114/// The value of that property of that configuration, see [`ConfigPropValue`].
1115fn prop_value(config: &AttrConfig, prop: ConfigProp) -> ConfigPropValue<'_> {
1116    match prop {
1117        ConfigProp::Description => ConfigPropValue::Text(config.doc.as_deref()),
1118        ConfigProp::Label => ConfigPropValue::Text(config.label.as_deref()),
1119        ConfigProp::Unit => ConfigPropValue::Text(config.unit.as_deref()),
1120        ConfigProp::StandardUnit => ConfigPropValue::Number(config.standard_unit),
1121        ConfigProp::DisplayUnit => ConfigPropValue::Text(config.display_unit.as_deref()),
1122        ConfigProp::Format => ConfigPropValue::Text(config.display_format.as_deref()),
1123        ConfigProp::MinValue => ConfigPropValue::Limit(config.range.min),
1124        ConfigProp::MaxValue => ConfigPropValue::Limit(config.range.max),
1125        ConfigProp::MinAlarm => ConfigPropValue::Limit(config.alarm.min),
1126        ConfigProp::MaxAlarm => ConfigPropValue::Limit(config.alarm.max),
1127        ConfigProp::MinWarning => ConfigPropValue::Limit(config.warning.min),
1128        ConfigProp::MaxWarning => ConfigPropValue::Limit(config.warning.max),
1129        ConfigProp::DeltaVal => ConfigPropValue::Limit(config.delta.map(|delta| delta.value)),
1130        ConfigProp::DeltaT => ConfigPropValue::Duration(config.delta.and_then(|delta| delta.time)),
1131        ConfigProp::RelChange => ConfigPropValue::Number(config.change_event.rel_change),
1132        ConfigProp::AbsChange => ConfigPropValue::Number(config.change_event.abs_change),
1133        ConfigProp::EventPeriod => ConfigPropValue::Duration(config.periodic_event.period),
1134        ConfigProp::ArchiveRelChange => ConfigPropValue::Number(config.archive_event.rel_change),
1135        ConfigProp::ArchiveAbsChange => ConfigPropValue::Number(config.archive_event.abs_change),
1136        ConfigProp::ArchivePeriod => ConfigPropValue::Duration(config.archive_event.period),
1137        ConfigProp::EnumLabels => ConfigPropValue::Labels(&config.enum_labels),
1138    }
1139}
1140
1141/// The attribute properties of the database that are part of the configuration of an
1142/// attribute, see [`ConfigLoader::apply`].
1143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1144pub enum ConfigProp {
1145    Description,
1146    Label,
1147    Unit,
1148    StandardUnit,
1149    DisplayUnit,
1150    Format,
1151    MinValue,
1152    MaxValue,
1153    MinAlarm,
1154    MaxAlarm,
1155    MinWarning,
1156    MaxWarning,
1157    DeltaVal,
1158    DeltaT,
1159    RelChange,
1160    AbsChange,
1161    EventPeriod,
1162    ArchiveRelChange,
1163    ArchiveAbsChange,
1164    ArchivePeriod,
1165    EnumLabels,
1166}
1167
1168impl ConfigProp {
1169
1170    /// Every property.
1171    const ALL: [Self; 21] = [
1172        Self::Description,
1173        Self::Label,
1174        Self::Unit,
1175        Self::StandardUnit,
1176        Self::DisplayUnit,
1177        Self::Format,
1178        Self::MinValue,
1179        Self::MaxValue,
1180        Self::MinAlarm,
1181        Self::MaxAlarm,
1182        Self::MinWarning,
1183        Self::MaxWarning,
1184        Self::DeltaVal,
1185        Self::DeltaT,
1186        Self::RelChange,
1187        Self::AbsChange,
1188        Self::EventPeriod,
1189        Self::ArchiveRelChange,
1190        Self::ArchiveAbsChange,
1191        Self::ArchivePeriod,
1192        Self::EnumLabels,
1193    ];
1194
1195    /// The name the database holds that property under.
1196    pub fn name(self) -> &'static str {
1197        match self {
1198            Self::Description => "description",
1199            Self::Label => "label",
1200            Self::Unit => "unit",
1201            Self::StandardUnit => "standard_unit",
1202            Self::DisplayUnit => "display_unit",
1203            Self::Format => "format",
1204            Self::MinValue => "min_value",
1205            Self::MaxValue => "max_value",
1206            Self::MinAlarm => "min_alarm",
1207            Self::MaxAlarm => "max_alarm",
1208            Self::MinWarning => "min_warning",
1209            Self::MaxWarning => "max_warning",
1210            Self::DeltaVal => "delta_val",
1211            Self::DeltaT => "delta_t",
1212            Self::RelChange => "rel_change",
1213            Self::AbsChange => "abs_change",
1214            Self::EventPeriod => "event_period",
1215            Self::ArchiveRelChange => "archive_rel_change",
1216            Self::ArchiveAbsChange => "archive_abs_change",
1217            Self::ArchivePeriod => "archive_period",
1218            Self::EnumLabels => "enum_labels",
1219        }
1220    }
1221
1222    /// Whether an attribute of that kind supports that property: the bounds, the delta and
1223    /// the change criteria are values of the attribute, which only a number has, and an
1224    /// enumerated attribute has no alarm, the way cppTango refuses them.
1225    fn supported_by(self, kind: Option<AttrDataKind>) -> bool {
1226        let numeric = !matches!(kind, None | Some(AttrDataKind::Bool | AttrDataKind::State | AttrDataKind::String | AttrDataKind::Encoded));
1227        match self {
1228            Self::MinAlarm | 
1229            Self::MaxAlarm | 
1230            Self::MinWarning | 
1231            Self::MaxWarning => numeric && kind != Some(AttrDataKind::Enum),
1232            Self::MinValue | 
1233            Self::MaxValue | 
1234            Self::DeltaVal | 
1235            Self::DeltaT | 
1236            Self::RelChange | 
1237            Self::AbsChange |
1238            Self::ArchiveRelChange | 
1239            Self::ArchiveAbsChange => numeric,
1240            _ => true,
1241        }
1242    }
1243
1244    /// The property of that name, matched ignoring the case like any name the database
1245    /// holds, absent for one that is not part of the configuration.
1246    fn from_name(name: &str) -> Option<Self> {
1247        Self::ALL.into_iter().find(|prop| name.eq_ignore_ascii_case(prop.name()))
1248    }
1249
1250}
1251
1252/// Whether that value of a property stands for no value at all.
1253fn is_unset(value: &str) -> bool {
1254    value.is_empty() || value.eq_ignore_ascii_case(NOT_SPECIFIED) || value.eq_ignore_ascii_case(NOT_A_NUMBER)
1255}
1256
1257/// A property that is a text, every line of which is one of its values.
1258fn text(values: &Spectrum<str>) -> Option<Cow<'static, str>> {
1259    let text = values.iter().collect::<Vec<_>>().join("\n");
1260    (!is_unset(text.trim())).then_some(Cow::Owned(text))
1261}
1262
1263/// The value of a property that has a single one, absent for one that has none.
1264fn single(values: &Spectrum<str>) -> std::result::Result<Option<&str>, String> {
1265    let mut iter = values.iter().map(str::trim);
1266    match (iter.next(), iter.next()) {
1267        (None, _) => Ok(None),
1268        (Some(value), None) => Ok((!is_unset(value)).then_some(value)),
1269        (Some(_), Some(_)) => Err(format!("expected a single value, got {}", values.len())),
1270    }
1271}
1272
1273/// Parse that value as a finite number.
1274fn parse_number(value: &str) -> std::result::Result<f64, String> {
1275    value.parse::<f64>().ok()
1276        .filter(|value| value.is_finite())
1277        .ok_or_else(|| format!("{value:?} is not a number"))
1278}
1279
1280/// A property that is a number.
1281fn number(values: &Spectrum<str>) -> std::result::Result<Option<f64>, String> {
1282    single(values)?.map(parse_number).transpose()
1283}
1284
1285/// A property that is a limit, which is a value of the attribute and is read as its type,
1286/// a value that type cannot hold being read as a double, see [`AttrNumber`].
1287fn limit(values: &Spectrum<str>, kind: LimitKind) -> std::result::Result<Option<AttrNumber>, String> {
1288    
1289    let Some(value) = single(values)? else {
1290        return Ok(None);
1291    };
1292    
1293    let typed = match kind {
1294        LimitKind::Long => value.parse::<i64>().ok().map(AttrNumber::Long),
1295        LimitKind::ULong => value.parse::<u64>().ok().map(AttrNumber::ULong),
1296        LimitKind::Double => None,
1297    };
1298
1299    match typed {
1300        Some(typed) => Ok(Some(typed)),
1301        None => parse_number(value).map(|value| Some(AttrNumber::Double(value))),
1302    }
1303
1304}
1305
1306/// A property that is a duration, which the database holds in milliseconds.
1307fn duration(values: &Spectrum<str>) -> std::result::Result<Option<Duration>, String> {
1308
1309    let Some(millis) = number(values)? else {
1310        return Ok(None);
1311    };
1312
1313    Duration::try_from_secs_f64(millis / 1000.0)
1314        .map(Some)
1315        .map_err(|_| format!("{millis} is not a duration in milliseconds"))
1316    
1317}
1318
1319/// A property that is the criterion of an event, which Tango allows to be two values, the
1320/// change downwards and the change upwards, either in two values or in one separated by a
1321/// comma. Only one change is kept for both, see [`crate::dev::AttrChangeEventConfig`], and
1322/// the smaller one is: an event is then sent at least as often as either asks for.
1323fn change(values: &Spectrum<str>) -> std::result::Result<Option<f64>, String> {
1324
1325    let changes = values.iter()
1326        .flat_map(|value| value.split(','))
1327        .map(str::trim)
1328        .filter(|value| !is_unset(value))
1329        .collect::<Vec<_>>();
1330
1331    if changes.len() > 2 {
1332        return Err(format!("expected at most two values, got {}", changes.len()));
1333    }
1334
1335    let mut min = None::<f64>;
1336    for change in changes {
1337        let change = parse_number(change)?.abs();
1338        min = Some(min.map_or(change, |min| min.min(change)));
1339    }
1340
1341    Ok(min)
1342    
1343}
1344
1345/// A property that is the labels of an enumerated attribute, one value each.
1346fn labels(values: &Spectrum<str>) -> Vec<Cow<'static, str>> {
1347    match single(values) {
1348        Ok(None) => Vec::new(),
1349        _ => values.iter().map(|label| Cow::Owned(label.to_string())).collect(),
1350    }
1351}
1352
1353/// What the limits of an attribute of that kind are read as: its limits are values of it,
1354/// so they are of its own type.
1355fn limit_kind(kind: Option<AttrDataKind>) -> LimitKind {
1356    match kind {
1357        Some(AttrDataKind::Short | AttrDataKind::Long | AttrDataKind::Long64 | AttrDataKind::Enum) => LimitKind::Long,
1358        Some(AttrDataKind::Octet | AttrDataKind::UShort | AttrDataKind::ULong | AttrDataKind::ULong64) => LimitKind::ULong,
1359        // The floating kinds, and the ones that have no limits to speak of.
1360        _ => LimitKind::Double,
1361    }
1362}
1363
1364/// The first property of that configuration that an attribute of that kind does not
1365/// support, see [`ConfigProp::supported_by`].
1366pub fn unsupported_prop(config: &AttrConfig, kind: Option<AttrDataKind>) -> Option<ConfigProp> {
1367    ConfigProp::ALL.into_iter().find(|&prop| !prop.supported_by(kind) && !prop_value(config, prop).is_unset())
1368}
1369
1370/// Why a property is refused by an attribute of that kind.
1371pub fn unsupported(kind: Option<AttrDataKind>) -> String {
1372    match kind {
1373        Some(kind) => format!("not supported by an attribute of type {kind:?}"),
1374        None => "not supported by an attribute of no type".to_string(),
1375    }
1376}
1377
1378/// The three kinds of number the limits of an attribute may be read as, see [`limit_kind`].
1379#[derive(Debug, Clone, Copy)]
1380enum LimitKind {
1381    Double,
1382    Long,
1383    ULong,
1384}
1385
1386// =========================== //
1387//  Changing the configuration //
1388// =========================== //
1389
1390/// What changing the configuration of an attribute on a device does to what the database
1391/// holds for it on that device, see [`Attr::change_config`].
1392#[derive(Debug, Default, PartialEq)]
1393pub struct ConfigChanges {
1394    /// The properties to store, with their values.
1395    pub put: Vec<(ConfigProp, Vec<String>)>,
1396    /// The properties to delete, the device then inheriting them.
1397    pub del: Vec<ConfigProp>,
1398}
1399
1400impl ConfigChanges {
1401    pub fn is_empty(&self) -> bool {
1402        self.put.is_empty() && self.del.is_empty()
1403    }
1404}
1405
1406/// Set that property of a configuration to the one of another.
1407/// 
1408/// The two halves of the delta alarm only make an alarm together, so a delta time given to
1409/// a configuration that has no delta value is lost, as it is when it is loaded, see
1410/// [`ConfigLoader::finish`].
1411fn copy_prop(dst: &mut AttrConfig, src: &AttrConfig, prop: ConfigProp) {
1412    match prop {
1413        ConfigProp::Description => dst.doc = src.doc.clone(),
1414        ConfigProp::Label => dst.label = src.label.clone(),
1415        ConfigProp::Unit => dst.unit = src.unit.clone(),
1416        ConfigProp::StandardUnit => dst.standard_unit = src.standard_unit,
1417        ConfigProp::DisplayUnit => dst.display_unit = src.display_unit.clone(),
1418        ConfigProp::Format => dst.display_format = src.display_format.clone(),
1419        ConfigProp::MinValue => dst.range.min = src.range.min,
1420        ConfigProp::MaxValue => dst.range.max = src.range.max,
1421        ConfigProp::MinAlarm => dst.alarm.min = src.alarm.min,
1422        ConfigProp::MaxAlarm => dst.alarm.max = src.alarm.max,
1423        ConfigProp::MinWarning => dst.warning.min = src.warning.min,
1424        ConfigProp::MaxWarning => dst.warning.max = src.warning.max,
1425        ConfigProp::DeltaVal => {
1426            let time = dst.delta.and_then(|delta| delta.time);
1427            dst.delta = src.delta.map(|delta| AttrDeltaConfig { value: delta.value, time });
1428        }
1429        ConfigProp::DeltaT => {
1430            if let Some(delta) = &mut dst.delta {
1431                delta.time = src.delta.and_then(|delta| delta.time);
1432            }
1433        }
1434        ConfigProp::RelChange => dst.change_event.rel_change = src.change_event.rel_change,
1435        ConfigProp::AbsChange => dst.change_event.abs_change = src.change_event.abs_change,
1436        ConfigProp::EventPeriod => dst.periodic_event.period = src.periodic_event.period,
1437        ConfigProp::ArchiveRelChange => dst.archive_event.rel_change = src.archive_event.rel_change,
1438        ConfigProp::ArchiveAbsChange => dst.archive_event.abs_change = src.archive_event.abs_change,
1439        ConfigProp::ArchivePeriod => dst.archive_event.period = src.archive_event.period,
1440        ConfigProp::EnumLabels => dst.enum_labels = src.enum_labels.clone(),
1441    }
1442}
1443
1444/// The error of a configuration that could not be applied, with that description, which
1445/// a read or write of its attribute fails with as its cause, see [`AttrSetup::error`].
1446#[cold]
1447#[track_caller]
1448fn config_err(description: impl Into<Cow<'static, str>>) -> Error {
1449    Error::new(ErrorKind::Reason(reason::API_AttrConfig)).with_description(description)
1450}
1451
1452/// What makes that configuration incoherent, if anything: a range whose minimum is not
1453/// below its maximum, which no value could be in, the way cppTango refuses it.
1454fn incoherence(config: &AttrConfig) -> Option<String> {
1455    for (what, range) in [
1456        ("value", &config.range), 
1457        ("alarm", &config.alarm), 
1458        ("warning", &config.warning)
1459    ] {
1460        if let (Some(min), Some(max)) = (range.min, range.max)
1461        && !(min < max) {
1462            return Some(format!("the minimum of its {what} range ({min}) is not below its maximum ({max})"));
1463        }
1464    }
1465    None
1466}
1467
1468// =========================== //
1469//      Memorized values       //
1470// =========================== //
1471
1472/// The attribute property the memorized value of an attribute is stored under, by the
1473/// device, see [`Attr::memorize`].
1474const MEM_VALUE: &str = "__value";
1475/// What cppTango stores as the memorized value of an attribute that was never written.
1476const MEM_NOT_USED: &str = "Not used yet";
1477
1478/// The string a memorized value is stored as, which is the one cppTango stores, so that a
1479/// device keeps its set point across servers written in either: a boolean is `true` or
1480/// `false`, an enumerated value its index, and any other number written out.
1481/// 
1482/// Only a scalar can be memorized, see [`super::def::AttrDef::memorized`].
1483fn mem_value_str(value: &Value) -> std::result::Result<String, String> {
1484    fn scalar<T: Element + ?Sized>(format: &ValueFormat<T>) -> std::result::Result<T::Ref<'_>, String> {
1485        format.scalar().map(|scalar| scalar.value()).ok_or_else(|| "only a scalar can be memorized".to_string())
1486    }
1487    Ok(match value {
1488        Value::Bool(format) => scalar(format)?.to_string(),
1489        Value::Octet(format) => scalar(format)?.to_string(),
1490        Value::UShort(format) => scalar(format)?.to_string(),
1491        Value::Short(format) => scalar(format)?.to_string(),
1492        Value::ULong(format) => scalar(format)?.to_string(),
1493        Value::Long(format) => scalar(format)?.to_string(),
1494        Value::ULong64(format) => scalar(format)?.to_string(),
1495        Value::Long64(format) => scalar(format)?.to_string(),
1496        Value::Float(format) => scalar(format)?.to_string(),
1497        Value::Double(format) => scalar(format)?.to_string(),
1498        Value::Enum(format) => scalar(format)?.0.to_string(),
1499        Value::String(format) => scalar(format)?.to_string(),
1500        _ => return Err("a value of that kind cannot be memorized".to_string()),
1501    })
1502}
1503
1504/// The memorized value stored for an attribute of that kind, parsed back, absent for an
1505/// attribute that was never written, see [`mem_value_str`].
1506fn parse_mem_value(kind: Option<&AttrDataKind>, values: &Spectrum<str>) -> std::result::Result<Option<Value>, String> {
1507
1508    let mut iter = values.iter();
1509    let value = match (iter.next(), iter.next()) {
1510        (None, _) => return Ok(None),
1511        (Some(value), None) => value,
1512        (Some(_), Some(_)) => return Err(format!("expected a single value, got {}", values.len())),
1513    };
1514
1515    if value == MEM_NOT_USED {
1516        return Ok(None);
1517    }
1518
1519    // Read the way cppTango reads it, as a stream would: blanks around a number are not
1520    // part of it, but they are of a string.
1521    fn parse<T: std::str::FromStr>(value: &str) -> std::result::Result<T, String> {
1522        value.trim().parse::<T>().map_err(|_| format!("{value:?} is not a value of the attribute"))
1523    }
1524
1525    Ok(Some(match kind {
1526        Some(AttrDataKind::Bool) => match value.trim() {
1527            value if value.eq_ignore_ascii_case("true") || value == "1" => Value::from(true),
1528            value if value.eq_ignore_ascii_case("false") || value == "0" => Value::from(false),
1529            _ => return Err(format!("{value:?} is not a boolean")),
1530        },
1531        Some(AttrDataKind::Octet) => Value::from(parse::<u8>(value)?),
1532        Some(AttrDataKind::UShort) => Value::from(parse::<u16>(value)?),
1533        Some(AttrDataKind::Short) => Value::from(parse::<i16>(value)?),
1534        Some(AttrDataKind::ULong) => Value::from(parse::<u32>(value)?),
1535        Some(AttrDataKind::Long) => Value::from(parse::<i32>(value)?),
1536        Some(AttrDataKind::ULong64) => Value::from(parse::<u64>(value)?),
1537        Some(AttrDataKind::Long64) => Value::from(parse::<i64>(value)?),
1538        Some(AttrDataKind::Float) => Value::from(parse::<f32>(value)?),
1539        Some(AttrDataKind::Double) => Value::from(parse::<f64>(value)?),
1540        Some(AttrDataKind::Enum) => Value::from(DevEnum(parse::<i16>(value)?)),
1541        Some(AttrDataKind::String) => Value::from(value),
1542        _ => return Err("a value of that kind cannot be memorized".to_string()),
1543    }))
1544
1545}
1546
1547#[cfg(test)]
1548mod tests {
1549
1550    use crate::dev::{AttrRangeConfig, AttrChangeEventConfig};
1551
1552    use super::*;
1553
1554    /// An attribute whose definition gives it a label, whose class is given a unit by the
1555    /// database, and which has a unit and a lower bound of its own on its device.
1556    fn current() -> AttrConfig {
1557        AttrConfig {
1558            label: Some("Position".into()),
1559            unit: Some("cm".into()),
1560            range: AttrRangeConfig { min: Some(AttrNumber::Long(-5)), max: None },
1561            ..AttrConfig::default()
1562        }
1563    }
1564
1565    /// The changes of that attribute to the configuration the given function makes of the
1566    /// one it has, along with the configuration it ends up with.
1567    fn changes(change: impl FnOnce(&mut AttrConfig)) -> (ConfigChanges, AttrConfig) {
1568        let attr = Attr::new(Arc::new(AttrDef::<()>::new("Position").kind(AttrDataKind::Long).label("Position")));
1569        attr.setup.lock().unwrap().config = current();
1570        let class = [Named::new("unit".into(), Spectrum::from_strs(["mm"]).unwrap())];
1571        let mut requested = current();
1572        change(&mut requested);
1573        let changes = attr.change_config(&class, &requested).unwrap();
1574        (changes, attr.config())
1575    }
1576
1577    fn put(pairs: &[(ConfigProp, &str)]) -> ConfigChanges {
1578        ConfigChanges {
1579            put: pairs.iter().map(|&(prop, value)| (prop, vec![value.to_string()])).collect(),
1580            del: Vec::new(),
1581        }
1582    }
1583
1584    fn del(props: &[ConfigProp]) -> ConfigChanges {
1585        ConfigChanges { put: Vec::new(), del: props.to_vec() }
1586    }
1587
1588    /// Numbers against a range whose bounds are of another variant, the widest integers
1589    /// compared exactly, and a not-a-number outside of any range.
1590    #[test]
1591    fn range_bounds() {
1592        let range = AttrRangeConfig { min: Some(AttrNumber::Long(-1)), max: Some(AttrNumber::Double(2.5)) };
1593        assert!(!outside(&range, AttrNumber::ULong(2)));
1594        assert!(!outside(&range, AttrNumber::Long(-1)));
1595        assert!(outside(&range, AttrNumber::ULong(3)));
1596        assert!(outside(&range, AttrNumber::Double(f64::NAN)));
1597        let range = AttrRangeConfig { min: None, max: Some(AttrNumber::ULong(u64::MAX - 1)) };
1598        assert!(outside(&range, AttrNumber::ULong(u64::MAX)));
1599        assert!(!outside(&range, AttrNumber::Long(i64::MIN)));
1600        assert!(!any_number(&Value::from("text"), |_| true));
1601    }
1602
1603    /// A range whose minimum is not below its maximum, an empty one included, and a range
1604    /// open on either side.
1605    #[test]
1606    fn range_incoherence() {
1607        let range = |min: i64, max: i64| AttrRangeConfig { min: Some(AttrNumber::Long(min)), max: Some(AttrNumber::Long(max)) };
1608        assert_eq!(incoherence(&AttrConfig { range: range(-1, 1), ..AttrConfig::default() }), None);
1609        assert!(incoherence(&AttrConfig { warning: range(1, 1), ..AttrConfig::default() }).unwrap().contains("warning"));
1610        assert!(incoherence(&AttrConfig { alarm: range(2, 1), ..AttrConfig::default() }).unwrap().contains("alarm"));
1611        let open = AttrRangeConfig { min: Some(AttrNumber::Long(1)), max: None };
1612        assert_eq!(incoherence(&AttrConfig { range: open, ..AttrConfig::default() }), None);
1613    }
1614
1615    /// A value: stored, unless it is what the device has already, or what it inherits.
1616    #[test]
1617    fn value_changes() {
1618        assert_eq!(changes(|_| ()).0, ConfigChanges::default());
1619        assert_eq!(changes(|config| config.label = Some("Pos".into())).0, put(&[(ConfigProp::Label, "Pos")]));
1620        assert_eq!(changes(|config| config.range.max = Some(AttrNumber::Long(5))).0, put(&[(ConfigProp::MaxValue, "5")]));
1621        assert_eq!(changes(|config| config.unit = Some("mm".into())).0, del(&[ConfigProp::Unit]));
1622    }
1623
1624    /// No value: what the device inherits, whatever it is, and nothing to do for a field
1625    /// the device has nothing of its own for.
1626    #[test]
1627    fn reset_changes() {
1628        let (unit, config) = changes(|config| config.unit = None);
1629        assert_eq!((unit, config.unit.as_deref()), (del(&[ConfigProp::Unit]), Some("mm")));
1630        let (min, config) = changes(|config| config.range.min = None);
1631        assert_eq!((min, config.range.min), (del(&[ConfigProp::MinValue]), None));
1632        let (label, config) = changes(|config| config.label = None);
1633        assert_eq!((label, config.label.as_deref()), (ConfigChanges::default(), Some("Position")));
1634    }
1635
1636    /// The bounds of a string, refused unless unset, and the alarms of an enumerated
1637    /// attribute, refused when its range is not.
1638    #[test]
1639    fn unsupported_props() {
1640        let load = |kind, name: &str, value: &str| {
1641            let props = [Named::new(name.into(), Spectrum::from_strs([value]).unwrap())];
1642            let mut loader = ConfigLoader::new(&AttrConfig::default(), Some(kind));
1643            loader.apply_level("device", Ok(&props));
1644            loader.finish().1.map(|err| err.description().to_string())
1645        };
1646        assert!(load(AttrDataKind::String, "max_alarm", "5").unwrap().contains("not supported by an attribute of type String"));
1647        assert_eq!(load(AttrDataKind::String, "max_alarm", "Not specified"), None);
1648        assert_eq!(load(AttrDataKind::String, "label", "Name"), None);
1649        assert!(load(AttrDataKind::Enum, "max_alarm", "2").is_some());
1650        assert_eq!(load(AttrDataKind::Enum, "max_value", "2"), None);
1651    }
1652
1653    /// Every field of a configuration stored and loaded back the same.
1654    #[test]
1655    fn prop_round_trip() {
1656
1657        let config = AttrConfig {
1658            doc: Some("First line\nSecond line".into()),
1659            standard_unit: Some(0.001),
1660            range: AttrRangeConfig { min: Some(AttrNumber::Long(-5)), max: Some(AttrNumber::Long(5)) },
1661            delta: Some(AttrDeltaConfig { value: AttrNumber::Long(1), time: Some(Duration::from_millis(250)) }),
1662            change_event: AttrChangeEventConfig { rel_change: Some(0.5), abs_change: None },
1663            enum_labels: vec!["Off".into(), "On".into()],
1664            ..AttrConfig::default()
1665        };
1666
1667        let attr = Attr::new(Arc::new(AttrDef::<()>::new("Position").kind(AttrDataKind::Long)));
1668        let changes = attr.change_config(&[], &config).unwrap();
1669        assert_eq!(attr.config(), config);
1670        let props = changes.put.into_iter()
1671            .map(|(prop, values)| Named::new(prop.name().into(), Spectrum::from_strs(values).unwrap()))
1672            .collect::<Vec<_>>();
1673
1674        let mut loader = ConfigLoader::new(&AttrConfig::default(), Some(AttrDataKind::Long));
1675        loader.apply_level("device", Ok(&props));
1676        let (loaded, error) = loader.finish();
1677        assert_eq!(loaded, config);
1678        assert!(error.is_none(), "{error:?}");
1679
1680    }
1681
1682}