use reqwest::header::WWW_AUTHENTICATE;
use reqwest::{RequestBuilder, Response, StatusCode};
use super::error::HttpError;
use super::provider::PaymentProvider;
use crate::protocol::core::{format_authorization, parse_www_authenticate, AUTHORIZATION_HEADER};
pub trait PaymentExt {
fn send_with_payment<P: PaymentProvider>(
self,
provider: &P,
) -> impl std::future::Future<Output = Result<Response, HttpError>> + Send;
}
impl PaymentExt for RequestBuilder {
async fn send_with_payment<P: PaymentProvider>(
self,
provider: &P,
) -> Result<Response, HttpError> {
let retry_builder = self.try_clone().ok_or(HttpError::CloneFailed)?;
let resp = self.send().await?;
if resp.status() != StatusCode::PAYMENT_REQUIRED {
return Ok(resp);
}
let www_auth = resp
.headers()
.get(WWW_AUTHENTICATE)
.ok_or(HttpError::MissingChallenge)?
.to_str()
.map_err(|e| HttpError::InvalidChallenge(e.to_string()))?;
let challenge = parse_www_authenticate(www_auth)
.map_err(|e| HttpError::InvalidChallenge(e.to_string()))?;
let credential = provider.pay(&challenge).await?;
let auth_header = format_authorization(&credential)
.map_err(|e| HttpError::InvalidCredential(e.to_string()))?;
let retry_resp = retry_builder
.header(AUTHORIZATION_HEADER, auth_header)
.send()
.await?;
Ok(retry_resp)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_payment_ext_trait_exists() {
fn assert_payment_ext<T: PaymentExt>() {}
assert_payment_ext::<RequestBuilder>();
}
}