taler-rust

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

error.rs (5422B)


      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: 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: HeaderMap::new(),
     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         self.headers.append(key, value);
     79         self
     80     }
     81 }
     82 
     83 impl From<sqlx::Error> for ApiError {
     84     fn from(value: sqlx::Error) -> Self {
     85         let (code, status) = match value {
     86             sqlx::Error::Configuration(_) => {
     87                 (ErrorCode::GENERIC_DB_SETUP_FAILED, StatusCode::BAD_GATEWAY)
     88             }
     89             sqlx::Error::Database(_) | sqlx::Error::Io(_) | sqlx::Error::Tls(_) => {
     90                 (ErrorCode::GENERIC_DB_FETCH_FAILED, StatusCode::BAD_GATEWAY)
     91             }
     92             sqlx::Error::PoolTimedOut => todo!(),
     93             _ => (
     94                 ErrorCode::BANK_UNMANAGED_EXCEPTION,
     95                 StatusCode::INTERNAL_SERVER_ERROR,
     96             ),
     97         };
     98         Self {
     99             code,
    100             hint: None,
    101             status: Some(status),
    102             log: Some(format!("db: {value}").into_boxed_str()),
    103             path: None,
    104             headers: HeaderMap::new(),
    105         }
    106     }
    107 }
    108 
    109 impl From<PaytoErr> for ApiError {
    110     fn from(value: PaytoErr) -> Self {
    111         ApiError::new(ErrorCode::GENERIC_PAYTO_URI_MALFORMED).with_hint(value.to_string())
    112     }
    113 }
    114 
    115 impl From<ParamsErr> for ApiError {
    116     fn from(value: ParamsErr) -> Self {
    117         ApiError::new(ErrorCode::GENERIC_PARAMETER_MALFORMED)
    118             .with_hint(value.to_string())
    119             .with_path(value.param)
    120     }
    121 }
    122 
    123 impl From<serde_path_to_error::Error<serde_json::Error>> for ApiError {
    124     fn from(value: serde_path_to_error::Error<serde_json::Error>) -> Self {
    125         ApiError::new(ErrorCode::GENERIC_JSON_INVALID)
    126             .with_hint(value.inner().to_string())
    127             .with_path(value.path().to_string())
    128             .with_log(value.to_string())
    129     }
    130 }
    131 
    132 impl IntoResponse for ApiError {
    133     fn into_response(self) -> Response {
    134         let status_code = self.status.unwrap_or_else(|| {
    135             let (status_code, _) = self.code.metadata();
    136             StatusCode::from_u16(status_code).expect("Invalid status code")
    137         });
    138         let log = self.log.or(self.hint.clone());
    139 
    140         let mut resp = (
    141             status_code,
    142             Json(ErrorDetail {
    143                 code: self.code as u32,
    144                 hint: self.hint,
    145                 detail: None,
    146                 parameter: None,
    147                 path: self.path,
    148                 offset: None,
    149                 index: None,
    150                 object: None,
    151                 currency: None,
    152                 type_expected: None,
    153                 type_actual: None,
    154                 extra: None,
    155             }),
    156         )
    157             .into_response();
    158         for (k, v) in self.headers {
    159             resp.headers_mut().append(k.unwrap(), v);
    160         }
    161         if let Some(log) = log {
    162             resp.extensions_mut().insert(log);
    163         };
    164 
    165         resp
    166     }
    167 }
    168 
    169 pub fn failure_code(code: ErrorCode) -> ApiError {
    170     ApiError::new(code)
    171 }
    172 
    173 pub fn failure(code: ErrorCode, hint: impl Into<Box<str>>) -> ApiError {
    174     ApiError::new(code).with_hint(hint)
    175 }
    176 
    177 pub fn failure_status(code: ErrorCode, hint: impl Into<Box<str>>, status: StatusCode) -> ApiError {
    178     ApiError::new(code).with_hint(hint).with_status(status)
    179 }
    180 
    181 pub fn not_implemented(hint: impl Into<Box<str>>) -> ApiError {
    182     ApiError::new(ErrorCode::END)
    183         .with_hint(hint)
    184         .with_status(StatusCode::NOT_IMPLEMENTED)
    185 }