taler-rust

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

api.rs (6420B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 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 std::borrow::Cow;
     18 
     19 use reqwest::{Client, Method, RequestBuilder, Response, StatusCode, Url, header};
     20 use serde::{Deserialize, Serialize, de::DeserializeOwned};
     21 use taler_common::error::FmtSource;
     22 use thiserror::Error;
     23 use tracing::Level;
     24 
     25 use crate::magnet_api::oauth::{Token, oauth};
     26 
     27 #[derive(Deserialize, Debug)]
     28 struct Header {
     29     #[serde(rename = "errorCode")]
     30     pub error_code: Option<u16>,
     31 }
     32 
     33 #[derive(Deserialize, Debug)]
     34 struct Empty {}
     35 
     36 #[derive(Deserialize, Error, Debug)]
     37 #[error("{error_code} {short_message} '{long_message}'")]
     38 pub struct MagnetError {
     39     #[serde(rename = "errorCode")]
     40     pub error_code: u16,
     41     #[serde(rename = "shortMessage")]
     42     pub short_message: String,
     43     #[serde(rename = "longMessage")]
     44     pub long_message: String,
     45 }
     46 
     47 #[derive(Error, Debug)]
     48 #[error("{method} {path} {kind}")]
     49 pub struct ApiErr {
     50     pub path: Cow<'static, str>,
     51     pub method: Method,
     52     pub kind: ErrKind,
     53 }
     54 
     55 #[derive(Error, Debug)]
     56 pub enum ErrKind {
     57     #[error("transport: {0}")]
     58     Transport(FmtSource<reqwest::Error>),
     59     #[error("magnet {0}")]
     60     Magnet(#[from] MagnetError),
     61     #[error("JSON body: {0}")]
     62     Json(#[from] serde_path_to_error::Error<serde_json::Error>),
     63     #[error("form body: {0}")]
     64     Form(#[from] serde_urlencoded::de::Error),
     65     #[error("status {0}")]
     66     Status(StatusCode),
     67     #[error("status {0} '{1}'")]
     68     StatusCause(StatusCode, String),
     69 }
     70 
     71 impl From<reqwest::Error> for ErrKind {
     72     fn from(value: reqwest::Error) -> Self {
     73         Self::Transport(value.into())
     74     }
     75 }
     76 
     77 pub type ApiResult<R> = std::result::Result<R, ApiErr>;
     78 
     79 /** Handle error from magnet API calls */
     80 async fn error_handling(res: reqwest::Result<Response>) -> Result<String, ErrKind> {
     81     let res = res?;
     82     let status = res.status();
     83     match status {
     84         StatusCode::OK => Ok(res.text().await?),
     85         StatusCode::BAD_REQUEST => Err(ErrKind::Status(status)),
     86         StatusCode::FORBIDDEN => {
     87             let cause = res
     88                 .headers()
     89                 .get(header::WWW_AUTHENTICATE)
     90                 .map(|s| s.to_str().unwrap_or_default())
     91                 .unwrap_or_default();
     92             Err(ErrKind::StatusCause(status, cause.to_string()))
     93         }
     94         _ => {
     95             if tracing::enabled!(Level::DEBUG) {
     96                 tracing::debug!("unexpected error: {:?}", &res);
     97                 let body = res.text().await;
     98                 tracing::debug!("unexpected error body: {:?}", body);
     99             }
    100             Err(ErrKind::Status(status))
    101         }
    102     }
    103 }
    104 
    105 /** Parse JSON and track error path */
    106 fn parse<'de, T: Deserialize<'de>>(str: &'de str) -> Result<T, ErrKind> {
    107     let deserializer = &mut serde_json::Deserializer::from_str(str);
    108     serde_path_to_error::deserialize(deserializer).map_err(ErrKind::Json)
    109 }
    110 
    111 /** Parse magnet JSON response */
    112 async fn magnet_json<T: DeserializeOwned>(res: reqwest::Result<Response>) -> Result<T, ErrKind> {
    113     let body = error_handling(res).await?;
    114     let header: Header = parse(&body)?;
    115     if header.error_code.unwrap_or(200) == 200 {
    116         parse(&body)
    117     } else {
    118         Err(ErrKind::Magnet(parse(&body)?))
    119     }
    120 }
    121 
    122 /** Parse magnet URL encoded response into our own type */
    123 async fn magnet_url<T: DeserializeOwned>(
    124     response: reqwest::Result<Response>,
    125 ) -> Result<T, ErrKind> {
    126     let body = error_handling(response).await?;
    127     serde_urlencoded::from_str(&body).map_err(ErrKind::Form)
    128 }
    129 
    130 pub struct MagnetRequest<'a> {
    131     path: Cow<'static, str>,
    132     method: Method,
    133     builder: RequestBuilder,
    134     consumer: &'a Token,
    135     access: Option<&'a Token>,
    136     verifier: Option<&'a str>,
    137 }
    138 
    139 impl<'a> MagnetRequest<'a> {
    140     pub fn new(
    141         client: &Client,
    142         method: Method,
    143         base_url: &Url,
    144         path: impl Into<Cow<'static, str>>,
    145         consumer: &'a Token,
    146         access: Option<&'a Token>,
    147         verifier: Option<&'a str>,
    148     ) -> Self {
    149         let path = path.into();
    150         let url = base_url.join(&path).unwrap();
    151         let builder = client.request(method.clone(), url);
    152         Self {
    153             path,
    154             method,
    155             builder,
    156             consumer,
    157             access,
    158             verifier,
    159         }
    160     }
    161 
    162     pub fn query<T: Serialize + ?Sized>(mut self, query: &T) -> Self {
    163         self.builder = self.builder.query(query);
    164         self
    165     }
    166 
    167     pub fn json<T: Serialize + ?Sized>(mut self, json: &T) -> Self {
    168         self.builder = self.builder.json(json);
    169         self
    170     }
    171 
    172     pub async fn parse_url<T: DeserializeOwned>(self) -> ApiResult<T> {
    173         let Self {
    174             path,
    175             builder,
    176             method,
    177             consumer,
    178             access,
    179             verifier,
    180         } = self;
    181         let (client, req) = builder.build_split();
    182         async {
    183             let mut req = req?;
    184             oauth(&mut req, consumer, access, verifier);
    185             let res = client.execute(req).await;
    186             magnet_url(res).await
    187         }
    188         .await
    189         .map_err(|kind| ApiErr { path, method, kind })
    190     }
    191 
    192     pub async fn parse_json<T: DeserializeOwned>(self) -> ApiResult<T> {
    193         let Self {
    194             path,
    195             builder,
    196             method,
    197             consumer,
    198             access,
    199             verifier,
    200         } = self;
    201         let (client, req) = builder.build_split();
    202         async {
    203             let mut req = req?;
    204             oauth(&mut req, consumer, access, verifier);
    205             let res = client.execute(req).await;
    206             magnet_json(res).await
    207         }
    208         .await
    209         .map_err(|kind| ApiErr { path, method, kind })
    210     }
    211 
    212     pub async fn parse_empty(self) -> ApiResult<()> {
    213         self.parse_json::<Empty>().await?;
    214         Ok(())
    215     }
    216 }