1use crate::{config, endpoint, response};
2use std::fmt;
3
4pub type Result<T> = core::result::Result<T, TransportError>;
6
7#[derive(Debug, thiserror::Error)]
8pub enum TransportError {
10 #[error("failed to construct Gingr URL: {0}")]
11 Url(#[from] url::ParseError),
13 #[error("HTTP transport is not implemented for this SDK slice")]
14 HttpNotImplemented,
16}
17
18#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct RequestParts {
21 method: endpoint::Method,
22 path: endpoint::Path,
23 parameters: Vec<(String, String)>,
24 sensitive_parameter_names: Vec<String>,
25}
26
27impl RequestParts {
28 pub fn builder() -> RequestPartsBuilder {
30 RequestPartsBuilder::default()
31 }
32
33 pub fn with_api_key(mut self, api_key: &config::ApiKey) -> Self {
35 self.parameters
36 .push(("key".to_owned(), api_key.expose_for_transport().to_owned()));
37 self.sensitive_parameter_names.push("key".to_owned());
38 self
39 }
40
41 pub fn method(&self) -> endpoint::Method {
43 self.method
44 }
45
46 pub fn path(&self) -> endpoint::Path {
48 self.path
49 }
50
51 pub fn query_pairs(&self) -> &[(String, String)] {
53 if self.method == endpoint::Method::Get {
54 &self.parameters
55 } else {
56 &[]
57 }
58 }
59
60 pub fn form_pairs(&self) -> &[(String, String)] {
62 if self.method == endpoint::Method::Post {
63 &self.parameters
64 } else {
65 &[]
66 }
67 }
68
69 pub fn redacted(&self) -> RedactedRequest {
71 RedactedRequest {
72 method: self.method,
73 path: self.path,
74 parameters: self
75 .parameters
76 .iter()
77 .map(|(key, value)| {
78 let rendered = if self
79 .sensitive_parameter_names
80 .iter()
81 .any(|name| name == key)
82 {
83 "<redacted>"
84 } else {
85 value
86 };
87 (key.clone(), rendered.to_owned())
88 })
89 .collect(),
90 }
91 }
92
93 fn url(&self, base_url: &config::BaseUrl) -> Result<url::Url> {
94 let mut url = base_url.join_path(self.path)?;
95 if self.method == endpoint::Method::Get {
96 url.query_pairs_mut().extend_pairs(self.parameters.iter());
97 }
98 Ok(url)
99 }
100}
101
102#[derive(Clone, Debug, Default, PartialEq, Eq)]
103pub struct RequestPartsBuilder {
105 method: Option<endpoint::Method>,
106 path: Option<endpoint::Path>,
107 parameters: Vec<(String, String)>,
108 sensitive_parameter_names: Vec<String>,
109}
110
111impl RequestPartsBuilder {
112 pub fn method(mut self, method: endpoint::Method) -> Self {
114 self.method = Some(method);
115 self
116 }
117
118 pub fn path(mut self, path: endpoint::Path) -> Self {
120 self.path = Some(path);
121 self
122 }
123
124 pub fn parameters(mut self, parameters: Vec<(String, String)>) -> Self {
126 self.parameters = parameters;
127 self
128 }
129
130 pub fn sensitive_parameter_names(mut self, names: &'static [&'static str]) -> Self {
132 self.sensitive_parameter_names = names.iter().map(|name| (*name).to_owned()).collect();
133 self
134 }
135
136 pub fn build(self) -> RequestParts {
138 RequestParts {
139 method: self.method.expect("request method is required"),
140 path: self.path.expect("request path is required"),
141 parameters: self.parameters,
142 sensitive_parameter_names: self.sensitive_parameter_names,
143 }
144 }
145}
146
147#[derive(Clone, Debug, PartialEq, Eq)]
148pub struct RedactedRequest {
150 method: endpoint::Method,
151 path: endpoint::Path,
152 parameters: Vec<(String, String)>,
153}
154
155impl fmt::Display for RedactedRequest {
156 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
157 write!(formatter, "{:?} {}", self.method, self.path)?;
158 if !self.parameters.is_empty() {
159 let prefix = match self.method {
160 endpoint::Method::Get => "?",
161 endpoint::Method::Post => " form:",
162 };
163 let params = self
164 .parameters
165 .iter()
166 .map(|(key, value)| format!("{key}={value}"))
167 .collect::<Vec<_>>()
168 .join("&");
169 write!(formatter, "{prefix}{params}")?;
170 }
171 Ok(())
172 }
173}
174
175pub trait Transport {
177 fn send(&self, config: &config::Client, request: RequestParts) -> Result<response::Raw>;
179}
180
181#[derive(Clone, Debug, Default)]
182pub struct MockTransport;
184
185impl Transport for MockTransport {
186 fn send(&self, _config: &config::Client, _request: RequestParts) -> Result<response::Raw> {
187 Ok(response::Raw::new(
188 response::HttpStatus::OK,
189 bytes::Bytes::from_static(b"{}"),
190 ))
191 }
192}
193
194#[derive(Clone, Debug, Default)]
195pub struct HttpTransport;
197
198impl Transport for HttpTransport {
199 fn send(&self, _config: &config::Client, _request: RequestParts) -> Result<response::Raw> {
200 Err(TransportError::HttpNotImplemented)
201 }
202}
203
204#[derive(Clone, Debug)]
205pub struct Client<T = HttpTransport> {
207 config: config::Client,
208 transport: T,
209}
210
211impl Client<HttpTransport> {
212 pub fn new(config: config::Client) -> Self {
214 Self {
215 config,
216 transport: HttpTransport,
217 }
218 }
219}
220
221impl<T> Client<T> {
222 pub fn with_transport(config: config::Client, transport: T) -> Self {
224 Self { config, transport }
225 }
226
227 pub fn config(&self) -> &config::Client {
229 &self.config
230 }
231
232 pub fn capture_request(&self, request: &impl endpoint::Request) -> Result<RequestParts> {
234 let request = request.request_parts().with_api_key(self.config.api_key());
235 let _ = request.url(self.config.base_url())?;
236 Ok(request)
237 }
238
239 pub fn redacted_request(&self, request: &impl endpoint::Request) -> Result<RedactedRequest> {
241 self.capture_request(request)
242 .map(|request| request.redacted())
243 }
244}
245
246impl<T: Transport> Client<T> {
247 pub fn send(&self, request: &impl endpoint::Request) -> Result<response::Raw> {
249 let request = self.capture_request(request)?;
250 self.transport.send(&self.config, request)
251 }
252}