Skip to main content

sonos_cli/
errors.rs

1//! Error types for the sonos-cli application.
2
3use std::process::ExitCode;
4use thiserror::Error;
5
6/// Domain error type with recovery hints and exit codes.
7#[derive(Error, Debug)]
8pub enum CliError {
9    #[error("speaker \"{0}\" not found")]
10    SpeakerNotFound(String),
11
12    #[error("group \"{0}\" not found")]
13    GroupNotFound(String),
14
15    #[error("SDK error: {0}")]
16    Sdk(#[from] sonos_sdk::SdkError),
17
18    #[error("configuration error: {0}")]
19    #[allow(dead_code)]
20    Config(String),
21
22    #[error("validation error: {0}")]
23    Validation(String),
24}
25
26impl CliError {
27    /// Returns actionable follow-up text for the user.
28    pub fn recovery_hint(&self) -> Option<&str> {
29        match self {
30            Self::SpeakerNotFound(_) | Self::GroupNotFound(_) => {
31                Some("Check that your speakers are on the same network, then retry.")
32            }
33            Self::Sdk(_) => Some("Check network connectivity and speaker power."),
34            Self::Config(_) | Self::Validation(_) => None,
35        }
36    }
37
38    /// Returns the appropriate exit code.
39    pub fn exit_code(&self) -> ExitCode {
40        match self {
41            Self::Validation(_) => ExitCode::from(2), // usage error
42            _ => ExitCode::from(1),                   // runtime error
43        }
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    #[test]
52    fn speaker_not_found_has_recovery_hint() {
53        let err = CliError::SpeakerNotFound("Kitchen".to_string());
54        assert!(err.recovery_hint().is_some());
55        assert!(err.recovery_hint().unwrap().contains("same network"));
56    }
57
58    #[test]
59    fn group_not_found_has_recovery_hint() {
60        let err = CliError::GroupNotFound("Living Room".to_string());
61        assert!(err.recovery_hint().is_some());
62    }
63
64    #[test]
65    fn validation_error_has_no_hint() {
66        let err = CliError::Validation("invalid volume".to_string());
67        assert!(err.recovery_hint().is_none());
68    }
69
70    #[test]
71    fn validation_error_returns_exit_code_2() {
72        let err = CliError::Validation("bad input".to_string());
73        assert_eq!(err.exit_code(), ExitCode::from(2));
74    }
75
76    #[test]
77    fn runtime_errors_return_exit_code_1() {
78        let err = CliError::SpeakerNotFound("x".to_string());
79        assert_eq!(err.exit_code(), ExitCode::from(1));
80    }
81}