#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
#![allow(clippy::multiple_crate_versions)]
use std::{collections::BTreeMap, marker::PhantomData};
use async_trait::async_trait;
use bytes::Bytes;
use gimbal_http_models::{Method, StatusCode};
use strum::{AsRefStr, EnumString};
use thiserror::Error;
pub use gimbal_http_models as models;
#[cfg(feature = "reqwest")]
pub mod reqwest;
#[cfg(feature = "simulator")]
pub mod simulator;
#[derive(Debug, Error)]
pub enum Error {
#[error("Decode")]
Decode,
#[cfg(feature = "json")]
#[error(transparent)]
Deserialize(#[from] serde_json::Error),
#[cfg(feature = "reqwest")]
#[error(transparent)]
Reqwest(#[from] ::reqwest::Error),
}
#[derive(Debug, Clone, Copy, EnumString, AsRefStr)]
#[strum(serialize_all = "kebab-case")]
pub enum Header {
Authorization,
UserAgent,
Range,
ContentLength,
}
#[async_trait]
pub trait GenericRequestBuilder<R>: Send + Sync {
fn header(&mut self, name: &str, value: &str);
#[allow(unused)]
fn body(&mut self, body: Bytes);
#[cfg(feature = "json")]
fn form(&mut self, form: &serde_json::Value);
async fn send(&mut self) -> Result<R, Error>;
}
pub trait GenericClientBuilder<RB, C: GenericClient<RB>>: Send + Sync {
fn build(self) -> Result<C, Error>;
}
pub trait GenericClient<RB>: Send + Sync {
fn get(&self, url: &str) -> RB {
self.request(Method::Get, url)
}
fn post(&self, url: &str) -> RB {
self.request(Method::Post, url)
}
fn put(&self, url: &str) -> RB {
self.request(Method::Put, url)
}
fn patch(&self, url: &str) -> RB {
self.request(Method::Patch, url)
}
fn delete(&self, url: &str) -> RB {
self.request(Method::Delete, url)
}
fn head(&self, url: &str) -> RB {
self.request(Method::Head, url)
}
fn options(&self, url: &str) -> RB {
self.request(Method::Options, url)
}
fn request(&self, method: Method, url: &str) -> RB;
}
#[async_trait]
pub trait GenericResponse: Send + Sync {
fn status(&self) -> StatusCode;
fn headers(&mut self) -> &BTreeMap<String, String>;
async fn text(&mut self) -> Result<String, Error>;
async fn bytes(&mut self) -> Result<Bytes, Error>;
#[cfg(feature = "stream")]
fn bytes_stream(
&mut self,
) -> std::pin::Pin<Box<dyn futures_core::Stream<Item = Result<Bytes, Error>> + Send>>;
}
pub struct RequestBuilderWrapper<R, B: GenericRequestBuilder<R>>(
pub(crate) B,
pub(crate) PhantomData<R>,
);
pub struct ClientWrapper<RB, T: GenericClient<RB>>(pub(crate) T, pub(crate) PhantomData<RB>);
pub struct ClientBuilderWrapper<RB, C: GenericClient<RB>, T: GenericClientBuilder<RB, C>>(
pub(crate) T,
PhantomData<RB>,
PhantomData<C>,
);
pub struct ResponseWrapper<T: GenericResponse>(pub(crate) T);
#[allow(unused)]
macro_rules! impl_http {
($module:ident, $local_module:ident $(,)?) => {
paste::paste! {
pub use [< impl_ $module >]::*;
}
mod $local_module {
use crate::*;
paste::paste! {
pub type [< $module:camel Response >] = ResponseWrapper<$module::Response>;
type ModuleResponse = [< $module:camel Response >];
pub type [< $module:camel RequestBuilder >] = RequestBuilderWrapper<ModuleResponse, $module::RequestBuilder>;
type ModuleRequestBuilder = [< $module:camel RequestBuilder >];
pub type [< $module:camel Client >] = ClientWrapper<ModuleRequestBuilder, $module::Client>;
type ModuleClient = [< $module:camel Client >];
pub type [< $module:camel ClientBuilder >] = ClientBuilderWrapper<ModuleRequestBuilder, ModuleClient, $module::ClientBuilder>;
type ModuleClientBuilder = [< $module:camel ClientBuilder >];
}
impl ModuleRequestBuilder {
#[must_use]
pub fn header(mut self, name: &str, value: &str) -> Self {
self.0.header(name, value);
self
}
pub async fn send(mut self) -> Result<ModuleResponse, Error> {
self.0.send().await
}
}
#[async_trait]
impl GenericRequestBuilder<ModuleResponse> for ModuleRequestBuilder {
fn header(&mut self, name: &str, value: &str) {
self.0.header(name, value);
}
fn body(&mut self, body: Bytes) {
self.0.body(body);
}
#[cfg(feature = "json")]
fn form(&mut self, form: &serde_json::Value) {
self.0.form(form);
}
async fn send(&mut self) -> Result<ModuleResponse, Error> {
self.0.send().await
}
}
#[cfg(feature = "json")]
impl ModuleRequestBuilder {
#[must_use]
pub fn json<T: serde::Serialize + ?Sized>(mut self, body: &T) -> Self {
let mut bytes: Vec<u8> = Vec::new();
serde_json::to_writer(&mut bytes, body).unwrap();
<Self as GenericRequestBuilder<ModuleResponse>>::body(&mut self, bytes.into());
self
}
#[must_use]
pub fn form<T: serde::Serialize + ?Sized>(mut self, form: &T) -> Self {
let value = serde_json::to_value(form).unwrap();
<Self as GenericRequestBuilder<ModuleResponse>>::form(&mut self, &value);
self
}
}
#[async_trait]
impl GenericResponse for ModuleResponse {
#[must_use]
fn status(&self) -> StatusCode {
self.0.status()
}
#[must_use]
fn headers(&mut self) -> &BTreeMap<String, String> {
self.0.headers()
}
#[must_use]
async fn text(&mut self) -> Result<String, Error> {
self.0.text().await
}
#[must_use]
async fn bytes(&mut self) -> Result<Bytes, Error> {
self.0.bytes().await
}
#[must_use]
#[cfg(feature = "stream")]
fn bytes_stream(
&mut self,
) -> std::pin::Pin<Box<dyn futures_core::Stream<Item = Result<Bytes, Error>> + Send>>
{
self.0.bytes_stream()
}
}
impl ModuleResponse {
#[must_use]
pub fn status(&self) -> StatusCode {
<Self as GenericResponse>::status(self)
}
#[must_use]
pub fn headers(&mut self) -> &BTreeMap<String, String> {
<Self as GenericResponse>::headers(self)
}
pub async fn text(mut self) -> Result<String, Error> {
<Self as GenericResponse>::text(&mut self).await
}
pub async fn bytes(mut self) -> Result<Bytes, Error> {
<Self as GenericResponse>::bytes(&mut self).await
}
}
impl GenericClientBuilder<ModuleRequestBuilder, ModuleClient> for ModuleClientBuilder {
fn build(self) -> Result<ModuleClient, Error> {
self.0.build()
}
}
impl ModuleClientBuilder {
pub fn build(self) -> Result<ModuleClient, Error> {
<Self as GenericClientBuilder<ModuleRequestBuilder, ModuleClient>>::build(self)
}
}
impl ModuleResponse {
#[cfg(feature = "stream")]
pub fn bytes_stream(
mut self,
) -> impl futures_core::Stream<Item = Result<Bytes, Error>> {
<Self as GenericResponse>::bytes_stream(&mut self)
}
}
impl ModuleResponse {
#[cfg(feature = "json")]
pub async fn json<T: serde::de::DeserializeOwned>(mut self) -> Result<T, Error> {
let bytes = <Self as GenericResponse>::bytes(&mut self).await?;
Ok(serde_json::from_slice(&bytes)?)
}
}
impl Default for ModuleClient {
fn default() -> Self {
Self::new()
}
}
impl ModuleClient {
#[must_use]
pub fn new() -> Self {
Self::builder().0.build().unwrap()
}
#[must_use]
pub const fn builder() -> ModuleClientBuilder {
ModuleClientBuilder::new()
}
#[must_use]
pub fn get(&self, url: &str) -> ModuleRequestBuilder {
<Self as GenericClient<ModuleRequestBuilder>>::get(self, url)
}
#[must_use]
pub fn post(&self, url: &str) -> ModuleRequestBuilder {
<Self as GenericClient<ModuleRequestBuilder>>::post(self, url)
}
#[must_use]
pub fn put(&self, url: &str) -> ModuleRequestBuilder {
<Self as GenericClient<ModuleRequestBuilder>>::put(self, url)
}
#[must_use]
pub fn patch(&self, url: &str) -> ModuleRequestBuilder {
<Self as GenericClient<ModuleRequestBuilder>>::patch(self, url)
}
#[must_use]
pub fn delete(&self, url: &str) -> ModuleRequestBuilder {
<Self as GenericClient<ModuleRequestBuilder>>::delete(self, url)
}
#[must_use]
pub fn head(&self, url: &str) -> ModuleRequestBuilder {
<Self as GenericClient<ModuleRequestBuilder>>::head(self, url)
}
#[must_use]
pub fn options(&self, url: &str) -> ModuleRequestBuilder {
<Self as GenericClient<ModuleRequestBuilder>>::options(self, url)
}
#[must_use]
pub fn request(&self, method: Method, url: &str) -> ModuleRequestBuilder {
<Self as GenericClient<ModuleRequestBuilder>>::request(self, method, url)
}
}
impl Default for ModuleClientBuilder {
fn default() -> Self {
Self::new()
}
}
impl GenericClient<ModuleRequestBuilder> for ModuleClient {
fn request(&self, method: Method, url: &str) -> ModuleRequestBuilder {
self.0.request(method, url)
}
}
}
};
}
#[cfg(feature = "simulator")]
impl_http!(simulator, impl_simulator);
#[cfg(feature = "reqwest")]
impl_http!(reqwest, impl_reqwest);
#[allow(unused)]
macro_rules! impl_gen_types {
($module:ident $(,)?) => {
paste::paste! {
pub type RequestBuilder = [< $module:camel RequestBuilder >];
pub type Client = [< $module:camel Client >];
pub type Response = [< $module:camel Response >];
}
};
}
#[cfg(feature = "simulator")]
impl_gen_types!(simulator);
#[cfg(all(not(feature = "simulator"), feature = "reqwest"))]
impl_gen_types!(reqwest);