Skip to main content

cow_sdk_core/config/
http.rs

1use std::time::Duration;
2
3use http::HeaderValue;
4
5use crate::errors::ValidationError;
6
7use super::{DEFAULT_HTTP_TIMEOUT, DEFAULT_MAX_RESPONSE_BYTES};
8
9/// Shared HTTP client policy used by transport-owning crates.
10#[non_exhaustive]
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct HttpClientPolicy {
13    timeout: Option<Duration>,
14    user_agent: String,
15    max_response_bytes: usize,
16}
17
18impl HttpClientPolicy {
19    /// Creates a policy with the default timeout and a validated user agent.
20    ///
21    /// # Errors
22    ///
23    /// Returns [`ValidationError`] if the user agent is empty or cannot be
24    /// encoded as an HTTP header value.
25    pub fn new(user_agent: impl Into<String>) -> Result<Self, ValidationError> {
26        Self::with_timeout_and_user_agent(DEFAULT_HTTP_TIMEOUT, user_agent)
27    }
28
29    /// Creates a policy with an explicit timeout and validated user agent.
30    ///
31    /// # Errors
32    ///
33    /// Returns [`ValidationError`] if the user agent is empty or cannot be
34    /// encoded as an HTTP header value.
35    pub fn with_timeout_and_user_agent(
36        timeout: Duration,
37        user_agent: impl Into<String>,
38    ) -> Result<Self, ValidationError> {
39        let user_agent = validate_user_agent(user_agent.into())?;
40
41        Ok(Self {
42            timeout: Some(timeout),
43            user_agent,
44            max_response_bytes: DEFAULT_MAX_RESPONSE_BYTES,
45        })
46    }
47
48    /// Returns a copy of this policy with timeouts disabled.
49    #[must_use]
50    pub const fn without_timeout(mut self) -> Self {
51        self.timeout = None;
52        self
53    }
54
55    /// Returns a copy of this policy with the supplied timeout.
56    #[must_use]
57    pub const fn with_timeout(mut self, timeout: Duration) -> Self {
58        self.timeout = Some(timeout);
59        self
60    }
61
62    /// Returns a copy of this policy with the supplied maximum response-body
63    /// size, in bytes. The HTTP transport refuses to buffer a response whose
64    /// decoded body would exceed this many bytes.
65    #[must_use]
66    pub const fn with_max_response_bytes(mut self, max_response_bytes: usize) -> Self {
67        self.max_response_bytes = max_response_bytes;
68        self
69    }
70
71    /// Returns a copy of this policy with a newly validated user agent.
72    ///
73    /// # Errors
74    ///
75    /// Returns [`ValidationError`] if the user agent is empty or cannot be
76    /// encoded as an HTTP header value.
77    pub fn try_with_user_agent(
78        mut self,
79        user_agent: impl Into<String>,
80    ) -> Result<Self, ValidationError> {
81        self.user_agent = validate_user_agent(user_agent.into())?;
82        Ok(self)
83    }
84
85    /// Returns the configured timeout, if one is enabled.
86    #[must_use]
87    pub const fn timeout(&self) -> Option<Duration> {
88        self.timeout
89    }
90
91    /// Returns the configured maximum response-body size, in bytes.
92    #[must_use]
93    pub const fn max_response_bytes(&self) -> usize {
94        self.max_response_bytes
95    }
96
97    /// Returns the configured user-agent header value.
98    #[must_use]
99    pub fn user_agent(&self) -> &str {
100        &self.user_agent
101    }
102}
103
104pub(super) fn validate_user_agent(user_agent: String) -> Result<String, ValidationError> {
105    if user_agent.trim().is_empty() {
106        return Err(ValidationError::EmptyField {
107            field: "user_agent",
108        });
109    }
110
111    validate_header_value(&user_agent, "user_agent")?;
112
113    Ok(user_agent)
114}
115
116pub(super) fn validate_header_value(
117    value: &str,
118    field: &'static str,
119) -> Result<(), ValidationError> {
120    HeaderValue::from_str(value).map_err(|_| ValidationError::InvalidHttpHeaderValue { field })?;
121    Ok(())
122}