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