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