1use 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
23pub struct Attr<M: Model> {
27 def: Arc<AttrDef<M>>,
29 role: AttrRole,
31 w_value: Mutex<Option<Value>>,
39 w_time: Mutex<Option<Instant>>,
43 history: Mutex<History<AttrRead<Value>>>,
46 setup: Mutex<AttrSetup>,
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub(super) enum AttrRole {
56 Interface,
58 State,
60 Status,
63}
64
65struct AttrSetup {
68 config: AttrConfig,
71 error: Option<Error>,
76 loaded: bool,
78 restore: Option<Value>,
81 mem_error: Option<Error>,
85}
86
87impl<M: Model> Attr<M> {
88
89 pub(super) fn new(def: Arc<AttrDef<M>>) -> Self {
92 Self::new_with_role(def, AttrRole::Interface)
93 }
94
95 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 #[inline]
119 pub fn name(&self) -> &str {
120 &self.def.name
121 }
122
123 pub fn config(&self) -> AttrConfig {
125 self.setup.lock().unwrap().config.clone()
126 }
127
128 #[inline]
130 pub(super) fn role(&self) -> AttrRole {
131 self.role
132 }
133
134 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 #[inline]
143 pub(super) fn display_level(&self) -> DisplayLevel {
144 self.def.display_level
145 }
146
147 #[inline]
150 pub(super) fn poll_period(&self) -> Option<Duration> {
151 self.def.poll_period
152 }
153
154 #[inline]
157 pub(super) fn is_pollable(&self) -> bool {
158 self.role != AttrRole::Interface || self.def.read.is_some()
159 }
160
161 pub(super) fn is_loaded(&self) -> bool {
164 self.setup.lock().unwrap().loaded
165 }
166
167 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 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 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 pub(super) fn change_config(&self, class: &[Named<Spectrum<str>>], requested: &AttrConfig) -> std::result::Result<ConfigChanges, String> {
273
274 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 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 setup.config = config;
322 setup.error = class_error;
323 Ok(changes)
324
325 }
326
327 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 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 #[inline]
381 pub fn is_memorized(&self) -> bool {
382 self.def.memorized.is_some()
383 }
384
385 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 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 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 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 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 pub(super) fn write_may_block(&self) -> bool {
459 matches!(&self.def.write, Some(WriteFn::SharedSync(_) | WriteFn::ExclusiveSync(_)))
460 }
461
462 pub fn info(&self) -> Result<AttrInfo> {
475
476 let mode = match (self.role, &self.def.read, &self.def.write) {
477 (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 (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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn history(&self, max: usize) -> Vec<RecordedResult<AttrRead<Value>>> {
745 self.history.lock().unwrap().entries(max)
746 }
747
748 pub fn clear_history(&self) {
751 self.history.lock().unwrap().clear();
752 }
753
754 pub fn history_depth(&self) -> usize {
756 self.history.lock().unwrap().depth()
757 }
758
759 pub fn set_history_depth(&self, depth: usize) {
762 self.history.lock().unwrap().set_depth(depth);
763 }
764
765 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 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 async fn write_inner(&self, dev: &Dev<M>, model: &mut ModelRef<'_, M>, value: Value) -> Result<()> {
802
803 self.check_limits(dev, &value)?;
804
805 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 *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 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
848pub enum AttrAlarm {
849 Alarm,
851 Warning,
853 Delta,
855 Given(AttrQuality),
857}
858
859impl AttrAlarm {
860
861 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 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
883fn 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
916fn 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
924fn 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
941pub type PropsResult<'a> = std::result::Result<&'a [Named<Spectrum<str>>], &'a Error>;
947
948const NOT_SPECIFIED: &str = "Not specified";
951const NOT_A_NUMBER: &str = "NaN";
952
953struct ConfigLoader {
956 config: AttrConfig,
957 kind: Option<AttrDataKind>,
960 delta_value: Option<AttrNumber>,
963 delta_time: Option<Duration>,
964 errors: Vec<String>,
966 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 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 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 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 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 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#[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 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 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
1114fn 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#[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 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 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 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 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
1252fn 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
1257fn 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
1263fn 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
1273fn 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
1280fn number(values: &Spectrum<str>) -> std::result::Result<Option<f64>, String> {
1282 single(values)?.map(parse_number).transpose()
1283}
1284
1285fn 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
1306fn 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
1319fn 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
1345fn 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
1353fn 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 _ => LimitKind::Double,
1361 }
1362}
1363
1364pub 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
1370pub 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#[derive(Debug, Clone, Copy)]
1380enum LimitKind {
1381 Double,
1382 Long,
1383 ULong,
1384}
1385
1386#[derive(Debug, Default, PartialEq)]
1393pub struct ConfigChanges {
1394 pub put: Vec<(ConfigProp, Vec<String>)>,
1396 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
1406fn 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#[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
1452fn 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
1468const MEM_VALUE: &str = "__value";
1475const MEM_NOT_USED: &str = "Not used yet";
1477
1478fn 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
1504fn 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 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 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 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 #[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 #[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 #[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 #[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 #[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 #[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}