taler-rust

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

builder.rs (9028B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 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::{
     18     borrow::Cow,
     19     fmt::{self},
     20 };
     21 
     22 use http::{
     23     HeaderMap, HeaderName, HeaderValue, StatusCode,
     24     header::{self},
     25 };
     26 use http_body_util::{BodyDataStream, BodyExt, Full};
     27 use hyper::{Method, body::Bytes};
     28 use serde::{Serialize, de::DeserializeOwned};
     29 use taler_common::encoding::base64;
     30 use tracing::{Level, trace};
     31 use url::Url;
     32 
     33 use crate::{Client, ClientErr, Ctx, headers::HeaderParser, sse::SseClient};
     34 
     35 pub struct Req {
     36     client: Client,
     37     url: Url,
     38     body: Bytes,
     39     headers: HeaderMap,
     40     err: Option<ClientErr>,
     41     ctx: Ctx,
     42 }
     43 
     44 impl Req {
     45     pub fn new(
     46         client: &Client,
     47         method: Method,
     48         base_url: &Url,
     49         path: impl Into<Cow<'static, str>>,
     50     ) -> Self {
     51         let path = path.into();
     52         let url = base_url.join(&path).unwrap();
     53         Self {
     54             client: client.clone(),
     55             url,
     56             headers: HeaderMap::new(),
     57             body: Bytes::default(),
     58             err: None,
     59             ctx: Ctx {
     60                 path,
     61                 method,
     62                 status: None,
     63             },
     64         }
     65     }
     66 
     67     pub fn method(&self) -> &Method {
     68         &self.ctx.method
     69     }
     70 
     71     pub fn url(&self) -> &Url {
     72         &self.url
     73     }
     74 
     75     pub fn headers(&self) -> &HeaderMap {
     76         &self.headers
     77     }
     78 
     79     pub fn body(&self) -> &Bytes {
     80         &self.body
     81     }
     82 
     83     pub fn header<K, V>(mut self, key: K, value: V) -> Self
     84     where
     85         K: TryInto<HeaderName>,
     86         <K as TryInto<HeaderName>>::Error: Into<http::Error>,
     87         V: TryInto<HeaderValue>,
     88         <V as TryInto<HeaderValue>>::Error: Into<http::Error>,
     89     {
     90         let res = (|| {
     91             let name = key.try_into().map_err(Into::into)?;
     92             let value = value.try_into().map_err(Into::into)?;
     93             self.headers.insert(name, value);
     94             Ok(())
     95         })();
     96         if let Err(e) = res
     97             && self.err.is_none()
     98         {
     99             self.err = Some(e);
    100         }
    101         self
    102     }
    103 
    104     pub fn sensitive_header<K, V>(mut self, key: K, value: V) -> Self
    105     where
    106         K: TryInto<HeaderName>,
    107         <K as TryInto<HeaderName>>::Error: Into<http::Error>,
    108         V: TryInto<HeaderValue>,
    109         <V as TryInto<HeaderValue>>::Error: Into<http::Error>,
    110     {
    111         let res = (|| {
    112             let name = key.try_into().map_err(Into::into)?;
    113             let mut value = value.try_into().map_err(Into::into)?;
    114             value.set_sensitive(true);
    115             self.headers.insert(name, value);
    116             Ok(())
    117         })();
    118         if let Err(e) = res
    119             && self.err.is_none()
    120         {
    121             self.err = Some(e);
    122         }
    123         self
    124     }
    125 
    126     pub fn query<T: Serialize>(mut self, name: &str, value: T) -> Self {
    127         let mut pairs = self.url.query_pairs_mut();
    128         let serializer = serde_urlencoded::Serializer::new(&mut pairs);
    129         if let Err(e) = [(name, value)].serialize(serializer)
    130             && self.err.is_none()
    131         {
    132             self.err = Some(e.into());
    133         }
    134         drop(pairs);
    135         self
    136     }
    137 
    138     pub fn json<T: Serialize + ?Sized>(mut self, json: &T) -> Self {
    139         let mut buf = Vec::new();
    140         let serializer: &mut serde_json::Serializer<&mut Vec<u8>> =
    141             &mut serde_json::Serializer::new(&mut buf);
    142         if let Err(e) = serde_path_to_error::serialize(json, serializer).map_err(ClientErr::ReqJson)
    143             && self.err.is_none()
    144         {
    145             self.err = Some(e);
    146             return self;
    147         };
    148         self.content(buf.into(), HeaderValue::from_static("application/json"))
    149     }
    150 
    151     pub fn content(mut self, body: Bytes, ty: HeaderValue) -> Self {
    152         self.headers.insert(header::CONTENT_TYPE, ty);
    153         self.body = body;
    154         self
    155     }
    156 
    157     pub fn basic_auth<U, P>(self, username: U, password: P) -> Req
    158     where
    159         U: fmt::Display,
    160         P: fmt::Display,
    161     {
    162         let header = format!("Basic {}", base64::fmt(format!("{username}:{password}")),);
    163         self.sensitive_header(header::AUTHORIZATION, header)
    164     }
    165 
    166     pub fn bearer_auth<T>(self, token: T) -> Req
    167     where
    168         T: fmt::Display,
    169     {
    170         let header = format!("Bearer {token}");
    171         self.sensitive_header(header::AUTHORIZATION, header)
    172     }
    173 
    174     pub fn req_sse(mut self, client: &SseClient) -> Req {
    175         self = self
    176             .header(
    177                 header::ACCEPT,
    178                 HeaderValue::from_static("text/event-stream"),
    179             )
    180             .header(header::CACHE_CONTROL, HeaderValue::from_static("no-cache"));
    181         if let Some(id) = &client.last_event_id {
    182             self = self.header(
    183                 HeaderName::from_static("last-event-id"),
    184                 HeaderValue::from_str(id).unwrap(),
    185             );
    186         }
    187         self
    188     }
    189 
    190     pub async fn send(self) -> Result<(Ctx, Res), (Ctx, ClientErr)> {
    191         let Self {
    192             client,
    193             ctx,
    194             url,
    195             err,
    196             headers,
    197             body,
    198         } = self;
    199         if let Some(e) = err {
    200             return Err((ctx, e));
    201         }
    202         let req = match async {
    203             let mut builder = http::request::Request::builder()
    204                 .uri(url.as_str())
    205                 .method(ctx.method.clone());
    206             if let Some(headers_mut) = builder.headers_mut() {
    207                 *headers_mut = headers;
    208             }
    209             let req = builder.body(Full::new(body))?;
    210             Ok(req)
    211         }
    212         .await
    213         {
    214             Ok(it) => it,
    215             Err(e) => return Err((ctx, e)),
    216         };
    217         match client.request(req).await {
    218             Ok(res) => {
    219                 let (head, body) = res.into_parts();
    220                 Ok((ctx, Res { head, body }))
    221             }
    222             Err(e) => Err((ctx, ClientErr::ReqTransport(e.into()))),
    223         }
    224     }
    225 }
    226 
    227 pub struct Res {
    228     head: http::response::Parts,
    229     body: hyper::body::Incoming,
    230 }
    231 
    232 impl Res {
    233     pub fn status(&self) -> StatusCode {
    234         self.head.status
    235     }
    236 
    237     pub fn headers(&self) -> &HeaderMap {
    238         &self.head.headers
    239     }
    240 
    241     pub fn str_header(&self, name: &'static str) -> Result<String, ClientErr> {
    242         self.head
    243             .headers
    244             .str_header(name)
    245             .map_err(ClientErr::Headers)
    246     }
    247 
    248     pub fn int_header(&self, name: &'static str) -> Result<u64, ClientErr> {
    249         self.head
    250             .headers
    251             .int_header(name)
    252             .map_err(ClientErr::Headers)
    253     }
    254 
    255     pub fn bool_header(&self, name: &'static str) -> Result<bool, ClientErr> {
    256         self.head
    257             .headers
    258             .bool_header(name)
    259             .map_err(ClientErr::Headers)
    260     }
    261 
    262     pub fn sse(self, client: &mut SseClient) -> Result<(), ClientErr> {
    263         // TODO check content type?
    264         // TODO check status
    265         client.connect(BodyDataStream::new(self.body));
    266         Ok(())
    267     }
    268 
    269     async fn full_body(self) -> Result<Bytes, ClientErr> {
    270         // TODO body size limit ?
    271         self.body
    272             .collect()
    273             .await
    274             .map(|it| it.to_bytes())
    275             .map_err(|e| ClientErr::ResTransport(e.into()))
    276     }
    277 
    278     /** Parse request body into a JSON type */
    279     pub async fn json<T: DeserializeOwned>(self) -> Result<T, ClientErr> {
    280         // TODO check content type?
    281         let body = self.full_body().await?;
    282         if tracing::enabled!(Level::TRACE) {
    283             let str = std::string::String::from_utf8_lossy(&body);
    284             trace!(target: "http", "JSON body: {str}");
    285         }
    286         let deserializer = &mut serde_json::Deserializer::from_slice(&body);
    287         let parsed = serde_path_to_error::deserialize(deserializer).map_err(ClientErr::ResJson)?;
    288         Ok(parsed)
    289     }
    290 
    291     /** Parse request body into a URL encoded type */
    292     pub async fn urlencoded<T: DeserializeOwned>(self) -> Result<T, ClientErr> {
    293         // TODO check content type?
    294         let body = self.full_body().await?;
    295         let parsed = serde_urlencoded::from_bytes(&body).map_err(ClientErr::Form)?;
    296         Ok(parsed)
    297     }
    298 
    299     /** Parse request body into as text */
    300     pub async fn text(self) -> Result<String, ClientErr> {
    301         let body = self.full_body().await?;
    302         let parsed =
    303             String::from_utf8(body.to_vec()).map_err(|e| ClientErr::Text(e.utf8_error()))?;
    304         Ok(parsed)
    305     }
    306 }