1use std::process::ExitCode;
4use thiserror::Error;
5
6use crate::diagnostics;
7
8#[derive(Error, Debug)]
10pub enum CliError {
11 #[error("speaker \"{0}\" not found")]
12 SpeakerNotFound(String),
13
14 #[error("group \"{0}\" not found")]
15 GroupNotFound(String),
16
17 #[error("SDK error: {0}")]
18 Sdk(#[from] sonos_sdk::SdkError),
19
20 #[error("configuration error: {0}")]
21 #[allow(dead_code)]
22 Config(String),
23
24 #[error("validation error: {0}")]
25 Validation(String),
26}
27
28impl CliError {
29 pub fn recovery_hint(&self) -> Option<&'static str> {
31 match self {
32 Self::SpeakerNotFound(_) | Self::GroupNotFound(_) => {
33 Some(diagnostics::discovery_hint())
34 }
35 Self::Sdk(e) => {
36 if matches!(e, sonos_sdk::SdkError::DiscoveryFailed(_))
37 || e.to_string().contains("Network error")
38 {
39 Some(diagnostics::discovery_hint())
40 } else {
41 Some("Check network connectivity and speaker power.")
42 }
43 }
44 Self::Config(_) | Self::Validation(_) => None,
45 }
46 }
47
48 pub fn exit_code(&self) -> ExitCode {
50 match self {
51 Self::Validation(_) => ExitCode::from(2), _ => ExitCode::from(1), }
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 #[test]
62 fn speaker_not_found_has_recovery_hint() {
63 let err = CliError::SpeakerNotFound("Kitchen".to_string());
64 assert!(err.recovery_hint().is_some());
65 assert!(err.recovery_hint().unwrap().contains("this could mean:"));
66 }
67
68 #[test]
69 fn group_not_found_has_recovery_hint() {
70 let err = CliError::GroupNotFound("Living Room".to_string());
71 assert!(err.recovery_hint().is_some());
72 }
73
74 #[test]
75 fn validation_error_has_no_hint() {
76 let err = CliError::Validation("invalid volume".to_string());
77 assert!(err.recovery_hint().is_none());
78 }
79
80 #[test]
81 fn validation_error_returns_exit_code_2() {
82 let err = CliError::Validation("bad input".to_string());
83 assert_eq!(err.exit_code(), ExitCode::from(2));
84 }
85
86 #[test]
87 fn runtime_errors_return_exit_code_1() {
88 let err = CliError::SpeakerNotFound("x".to_string());
89 assert_eq!(err.exit_code(), ExitCode::from(1));
90 }
91}