Skip to main content

hid_rgb_ctl/
error.rs

1//! Error types for hid-rgb-ctl.
2//!
3//! Hand-written error enum with `Display` and `Error` trait implementations.
4//! Supports `?` operator via `From` impls for `std::io::Error` and `lexopt::Error`.
5
6use std::fmt;
7
8/// All errors that can occur in hid-rgb-ctl.
9#[derive(Debug)]
10pub enum Error {
11    /// Permission denied accessing a hidraw device.
12    PermissionDenied { path: String },
13    /// The hidraw device path does not exist.
14    DeviceNotFound { path: String },
15    /// A required HID report type is missing from the device descriptor.
16    MissingReport { report_name: String },
17    /// LampAttributesResponseReport returned an unexpected LampId.
18    LampIdMismatch { expected: u16, got: u16 },
19    /// Caller specified a LampId that exceeds the device's LampCount.
20    LampIdOutOfRange { lamp_id: u16, lamp_count: u16 },
21    /// Caller specified the same LampId in multiple update slots.
22    DuplicateLampId { lamp_id: u16 },
23    /// The `auto` command was used on a non-LampArray device.
24    NoAutonomousMode,
25    /// The `set-lamp` command was used on a non-LampArray device.
26    NoMultiUpdate,
27    /// Device returned a report shorter than expected.
28    TruncatedReport {
29        report_name: &'static str,
30        expected: usize,
31        got: usize,
32    },
33    /// Attempted to write to an Input-only report (device-to-host).
34    UnsupportedReportType,
35    /// Invalid subcommand or argument.
36    InvalidArgument(String),
37    /// Wrapped I/O error.
38    Io(std::io::Error),
39    /// Wrapped lexopt argument parsing error.
40    Arg(lexopt::Error),
41}
42
43impl fmt::Display for Error {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        match self {
46            Self::PermissionDenied { path } => {
47                write!(f, "Permission denied on {path}")
48            }
49            Self::DeviceNotFound { path } => {
50                write!(f, "Device not found: {path}")
51            }
52            Self::MissingReport { report_name } => {
53                write!(f, "Device has no '{report_name}' report")
54            }
55            Self::LampIdMismatch { expected, got } => {
56                write!(
57                    f,
58                    "LampAttributesResponse returned LampId {got}, expected {expected}"
59                )
60            }
61            Self::LampIdOutOfRange {
62                lamp_id,
63                lamp_count,
64            } => {
65                write!(f, "LampId {lamp_id} exceeds device LampCount {lamp_count}")
66            }
67            Self::DuplicateLampId { lamp_id } => {
68                write!(f, "Duplicate LampId {lamp_id} in update request")
69            }
70            Self::NoAutonomousMode => {
71                write!(
72                    f,
73                    "This device does not support autonomous mode. \
74                     Only LampArray (Usage Page 0x59) devices have this feature."
75                )
76            }
77            Self::NoMultiUpdate => {
78                write!(
79                    f,
80                    "Per-lamp color control requires a LampArray device (Usage Page 0x59) \
81                     with a LampMultiUpdateReport."
82                )
83            }
84            Self::TruncatedReport {
85                report_name,
86                expected,
87                got,
88            } => {
89                write!(
90                    f,
91                    "Truncated {report_name} report: expected at least {expected} bytes, got {got}"
92                )
93            }
94            Self::UnsupportedReportType => {
95                write!(f, "Cannot write to an Input report (device-to-host only)")
96            }
97            Self::InvalidArgument(msg) => {
98                write!(f, "{msg}")
99            }
100            Self::Io(e) => write!(f, "{e}"),
101            Self::Arg(e) => write!(f, "{e}"),
102        }
103    }
104}
105
106impl std::error::Error for Error {
107    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
108        match self {
109            Self::Io(e) => Some(e),
110            Self::Arg(e) => Some(e),
111            _ => None,
112        }
113    }
114}
115
116impl From<std::io::Error> for Error {
117    fn from(e: std::io::Error) -> Self {
118        Self::Io(e)
119    }
120}
121
122impl From<lexopt::Error> for Error {
123    fn from(e: lexopt::Error) -> Self {
124        Self::Arg(e)
125    }
126}
127
128/// Convenience type alias.
129pub type Result<T> = std::result::Result<T, Error>;