1
use std::fmt::Display;
2

            
3
/// Error type for the application, encapsulating various error scenarios
4
#[derive(Debug)]
5
pub enum Error {
6
    ReadPrivateKey(String, std::io::Error),
7
    EncodingKey(jsonwebtoken::errors::Error),
8
    #[allow(clippy::upper_case_acronyms)]
9
    JWT(jsonwebtoken::errors::Error),
10
    InvalidBearerToken(),
11
    CreateRequest(reqwest::Error),
12
    Send(reqwest::Error),
13
    NonOkStatus(String, reqwest::StatusCode),
14
    Parse(&'static str, Box<dyn std::error::Error>),
15
    ReceiveBody(reqwest::Error),
16
    Serve(std::io::Error),
17
    BindPort(Box<dyn std::error::Error>),
18
    ReadConfigFile(String, std::io::Error),
19
    ParseConfigFile(String, serde_yaml::Error),
20
    InvalidConfig(&'static str),
21
}
22

            
23
impl Display for Error {
24
7
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25
7
        match self {
26
2
            Error::ReadPrivateKey(path, err) => {
27
2
                write!(f, "Failed to read private key '{path}': {err}")
28
            }
29
            Error::EncodingKey(err) => {
30
                write!(f, "Failed to create encoding key: {err}")
31
            }
32
            Error::JWT(err) => {
33
                write!(f, "Failed to create JWT token: {err}")
34
            }
35
            Error::InvalidBearerToken() => {
36
1
                write!(f, "Invalid bearer token provided.")
37
            }
38
            Error::CreateRequest(err) => {
39
                write!(f, "Failed to create request: {err}")
40
            }
41
            Error::Send(err) => {
42
                write!(f, "Failed to send request: {}", full_error_stack(err))
43
            }
44
1
            Error::NonOkStatus(url, status) => {
45
1
                write!(f, "Request to '{url}' failed with status: {status}")
46
            }
47
            Error::Parse(url, err) => {
48
                write!(f, "Failed to parse response from '{url}': {err}")
49
            }
50
            Error::ReceiveBody(err) => {
51
                write!(f, "Failed to read response body: {err}")
52
            }
53
            Error::Serve(err) => {
54
                write!(f, "Server error: {err}")
55
            }
56
1
            Error::BindPort(err) => {
57
1
                write!(f, "Failed to bind port: {err}")
58
            }
59
1
            Error::ReadConfigFile(path, err) => {
60
1
                write!(f, "Failed to read config file '{path}': {err}")
61
            }
62
            Error::ParseConfigFile(path, err) => {
63
                write!(f, "Failed to parse config file '{path}': {err}")
64
            }
65
1
            Error::InvalidConfig(msg) => {
66
1
                write!(f, "Invalid configuration: {msg}")
67
            }
68
        }
69
7
    }
70
}
71

            
72
impl std::error::Error for Error {}
73

            
74
1
fn full_error_stack(mut e: &dyn std::error::Error) -> String {
75
1
    let mut s = format!("{e}");
76
1
    while let Some(src) = e.source() {
77
        s.push_str(&format!(": {src}"));
78
        e = src;
79
    }
80
1
    s
81
1
}
82

            
83
#[cfg(test)]
84
mod tests {
85
    use super::*;
86
    use std::io;
87

            
88
    #[test]
89
1
    fn test_error_display_read_private_key() {
90
1
        let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
91
1
        let error = Error::ReadPrivateKey("/path/to/key".to_string(), io_error);
92
1
        let display_string = format!("{}", error);
93
1
        assert!(display_string.contains("Failed to read private key '/path/to/key'"));
94
1
        assert!(display_string.contains("file not found"));
95
1
    }
96

            
97
    #[test]
98
1
    fn test_error_display_invalid_bearer_token() {
99
1
        let error = Error::InvalidBearerToken();
100
1
        let display_string = format!("{}", error);
101
1
        assert_eq!(display_string, "Invalid bearer token provided.");
102
1
    }
103

            
104
    #[test]
105
1
    fn test_error_display_non_ok_status() {
106
1
        let error = Error::NonOkStatus(
107
1
            "https://api.github.com".to_string(),
108
1
            reqwest::StatusCode::NOT_FOUND,
109
1
        );
110
1
        let display_string = format!("{}", error);
111
1
        assert_eq!(
112
            display_string,
113
            "Request to 'https://api.github.com' failed with status: 404 Not Found"
114
        );
115
1
    }
116

            
117
    #[test]
118
1
    fn test_error_display_invalid_config() {
119
1
        let error = Error::InvalidConfig("missing required field");
120
1
        let display_string = format!("{}", error);
121
1
        assert_eq!(
122
            display_string,
123
            "Invalid configuration: missing required field"
124
        );
125
1
    }
126

            
127
    #[test]
128
1
    fn test_error_display_read_config_file() {
129
1
        let io_error = io::Error::new(io::ErrorKind::PermissionDenied, "permission denied");
130
1
        let error = Error::ReadConfigFile("/etc/config.yaml".to_string(), io_error);
131
1
        let display_string = format!("{}", error);
132
1
        assert!(display_string.contains("Failed to read config file '/etc/config.yaml'"));
133
1
        assert!(display_string.contains("permission denied"));
134
1
    }
135

            
136
    #[test]
137
1
    fn test_error_display_bind_port() {
138
1
        let io_error = io::Error::new(io::ErrorKind::AddrInUse, "address already in use");
139
1
        let error = Error::BindPort(Box::new(io_error));
140
1
        let display_string = format!("{}", error);
141
1
        assert!(display_string.contains("Failed to bind port"));
142
1
        assert!(display_string.contains("address already in use"));
143
1
    }
144

            
145
    #[test]
146
1
    fn test_error_is_error_trait() {
147
1
        let error = Error::InvalidBearerToken();
148
        // Test that Error implements std::error::Error
149
1
        let _: &dyn std::error::Error = &error;
150
1
    }
151

            
152
    #[test]
153
1
    fn test_full_error_stack() {
154
1
        let inner_error = io::Error::new(io::ErrorKind::NotFound, "inner error");
155
1
        let outer_error = Error::ReadPrivateKey("test".to_string(), inner_error);
156
1
        let stack = full_error_stack(&outer_error);
157
1
        assert!(stack.contains("Failed to read private key 'test'"));
158
1
        assert!(stack.contains("inner error"));
159
1
    }
160
}