taler-rust

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

builder.rs (8810B)


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