lora_server/app/
errors.rs1use axum::{
2 extract::rejection::JsonRejection,
3 http::StatusCode,
4 response::{IntoResponse, Response},
5 Json,
6};
7use lora_database::{LoraError, LoraErrorCategory, LoraErrorCode};
8use serde::Serialize;
9
10#[derive(Debug, Serialize)]
23pub struct ErrorResponse {
24 pub error: ErrorBody,
25}
26
27#[derive(Debug, Serialize)]
28pub struct ErrorBody {
29 pub code: &'static str,
30 pub message: String,
31 pub category: &'static str,
32}
33
34impl ErrorResponse {
35 fn from_lora(err: &LoraError) -> Self {
36 Self {
37 error: ErrorBody {
38 code: err.code().as_str(),
39 message: err.public_message(),
40 category: err.category().as_str(),
41 },
42 }
43 }
44
45 pub(crate) fn from_parts(code: LoraErrorCode, message: impl Into<String>) -> Self {
48 Self {
49 error: ErrorBody {
50 code: code.as_str(),
51 message: message.into(),
52 category: code.category().as_str(),
53 },
54 }
55 }
56}
57
58pub(crate) fn json_rejection_error(rejection: JsonRejection) -> LoraError {
59 LoraError::new(
60 LoraErrorCode::InvalidParams,
61 format!("invalid JSON request body: {}", rejection.body_text()),
62 )
63}
64
65fn status_for(err: &LoraError) -> StatusCode {
79 match err.code() {
80 LoraErrorCode::WalPoisoned | LoraErrorCode::Connection => StatusCode::SERVICE_UNAVAILABLE,
82 LoraErrorCode::Io
83 | LoraErrorCode::WalCorruption
84 | LoraErrorCode::SnapshotCodec
85 | LoraErrorCode::SnapshotCrypto
86 | LoraErrorCode::Internal => StatusCode::INTERNAL_SERVER_ERROR,
87 LoraErrorCode::Timeout => StatusCode::REQUEST_TIMEOUT,
89 LoraErrorCode::NotFound => StatusCode::NOT_FOUND,
90 LoraErrorCode::InvalidParams | LoraErrorCode::InvalidVector | LoraErrorCode::Validation => {
91 StatusCode::UNPROCESSABLE_ENTITY
92 }
93 LoraErrorCode::ConstraintViolation
94 | LoraErrorCode::UniqueConstraint
95 | LoraErrorCode::NotNullConstraint
96 | LoraErrorCode::ForeignKeyViolation
97 | LoraErrorCode::TransactionFailure => StatusCode::CONFLICT,
98 LoraErrorCode::Parse
99 | LoraErrorCode::Semantic
100 | LoraErrorCode::ReadOnlyViolation
101 | LoraErrorCode::DatabaseName
102 | LoraErrorCode::Config => StatusCode::BAD_REQUEST,
103 }
104}
105
106pub(crate) fn lora_error_response(err: impl Into<LoraError>) -> Response {
107 let lora = err.into();
108 let status = status_for(&lora);
109 match lora.category() {
110 LoraErrorCategory::Client => {
111 tracing::warn!(
112 code = lora.code().as_str(),
113 category = lora.category().as_str(),
114 public_message = %lora.public_message(),
115 diagnostic = %lora.debug_context(),
116 "database request failed"
117 );
118 }
119 LoraErrorCategory::Server => {
120 tracing::error!(
121 code = lora.code().as_str(),
122 category = lora.category().as_str(),
123 public_message = %lora.public_message(),
124 diagnostic = %lora.debug_context(),
125 "database request failed"
126 );
127 }
128 }
129 (status, Json(ErrorResponse::from_lora(&lora))).into_response()
130}