use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
use vantage_api_pool::resilient::{ResilientClient, TransportEvent};
use vantage_core::{Priority, error};
use crate::transport::AuthHeader;
mod builder;
mod config;
mod fetch;
mod page;
mod preview;
mod request;
mod window;
pub use builder::RestApiBuilder;
pub(crate) use config::Order;
pub use config::{FilterStrategy, OrderingParams, PaginationMode, PaginationParams, ResponseShape};
pub(crate) const REST_DRIVER: &str = "rest-api";
#[derive(Clone, Debug)]
pub struct RestApi {
base_url: String,
client: ResilientClient,
auth_header: AuthHeader,
response_shape: ResponseShape,
pagination: PaginationParams,
paged: bool,
no_pagination: bool,
filter_strategy: FilterStrategy,
total_key: Option<String>,
ordering: Option<OrderingParams>,
debug: bool,
}
impl RestApi {
pub fn new(base_url: impl Into<String>) -> Self {
RestApi::builder(base_url).build()
}
pub fn builder(base_url: impl Into<String>) -> RestApiBuilder {
RestApiBuilder::new(base_url.into())
}
pub fn total_key(&self) -> Option<&str> {
self.total_key.as_deref()
}
pub(crate) fn serves_windows(&self) -> bool {
!self.no_pagination && (self.paged || self.total_key.is_some())
}
pub fn ordering(&self) -> Option<&OrderingParams> {
self.ordering.as_ref()
}
pub fn breaker_state(&self) -> Option<crate::BreakerState> {
self.client.breaker_state()
}
pub fn client(&self) -> &ResilientClient {
&self.client
}
pub fn base_url(&self) -> &str {
&self.base_url
}
pub fn with_bearer_source(&self, source: crate::BearerSource) -> Self {
Self {
auth_header: AuthHeader::Source(source),
..self.clone()
}
}
pub async fn http_request(
&self,
method: reqwest::Method,
path: &str,
headers: &[(&str, &str)],
body: Option<&serde_json::Value>,
) -> vantage_core::Result<reqwest::Response> {
let url = join_base_path(&self.base_url, path);
if let AuthHeader::Fixed(auth) = &self.auth_header {
HeaderValue::from_str(auth)
.map_err(|_| error!("Configured Authorization header value is not valid"))?;
}
let mut header_map = HeaderMap::new();
for (name, value) in headers {
let header_name = HeaderName::from_bytes(name.as_bytes())
.map_err(|_| error!("HTTP header name is not valid", name = *name))?;
let header_value = HeaderValue::from_str(value)
.map_err(|_| error!("HTTP header value is not valid", name = *name))?;
header_map.insert(header_name, header_value);
}
let policy = crate::transport::policy_for(Priority::current());
let response = self
.client
.execute_with(&policy, |http| {
let req = self
.auth_header
.apply(http.request(method.clone(), &url))
.headers(header_map.clone());
match body {
Some(body) => req.json(body),
None => req,
}
})
.await
.map_err(|e| crate::transport::client_error(e, "API request failed", &url))?;
if method != reqwest::Method::GET {
self.client.report(TransportEvent::WritePushed);
}
Ok(response)
}
}
fn join_base_path(base: &str, path: &str) -> String {
format!(
"{}/{}",
base.trim_end_matches('/'),
path.trim_start_matches('/')
)
}