Skip to main content

yesser_todo_errors/
server_error.rs

1use std::fmt::Display;
2
3#[cfg(feature = "server")]
4use axum::{Json, response::IntoResponse};
5
6use http::StatusCode;
7use serde::{Deserialize, Serialize};
8use thiserror::Error;
9
10use crate::db_error::DatabaseError;
11
12#[derive(Debug, Error, Serialize, Deserialize)]
13pub enum ServerError {
14    #[error("{0} not found!")]
15    NotFound(TaskSelector),
16    #[error("{0} already exists!")]
17    Conflict(TaskSelector),
18    #[error("IO error: {0}")]
19    IOError(String),
20}
21
22impl ServerError {
23    pub fn to_status_code(&self) -> StatusCode {
24        match self {
25            ServerError::NotFound(_) => StatusCode::NOT_FOUND,
26            ServerError::Conflict(_) => StatusCode::CONFLICT,
27            ServerError::IOError(_) => StatusCode::INTERNAL_SERVER_ERROR,
28        }
29    }
30}
31
32impl From<DatabaseError> for ServerError {
33    fn from(value: DatabaseError) -> Self {
34        Self::IOError(format!("{value}"))
35    }
36}
37
38#[cfg(feature = "server")]
39impl IntoResponse for ServerError {
40    fn into_response(self) -> axum::response::Response {
41        let status = self.to_status_code();
42        (status, Json(self)).into_response()
43    }
44}
45
46#[derive(Debug, Serialize, Deserialize)]
47pub enum TaskSelector {
48    Index(usize),
49    Name(String),
50}
51
52impl Display for TaskSelector {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        match self {
55            TaskSelector::Index(index) => write!(f, "Task of index {index}"),
56            TaskSelector::Name(name) => write!(f, "Task {name}"),
57        }
58    }
59}
60
61impl From<TaskSelector> for Option<String> {
62    fn from(val: TaskSelector) -> Self {
63        match val {
64            TaskSelector::Index(_) => None,
65            TaskSelector::Name(name) => Some(name),
66        }
67    }
68}
69impl From<TaskSelector> for Option<usize> {
70    fn from(val: TaskSelector) -> Self {
71        match val {
72            TaskSelector::Index(index) => Some(index),
73            TaskSelector::Name(_) => None,
74        }
75    }
76}
77
78impl From<usize> for TaskSelector {
79    fn from(value: usize) -> Self {
80        Self::Index(value)
81    }
82}
83impl From<String> for TaskSelector {
84    fn from(value: String) -> Self {
85        Self::Name(value)
86    }
87}
88
89#[cfg(feature = "server")]
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn test_to_status_code_not_found() {
96        let err = ServerError::NotFound(TaskSelector::Name("test".into()));
97        assert_eq!(err.to_status_code(), StatusCode::NOT_FOUND);
98    }
99
100    #[test]
101    fn test_to_status_code_conflict() {
102        let err = ServerError::Conflict(TaskSelector::Index(1));
103        assert_eq!(err.to_status_code(), StatusCode::CONFLICT);
104    }
105
106    #[test]
107    fn test_to_status_code_io_error() {
108        let err = ServerError::IOError("test error".into());
109        assert_eq!(err.to_status_code(), StatusCode::INTERNAL_SERVER_ERROR);
110    }
111
112    #[test]
113    fn test_display_not_found() {
114        let err = ServerError::NotFound(TaskSelector::Name("my task".into()));
115        assert_eq!(format!("{}", err), "Task my task not found!");
116    }
117
118    #[test]
119    fn test_display_conflict_index() {
120        let err = ServerError::Conflict(TaskSelector::Index(5));
121        assert_eq!(format!("{}", err), "Task of index 5 already exists!");
122    }
123
124    #[test]
125    fn test_display_io_error() {
126        let err = ServerError::IOError("disk full".into());
127        assert_eq!(format!("{}", err), "IO error: disk full");
128    }
129
130    #[test]
131    fn test_task_selector_display_index() {
132        let selector = TaskSelector::Index(42);
133        assert_eq!(format!("{}", selector), "Task of index 42");
134    }
135
136    #[test]
137    fn test_task_selector_display_name() {
138        let selector = TaskSelector::Name("buy milk".into());
139        assert_eq!(format!("{}", selector), "Task buy milk");
140    }
141}