Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions src/authorize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use core::fmt;
use std::sync::Arc;

use reqwest::Request;

use crate::credentials::{AuthorizationError, Credentials};

// AUTHORIZE ///////////////////////////////////////////////////////////////////

pub trait Authorize: fmt::Debug + Send + Sync + 'static {
type Error: Send + 'static;

/// Appends an `Authorization` header to the `request`, if this authorizer
/// has credentials and unless it is already set.
///
/// Returns `true` if the `Authorization` header was inserted.
///
/// # Errors
///
/// Upon failure to produce the `Authorization` header value or if the request
/// has too many headers.
fn authorize(&self, request: &mut Request) -> Result<bool, Self::Error>;
}

impl Authorize for Credentials {
type Error = AuthorizationError;

#[inline]
fn authorize(&self, request: &mut Request) -> Result<bool, Self::Error> {
self.as_ref().authorize(request)
}
}

impl<T: Authorize> Authorize for Option<T> {
type Error = T::Error;

#[inline]
fn authorize(&self, request: &mut Request) -> Result<bool, Self::Error> {
match self.as_ref() {
None => Ok(false),
Some(authorizer) => authorizer.authorize(request),
}
}
}

impl<T: Authorize> Authorize for Arc<T> {
type Error = T::Error;

#[inline]
fn authorize(&self, request: &mut Request) -> Result<bool, Self::Error> {
(&**self).authorize(request)

Check failure on line 51 in src/authorize.rs

View workflow job for this annotation

GitHub Actions / Lint source code

this expression borrows a value the compiler would automatically borrow

Check failure on line 51 in src/authorize.rs

View workflow job for this annotation

GitHub Actions / Lint source code

this expression borrows a value the compiler would automatically borrow
}
}
127 changes: 32 additions & 95 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -1,146 +1,83 @@
//! HTTP client

use core::fmt;

use reqwest::{Request, Response};

use crate::{
credentials::{AuthorizationError, Credentials},
execute::ExecuteRequest,
};
use crate::{authorize::Authorize, credentials::Credentials, execute::ExecuteRequest};

// CLIENT ERROR ////////////////////////////////////////////////////////////////

#[derive(Debug, thiserror::Error)]
pub enum ClientError<E = reqwest::Error> {
pub enum ClientError<E, A> {
#[error("failed to authorize request, {0}")]
Authorize(#[from] AuthorizationError),
Authorize(A),
#[error("failed to execute request, {0}")]
Execute(E),
}

// CLIENT //////////////////////////////////////////////////////////////////////

/// HTTP client with optional [`Credentials`].
///
/// When credentials are provided, the client, will ensure requests are authorized
/// before they are executed.
#[derive(Debug, Default, Clone)]
/// HTTP client that authorize requests before execution.
#[derive(Debug, Clone)]
#[must_use]
pub struct Client<T = reqwest::Client> {
inner: T,
credentials: Option<Credentials>,
pub struct Client<T = reqwest::Client, A = Option<Credentials>> {
executer: T,
authorizer: A,
}

impl<T> From<T> for Client<T> {
impl<T, A: Default> From<T> for Client<T, A> {
fn from(value: T) -> Self {
Self {
inner: value,
credentials: None,
executer: value,
authorizer: A::default(),
}
}
}

impl From<&Credentials> for Client {
fn from(value: &Credentials) -> Self {
Self::from(value.clone())
impl Default for Client {
fn default() -> Self {
Self::from(reqwest::Client::default())
}
}

impl<T: Into<Box<str>>> From<Credentials<T>> for Client {
fn from(value: Credentials<T>) -> Self {
impl<T: fmt::Debug, A: fmt::Debug> Client<T, A> {
#[cfg_attr(feature = "tracing", tracing::instrument)]
pub fn new(executer: T, authorizer: A) -> Self {
Self {
inner: reqwest::Client::new(),
credentials: Some(value.into()),
executer,
authorizer,
}
}
}

impl Client {
pub fn new() -> Self {
Self::default()
pub fn executer(&self) -> &T {
&self.executer
}
}

impl<T: fmt::Debug> Client<T> {
/// Sets the `credentials` to be used by the client to authorize HTTP request,
/// discarding the current value, if any.
#[cfg_attr(feature = "tracing", tracing::instrument)]
pub fn set_credentials(&mut self, credentials: Option<Credentials>) {
self.credentials = credentials;
pub fn executer_mut(&mut self) -> &mut T {
&mut self.executer
}

#[cfg_attr(feature = "tracing", tracing::instrument)]
fn set_credentials_from<U: Into<Box<str>> + fmt::Debug>(
&mut self,
credentials: Option<Credentials<U>>,
) {
self.credentials = credentials.map(Credentials::into);
pub fn authorizer(&self) -> &A {
&self.authorizer
}

/// Fills the `credentials` to be used by the client to authorize HTTP request,
/// discarding the current value, if any.
pub fn with_credentials<U: Into<Box<str>> + fmt::Debug>(
mut self,
credentials: impl Into<Option<Credentials<U>>>,
) -> Self {
self.set_credentials_from(credentials.into());
self
}

/// Returns the credentials that will be used by this client to authorized
/// subsequent HTTP requests.
#[cfg_attr(feature = "tracing", tracing::instrument)]
pub fn credentials(&self) -> Option<Credentials<&str>> {
self.credentials.as_ref().map(Credentials::as_ref)
}

/// Returns a shared reference to the inner HTTP client.
#[cfg_attr(feature = "tracing", tracing::instrument)]
pub fn inner(&self) -> &T {
&self.inner
}

/// Appends an `Authorization` header to the `request`, if this client has credentials and unless it is already set.
///
/// Returns `true` if the `Authorization` header was inserted.
///
/// # Errors
///
/// Upon failure to produce the header value.
///
/// If the client doesn't have credentials, this method is infallible.
#[cfg_attr(feature = "tracing", tracing::instrument)]
pub fn authorize(&self, request: &mut Request) -> Result<bool, AuthorizationError> {
match self.credentials() {
None => Ok(false),
Some(credentials) => credentials.authorize(request),
}
pub fn authorizer_mut(&mut self) -> &mut A {
&mut self.authorizer
}
}

impl<T: ExecuteRequest> ExecuteRequest for Client<T> {
type Error = ClientError<T::Error>;
impl<T: ExecuteRequest, A: Authorize> ExecuteRequest for Client<T, A> {
type Error = ClientError<T::Error, A::Error>;

fn execute_request(
&self,
mut request: Request,
) -> impl Future<Output = Result<Response, Self::Error>> + Send + 'static {
let result = self
.authorizer
.authorize(&mut request)
.map(|_| self.inner.execute_request(request));
.map_err(ClientError::Authorize)
.map(|_| self.executer.execute_request(request));

async move { result?.await.map_err(ClientError::Execute) }
}
}

#[cfg(feature = "zeroize")]
impl<T> Drop for Client<T> {
fn drop(&mut self) {
use zeroize::Zeroize;

if let Some(mut credentials) = self.credentials.take() {
credentials.zeroize();
}
}
}
Loading
Loading