api.rs (4947B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2025, 2026 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 http_client::{ 20 ApiErr, Client, ClientErr, Ctx, 21 builder::{Req, Res}, 22 }; 23 use hyper::{Method, StatusCode, header}; 24 use serde::{Deserialize, Serialize, de::DeserializeOwned}; 25 use thiserror::Error; 26 use tracing::Level; 27 use url::Url; 28 29 use crate::magnet_api::oauth::{Token, oauth}; 30 31 #[derive(Deserialize, Debug)] 32 struct Header { 33 #[serde(rename = "errorCode")] 34 pub error_code: Option<u16>, 35 } 36 37 #[derive(Deserialize, Debug)] 38 struct Empty {} 39 40 #[derive(Deserialize, Error, Debug)] 41 #[error("{error_code} {short_message} '{long_message}'")] 42 pub struct MagnetError { 43 #[serde(rename = "errorCode")] 44 pub error_code: u16, 45 #[serde(rename = "shortMessage")] 46 pub short_message: String, 47 #[serde(rename = "longMessage")] 48 pub long_message: String, 49 } 50 51 #[derive(Error, Debug)] 52 pub enum MagnetErr { 53 #[error("magnet {0}")] 54 Magnet(#[from] MagnetError), 55 #[error("status {0}")] 56 Status(StatusCode), 57 #[error("status {0} '{1}'")] 58 StatusCause(StatusCode, String), 59 #[error(transparent)] 60 Client(#[from] ClientErr), 61 } 62 pub type ApiResult<R> = std::result::Result<R, ApiErr<MagnetErr>>; 63 64 /** Handle error from magnet API calls */ 65 async fn error_handling(res: Res) -> Result<Res, MagnetErr> { 66 let status = res.status(); 67 match status { 68 StatusCode::OK => Ok(res), 69 StatusCode::BAD_REQUEST => Err(MagnetErr::Status(status)), 70 StatusCode::FORBIDDEN => { 71 let cause = res.str_header(header::WWW_AUTHENTICATE.as_str())?; 72 Err(MagnetErr::StatusCause(status, cause.to_string())) 73 } 74 _ => { 75 if tracing::enabled!(Level::DEBUG) { 76 let body = res.text().await; 77 tracing::debug!("unexpected error body: {:?}", body); 78 } 79 Err(MagnetErr::Status(status)) 80 } 81 } 82 } 83 84 /** Parse JSON and track error path */ 85 fn parse<'de, T: Deserialize<'de>>(str: &'de str) -> Result<T, MagnetErr> { 86 let deserializer = &mut serde_json::Deserializer::from_str(str); 87 serde_path_to_error::deserialize(deserializer) 88 .map_err(|e| MagnetErr::Client(ClientErr::ResJson(e))) 89 } 90 91 pub struct MagnetRequest<'a> { 92 req: Req, 93 consumer: &'a Token, 94 access: Option<&'a Token>, 95 verifier: Option<&'a str>, 96 } 97 98 impl<'a> MagnetRequest<'a> { 99 pub fn new( 100 client: &Client, 101 method: Method, 102 base_url: &Url, 103 path: impl Into<Cow<'static, str>>, 104 consumer: &'a Token, 105 access: Option<&'a Token>, 106 verifier: Option<&'a str>, 107 ) -> Self { 108 Self { 109 req: Req::new(client, method, base_url, path), 110 consumer, 111 access, 112 verifier, 113 } 114 } 115 116 pub fn query<T: Serialize>(mut self, name: &str, value: T) -> Self { 117 self.req = self.req.query(name, value); 118 self 119 } 120 121 pub fn json<T: Serialize + ?Sized>(mut self, json: &T) -> Self { 122 self.req = self.req.json(json); 123 self 124 } 125 126 async fn send(self) -> ApiResult<(Ctx, Res)> { 127 let Self { 128 req, 129 consumer, 130 access, 131 verifier, 132 } = self; 133 oauth(req, consumer, access, verifier) 134 .send() 135 .await 136 .map_err(|(ctx, e)| ctx.wrap(e.into())) 137 } 138 139 pub async fn parse_url<T: DeserializeOwned>(self) -> ApiResult<T> { 140 let (ctx, res) = self.send().await?; 141 async { Ok(error_handling(res).await?.urlencoded().await?) } 142 .await 143 .map_err(|e| ctx.wrap(e)) 144 } 145 146 pub async fn parse_json<T: DeserializeOwned>(self) -> ApiResult<T> { 147 let (ctx, res) = self.send().await?; 148 async { 149 let res = error_handling(res).await?; 150 let raw: Box<serde_json::value::RawValue> = res.json().await?; 151 let header: Header = parse(raw.get())?; 152 if header.error_code.unwrap_or(200) == 200 { 153 Ok(parse(raw.get())?) 154 } else { 155 Err(MagnetErr::Magnet(parse(raw.get())?)) 156 } 157 } 158 .await 159 .map_err(|e| ctx.wrap(e)) 160 } 161 162 pub async fn parse_empty(self) -> ApiResult<()> { 163 self.parse_json::<Empty>().await?; 164 Ok(()) 165 } 166 }