product-os-request 0.0.55

Product OS : Request provides a fully featured HTTP request library combining elements of reqwest and hyper for async requests with a series of helper methods to allow for easier usage depending upon your needs for one-time or repeat usage.
Documentation
//! HTTP request configuration and management
//!
//! This module provides the `ProductOSRequester` type for managing HTTP client configuration.

use alloc::collections::BTreeMap;
use alloc::string::String;
#[allow(unused_imports)]
use alloc::string::ToString;
use alloc::vec;
use alloc::vec::Vec;
use core::time::Duration;

#[cfg(any(feature = "request", feature = "request_std"))]
use crate::protocol::{Protocol, Proxy};

#[cfg(any(feature = "request", feature = "request_std"))]
use crate::policy::RedirectPolicy;

#[cfg(any(feature = "request", feature = "request_std"))]
use crate::method::Method;

#[cfg(any(feature = "request", feature = "request_std"))]
use crate::error::ProductOSRequestError;

#[cfg(any(feature = "request", feature = "request_std"))]
use crate::request::ProductOSRequest;

#[cfg(any(feature = "request", feature = "request_std"))]
use crate::response::ProductOSResponse;

#[cfg(any(feature = "request", feature = "request_std"))]
use crate::client::ProductOSClient;

#[cfg(any(feature = "request", feature = "request_std"))]
pub use product_os_http::Request;

/// HTTP client configuration manager
///
/// Provides configuration for HTTP clients including headers, timeouts,
/// certificates, and proxy settings.
#[cfg(any(feature = "request", feature = "request_std"))]
#[derive(Clone)]
pub struct ProductOSRequester {
    pub(crate) headers: product_os_http::header::HeaderMap,
    pub(crate) secure: bool,
    pub(crate) timeout: Duration,
    pub(crate) connect_timeout: Duration,
    pub(crate) certificates: Vec<Vec<u8>>,
    pub(crate) trust_all_certificates: bool,
    pub(crate) trust_any_certificate_for_hostname: bool,
    pub(crate) proxy: Option<Proxy>,
    pub(crate) redirect_policy: RedirectPolicy,
}

#[cfg(any(feature = "request", feature = "request_std"))]
impl ProductOSRequester {
    /// Create a new requester with default settings
    ///
    /// Default settings:
    /// - HTTPS only (secure: true)
    /// - 1 second timeout
    /// - 1 second connect timeout
    /// - No custom certificates
    /// - Do not trust invalid certificates
    /// - Default redirect policy (follow up to 10 redirects)
    pub fn new() -> Self {
        Self::default()
    }

    /// Add a header that will be sent with every request
    ///
    /// # Arguments
    ///
    /// * `name` - Header name
    /// * `value` - Header value
    /// * `is_sensitive` - Whether the header contains sensitive data
    pub fn add_header(&mut self, name: &str, value: &str, is_sensitive: bool) {
        let header_name = match product_os_http::header::HeaderName::try_from(name) {
            Ok(n) => n,
            Err(e) => {
                tracing::error!("Invalid header name '{}': {:?}", name, e);
                return;
            }
        };

        let mut header_value = match product_os_http::header::HeaderValue::try_from(value) {
            Ok(v) => v,
            Err(e) => {
                tracing::error!("Invalid header value for '{}': {:?}", name, e);
                return;
            }
        };

        header_value.set_sensitive(is_sensitive);

        self.headers.append(header_name, header_value);
    }

    /// Replace all default headers
    ///
    /// # Arguments
    ///
    /// * `headers` - Map of header names to values
    pub fn set_headers(&mut self, headers: BTreeMap<String, String>) {
        let mut header_map = product_os_http::header::HeaderMap::new();

        for (name, value) in headers {
            match product_os_http::header::HeaderName::try_from(name.clone()) {
                Ok(header_name) => match product_os_http::header::HeaderValue::try_from(value) {
                    Ok(header_value) => {
                        header_map.insert(header_name, header_value);
                    }
                    Err(e) => {
                        tracing::warn!("Skipping invalid header value for '{}': {:?}", name, e);
                    }
                },
                Err(e) => {
                    tracing::warn!("Skipping invalid header name '{}': {:?}", name, e);
                }
            }
        }

        self.headers = header_map;
    }

    /// Force HTTPS-only connections
    ///
    /// # Arguments
    ///
    /// * `sec` - Whether to enforce HTTPS
    pub fn force_secure(&mut self, sec: bool) {
        self.secure = sec;
    }

    /// Trust all certificates (including invalid ones)
    ///
    /// # Warning
    ///
    /// This disables certificate validation and should only be used
    /// for testing or in controlled environments.
    ///
    /// # Arguments
    ///
    /// * `trust` - Whether to trust all certificates
    pub fn trust_all_certificates(&mut self, trust: bool) {
        self.trust_all_certificates = trust;
    }

    /// Trust any certificate for a given hostname
    ///
    /// # Warning
    ///
    /// This disables hostname validation and should only be used
    /// for testing or in controlled environments.
    ///
    /// # Arguments
    ///
    /// * `trust` - Whether to trust any certificate for any hostname
    pub fn trust_any_certificate_for_hostname(&mut self, trust: bool) {
        self.trust_any_certificate_for_hostname = trust;
    }

