yesser_todo_errors/
server_error.rs1use 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}