1use http::StatusCode;
4use serde::Serialize;
5use std::fmt;
6
7pub type Result<T, E = ApiError> = std::result::Result<T, E>;
9
10#[derive(Debug, Clone)]
14pub struct ApiError {
15 pub status: StatusCode,
17 pub error_type: String,
19 pub message: String,
21 pub fields: Option<Vec<FieldError>>,
23 pub(crate) internal: Option<String>,
25}
26
27#[derive(Debug, Clone, Serialize)]
29pub struct FieldError {
30 pub field: String,
32 pub code: String,
34 pub message: String,
36}
37
38impl ApiError {
39 pub fn new(status: StatusCode, error_type: impl Into<String>, message: impl Into<String>) -> Self {
41 Self {
42 status,
43 error_type: error_type.into(),
44 message: message.into(),
45 fields: None,
46 internal: None,
47 }
48 }
49
50 pub fn validation(fields: Vec<FieldError>) -> Self {
52 Self {
53 status: StatusCode::UNPROCESSABLE_ENTITY,
54 error_type: "validation_error".to_string(),
55 message: "Request validation failed".to_string(),
56 fields: Some(fields),
57 internal: None,
58 }
59 }
60
61 pub fn bad_request(message: impl Into<String>) -> Self {
63 Self::new(StatusCode::BAD_REQUEST, "bad_request", message)
64 }
65
66 pub fn unauthorized(message: impl Into<String>) -> Self {
68 Self::new(StatusCode::UNAUTHORIZED, "unauthorized", message)
69 }
70
71 pub fn forbidden(message: impl Into<String>) -> Self {
73 Self::new(StatusCode::FORBIDDEN, "forbidden", message)
74 }
75
76 pub fn not_found(message: impl Into<String>) -> Self {
78 Self::new(StatusCode::NOT_FOUND, "not_found", message)
79 }
80
81 pub fn conflict(message: impl Into<String>) -> Self {
83 Self::new(StatusCode::CONFLICT, "conflict", message)
84 }
85
86 pub fn internal(message: impl Into<String>) -> Self {
88 Self::new(StatusCode::INTERNAL_SERVER_ERROR, "internal_error", message)
89 }
90
91 pub fn with_internal(mut self, details: impl Into<String>) -> Self {
93 self.internal = Some(details.into());
94 self
95 }
96}
97
98impl fmt::Display for ApiError {
99 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 write!(f, "{}: {}", self.error_type, self.message)
101 }
102}
103
104impl std::error::Error for ApiError {}
105
106#[derive(Serialize)]
108pub(crate) struct ErrorResponse {
109 pub error: ErrorBody,
110 #[serde(skip_serializing_if = "Option::is_none")]
111 pub request_id: Option<String>,
112}
113
114#[derive(Serialize)]
115pub(crate) struct ErrorBody {
116 #[serde(rename = "type")]
117 pub error_type: String,
118 pub message: String,
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub fields: Option<Vec<FieldError>>,
121}
122
123impl From<ApiError> for ErrorResponse {
124 fn from(err: ApiError) -> Self {
125 Self {
126 error: ErrorBody {
127 error_type: err.error_type,
128 message: err.message,
129 fields: err.fields,
130 },
131 request_id: None, }
133 }
134}
135
136impl From<serde_json::Error> for ApiError {
138 fn from(err: serde_json::Error) -> Self {
139 ApiError::bad_request(format!("Invalid JSON: {}", err))
140 }
141}
142
143impl From<std::io::Error> for ApiError {
144 fn from(err: std::io::Error) -> Self {
145 ApiError::internal("I/O error").with_internal(err.to_string())
146 }
147}
148
149impl From<hyper::Error> for ApiError {
150 fn from(err: hyper::Error) -> Self {
151 ApiError::internal("HTTP error").with_internal(err.to_string())
152 }
153}
154
155impl From<rustapi_validate::ValidationError> for ApiError {
156 fn from(err: rustapi_validate::ValidationError) -> Self {
157 let fields = err.fields.into_iter().map(|f| FieldError {
158 field: f.field,
159 code: f.code,
160 message: f.message,
161 }).collect();
162
163 ApiError::validation(fields)
164 }
165}
166
167impl ApiError {
168 pub fn from_validation_error(err: rustapi_validate::ValidationError) -> Self {
170 err.into()
171 }
172}
173