taler-rust

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

builder.rs (8749B)


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