166 lines
4.4 KiB
Rust
166 lines
4.4 KiB
Rust
use std::{collections::BTreeMap, time::Duration};
|
|
|
|
use crank_core::{HttpMethod, RestTarget};
|
|
use reqwest::{
|
|
Client,
|
|
header::{HeaderMap, HeaderName, HeaderValue},
|
|
};
|
|
use serde_json::Value;
|
|
|
|
use crate::{RestAdapterError, RestRequest, RestResponse};
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct RestAdapter {
|
|
client: Client,
|
|
}
|
|
|
|
impl Default for RestAdapter {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl RestAdapter {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
client: Client::new(),
|
|
}
|
|
}
|
|
|
|
pub async fn execute(
|
|
&self,
|
|
target: &RestTarget,
|
|
request: &RestRequest,
|
|
) -> Result<RestResponse, RestAdapterError> {
|
|
let url = build_url(target, request)?;
|
|
let headers = build_headers(target, request)?;
|
|
let mut builder = self
|
|
.client
|
|
.request(to_reqwest_method(target.method), url)
|
|
.headers(headers)
|
|
.timeout(Duration::from_millis(request.timeout_ms));
|
|
|
|
if let Some(body) = &request.body {
|
|
builder = builder.json(body);
|
|
}
|
|
|
|
let response = builder.send().await?;
|
|
let status = response.status();
|
|
let headers = normalize_headers(response.headers());
|
|
let body = decode_body(response).await?;
|
|
|
|
if !status.is_success() {
|
|
return Err(RestAdapterError::UnexpectedStatus {
|
|
status: status.as_u16(),
|
|
body,
|
|
});
|
|
}
|
|
|
|
Ok(RestResponse {
|
|
status_code: status.as_u16(),
|
|
headers,
|
|
body,
|
|
})
|
|
}
|
|
}
|
|
|
|
fn build_url(target: &RestTarget, request: &RestRequest) -> Result<reqwest::Url, RestAdapterError> {
|
|
let base_url =
|
|
reqwest::Url::parse(&target.base_url).map_err(|_| RestAdapterError::InvalidBaseUrl {
|
|
url: target.base_url.clone(),
|
|
})?;
|
|
let path = substitute_path_params(&target.path_template, &request.path_params);
|
|
let mut url = base_url.join(path.trim_start_matches('/')).map_err(|_| {
|
|
RestAdapterError::InvalidBaseUrl {
|
|
url: target.base_url.clone(),
|
|
}
|
|
})?;
|
|
|
|
{
|
|
let mut query = url.query_pairs_mut();
|
|
for (key, value) in &request.query_params {
|
|
query.append_pair(key, value);
|
|
}
|
|
}
|
|
|
|
Ok(url)
|
|
}
|
|
|
|
fn substitute_path_params(path_template: &str, path_params: &BTreeMap<String, String>) -> String {
|
|
let mut rendered = path_template.to_owned();
|
|
|
|
for (key, value) in path_params {
|
|
rendered = rendered.replace(&format!("{{{key}}}"), value);
|
|
}
|
|
|
|
rendered
|
|
}
|
|
|
|
fn build_headers(
|
|
target: &RestTarget,
|
|
request: &RestRequest,
|
|
) -> Result<HeaderMap, RestAdapterError> {
|
|
let mut headers = HeaderMap::new();
|
|
|
|
for (name, value) in &target.static_headers {
|
|
insert_header(&mut headers, name, value)?;
|
|
}
|
|
|
|
for (name, value) in &request.headers {
|
|
insert_header(&mut headers, name, value)?;
|
|
}
|
|
|
|
Ok(headers)
|
|
}
|
|
|
|
fn insert_header(headers: &mut HeaderMap, name: &str, value: &str) -> Result<(), RestAdapterError> {
|
|
let header_name =
|
|
HeaderName::try_from(name).map_err(|_| RestAdapterError::InvalidHeaderName {
|
|
header: name.to_owned(),
|
|
})?;
|
|
let header_value =
|
|
HeaderValue::try_from(value).map_err(|_| RestAdapterError::InvalidHeaderValue {
|
|
header: name.to_owned(),
|
|
})?;
|
|
|
|
headers.insert(header_name, header_value);
|
|
Ok(())
|
|
}
|
|
|
|
async fn decode_body(response: reqwest::Response) -> Result<Value, RestAdapterError> {
|
|
let bytes = response.bytes().await?;
|
|
|
|
if bytes.is_empty() {
|
|
return Ok(Value::Null);
|
|
}
|
|
|
|
match serde_json::from_slice::<Value>(&bytes) {
|
|
Ok(value) => Ok(value),
|
|
Err(_) => Ok(Value::String(
|
|
String::from_utf8_lossy(&bytes).trim().to_owned(),
|
|
)),
|
|
}
|
|
}
|
|
|
|
fn normalize_headers(headers: &HeaderMap) -> BTreeMap<String, String> {
|
|
headers
|
|
.iter()
|
|
.filter_map(|(name, value)| {
|
|
value
|
|
.to_str()
|
|
.ok()
|
|
.map(|value| (name.as_str().to_owned(), value.to_owned()))
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn to_reqwest_method(method: HttpMethod) -> reqwest::Method {
|
|
match method {
|
|
HttpMethod::Get => reqwest::Method::GET,
|
|
HttpMethod::Post => reqwest::Method::POST,
|
|
HttpMethod::Put => reqwest::Method::PUT,
|
|
HttpMethod::Patch => reqwest::Method::PATCH,
|
|
HttpMethod::Delete => reqwest::Method::DELETE,
|
|
}
|
|
}
|