util: refactor HTTP request API

This commit is contained in:
anna 2023-01-19 14:39:39 +01:00
parent 20b5cc32cc
commit 605a7caa2c
Signed by: fef
GPG key ID: EC22E476DC2D3D84

View file

@ -1,18 +1,30 @@
use reqwest::header::{HeaderName, HeaderValue, AUTHORIZATION, USER_AGENT}; use reqwest::header::{HeaderName, AUTHORIZATION, USER_AGENT};
use reqwest::{header::HeaderMap, RequestBuilder, Response}; use reqwest::{RequestBuilder, Response};
use std::collections::HashMap;
use crate::core::*; use crate::core::*;
use crate::middle::AuthData;
use crate::state::AppState; use crate::state::AppState;
use crate::util::bear::Bearcap; use crate::util::bear::Bearcap;
/// Perform an HTTP GET request to the specified URL (supports bearcaps). /// Perform an HTTP GET request to the specified URL (supports bearcaps).
/// Use the [`headers!`] macro for the `headers` parameter. /// Use the [`crate::headers`] macro for the `headers` parameter.
///
/// If `url` starts with `"bear:"`, it will be parsed as a bearcap URI
/// and automatically append an `Authorization` header to the request,
/// overwriting an authorization header that was passed explicitly as an
/// argument. The request will fail if parsing the bearcap failed.
///
/// ## Default Headers
///
/// By default, the API injects the following headers:
///
/// - `User-Agent`
/// - `Authorization` (only when passing a bearcap URI)
/// ///
/// ## Example /// ## Example
/// ///
/// ``` /// ```
/// use reqwest::header::ACCEPT;
///
/// let response = get( /// let response = get(
/// &state, /// &state,
/// "https://www.example.com", /// "https://www.example.com",
@ -24,12 +36,12 @@ use crate::util::bear::Bearcap;
/// .await?; /// .await?;
/// ///
/// // automatically injects an "Authorization: Bearer b4dc0ffee" header /// // automatically injects an "Authorization: Bearer b4dc0ffee" header
/// let response = get(&state, "bear:?t=b4dc0ffee&u=https://www.example.com", headers! {}).await?; /// let response = get(&state, "bear:?t=b4dc0ffee&u=https://www.example.com", None).await?;
/// ``` /// ```
pub async fn get( pub async fn get(
state: &AppState, state: &AppState,
url: &str, url: &str,
headers: &[(GenericHeaderName, String)], headers: Option<&[(GenericHeaderName<'_>, String)]>,
) -> Result<Response> { ) -> Result<Response> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let builder = if url.starts_with("bear:") { let builder = if url.starts_with("bear:") {
@ -40,49 +52,57 @@ pub async fn get(
} else { } else {
client.get(url) client.get(url)
}; };
perform_request(state, builder, headers).await perform_request(state, builder, headers.unwrap_or(&[])).await
} }
// the state reference will be used for caching // the state reference will be used for caching
async fn perform_request( async fn perform_request(
_state: &AppState, _state: &AppState,
mut builder: RequestBuilder, mut builder: RequestBuilder,
headers: &[(GenericHeaderName, String)], headers: &[(GenericHeaderName<'_>, String)],
) -> Result<Response> { ) -> Result<Response> {
builder = headers.into_iter().fold(builder, |b, (k, v)| match k { builder = headers.iter().fold(builder, |b, (k, v)| match k {
GenericHeaderName::Standard(hdr) => b.header(hdr, v), GenericHeaderName::Standard(hdr) => b.header(hdr, v),
GenericHeaderName::Custom(hdr) => b.header(hdr, v), GenericHeaderName::CustomOwned(hdr) => b.header(hdr, v),
GenericHeaderName::CustomBorrow(hdr) => b.header(*hdr, v),
}); });
builder = builder.header(USER_AGENT, "nyano"); builder = builder.header(USER_AGENT, "nyano");
Ok(builder.send().await?) Ok(builder.send().await?)
} }
/// Generate a list of HTTP request headers for passing to [`get`].
#[macro_export] #[macro_export]
macro_rules! headers { macro_rules! headers {
($($k:expr => $v:expr),* $(,)?) => {{ ($($k:expr => $v:expr),* $(,)?) => {{
&[$((crate::util::http::GenericHeaderName::from($k), ::std::string::String::from($v)),)*] // The [..] is required because of a bug in the IntelliJ Rust plugin:
// https://github.com/intellij-rust/intellij-rust/issues/10005
::core::option::Option::Some(&[
$(($crate::util::http::GenericHeaderName::from($k), ::std::string::String::from($v)),)*
][..])
}} }}
} }
pub enum GenericHeaderName { /// Only used within this module, use [`crate::headers`] to generate headers.
Custom(String), pub enum GenericHeaderName<'a> {
CustomOwned(String),
CustomBorrow(&'a str),
Standard(HeaderName), Standard(HeaderName),
} }
impl From<String> for GenericHeaderName { impl<'a> From<String> for GenericHeaderName<'a> {
fn from(val: String) -> GenericHeaderName { fn from(val: String) -> GenericHeaderName<'a> {
GenericHeaderName::Custom(val) GenericHeaderName::CustomOwned(val)
} }
} }
impl From<&str> for GenericHeaderName { impl<'a> From<&'a str> for GenericHeaderName<'a> {
fn from(val: &str) -> GenericHeaderName { fn from(val: &'a str) -> GenericHeaderName<'a> {
GenericHeaderName::Custom(String::from(val)) GenericHeaderName::CustomBorrow(val)
} }
} }
impl From<HeaderName> for GenericHeaderName { impl<'a> From<HeaderName> for GenericHeaderName<'a> {
fn from(val: HeaderName) -> GenericHeaderName { fn from(val: HeaderName) -> GenericHeaderName<'a> {
GenericHeaderName::Standard(val) GenericHeaderName::Standard(val)
} }
} }