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 response handling
//!
//! This module provides the `ProductOSResponse` type for handling HTTP responses.

use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};

#[cfg(any(feature = "response", feature = "response_std"))]
pub use product_os_http::{response::Parts, Response, StatusCode};

/// HTTP response wrapper
///
/// Wraps an HTTP response with additional metadata.
#[cfg(any(feature = "response", feature = "response_std"))]
pub struct ProductOSResponse<DRes: product_os_http_body::Body> {
    response_url: String,
    pub(crate) response_async: Option<Response<DRes>>,
}

#[cfg(any(feature = "response", feature = "response_std"))]
impl<DRes: product_os_http_body::Body> ProductOSResponse<DRes> {
    /// Create a new response with only a body (defaults to status 200)
    ///
    /// # Deprecated
    ///
    /// Use `from_response()` or `from_parts()` instead, which preserve the
    /// actual HTTP status code and headers from the backend response.
    ///
    /// The type parameter `D` is unused and will be removed in a future version.
    ///
    /// # Arguments
    ///
    /// * `r` - The response body
    /// * `url` - The final URL (after redirects)
    #[deprecated(
        since = "0.0.54",
        note = "Use ProductOSResponse::from_parts() or ProductOSResponse::from_response() instead, which preserve status codes and headers"
    )]
    pub fn new<D>(r: DRes, url: String) -> Self {
        Self {
            response_url: url,
            response_async: Some(Response::new(r)),
        }
    }

    /// Create a new response from a body and URL
    ///
    /// Creates a response with default status (200 OK). Prefer `from_parts()`
    /// or `from_response()` to preserve actual HTTP status codes and headers.
    ///
    /// # Arguments
    ///
    /// * `body` - The response body
    /// * `url` - The final URL (after redirects)
    pub fn from_body(body: DRes, url: String) -> Self {
        Self {
            response_url: url,
            response_async: Some(Response::new(body)),
        }
    }

    /// Create a response from a full HTTP response object
    ///
    /// Preserves the status code, headers, and body from the original response.
    ///
    /// # Arguments
    ///
    /// * `response` - The full HTTP response
    /// * `url` - The final URL (after redirects)
    pub fn from_response(response: Response<DRes>, url: String) -> Self {
        Self {
            response_url: url,
            response_async: Some(response),
        }
    }

    /// Create a response from parts and body
    ///
    /// Allows constructing a response with specific status code, headers, and body.
    ///
    /// # Arguments
    ///
    /// * `parts` - Response parts (status, headers, etc.)
    /// * `body` - The response body
    /// * `url` - The final URL (after redirects)
    pub fn from_parts(parts: Parts, body: DRes, url: String) -> Self {
        Self {
            response_url: url,
            response_async: Some(Response::from_parts(parts, body)),
        }
    }

    /// Decompose the response into parts and body
    ///
    /// Returns a tuple of (Parts, Body) where Parts contains
    /// status code, headers, and other metadata.
    ///
    /// # Panics
    ///
    /// Panics if the response has already been consumed.
    /// Prefer `try_parts()` for a non-panicking alternative.
    #[deprecated(
        since = "0.0.54",
        note = "Use try_parts() instead which returns Option"
    )]
    #[allow(clippy::unwrap_used)]
    pub fn parts(self) -> (Parts, DRes) {
        self.response_async.unwrap().into_parts()
    }

    /// Decompose the response into parts and body
    ///
    /// Returns `Some((Parts, Body))` if the response hasn't been consumed,
    /// or `None` if it has.
    pub fn try_parts(self) -> Option<(Parts, DRes)> {
        self.response_async.map(|r| r.into_parts())
    }

    /// Get the response status code as `StatusCode`
    ///
    /// # Panics
    ///
    /// Panics if the response has already been consumed.
    /// Prefer `try_status()` for a non-panicking alternative.
    #[deprecated(
        since = "0.0.54",
        note = "Use try_status() instead which returns Option"
    )]
    #[allow(clippy::unwrap_used)]
    pub fn status(&self) -> StatusCode {
        self.response_async.as_ref().unwrap().status()
    }

    /// Get the response status code as `StatusCode`
    ///
    /// Returns `None` if the response has been consumed.
    pub fn try_status(&self) -> Option<StatusCode> {
        self.response_async.as_ref().map(|r| r.status())
    }

    /// Get the response status code as u16
    ///
    /// Returns 0 if the response has been consumed.
    pub fn status_code(&self) -> u16 {
        self.try_status().map(|s| s.as_u16()).unwrap_or(0)
    }

    /// Get all response headers as a map
    ///
    /// Returns a `BTreeMap` of header names to header values.
    ///
    /// # Panics
    ///
    /// Panics if the response has already been consumed or if a header value is not valid UTF-8.
    /// Prefer `try_get_headers()` for a non-panicking alternative.
    #[deprecated(
        since = "0.0.54",
        note = "Use try_get_headers() instead which returns Option"
    )]
    #[allow(clippy::unwrap_used)]
    pub fn get_headers(&self) -> BTreeMap<String, String> {
        let mut map = BTreeMap::new();

        let headers = self.response_async.as_ref().unwrap().headers();

        for (name, value) in headers {
            map.insert(name.to_string(), value.to_str().unwrap().to_string());
        }

        map
    }

    /// Get all response headers as a map
    ///
    /// Returns `Some(BTreeMap)` of header names to header values, or `None`
    /// if the response has been consumed. Non-UTF-8 header values are skipped.
    pub fn try_get_headers(&self) -> Option<BTreeMap<String, String>> {
        self.response_async.as_ref().map(|r| {
            let mut map = BTreeMap::new();
            for (name, value) in r.headers() {
                if let Ok(v) = value.to_str() {
                    map.insert(name.to_string(), v.to_string());
                }
            }
            map
        })
    }

    /// Get the response URL (after redirects)
    pub fn response_url(&self) -> &str {
        &self.response_url
    }

    /// Extract the response body
    ///
    /// Consumes the response and returns the body if present.
    pub fn into_body(self) -> Option<DRes> {
        self.response_async.map(|response| response.into_body())
    }
}