rstango 0.4.0

Implementation of the Tango distributed control system
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
//! The standard Tango error type.

use std::fmt::{self, Debug, Display, Write as _};
use std::panic::Location;
use std::borrow::Cow;
use std::sync::Arc;
use std::error;


/// Type alias for a result with the standard Tango [`Error`] type.
pub type Result<T> = std::result::Result<T, Error>;

/// A common error type for all Tango operations, which supports metadata and easy chaining of
/// errors.
#[derive(Clone)]
#[repr(transparent)]
pub struct Error {
    inner: Arc<ErrorInner>,
}

#[derive(Debug, Clone)]
struct ErrorInner {
    /// The standardized reason of the error.
    reason: Cow<'static, str>,
    /// The human readable description of the error.
    description: Cow<'static, str>,
    /// The origin, in file/code, of the error.
    origin: Cow<'static, str>,
    /// The severity of the error.
    severity: ErrorSeverity,
    /// If the reason corresponds to a non-raw kind, then it's cached here.
    kind: Option<ErrorKind<'static>>,
    /// The source of the error.
    source: Option<ErrorSource>,
}

#[derive(Debug, Clone)]
enum ErrorSource {
    /// The parent of the error is another Tango error.
    Parent(Error),
    /// Any other error.
    Generic(Arc<dyn error::Error + Send + Sync>),
}

impl From<std::convert::Infallible> for Error {
    fn from(never: std::convert::Infallible) -> Self {
        match never {}
    }
}

impl Error {

    /// Create a new error of the given kind, this will grab the origin of the error from
    /// the caller's location. A default description is written depending on it by default,
    /// you can append or replace this description later.
    #[track_caller]
    pub fn new(kind: ErrorKind<'_>) -> Self {
        let mut ret = Self {
            inner: Arc::new(ErrorInner {
                reason: Cow::Borrowed(""),
                description: Cow::Borrowed(""),
                origin: Cow::Borrowed(""),
                severity: ErrorSeverity::Error,
                kind: None,
                source: None,
            })
        };
        ret.set_kind(kind);
        ret.set_origin_from_caller();
        ret
    }

    pub(crate) fn not_implemented() -> Self {
        Self::new(ErrorKind::NotImplemented)
    }

    /// Return the standardized textual reason.
    pub fn reason(&self) -> &str {
        &self.inner.reason
    }

