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 }