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 }