    /// Get the kind of error, returning the raw reason if not categorized.
    pub fn kind(&self) -> ErrorKind<'_> {
        if let Some(kind) = self.inner.kind {
            kind
        } else {
            ErrorKind::Reason(&self.inner.reason)
        }
    }

    /// Change the kind of this error, this also ensures that the associated reason
    /// corresponds to the new error kind. If the description is empty at this point,
    /// the description is overridden with a default description.
    pub fn set_kind(&mut self, kind: ErrorKind<'_>) {
        
        // Only a fallback: an error that already says something specific keeps saying it.
        let inner = Arc::make_mut(&mut self.inner);
        if inner.description.is_empty() {
            inner.description = Cow::Borrowed(kind.description());
        }

        let (reason, kind) = match kind.into_static() {
            Ok(kind) => (Cow::Borrowed(kind.reason()), Some(kind)),
            Err(kind) => (Cow::Owned(kind.reason().to_string()), None),
        };

        inner.kind = kind;
        inner.reason = reason;

    }

    /// Change the kind of this error, and return itself.
    pub fn with_kind(mut self, kind: ErrorKind<'_>) -> Self {
        self.set_kind(kind);
        self
    }

    /// Return the description of the error.
    pub fn description(&self) -> &str {
        &self.inner.description
    }

    /// Append the given additional description to the error's description, if the description
    /// is currently empty, then this replaces the previous description. If a description
    /// already exists, it appends the description on a new line.
    pub fn append_description(&mut self, additional: impl Display) {
        let inner = Arc::make_mut(&mut self.inner);
        if inner.description.is_empty() {
            inner.description = Cow::Owned(additional.to_string());
        } else {
            match inner.description {
                Cow::Borrowed(slice) => inner.description = Cow::Owned(format!("{slice}\n{additional}")),
                Cow::Owned(ref mut owned) => write!(owned, "\n{additional}").unwrap(),
            }
        }
    }

    /// Overwrite any existing description with the given one.
    pub fn set_description(&mut self, description: impl Into<Cow<'static, str>>) {
        Arc::make_mut(&mut self.inner).description = description.into();
    }

    /// Same as [`Self::append_description`], and return itself.
    pub fn with_append_description(mut self, additional: impl Display) -> Self {
        self.append_description(additional);
        self
    }

    /// Overwrite any existing description with the given one, and return itself.
    pub fn with_description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
        self.set_description(description);
        self
    }

    /// Return the severity of this error.
    pub fn severity(&self) -> ErrorSeverity {
        self.inner.severity
    }

    /// Set the severity of the error.
    pub fn set_severity(&mut self, severity: ErrorSeverity) {
        Arc::make_mut(&mut self.inner).severity = severity;
    }

    /// Set the severity of the error, and return itself.
    pub fn with_severity(mut self, severity: ErrorSeverity) -> Self {
        self.set_severity(severity);
        self
    }

    /// Return the origin of this error, set by default to the caller's code location.
    pub fn origin(&self) -> &str {
        &self.inner.origin
    }

    /// Append the given additional origin to the existing origin, in order to add 
    /// details, a space is added before insertion. If the origin has been replaced 
    /// and is empty, then no space is added.
    pub fn append_origin(&mut self, additional: impl Display) {
        let inner = Arc::make_mut(&mut self.inner);
        if inner.origin.is_empty() {
            inner.origin = Cow::Owned(additional.to_string());
        } else {
            match inner.origin {
                Cow::Borrowed(slice) => inner.origin = Cow::Owned(format!("{slice} {additional}")),
                Cow::Owned(ref mut owned) => write!(owned, " {additional}").unwrap(),
            }
        }
    }

    /// Overwrite any existing origin with the given one.
    pub fn set_origin(&mut self, origin: impl Into<Cow<'static, str>>) {
        Arc::make_mut(&mut self.inner).origin = origin.into();
    }

    /// Same as [`Self::append_origin`], and return itself.
    pub fn with_append_origin(mut self, additional: impl Display) -> Self {
        self.append_origin(additional);
        self
    }

    /// Overwrite any existing origin with the given one, and return itself.
    pub fn with_origin(mut self, origin: impl Into<Cow<'static, str>>) -> Self {
        self.set_origin(origin);
        self
    }

    /// Overwrite the existing origin with the current location of the caller.
    #[track_caller]
    pub fn set_origin_from_caller(&mut self) {
        let location = Location::caller();
        self.set_origin(format!("{location}"));
    }

    /// Overwrite the existing origin with the current location of the caller, and return 
    /// itself.
    #[track_caller]
    pub fn with_origin_from_caller(mut self) -> Self {
        self.set_origin_from_caller();
        self
    }

    /// If the source of this error is a parent Tango error, returns a shared ref to it.
    /// To get a more generic source for this error, use the standard 
    /// [`error::Error::source`] method.
    pub fn parent(&self) -> Option<&Error> {
        if let Some(ErrorSource::Parent(err)) = &self.inner.source {
            Some(err)
        } else {
            None
        }
    }

    /// Set the parent source of this error.
    pub fn set_parent(&mut self, parent: Error) {
        Arc::make_mut(&mut self.inner).source = Some(ErrorSource::Parent(parent));
    }

    /// Set the parent source of this error and return itself.
    pub fn with_parent(mut self, parent: Error) -> Self {
        self.set_parent(parent);
        self
    }

    /// Set the generic source of this error.
    pub fn set_source(&mut self, source: impl Into<Box<dyn error::Error + Send + Sync>>) {
        Arc::make_mut(&mut self.inner).source = Some(ErrorSource::Generic(Arc::from(source.into())));
    }

    /// Set the generic source of this error and return itself.
    pub fn with_source(mut self, source: impl Into<Box<dyn error::Error + Send + Sync>>) -> Self {
        self.set_source(source);
        self
    }

}

impl error::Error for Error {

    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        match &self.inner.source {
            Some(ErrorSource::Parent(err)) => Some(err),
            Some(ErrorSource::Generic(err)) => Some(&**err),
            None => None,
        }
    }

}

/// The whole chain of this error and its parents, on a single line, each error giving its
/// reason and its description, escaped so that a description of several lines stays on
/// that line: this is what a log line carries, see `{:#}` for what a person reads.
///
/// The alternate form gives an error per block, the origin of each of them along with
/// it: a code location is noise in a log line, and the first thing wanted when debugging.
impl Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {

        let separator = if f.alternate() { "\ncaused by " } else { " <- " };

        let mut error = self;
        loop {

            let severity = match error.severity() {
                ErrorSeverity::Warning => "WARNING",
                ErrorSeverity::Error => "ERROR",
                ErrorSeverity::Panic => "PANIC",
            };

            write!(f, "{severity}/{}", error.reason())?;

            if !f.alternate() {
                if !error.description().is_empty() {
                    f.write_str("(")?;
                    for (i, line) in error.description().lines().enumerate() {
                        if i != 0 {
                            f.write_str(" ")?;
                        }
                        write!(f, "{line:?}")?;
                    }
                    f.write_str(")")?;
                }
            } else {
                if !error.origin().is_empty() {
                    write!(f, " @ {}", error.origin())?;
                }
                for line in error.description().lines() {
                    write!(f, "\n  {line}")?;
                }
            }

            match &error.inner.source {
                Some(ErrorSource::Parent(parent)) => {
                    f.write_str(separator)?;
                    error = parent;
                }
                Some(ErrorSource::Generic(source)) => {
                    // What is not a Tango error is told apart from one, being the cause of
                    // the chain rather than a level of it.
                    let mut source: &dyn error::Error = &**source;
                    loop {
                        write!(f, "{separator}{source}")?;
                        match source.source() {
                            Some(next) => source = next,
                            None => break,
                        }
                    }
                    break Ok(());
                }
                None => {
                    break Ok(());
                }
            }

        }

    }
}

