taler-rust

GNU Taler code in Rust. Largely core banking integrations.
Log | Files | Refs | Submodules | README | LICENSE

error.rs (5530B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2024-2025 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 
     17 use axum::{
     18     Json,
     19     http::{HeaderMap, HeaderValue, StatusCode, header::IntoHeaderName},
     20     response::{IntoResponse, Response},
     21 };
     22 use taler_common::{
     23     api_common::ErrorDetail, api_params::ParamsErr, error_code::ErrorCode, types::payto::PaytoErr,
     24 };
     25 
     26 pub type ApiResult<T> = Result<T, ApiError>;
     27 
     28 pub struct ApiError {
     29     code: ErrorCode,
     30     hint: Option<Box<str>>,
     31     log: Option<Box<str>>,
     32     status: Option<StatusCode>,
     33     path: Option<Box<str>>,
     34     headers: Option<Box<HeaderMap>>,
     35 }
     36 
     37 impl ApiError {
     38     pub fn new(code: ErrorCode) -> Self {
     39         Self {
     40             code,
     41             hint: None,
     42             log: None,
     43             status: None,
     44             path: None,
     45             headers: None,
     46         }
     47     }
     48 
     49     pub fn with_hint(self, hint: impl Into<Box<str>>) -> Self {
     50         Self {
     51             hint: Some(hint.into()),
     52             ..self
     53         }
     54     }
     55 
     56     pub fn with_log(self, log: impl Into<Box<str>>) -> Self {
     57         Self {
     58             log: Some(log.into()),
     59             ..self
     60         }
     61     }
     62 
     63     pub fn with_status(self, code: StatusCode) -> Self {
     64         Self {
     65             status: Some(code),
     66             ..self
     67         }
     68     }
     69 
     70     pub fn with_path(self, path: impl Into<Box<str>>) -> Self {
     71         Self {
     72             path: Some(path.into()),
     73             ..self
     74         }
     75     }
     76 
     77     pub fn with_header(mut self, key: impl IntoHeaderName, value: HeaderValue) -> Self {
     78         let headers = self.headers.get_or_insert_default();
     79         headers.append(key, value);
     80         self
     81     }
     82 }
     83 
     84 impl From<sqlx::Error> for ApiError {
     85     fn from(value: sqlx::Error) -> Self {
     86         let (code, status) = match value {
     87             sqlx::Error::Configuration(_) => {
     88                 (ErrorCode::GENERIC_DB_SETUP_FAILED, StatusCode::BAD_GATEWAY)
     89             }
     90             sqlx::Error::Database(_) | sqlx::Error::Io(_) | sqlx::Error::Tls(_) => {
     91                 (ErrorCode::GENERIC_DB_FETCH_FAILED, StatusCode::BAD_GATEWAY)
     92             }
     93             sqlx::Error::PoolTimedOut => todo!(),
     94             _ => (
     95                 ErrorCode::BANK_UNMANAGED_EXCEPTION,
     96                 StatusCode::INTERNAL_SERVER_ERROR,
     97             ),
     98         };
     99         Self {
    100             code,
    101             hint: None,
    102             status: Some(status),
    103             log: Some(format!("db: {value}").into_boxed_str()),
    104             path: None,
    105             headers: None,
    106         }
    107     }
    108 }
    109 
    110 impl From<PaytoErr> for ApiError {
    111     fn from(value: PaytoErr) -> Self {
    112         ApiError::new(ErrorCode::GENERIC_PAYTO_URI_MALFORMED).with_hint(value.to_string())
    113     }
    114 }
    115 
    116 impl From<ParamsErr> for ApiError {
    117     fn from(value: ParamsErr) -> Self {
    118         ApiError::new(ErrorCode::GENERIC_PARAMETER_MALFORMED)
    119             .with_hint(value.to_string())
    120             .with_path(value.param)
    121     }
    122 }
    123 
    124 impl From<serde_path_to_error::Error<serde_json::Error>> for ApiError {
    125     fn from(value: serde_path_to_error::Error<serde_json::Error>) -> Self {
    126         ApiError::new(ErrorCode::GENERIC_JSON_INVALID)
    127             .with_hint(value.inner().to_string())
    128             .with_path(value.path().to_string())
    129             .with_log(value.to_string())
    130     }
    131 }
    132 
    133 impl IntoResponse for ApiError {
    134     fn into_response(self) -> Response {
    135         let status_code = self.status.unwrap_or_else(|| {
    136             let (status_code, _) = self.code.metadata();
    137             StatusCode::from_u16(status_code).expect("Invalid status code")
    138         });
    139         let log = self.log.or(self.hint.clone());
    140 
    141         let mut resp = (
    142             status_code,
    143             Json(ErrorDetail {
    144                 code: self.code as u32,
    145                 hint: self.hint,
    146                 detail: None,
    147                 parameter: None,
    148                 path: self.path,
    149                 offset: None,
    150                 index: None,
    151                 object: None,
    152                 currency: None,
    153                 type_expected: None,
    154                 type_actual: None,
    155                 extra: None,
    156             }),
    157         )
    158             .into_response();
    159         if let Some(headers) = self.headers {
    160             for (k, v) in *headers {
    161                 resp.headers_mut().append(k.unwrap(), v);
    162             }
    163         }
    164         if let Some(log) = log {
    165             resp.extensions_mut().insert(log);
    166         };
    167 
    168         resp
    169     }
    170 }
    171 
    172 pub fn failure_code(code: ErrorCode) -> ApiError {
    173     ApiError::new(code)
    174 }
    175 
    176 pub fn failure(code: ErrorCode, hint: impl Into<Box<str>>) -> ApiError {
    177     ApiError::new(code).with_hint(hint)
    178 }
    179 
    180 pub fn failure_status(code: ErrorCode, hint: impl Into<Box<str>>, status: StatusCode) -> ApiError {
    181     ApiError::new(code).with_hint(hint).with_status(status)
    182 }
    183 
    184 pub fn not_implemented(hint: impl Into<Box<str>>) -> ApiError {
    185     ApiError::new(ErrorCode::END)
    186         .with_hint(hint)
    187         .with_status(StatusCode::NOT_IMPLEMENTED)
    188 }