lib.rs (3669B)
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::Display, str::Utf8Error}; 18 19 use http_body_util::Full; 20 use hyper::{Method, StatusCode, body::Bytes}; 21 use hyper_rustls::ConfigBuilderExt as _; 22 use hyper_util::rt::TokioExecutor; 23 use rustls::crypto::CryptoProvider; 24 use taler_common::error::FmtSource; 25 use thiserror::Error; 26 27 use crate::headers::HeaderError; 28 29 pub mod builder; 30 pub mod headers; 31 pub mod sse; 32 33 pub type Client = hyper_util::client::legacy::Client< 34 hyper_rustls::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>, 35 Full<Bytes>, 36 >; 37 38 #[derive(Error, Debug)] 39 /// API call errors 40 pub enum ClientErr { 41 #[error("request: {0}")] 42 Http(#[from] http::Error), 43 #[error("request query: {0}")] 44 Query(#[from] serde_urlencoded::ser::Error), 45 #[error("request JSON body: {0}")] 46 ReqJson(serde_path_to_error::Error<serde_json::Error>), 47 #[error("request: {0}")] 48 ReqTransport(FmtSource<hyper_util::client::legacy::Error>), 49 #[error("response JSON body: {0}")] 50 ResJson(serde_path_to_error::Error<serde_json::Error>), 51 #[error("response txt body: {0}")] 52 Text(#[from] Utf8Error), 53 #[error("response form body: {0}")] 54 Form(#[from] serde_urlencoded::de::Error), 55 #[error("response headers: {0}")] 56 Headers(#[from] HeaderError), 57 #[error("response: {0}")] 58 ResTransport(FmtSource<hyper::Error>), 59 } 60 61 pub fn client() -> Client { 62 if CryptoProvider::get_default().is_none() { 63 rustls::crypto::aws_lc_rs::default_provider() 64 .install_default() 65 .expect("failed to install the default TLS provider"); 66 } 67 68 // Prepare the TLS client config 69 let tls = rustls::ClientConfig::builder() 70 .try_with_platform_verifier() 71 .expect("failed to setup platform TLS verifier") 72 .with_no_client_auth(); 73 74 // Prepare the HTTPS connector 75 let https = hyper_rustls::HttpsConnectorBuilder::new() 76 .with_tls_config(tls) 77 .https_or_http() 78 .enable_http1() 79 .enable_http2() 80 .build(); 81 82 // Build the hyper client from the HTTPS connector. 83 hyper_util::client::legacy::Client::builder(TokioExecutor::new()).build(https) 84 } 85 86 #[derive(Debug, Clone)] 87 pub struct Ctx { 88 path: Cow<'static, str>, 89 method: Method, 90 status: Option<StatusCode>, 91 } 92 93 impl Ctx { 94 pub fn wrap<E: std::error::Error>(self, err: E) -> ApiErr<E> { 95 ApiErr { 96 ctx: self, 97 err: Box::new(err), 98 } 99 } 100 } 101 102 impl Display for Ctx { 103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 104 let Self { 105 path, 106 method, 107 status, 108 } = self; 109 write!(f, "{path} {method}")?; 110 if let Some(status) = status { 111 write!(f, " {status}")?; 112 } 113 Ok(()) 114 } 115 } 116 117 #[derive(Debug, Error)] 118 /// Error happening with api request context 119 #[error("{ctx} {err}")] 120 pub struct ApiErr<E: std::error::Error> { 121 pub ctx: Ctx, 122 pub err: Box<E>, 123 }