/// Written like [`Display`], within `Error(...)`: the derived struct dump is what a `main`
/// returning a [`Result`] would otherwise print, and it is unreadable. The alternate form
/// gives the alternate one of [`Display`], one error per block, with their origins.
impl Debug for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if !f.alternate() {
            write!(f, "Error({self})")
        } else {
            f.write_str("Error(\n")?;
            for line in format!("{self:#}").lines() {
                writeln!(f, "    {line}")?;
            }
            f.write_str(")")
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorKind<'r> {
    /// For backends that could not handle a specific operation, this usually means to
    /// fallback to the next backend to retry.
    NotHandled,
    /// For features that are not implemented.
    NotImplemented,
    // Database
    TangoHostNotSet,
    DatabaseNotConnected,
    DatabaseNeeded,
    AliasNotFound,
    DevNotFound,
    DevNotExported,
    // Device
    DevNameInvalid,
    DevNotConnected,
    // Server
    ServerNameInvalid,
    ClassAlreadyDefined,
    DevAlreadyDefined,
    DevInitFailed,
    CommandAlreadyDefined,
    AttrAlreadyDefined,
    // Class
    ClassNameInvalid,
    ClassNotFound,
    // Command
    CommandNameInvalid,
    CommandNotFound,
    CommandNotPolled,
    CommandArgumentKindInvalid,
    // Attr
    AttrNameInvalid,
    AttrNotFound,
    AttrNotPolled,
    AttrNotWritable,
    AttrKindInvalid,
    // Event
    EventNotConnected,
    // Misc
    InvalidIndex,
    InvalidData,
    InvalidProp,
    ValueOutdated,
    TimedOut,
    Aborted,
    Corba,
    Zmq,
    Internal,
    Reason(&'r str),
}

impl<'r> ErrorKind<'r> {

    fn into_static(self) -> std::result::Result<ErrorKind<'static>, Self> {
        Ok(match self {
            ErrorKind::NotHandled => ErrorKind::NotHandled,
            ErrorKind::NotImplemented => ErrorKind::NotImplemented,
            ErrorKind::TangoHostNotSet => ErrorKind::TangoHostNotSet,
            ErrorKind::DatabaseNotConnected => ErrorKind::DatabaseNotConnected,
            ErrorKind::DatabaseNeeded => ErrorKind::DatabaseNeeded,
            ErrorKind::AliasNotFound => ErrorKind::AliasNotFound,
            ErrorKind::DevNotFound => ErrorKind::DevNotFound,
            ErrorKind::DevNotExported => ErrorKind::DevNotExported,
            ErrorKind::DevNameInvalid => ErrorKind::DevNameInvalid,
            ErrorKind::DevNotConnected => ErrorKind::DevNotConnected,
            ErrorKind::ServerNameInvalid => ErrorKind::ServerNameInvalid,
            ErrorKind::ClassAlreadyDefined => ErrorKind::ClassAlreadyDefined,
            ErrorKind::DevAlreadyDefined => ErrorKind::DevAlreadyDefined,
            ErrorKind::DevInitFailed => ErrorKind::DevInitFailed,
            ErrorKind::CommandAlreadyDefined => ErrorKind::CommandAlreadyDefined,
            ErrorKind::AttrAlreadyDefined => ErrorKind::AttrAlreadyDefined,
            ErrorKind::ClassNameInvalid => ErrorKind::ClassNameInvalid,
            ErrorKind::ClassNotFound => ErrorKind::ClassNotFound,
            ErrorKind::CommandNameInvalid => ErrorKind::CommandNameInvalid,
            ErrorKind::CommandNotFound => ErrorKind::CommandNotFound,
            ErrorKind::CommandNotPolled => ErrorKind::CommandNotPolled,
            ErrorKind::CommandArgumentKindInvalid => ErrorKind::CommandArgumentKindInvalid,
            ErrorKind::AttrNameInvalid => ErrorKind::AttrNameInvalid,
            ErrorKind::AttrNotFound => ErrorKind::AttrNotFound,
            ErrorKind::AttrNotPolled => ErrorKind::AttrNotPolled,
            ErrorKind::AttrNotWritable => ErrorKind::AttrNotWritable,
            ErrorKind::AttrKindInvalid => ErrorKind::AttrKindInvalid,
            ErrorKind::EventNotConnected => ErrorKind::EventNotConnected,
            ErrorKind::ValueOutdated => ErrorKind::ValueOutdated,
            ErrorKind::InvalidIndex => ErrorKind::InvalidIndex,
            ErrorKind::InvalidData => ErrorKind::InvalidData,
            ErrorKind::InvalidProp => ErrorKind::InvalidProp,
            ErrorKind::TimedOut => ErrorKind::TimedOut,
            ErrorKind::Aborted => ErrorKind::Aborted,
            ErrorKind::Corba => ErrorKind::Corba,
            ErrorKind::Zmq => ErrorKind::Zmq,
            ErrorKind::Internal => ErrorKind::Internal,
            ErrorKind::Reason(_) => return Err(self),
        })
    }

    /// Return the textual and standard reason for this kind.
    pub fn reason(&self) -> &'r str {
        match self {
            ErrorKind::NotHandled => reason::API_InternalError,
            ErrorKind::NotImplemented => reason::API_NotSupported,
            ErrorKind::TangoHostNotSet => reason::API_TangoHostNotSet,
            ErrorKind::DatabaseNotConnected => reason::API_CantConnectToDatabase,
            ErrorKind::DatabaseNeeded => reason::API_NonDatabaseDevice,
            ErrorKind::AliasNotFound => reason::API_AliasNotDefined,
            ErrorKind::DevNotFound => reason::API_DeviceNotDefined,
            ErrorKind::DevNotExported => reason::API_DeviceNotExported,
            ErrorKind::DevNameInvalid => reason::API_WrongDeviceNameSyntax,
            ErrorKind::DevNotConnected => reason::API_CantConnectToDevice,
            ErrorKind::ServerNameInvalid => reason::API_InternalError,
            ErrorKind::ClassAlreadyDefined => reason::API_InternalError,
            ErrorKind::DevAlreadyDefined => reason::API_DeviceAlreadyDefined,
            ErrorKind::DevInitFailed => reason::API_InitThrowsException,
            ErrorKind::CommandAlreadyDefined => reason::API_InternalError,
            ErrorKind::AttrAlreadyDefined => reason::API_InternalError,
            ErrorKind::ClassNameInvalid => reason::API_InternalError,
            ErrorKind::ClassNotFound => reason::API_ClassNotFound,
            ErrorKind::CommandNameInvalid => reason::API_InternalError,
            ErrorKind::CommandNotFound => reason::API_CommandNotFound,
            ErrorKind::CommandNotPolled => reason::API_CmdNotPolled,
            ErrorKind::CommandArgumentKindInvalid => reason::API_IncompatibleCmdArgumentType,
            ErrorKind::AttrNameInvalid => reason::API_WrongAttributeNameSyntax,
            ErrorKind::AttrNotFound => reason::API_AttrNotFound,
            ErrorKind::AttrNotPolled => reason::API_AttrNotPolled,
            ErrorKind::AttrNotWritable => reason::API_AttrNotWritable,
            ErrorKind::AttrKindInvalid => reason::API_IncompatibleAttrDataType,
            ErrorKind::EventNotConnected => reason::API_EventNotFound,
            ErrorKind::ValueOutdated => reason::API_NotUpdatedAnyMore,
            ErrorKind::InvalidIndex => reason::API_InternalError,
            ErrorKind::InvalidData => reason::API_InternalError,
            ErrorKind::InvalidProp => reason::API_InternalError,
            ErrorKind::TimedOut => reason::API_DeviceTimedOut,
            ErrorKind::Aborted => reason::API_ShutdownInProgress,
            ErrorKind::Corba => reason::API_CorbaException,
            ErrorKind::Zmq => reason::API_ZmqFailed,
            ErrorKind::Internal => reason::API_InternalError,
            ErrorKind::Reason(reason) => reason,
        }
    }

    /// Return a statically defined description of the error.
    pub fn description(&self) -> &'static str {
        match self {
            ErrorKind::NotHandled => "Backend cannot handle this request",
            ErrorKind::NotImplemented => "Functionality is not implemented",
            ErrorKind::TangoHostNotSet => "TANGO_HOST is not set or invalid, it should be a comma separated list of host:port",
            ErrorKind::DatabaseNotConnected => "Database cannot be reached",
            ErrorKind::DatabaseNeeded => "Operation needs the device to be reached through a database",
            ErrorKind::AliasNotFound => "Alias is not defined in the database",
            ErrorKind::DevNotFound => "Device is not defined in the database",
            ErrorKind::DevNotExported => "Device is not exported by any running server",
            ErrorKind::DevNameInvalid => "Device name is invalid, it should be domain/family/member, each part made of letters, digits, '_' and '-', at most 85 long",
            ErrorKind::DevNotConnected => "Device cannot be reached",
            ErrorKind::ServerNameInvalid => "Server name is invalid, it should be exe/inst, each part made of letters, digits, '_' and '-'",
            ErrorKind::ClassAlreadyDefined => "Class is already defined on the server",
            ErrorKind::DevAlreadyDefined => "Device is already defined on the server",
            ErrorKind::DevInitFailed => "Device failed to initialize",
            ErrorKind::CommandAlreadyDefined => "Command is already defined on the device",
            ErrorKind::AttrAlreadyDefined => "Attribute is already defined on the device",
            ErrorKind::ClassNameInvalid => "Class name is invalid, it should be made of letters, digits, '_' and '-'",
            ErrorKind::ClassNotFound => "Class is not defined on the server",
            ErrorKind::CommandNameInvalid => "Command name is invalid, it should be made of letters, digits, '_' and '-', at most 255 long",
            ErrorKind::CommandNotFound => "Command is not defined on the device",
            ErrorKind::CommandNotPolled => "Command has no history recorded",
            ErrorKind::CommandArgumentKindInvalid => "Command argument type is incompatible",
            ErrorKind::AttrNameInvalid => "Attribute name is invalid, it should be made of letters, digits, '_' and '-', at most 255 long",
            ErrorKind::AttrNotFound => "Attribute is not defined on the device",
            ErrorKind::AttrNotPolled => "Attribute has no history recorded",
            ErrorKind::AttrNotWritable => "Attribute is not writable",
            ErrorKind::AttrKindInvalid => "Attribute data type is incompatible",
            ErrorKind::EventNotConnected => "Event subscription cannot be established",
            ErrorKind::ValueOutdated => "No up-to-date value is available",
            ErrorKind::InvalidIndex => "Index is out of range",
            ErrorKind::InvalidData => "Data is invalid",
            ErrorKind::InvalidProp => "Property is invalid",
            ErrorKind::TimedOut => "Operation timed out",
            ErrorKind::Aborted => "Operation was aborted",
            ErrorKind::Corba => "CORBA backend failed",
            ErrorKind::Zmq => "ZMQ backend failed",
            ErrorKind::Internal => "Internal error in the library, most likely a bug",
            ErrorKind::Reason(_) => "",
        }
    }

}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ErrorSeverity {
    Warning,
    Error,
    Panic,
}

