diff --git a/Cargo.lock b/Cargo.lock index a30738c69d..082bb476de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -497,9 +497,9 @@ dependencies = [ [[package]] name = "async-channel" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", "event-listener", @@ -1464,6 +1464,7 @@ dependencies = [ "fake", "futures", "hex", + "http", "masking", "md5", "nanoid", @@ -1473,6 +1474,7 @@ dependencies = [ "quick-xml", "rand 0.8.5", "regex", + "reqwest", "ring", "router_env", "serde", @@ -1480,6 +1482,7 @@ dependencies = [ "serde_urlencoded", "signal-hook", "signal-hook-tokio", + "strum 0.24.1", "test-case", "thiserror", "time 0.3.22", @@ -2080,9 +2083,9 @@ dependencies = [ [[package]] name = "fake" -version = "2.6.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a44c765350db469b774425ff1c833890b16ceb9612fb5d7c4bbdf4a1b55f876" +checksum = "9af7b0c58ac9d03169e27f080616ce9f64004edca3d2ef4147a811c21b23b319" dependencies = [ "rand 0.8.5", "unidecode", diff --git a/crates/common_utils/Cargo.toml b/crates/common_utils/Cargo.toml index 166f597392..e21aecbb42 100644 --- a/crates/common_utils/Cargo.toml +++ b/crates/common_utils/Cargo.toml @@ -19,17 +19,20 @@ diesel = "2.1.0" error-stack = "0.3.1" futures = { version = "0.3.28", optional = true } hex = "0.4.3" +http = "0.2.9" md5 = "0.7.0" nanoid = "0.4.0" once_cell = "1.18.0" quick-xml = { version = "0.28.2", features = ["serialize"] } rand = "0.8.5" regex = "1.8.4" +reqwest = { version = "0.11.18", features = ["json", "native-tls", "gzip", "multipart"] } ring = { version = "0.16.20", features = ["std"] } serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.96" serde_urlencoded = "0.7.1" signal-hook = { version = "0.3.15", optional = true } +strum = { version = "0.24.1", features = ["derive"] } thiserror = "1.0.40" time = { version = "0.3.21", features = ["serde", "serde-well-known", "std"] } tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"], optional = true } diff --git a/crates/common_utils/src/lib.rs b/crates/common_utils/src/lib.rs index 0bdd84848f..01c9c80fce 100644 --- a/crates/common_utils/src/lib.rs +++ b/crates/common_utils/src/lib.rs @@ -9,6 +9,8 @@ pub mod errors; pub mod ext_traits; pub mod fp_utils; pub mod pii; +#[allow(missing_docs)] // Todo: add docs +pub mod request; #[cfg(feature = "signals")] pub mod signals; pub mod validation; diff --git a/crates/common_utils/src/request.rs b/crates/common_utils/src/request.rs new file mode 100644 index 0000000000..64bce8649d --- /dev/null +++ b/crates/common_utils/src/request.rs @@ -0,0 +1,206 @@ +use masking::{Maskable, Secret}; +#[cfg(feature = "logs")] +use router_env::logger; +use serde::{Deserialize, Serialize}; + +use crate::errors; + +pub type Headers = std::collections::HashSet<(String, Maskable)>; + +#[derive( + Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize, strum::Display, strum::EnumString, +)] +#[serde(rename_all = "UPPERCASE")] +#[strum(serialize_all = "UPPERCASE")] +pub enum Method { + Get, + Post, + Put, + Delete, +} + +#[derive(Deserialize, Serialize, Debug)] +pub enum ContentType { + Json, + FormUrlEncoded, + FormData, +} + +fn default_request_headers() -> [(String, Maskable); 1] { + use http::header; + + [(header::VIA.to_string(), "HyperSwitch".to_string().into())] +} + +#[derive(Debug)] +pub struct Request { + pub url: String, + pub headers: Headers, + pub payload: Option>, + pub method: Method, + pub content_type: Option, + pub certificate: Option, + pub certificate_key: Option, + pub form_data: Option, +} + +impl Request { + pub fn new(method: Method, url: &str) -> Self { + Self { + method, + url: String::from(url), + headers: std::collections::HashSet::new(), + payload: None, + content_type: None, + certificate: None, + certificate_key: None, + form_data: None, + } + } + + pub fn set_body(&mut self, body: String) { + self.payload = Some(body.into()); + } + + pub fn add_default_headers(&mut self) { + self.headers.extend(default_request_headers()); + } + + pub fn add_header(&mut self, header: &str, value: Maskable) { + self.headers.insert((String::from(header), value)); + } + + pub fn add_content_type(&mut self, content_type: ContentType) { + self.content_type = Some(content_type); + } + + pub fn add_certificate(&mut self, certificate: Option) { + self.certificate = certificate; + } + + pub fn add_certificate_key(&mut self, certificate_key: Option) { + self.certificate = certificate_key; + } + + pub fn set_form_data(&mut self, form_data: reqwest::multipart::Form) { + self.form_data = Some(form_data); + } +} + +#[derive(Debug)] +pub struct RequestBuilder { + pub url: String, + pub headers: Headers, + pub payload: Option>, + pub method: Method, + pub content_type: Option, + pub certificate: Option, + pub certificate_key: Option, + pub form_data: Option, +} + +impl RequestBuilder { + pub fn new() -> Self { + Self { + method: Method::Get, + url: String::with_capacity(1024), + headers: std::collections::HashSet::new(), + payload: None, + content_type: None, + certificate: None, + certificate_key: None, + form_data: None, + } + } + + pub fn url(mut self, url: &str) -> Self { + self.url = url.into(); + self + } + + pub fn method(mut self, method: Method) -> Self { + self.method = method; + self + } + + pub fn attach_default_headers(mut self) -> Self { + self.headers.extend(default_request_headers()); + self + } + + pub fn header(mut self, header: &str, value: &str) -> Self { + self.headers.insert((header.into(), value.into())); + self + } + + pub fn headers(mut self, headers: Vec<(String, Maskable)>) -> Self { + let mut h = headers.into_iter().map(|(h, v)| (h, v)); + self.headers.extend(&mut h); + self + } + + pub fn form_data(mut self, form_data: Option) -> Self { + self.form_data = form_data; + self + } + + pub fn body(mut self, option_body: Option) -> Self { + self.payload = option_body.map(RequestBody::get_inner_value); + self + } + + pub fn content_type(mut self, content_type: ContentType) -> Self { + self.content_type = Some(content_type); + self + } + + pub fn add_certificate(mut self, certificate: Option) -> Self { + self.certificate = certificate; + self + } + + pub fn add_certificate_key(mut self, certificate_key: Option) -> Self { + self.certificate_key = certificate_key; + self + } + + pub fn build(self) -> Request { + Request { + method: self.method, + url: self.url, + headers: self.headers, + payload: self.payload, + content_type: self.content_type, + certificate: self.certificate, + certificate_key: self.certificate_key, + form_data: self.form_data, + } + } +} + +impl Default for RequestBuilder { + fn default() -> Self { + Self::new() + } +} + +#[derive(Clone, Debug)] +pub struct RequestBody(Secret); + +impl RequestBody { + pub fn log_and_get_request_body( + body: T, + encoder: F, + ) -> errors::CustomResult + where + F: FnOnce(T) -> errors::CustomResult, + T: std::fmt::Debug, + { + #[cfg(feature = "logs")] + logger::info!(connector_request_body=?body); + Ok(Self(Secret::new(encoder(body)?))) + } + pub fn get_inner_value(request_body: Self) -> Secret { + request_body.0 + } +} diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 8b9b0b6afe..99400050df 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -14,14 +14,14 @@ use actix_web::{body, HttpRequest, HttpResponse, Responder, ResponseError}; use api_models::enums::CaptureMethod; pub use client::{proxy_bypass_urls, ApiClient, MockApiClient, ProxyClient}; use common_utils::errors::ReportSwitchExt; +pub use common_utils::request::{ContentType, Method, Request, RequestBuilder}; use error_stack::{report, IntoReport, Report, ResultExt}; use masking::{ExposeOptionInterface, PeekInterface}; use router_env::{instrument, tracing, Tag}; use serde::Serialize; use serde_json::json; -use self::request::{ContentType, HeaderExt, RequestBuilderExt}; -pub use self::request::{Method, Request, RequestBuilder}; +use self::request::{HeaderExt, RequestBuilderExt}; use crate::{ configs::settings::{Connectors, Settings}, consts, @@ -1229,9 +1229,9 @@ pub fn build_redirection_form( f.method='POST'; i.name = 'authentication_response'; i.value = JSON.stringify(payload); - f.appendChild(i); + f.appendChild(i); f.body = JSON.stringify(payload); - document.body.appendChild(f); + document.body.appendChild(f); f.submit(); }} }}); diff --git a/crates/router/src/services/api/request.rs b/crates/router/src/services/api/request.rs index 21b4a5acdc..1f672b0f0d 100644 --- a/crates/router/src/services/api/request.rs +++ b/crates/router/src/services/api/request.rs @@ -1,193 +1,12 @@ -use std::{collections, str::FromStr}; +use std::str::FromStr; +pub use common_utils::request::ContentType; +use common_utils::request::Headers; use error_stack::{IntoReport, ResultExt}; -use masking::Secret; pub use masking::{Mask, Maskable}; use router_env::{instrument, tracing}; -use serde::{Deserialize, Serialize}; -use crate::{ - core::errors::{self, CustomResult}, - types, -}; - -pub(crate) type Headers = collections::HashSet<(String, Maskable)>; - -#[derive( - Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize, strum::Display, strum::EnumString, -)] -#[serde(rename_all = "UPPERCASE")] -#[strum(serialize_all = "UPPERCASE")] -pub enum Method { - Get, - Post, - Put, - Delete, -} - -#[derive(Deserialize, Serialize, Debug)] -pub enum ContentType { - Json, - FormUrlEncoded, - FormData, -} - -fn default_request_headers() -> [(String, Maskable); 1] { - use http::header; - - [(header::VIA.to_string(), "HyperSwitch".to_string().into())] -} - -#[derive(Debug)] -pub struct Request { - pub url: String, - pub headers: Headers, - pub payload: Option>, - pub method: Method, - pub content_type: Option, - pub certificate: Option, - pub certificate_key: Option, - pub form_data: Option, -} - -impl Request { - pub fn new(method: Method, url: &str) -> Self { - Self { - method, - url: String::from(url), - headers: collections::HashSet::new(), - payload: None, - content_type: None, - certificate: None, - certificate_key: None, - form_data: None, - } - } - - pub fn set_body(&mut self, body: String) { - self.payload = Some(body.into()); - } - - pub fn add_default_headers(&mut self) { - self.headers.extend(default_request_headers()); - } - - pub fn add_header(&mut self, header: &str, value: Maskable) { - self.headers.insert((String::from(header), value)); - } - - pub fn add_content_type(&mut self, content_type: ContentType) { - self.content_type = Some(content_type); - } - - pub fn add_certificate(&mut self, certificate: Option) { - self.certificate = certificate; - } - - pub fn add_certificate_key(&mut self, certificate_key: Option) { - self.certificate = certificate_key; - } - - pub fn set_form_data(&mut self, form_data: reqwest::multipart::Form) { - self.form_data = Some(form_data); - } -} - -pub struct RequestBuilder { - pub url: String, - pub headers: Headers, - pub payload: Option>, - pub method: Method, - pub content_type: Option, - pub certificate: Option, - pub certificate_key: Option, - pub form_data: Option, -} - -impl RequestBuilder { - pub fn new() -> Self { - Self { - method: Method::Get, - url: String::with_capacity(1024), - headers: std::collections::HashSet::new(), - payload: None, - content_type: None, - certificate: None, - certificate_key: None, - form_data: None, - } - } - - pub fn url(mut self, url: &str) -> Self { - self.url = url.into(); - self - } - - pub fn method(mut self, method: Method) -> Self { - self.method = method; - self - } - - pub fn attach_default_headers(mut self) -> Self { - self.headers.extend(default_request_headers()); - self - } - - pub fn header(mut self, header: &str, value: &str) -> Self { - self.headers.insert((header.into(), value.into())); - self - } - - pub fn headers(mut self, headers: Vec<(String, Maskable)>) -> Self { - let mut h = headers.into_iter().map(|(h, v)| (h, v)); - self.headers.extend(&mut h); - self - } - - pub fn form_data(mut self, form_data: Option) -> Self { - self.form_data = form_data; - self - } - - pub fn body(mut self, option_body: Option) -> Self { - self.payload = option_body.map(types::RequestBody::get_inner_value); - self - } - - pub fn content_type(mut self, content_type: ContentType) -> Self { - self.content_type = Some(content_type); - self - } - - pub fn add_certificate(mut self, certificate: Option) -> Self { - self.certificate = certificate; - self - } - - pub fn add_certificate_key(mut self, certificate_key: Option) -> Self { - self.certificate_key = certificate_key; - self - } - - pub fn build(self) -> Request { - Request { - method: self.method, - url: self.url, - headers: self.headers, - payload: self.payload, - content_type: self.content_type, - certificate: self.certificate, - certificate_key: self.certificate_key, - form_data: self.form_data, - } - } -} - -impl Default for RequestBuilder { - fn default() -> Self { - Self::new() - } -} +use crate::core::errors::{self, CustomResult}; pub(super) trait HeaderExt { fn construct_header_map( diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index 4c1c3d8c3a..b1488d6df3 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -17,6 +17,7 @@ pub use api_models::{ enums::{Connector, PayoutConnectors}, payouts as payout_types, }; +pub use common_utils::request::RequestBody; use common_utils::{pii, pii::Email}; use data_models::mandates::MandateData; use error_stack::{IntoReport, ResultExt}; @@ -1062,26 +1063,6 @@ impl From<(&RouterData, T2)> } } -#[derive(Clone, Debug)] -pub struct RequestBody(Secret); - -impl RequestBody { - pub fn log_and_get_request_body( - body: T, - encoder: F, - ) -> errors::CustomResult - where - F: FnOnce(T) -> errors::CustomResult, - T: std::fmt::Debug, - { - router_env::logger::info!(connector_request_body=?body); - Ok(Self(Secret::new(encoder(body)?))) - } - pub fn get_inner_value(request_body: Self) -> Secret { - request_body.0 - } -} - #[cfg(feature = "payouts")] impl From<(