    /// Set the request timeout in milliseconds
    ///
    /// # Arguments
    ///
    /// * `time` - Timeout in milliseconds
    pub fn set_timeout(&mut self, time: u64) {
        self.timeout = Duration::from_millis(time);
    }

    /// Set the connection timeout in milliseconds
    ///
    /// # Arguments
    ///
    /// * `time` - Connect timeout in milliseconds
    pub fn set_connect_timeout(&mut self, time: u64) {
        self.connect_timeout = Duration::from_millis(time);
    }

    /// Add a trusted root certificate in DER format
    ///
    /// # Deprecated
    ///
    /// Misleadingly named -- the certificate should be in DER format,
    /// not PEM. Use `add_trusted_certificate_der()` instead.
    ///
    /// # Arguments
    ///
    /// * `certificate` - Certificate bytes in DER format
    #[deprecated(
        since = "0.0.54",
        note = "Renamed to add_trusted_certificate_der() to correctly reflect DER format"
    )]
    pub fn add_trusted_certificate_pem(&mut self, certificate: Vec<u8>) {
        self.add_trusted_certificate_der(certificate);
    }

    /// Add a trusted root certificate in DER format
    ///
    /// # Arguments
    ///
    /// * `certificate` - Certificate bytes in DER format
    pub fn add_trusted_certificate_der(&mut self, certificate: Vec<u8>) {
        self.certificates.push(certificate);
    }

    /// Get all trusted certificates
    ///
    /// # Deprecated
    ///
    /// Use `trusted_certificates()` instead which returns `&[Vec<u8>]`.
    #[deprecated(
        since = "0.0.54",
        note = "Use trusted_certificates() instead which returns &[Vec<u8>]"
    )]
    pub fn get_trusted_certificates(&self) -> &Vec<Vec<u8>> {
        &self.certificates
    }

    /// Get all trusted certificates as a slice
    ///
    /// Returns a slice of the certificate bytes.
    pub fn trusted_certificates(&self) -> &[Vec<u8>] {
        &self.certificates
    }

    /// Set the redirect policy
    ///
    /// # Arguments
    ///
    /// * `policy` - The redirect policy to use
    pub fn set_redirect_policy(&mut self, policy: RedirectPolicy) {
        self.redirect_policy = policy;
    }

    /// Set proxy configuration
    ///
    /// # Arguments
    ///
    /// * `proxy` - Optional tuple of (Protocol, address) or None to disable proxy
    pub fn set_proxy(&mut self, proxy: Option<(Protocol, String)>) {
        match proxy {
            None => self.proxy = None,
            Some((protocol, address)) => {
                let proxy = Proxy { protocol, address };

                self.proxy = Some(proxy);
            }
        }
    }

    /// Build a client with this configuration
    ///
    /// # Arguments
    ///
    /// * `client` - The client to configure
    pub fn build<DReq: product_os_http_body::Body, DRes: product_os_http_body::Body>(
        &self,
        client: &mut impl ProductOSClient<DReq, DRes>,
    ) {
        client.build(self)
    }

    // Internal methods for making requests through clients
    #[allow(dead_code)]
    pub(crate) fn new_request<
        DReq: product_os_http_body::Body,
        DRes: product_os_http_body::Body,
    >(
        &self,
        method: Method,
        url: &str,
        client: &mut impl ProductOSClient<DReq, DRes>,
    ) -> ProductOSRequest<DReq> {
        client.new_request(method, url)
    }

    #[allow(dead_code)]
    pub(crate) async fn request<
        DReq: product_os_http_body::Body,
        DRes: product_os_http_body::Body,
    >(
        &self,
        r: ProductOSRequest<DReq>,
        client: &mut impl ProductOSClient<DReq, DRes>,
    ) -> Result<ProductOSResponse<DRes>, ProductOSRequestError> {
        client.request(r).await
    }

    #[allow(dead_code)]
    pub(crate) async fn request_simple<
        DReq: product_os_http_body::Body,
        DRes: product_os_http_body::Body,
    >(
        &self,
        method: Method,
        url: &str,
        client: &mut impl ProductOSClient<DReq, DRes>,
    ) -> Result<ProductOSResponse<DRes>, ProductOSRequestError> {
        client.request_simple(method, url).await
    }

    #[allow(dead_code)]
    pub(crate) async fn request_raw<
        DReq: product_os_http_body::Body,
        DRes: product_os_http_body::Body,
    >(
        &self,
        r: Request<DReq>,
        client: &mut impl ProductOSClient<DReq, DRes>,
    ) -> Result<ProductOSResponse<DRes>, ProductOSRequestError> {
        client.request_raw(r).await
    }
}

#[cfg(any(feature = "request", feature = "request_std"))]
impl Default for ProductOSRequester {
    fn default() -> Self {
        Self {
            headers: product_os_http::header::HeaderMap::new(),
            secure: true,
            timeout: Duration::from_millis(1000),
            connect_timeout: Duration::from_millis(1000),
            certificates: vec![],
            trust_all_certificates: false,
            trust_any_certificate_for_hostname: false,
            proxy: None,
            redirect_policy: RedirectPolicy::Default,
        }
    }
}