/// Standard device failed reasons, documented after their usage in cppTango and `Databaseds`;
/// the ones noted as *not used by cppTango* only exist there as a constant.
#[allow(non_upper_case_globals)]
pub mod reason {

    macro_rules! define_reasons {
        ( $( $(#[$meta:meta])* $reason:ident ),* $(,)? ) => {
            $( $(#[$meta])* pub const $reason: &str = stringify!($reason); )*
        };
    }

    define_reasons![
        /// The device or attribute alias is not defined in the database.
        API_AliasNotDefined,
        /// The command or attribute is already in the polling list of the device.
        API_AlreadyPolled,
        /// The reply of an asynchronous call has not arrived yet.
        API_AsynReplyNotArrived,
        /// Attribute configuration could not be read or applied.
        API_AttrConfig,
        /// Attribute event properties are wrong or cannot be set. Not used by cppTango.
        API_AttrEventProp,
        /// Wrapper reason stacked on the real error when reading or writing an attribute failed.
        API_AttributeFailed,
        /// A `data_ready` event was asked on an attribute not data ready enabled.
        API_AttributeNotDataReadyEnabled,
        /// An event was asked but the polling needed to push it is not started.
        API_AttributePollingNotStarted,
        /// Incorrect number of elements given when writing an attribute value.
        API_AttrIncorrectDataNumber,
        /// Alarm information was asked on an attribute which has no alarm defined.
        API_AttrNoAlarm,
        /// The attribute operation is forbidden, e.g. a property not changeable at run time.
        API_AttrNotAllowed,
        /// The attribute does not exist on the device or in its class.
        API_AttrNotFound,
        /// Attribute history or cached value was asked while the attribute is not polled.
        API_AttrNotPolled,
        /// A write was attempted on an attribute which is not `READ_WRITE`/`READ_WITH_WRITE`.
        API_AttrNotWritable,
        /// Problem with the attribute value or its optional properties: null pointer, bad type.
        API_AttrOptProp,
        /// The numeric representation of an attribute property value has not been set.
        API_AttrPropValueNotSet,
        /// The attribute value was never updated, it cannot be read nor pushed as an event.
        API_AttrValueNotSet,
        /// The attribute is badly defined in its class: bad data type, association, memorized.
        API_AttrWrongDefined,
        /// The memorized value stored in the database for this attribute has an incorrect format.
        API_AttrWrongMemValue,
        /// The asynchronous callback request being looked up does not exist.
        API_BadAsyn,
        /// Invalid asynchronous request identifier given for the polling model.
        API_BadAsynPollId,
        /// The asynchronous request type is incompatible with the call made on it.
        API_BadAsynReqType,
        /// A device configuration property has a wrong syntax, e.g. `polled_attr`.
        API_BadConfigurationProperty,
        /// The argument given to read the black box is out of range.
        API_BlackBoxArgument,
        /// Nothing is stored in the device black box yet.
        API_BlackBoxEmpty,
        /// The access control device could not be imported, rights cannot be checked.
        API_CannotCheckAccessControl,
        /// A file could not be opened, in cppTango the device server logging file.
        API_CannotOpenFile,
        /// Activation of the CORBA POA manager failed. Not used by cppTango.
        API_CantActivatePOAManager,
        /// The database device could not be reached on the given host and port.
        API_CantConnectToDatabase,
        /// The connection to the device could not be built, e.g. nil reference or dead server.
        API_CantConnectToDevice,
        /// Creation of the CORBA POA of a device class failed. Not used by cppTango.
        API_CantCreateClassPoa,
        /// The thread keeping a device lock alive could not be created.
        API_CantCreateLockingThread,
        /// The device to remove is not in the device list of its Tango class.
        API_CantDestroyDevice,
        /// No locking thread was found for that device, e.g. unlocking a non locked device.
        API_CantFindLockingThread,
        /// The client identification, required by the lock commands, could not be retrieved.
        API_CantGetClientIdent,
        /// The CORBA reference id of a device could not be obtained.
        API_CantGetDevObjectId,
        /// Installing a signal handler failed, bad signal number or OS error.
        API_CantInstallSignal,
        /// The addresses of a host could not be resolved. Specific to rstango: cppTango has
        /// no such reason.
        API_CantResolveHost,
        /// The Tango class of a device could not be retrieved.
        API_CantRetrieveClass,
        /// The class list of a device server could not be retrieved. Not used by cppTango.
        API_CantRetrieveClassList,
        /// The device class could not be inserted in the client side class cache.
        API_CantStoreDeviceClass,
        /// The Tango class is not known by the device server process.
        API_ClassNotFound,
        /// The client Tango release is too old for events or device locking.
        API_ClientTooOld,
        /// The command argument data type is not handled by this code path.
        API_CmdArgumentTypeNotSupported,
        /// Command history or cached result was asked while the command is not polled.
        API_CmdNotPolled,
        /// Wrapper reason stacked on the real error when a `command_inout` failed.
        API_CommandFailed,
        /// The command is not allowed in the current device state.
        API_CommandNotAllowed,
        /// The command does not exist in the command list of the device class.
        API_CommandNotFound,
        /// A server side timeout expired on a serialization monitor or a blocked polling thread.
        API_CommandTimedOut,
        /// The network exchange with the device failed, wraps the transport error.
        API_CommunicationFailed,
        /// A connection to a peer failed, in cppTango the log consumer device.
        API_ConnectionFailed,
        /// A CORBA user exception was received and translated into a Tango error.
        API_CorbaException,
        /// A CORBA system exception was caught, an unexpected ORB or transport failure.
        API_CorbaSysException,
        /// The database returned inconsistent attribute or pipe properties.
        API_CorruptedDatabase,
        /// An error occurred while reading from or writing to the database.
        API_DatabaseAccess,
        /// The entry is not available from the database cache built at server startup.
        API_DatabaseCacheAccess,
        /// Error with the file database used in `-file=` mode: syntax, access or missing class.
        API_DatabaseFileError,
        /// Decoding of a `DevEncoded` payload failed.
        API_DecodeErr,
        /// Encoding of a `DevEncoded` payload failed.
        API_EncodeErr,
        /// A deprecated admin command was called, e.g. `SetTraceLevel`.
        API_DeprecatedCommand,
        /// The device is already defined in the database. Not used by cppTango.
        API_DeviceAlreadyDefined,
        /// The device is locked by another client, the request is refused.
        API_DeviceLocked,
        /// The device is not defined in the database, it cannot be imported.
        API_DeviceNotDefined,
        /// The device is defined but not exported, its device server is not running.
        API_DeviceNotExported,
        /// The device was not found in the device list of the server.
        API_DeviceNotFound,
        /// The operation requires the device to be locked first.
        API_DeviceNotLocked,
        /// The device, or one of its objects, is not polled.
        API_DeviceNotPolled,
        /// The client side timeout expired while waiting for the device answer.
        API_DeviceTimedOut,
        /// The device cannot be locked at all, e.g. the admin or database device.
        API_DeviceUnlockable,
        /// The lock owned on the device has been released, reported to its holder.
        API_DeviceUnlocked,
        /// The `DServer` class singleton is not initialized yet.
        API_DServerClassNotInitialised,
        /// The device server refused or failed the event registration.
        API_DSFailedRegisteringEvent,
        /// Placeholder reason with no meaning, used internally and never propagated.
        API_DummyException,
        /// The pipe data element is empty, it holds no value to extract.
        API_EmptyDataElement,
        /// The `DbDatum` holds no data, the requested extraction is impossible.
        API_EmptyDbDatum,
        /// Same as [`API_EmptyDbDatum`], kept because of a historical typo in cppTango.
        API_EmptyDbDbDatum,
        /// The `DeviceAttribute` holds no data, the read failed or it is uninitialized.
        API_EmptyDeviceAttribute,
        /// The `DeviceData` holds no data, the requested extraction is impossible.
        API_EmptyDeviceData,
        /// The notifd event channel could not be found, no notifd runs on that host.
        API_EventChannelNotExported,
        /// No event consumer object is available, no subscription was done before.
        API_EventConsumer,
        /// The given event id is unknown, on unsubscribe or event queue access.
        API_EventNotFound,
        /// The event properties needed by the requested event are not configured.
        API_EventPropertiesNotSet,
        /// Misuse of the event queue: none configured, or no event available to return.
        API_EventQueues,
        /// The event supplier of the server is not created yet.
        API_EventSupplierNotConstructed,
        /// No event was received within the keep alive period, pushed to the callback.
        API_EventTimeout,
        /// Internal inconsistency in the forwarded attribute registry maps.
        API_FwdAttrInconsistency,
        /// The forwarded attribute has no valid root. Not used by cppTango, see [`API_AttrConfig`].
        API_FwdAttrNotConfigured,
        /// The history data received is invalid, its format could not be interpreted.
        API_HistoryInvalid,
        /// The data received from the database device is not what the client expected.
        API_IncoherentDbData,
        /// The data received from the device is not coherent with the type extracted.
        API_IncoherentDevData,
        /// Two related property values are incoherent, e.g. `min` greater than `max`.
        API_IncoherentValues,
        /// The argument type is incompatible with the target type, e.g. for an enum.
        API_IncompatibleArgumentType,
        /// The extracted type does not match the `DeviceAttribute` content.
        API_IncompatibleAttrArgumentType,
        /// The provided value data type does not match the attribute data type.
        API_IncompatibleAttrDataType,
        /// The command argument type does not match the type the command expects.
        API_IncompatibleCmdArgumentType,
        /// No `init_device` method found for the class. Not used by cppTango.
        API_InitMethodNotFound,
        /// The `init_device` method is not public and cannot be called. Not used by cppTango.
        API_InitNotPublic,
        /// `init_device()` threw, so the device is no longer available.
        API_InitThrowsException,
        /// Unexpected internal state of the library, and generic fallback reason.
        API_InternalError,
        /// An invalid argument was given to a library call or on the server command line.
        API_InvalidArgs,
        /// The received CORBA `Any` has an unexpected kind and cannot be interpreted.
        API_InvalidCorbaAny,
        /// A Java runtime security error occurred. JTango only.
        API_JavaRuntimeSecurityException,
        /// Writing a memorized attribute failed during the device startup sequence.
        API_MemAttFailedDuringInit,
        /// A memory allocation failed in the server or in the client.
        API_MemoryAllocation,
        /// A method argument is missing, of the wrong type, or in the wrong number.
        API_MethodArgument,
        /// The requested method was not found. JTango and PyTango only.
        API_MethodNotFound,
        /// Some events were lost, e.g. ZMQ high water mark reached, pushed to the callback.
        API_MissedEvents,
        /// Polling is configured but its cache holds no data yet for that object.
        API_NoDataYet,
        /// The method requires a database, but the device was built without one.
        API_NonDatabaseDevice,
        /// No write (set) value is available in the `DeviceAttribute`.
        API_NoSetValueAvailable,
        /// The notifd notification service failed, e.g. its factory import.
        API_NotificationServiceFailed,
        /// The operation is not supported in this context, e.g. polling not externally triggered.
        API_NotSupported,
        /// The peer does not support the feature, its IDL release is too old.
        API_NotSupportedFeature,
        /// The polling cache data is not refreshed any more, polling stopped or in error.
        API_NotUpdatedAnyMore,
        /// Creation of the Windows debug or main output window failed.
        API_NtDebugWindowError,
        /// Command or method overloading is not supported. PyTango and JTango only.
        API_OverloadingNotSupported,
        /// Data element not found in the pipe blob. Not used by cppTango, see [`API_PipeWrongArg`].
        API_PipeDataEltNotFound,
        /// Two data elements of the same pipe blob have the same name.
        API_PipeDuplicateDEName,
        /// Wrapper reason stacked on the real error when reading or writing a pipe failed.
        API_PipeFailed,
        /// No data element is available in the pipe blob for insertion or extraction.
        API_PipeNoDataElement,
        /// Reading or writing the pipe is currently not allowed, e.g. by the serial model.
        API_PipeNotAllowed,
        /// The pipe does not exist on the device or in its class.
        API_PipeNotFound,
        /// A write was attempted on a pipe which is not writable.
        API_PipeNotWritable,
        /// Problem with a pipe data element property, e.g. a null data pointer.
        API_PipeOptProp,
        /// The pipe value was never updated by the device, it cannot be read.
        API_PipeValueNotSet,
        /// Wrong argument used on a pipe blob: bad index, bad data type, or blob never received.
        API_PipeWrongArg,
        /// Wrong number of data elements given for a pipe. Not used by cppTango.
        API_PipeWrongArgNumber,
        /// The polling pool is full and the device is not in the pool configuration.
        API_PolledDeviceNotInPoolConf,
        /// The polling pool is full and the device is missing from the polled map.
        API_PolledDeviceNotInPoolMap,
        /// No polling thread was found for that device or thread id.
        API_PollingThreadNotFound,
        /// The command or attribute is not in the polled object list of the device.
        API_PollObjNotFound,
        /// Not enough data is stored yet in the polling ring buffer.
        API_PollRingBufferEmpty,
        /// The polling thread lost its schedule and is out of sync.
        API_PollThreadOutOfSync,
        /// Access control refused the request, the client only has read access.
        API_ReadOnlyMode,
        /// Root attribute access failed. Not used by cppTango, see [`API_AttributeFailed`].
        API_RootAttrFailed,
        /// The device server process is not running, reported without database.
        API_ServerNotRunning,
        /// The device server is shutting down and refuses the request.
        API_ShutdownInProgress,
        /// The signal number is out of range or cannot be handled by a user handler.
        API_SignalOutOfRange,
        /// An error occurred in the device server startup sequence.
        API_StartupSequence,
        /// A `std::exception` was caught and translated into a Tango error.
        API_StdException,
        /// A system call failed, e.g. socket creation or `getifaddrs()`.
        API_SystemCallFailed,
        /// The `TANGO_HOST` variable is not set, or its syntax is incorrect.
        API_TangoHostNotSet,
        /// Generic reason for an exception thrown on purpose. Not used by cppTango.
        API_ThrowException,
        /// The attribute is not supported by the device.
        API_UnsupportedAttribute,
        /// Unsupported database modifier in a name, only `#dbase=no|yes` are valid.
        API_UnsupportedDBaseModifier,
        /// The feature is absent from this build, e.g. a Tango built without jpeg.
        API_UnsupportedFeature,
        /// Unsupported protocol prefix in a name, only `tango://` is supported.
        API_UnsupportedProtocol,
        /// The `Util` singleton is not created, Tango is not initialized.
        API_UtilSingletonNotCreated,
        /// The `Util` singleton was inherited through a `fork()` without `exec()`.
        API_UtilSingletonPoisoned,
        /// The written value is outside the attribute limits, or is NaN/Inf, or too large.
        API_WAttrOutsideLimit,
        /// A property is already defined in the wizard configuration of the class.
        API_WizardConfError,
        /// Invalid attribute name syntax, four fields or a plain alias are expected.
        API_WrongAttributeNameSyntax,
        /// Wrong command line arguments given to the device server.
        API_WrongCmdLineArgs,
        /// Invalid device name syntax, `domain/family/member` is expected.
        API_WrongDeviceNameSyntax,
        /// The event data received is malformed and cannot be turned into a value.
        API_WrongEventData,
        /// The `DevEncoded` data is not in the expected image format.
        API_WrongFormat,
        /// The history data buffer received from the server is not valid.
        API_WrongHistoryDataBuffer,
        /// The locking status returned by the server is invalid.
        API_WrongLockingStatus,
        /// Wrong number of arguments given to a command, mostly the admin ones.
        API_WrongNumberOfArgs,
        /// Wrong use of wildcards in a pattern, only one `*` is allowed.
        API_WrongWildcardUsage,
        /// A ZMQ call failed at run time, when binding or pushing an event or heartbeat.
        API_ZmqFailed,
        /// ZMQ initialisation failed, on context creation or socket bind at startup.
        API_ZmqInitFailed,
        /// `Databaseds`: no alias for that device/attribute, or nothing matches that alias.
        DB_AliasNotDefined,
        /// The Tango class was not found in the startup database cache.
        DB_ClassNotFoundInCache,
        /// `Databaseds`: device, server, event or class not defined in the database.
        DB_DeviceNotDefined,
        /// The device or object was not found in the startup database cache.
        DB_DeviceNotFoundInCache,
        /// `Databaseds`: a MySQL query, a stored procedure or the given SQL command failed.
        DB_SQLError,
        /// The database stored procedure is too old, e.g. pipes need release 1.9.
        DB_TooOldStoredProc,
    ];

}