lib.rs (3616B)
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 { ctx: self, err } 96 } 97 } 98 99 impl Display for Ctx { 100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 101 let Self { 102 path, 103 method, 104 status, 105 } = self; 106 write!(f, "{path} {method}")?; 107 if let Some(status) = status { 108 write!(f, " {status}")?; 109 } 110 Ok(()) 111 } 112 } 113 114 #[derive(Debug, Error)] 115 /// Error happening with api request context 116 #[error("{ctx} {err}")] 117 pub struct ApiErr<E: std::error::Error> { 118 pub ctx: Ctx, 119 pub err: E, 120 }