use crate::{
error::SocketError,
metric::{Field, Metric, Tag},
protocol::http::{BuildStrategy, HttpParser, rest::RestRequest},
};
use bytes::Bytes;
use chrono::Utc;
use std::borrow::Cow;
#[derive(Debug)]
pub struct RestClient<'a, Strategy, Parser> {
pub http_client: reqwest::Client,
pub base_url: Cow<'a, str>,
pub strategy: Strategy,
pub parser: Parser,
}
impl<Strategy, Parser> RestClient<'_, Strategy, Parser>
where
Strategy: BuildStrategy,
Parser: HttpParser,
{
pub async fn execute<Request>(
&self,
request: Request,
) -> Result<(Request::Response, Metric), Parser::OutputError>
where
Request: RestRequest,
{
let request = self.build(request)?;
let (status, payload, latency) = self.measured_execution::<Request>(request).await?;
self.parser
.parse::<Request::Response>(status, &payload)
.map(|response| (response, latency))
}
pub fn build<Request>(&self, request: Request) -> Result<reqwest::Request, SocketError>
where
Request: RestRequest,
{
let url = format!("{}{}", self.base_url, request.path());
let mut builder = self
.http_client
.request(Request::method(), url)
.timeout(Request::timeout());
if let Some(query_params) = request.query_params() {
builder = builder.query(query_params);
}
if let Some(body) = request.body() {
builder = builder.json(body);
}
self.strategy.build(request, builder)
}
pub async fn measured_execution<Request>(
&self,
request: reqwest::Request,
) -> Result<(reqwest::StatusCode, Bytes, Metric), SocketError>
where
Request: RestRequest,
{
#[allow(clippy::cast_sign_loss)]
let time = Utc::now().timestamp_millis() as u64;
let mut latency = Metric {
name: "http_request_duration",
time,
tags: vec![
Tag::new("http_method", Request::method().as_str()),
Tag::new("base_url", self.base_url.as_ref()),
Tag::new("path", request.url().path()),
],
fields: Vec::with_capacity(1),
};
let start = std::time::Instant::now();
let response = self.http_client.execute(request).await?;
#[allow(clippy::cast_possible_truncation)]
let duration = start.elapsed().as_millis() as u64;
latency
.tags
.push(Tag::new("status_code", response.status().as_str()));
latency.fields.push(Field::new("duration", duration));
let status_code = response.status();
let payload = response.bytes().await?;
Ok((status_code, payload, latency))
}
}
impl<'a, Strategy, Parser> RestClient<'a, Strategy, Parser> {
pub fn new<Url: Into<Cow<'a, str>>>(base_url: Url, strategy: Strategy, parser: Parser) -> Self {
Self {
http_client: reqwest::Client::new(),
base_url: base_url.into(),
strategy,
parser,
}
}
}