url_cleaner_engine/glue/
proxy.rs

1//! Glue for [`reqwest::Proxy`].
2
3use std::str::FromStr;
4
5use serde::{Serialize, Deserialize};
6use url::Url;
7use reqwest::header::HeaderValue;
8use reqwest::Proxy;
9
10use crate::glue::*;
11use crate::util::*;
12
13#[expect(unused_imports, reason = "Used in a doc comment.")]
14use crate::glue::HttpClientConfig;
15
16/// Rules on how to make a [`reqwest::Proxy`].
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Suitability)]
18#[serde(deny_unknown_fields)]
19#[serde(remote = "Self")]
20pub struct ProxyConfig {
21    /// The [`Url`] to proxy requests to.
22    pub url: Url,
23    /// The protocol(s) to redirect.
24    ///
25    /// Defaults to [`ProxyMode::All`].
26    #[serde(default, skip_serializing_if = "is_default")]
27    pub mode: ProxyMode,
28    /// The authentication to use.
29    ///
30    /// Defaults to [`None`].
31    #[serde(default, skip_serializing_if = "is_default")]
32    pub auth: Option<ProxyAuth>
33}
34
35crate::util::string_or_struct_magic!(ProxyConfig);
36
37impl FromStr for ProxyConfig {
38    type Err = <Url as FromStr>::Err;
39    fn from_str(s: &str) -> Result<Self, Self::Err> {
40        Ok(Url::from_str(s)?.into())
41    }
42}
43
44impl TryFrom<&str> for ProxyConfig {
45    type Error = <Self as FromStr>::Err;
46    fn try_from(value: &str) -> Result<Self, Self::Error> {
47        Self::from_str(value)
48    }
49}
50
51impl From<Url> for ProxyConfig {
52    fn from(url: Url) -> Self {
53        Self {
54            url,
55            mode: ProxyMode::default(),
56            auth: None
57        }
58    }
59}
60
61/// The protocol(s) to proxy.
62///
63/// Defaults to [`Self::All`].
64#[derive(Debug, Clone, Copy, PartialEq, Default, Eq, Serialize, Deserialize, Suitability)]
65#[serde(deny_unknown_fields)]
66pub enum ProxyMode {
67    /// Proxy only HTTP.
68    ///
69    /// Corresponds to [`Proxy::http`].
70    Http,
71    /// Proxy only HTTPS.
72    ///
73    /// Corresponds to [`Proxy::https`].
74    Https,
75    /// Proxy all protocols.
76    ///
77    /// Corresponds to [`Proxy::all`].
78    #[default]
79    All
80}
81
82/// The authentication to use for a proxy.
83///
84/// Uses the [`Proxy-Authentication`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Proxy-Authorization) HTTP header.
85#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Suitability)]
86#[serde(deny_unknown_fields)]
87pub enum ProxyAuth {
88    /// Uses the [`Basic`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Authentication#basic_authentication_scheme) mode.
89    ///
90    /// Corresponds to [`Proxy::basic_auth`].
91    Basic {
92        /// The username to use.
93        username: String,
94        /// The password to use.
95        password: String
96    },
97    /// Uses a custom value.
98    ///
99    /// Corresponds to [`Proxy::custom_http_auth`].
100    Custom(#[serde(with = "serde_headervalue")] HeaderValue)
101}
102
103impl TryFrom<ProxyConfig> for reqwest::Proxy {
104    type Error = reqwest::Error;
105
106    fn try_from(value: ProxyConfig) -> reqwest::Result<Self> {
107        let temp = match value.mode {
108            ProxyMode::Http  => Proxy::http (value.url),
109            ProxyMode::Https => Proxy::https(value.url),
110            ProxyMode::All   => Proxy::all  (value.url)
111        }?;
112        Ok(match &value.auth {
113            None => temp,
114            Some(ProxyAuth::Basic {username, password}) => temp.basic_auth(username, password),
115            Some(ProxyAuth::Custom(value)) => temp.custom_http_auth(value.clone())
116        })
117    }
118}
119
120impl ProxyConfig {
121    /// Makes a [`reqwest::Proxy`].
122    /// # Errors
123    /// If the call to [`Proxy::http`], [`Proxy::https`], or [`Proxy::all`] return an error, that error is returned.
124    pub fn make(self) -> reqwest::Result<Proxy> {
125        self.try_into()
126    }
127}