taler-rust

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

builder.rs (8830B)


      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::{borrow::Cow, fmt};
     18 
     19 use base64::Engine;
     20 use http::{
     21     HeaderMap, HeaderName, HeaderValue, StatusCode,
     22     header::{self},
     23 };
     24 use http_body_util::BodyExt;
     25 use http_body_util::{BodyDataStream, Full};
     26 use hyper::{Method, body::Bytes};
     27 use serde::{Serialize, de::DeserializeOwned};
     28 use tracing::{Level, trace};
     29 use url::Url;
     30 
     31 use crate::{Client, ClientErr, Ctx, headers::HeaderParser, sse::SseClient};
     32 
     33 struct Builder {
     34     headers: HeaderMap,
     35     body: Full<Bytes>,
     36 }
     37 
     38 pub struct Req {
     39     client: Client,
     40     url: Url,
     41     builder: Result<Builder, ClientErr>,
     42     ctx: Ctx,
     43 }
     44 
     45 impl Req {
     46     pub fn new(
     47         client: &Client,
     48         method: Method,
     49         base_url: &Url,
     50         path: impl Into<Cow<'static, str>>,
     51     ) -> Self {
     52         let path = path.into();
     53         let url = base_url.join(&path).unwrap();
     54         Self {
     55             client: client.clone(),
     56             url,
     57             builder: Ok(Builder {
     58                 headers: HeaderMap::new(),
     59                 body: Full::default(),
     60             }),
     61             ctx: Ctx {
     62                 path,
     63                 method,
     64                 status: None,
     65             },
     66         }
     67     }
     68 
     69     pub fn method(&self) -> &Method {
     70         &self.ctx.method
     71     }
     72 
     73     pub fn url(&self) -> &Url {
     74         &self.url
     75     }
     76 
     77     pub fn header<K, V>(mut self, key: K, value: V) -> Self
     78     where
     79         K: TryInto<HeaderName>,
     80         <K as TryInto<HeaderName>>::Error: Into<http::Error>,
     81         V: TryInto<HeaderValue>,
     82         <V as TryInto<HeaderValue>>::Error: Into<http::Error>,
     83     {
     84         self.builder = self.builder.and_then(move |mut builder| {
     85             let name = key.try_into().map_err(Into::into)?;
     86             let value = value.try_into().map_err(Into::into)?;
     87             builder.headers.insert(name, value);
     88             Ok(builder)
     89         });
     90         self
     91     }
     92 
     93     pub fn sensitive_header<K, V>(mut self, key: K, value: V) -> Self
     94     where
     95         K: TryInto<HeaderName>,
     96         <K as TryInto<HeaderName>>::Error: Into<http::Error>,
     97         V: TryInto<HeaderValue>,
     98         <V as TryInto<HeaderValue>>::Error: Into<http::Error>,
     99     {
    100         self.builder = self.builder.and_then(move |mut builder| {
    101             let name = key.try_into().map_err(Into::into)?;
    102             let mut value = value.try_into().map_err(Into::into)?;
    103             value.set_sensitive(true);
    104             builder.headers.insert(name, value);
    105             Ok(builder)
    106         });
    107         self
    108     }
    109 
    110     pub fn query<T: Serialize>(mut self, name: &str, value: T) -> Self {
    111         if self.builder.is_ok() {
    112             let mut pairs = self.url.query_pairs_mut();
    113             let serializer = serde_urlencoded::Serializer::new(&mut pairs);
    114             if let Err(e) = [(name, value)].serialize(serializer) {
    115                 drop(pairs);
    116                 self.builder = Err(e.into());
    117                 return self;
    118             }
    119         }
    120 
    121         self
    122     }
    123 
    124     pub fn json<T: Serialize + ?Sized>(mut self, json: &T) -> Self {
    125         let mut buf = Vec::new();
    126         let serializer: &mut serde_json::Serializer<&mut Vec<u8>> =
    127             &mut serde_json::Serializer::new(&mut buf);
    128         if let Err(e) = serde_path_to_error::serialize(json, serializer).map_err(ClientErr::ReqJson)
    129         {
    130             self.builder = Err(e);
    131             return self;
    132         };
    133         if let Ok(builder) = &mut self.builder {
    134             builder.headers.insert(
    135                 header::CONTENT_TYPE,
    136                 HeaderValue::from_static("application/json"),
    137             );
    138             builder.body = Full::new(buf.into())
    139         }
    140         self
    141     }
    142 
    143     pub fn basic_auth<U, P>(self, username: U, password: P) -> Req
    144     where
    145         U: fmt::Display,
    146         P: fmt::Display,
    147     {
    148         let token = format!("{username}:{password}");
    149         let mut header = "Basic ".to_string();
    150         base64::engine::general_purpose::STANDARD.encode_string(token, &mut header);
    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 }