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