Skip to main content

qubit_http/options/
http_timeout_options.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026.
4 *    Haixing Hu, Qubit Co. Ltd.
5 *
6 *    All rights reserved.
7 *
8 ******************************************************************************/
9
10use std::time::Duration;
11
12use qubit_config::{ConfigReader, ConfigResult};
13
14use super::HttpConfigError;
15use crate::constants::{
16    DEFAULT_CONNECT_TIMEOUT_SECS, DEFAULT_READ_TIMEOUT_SECS, DEFAULT_WRITE_TIMEOUT_SECS,
17};
18
19/// Connect, read, write, and optional whole-request timeouts for HTTP I/O.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct HttpTimeoutOptions {
22    /// Connect timeout.
23    pub connect_timeout: Duration,
24    /// Read timeout.
25    pub read_timeout: Duration,
26    /// Write timeout.
27    pub write_timeout: Duration,
28    /// Optional global request timeout.
29    pub request_timeout: Option<Duration>,
30}
31
32impl Default for HttpTimeoutOptions {
33    /// Connect / read / write durations use
34    /// [`crate::constants::DEFAULT_CONNECT_TIMEOUT_SECS`],
35    /// [`crate::constants::DEFAULT_READ_TIMEOUT_SECS`], and
36    /// [`crate::constants::DEFAULT_WRITE_TIMEOUT_SECS`]; no global request timeout.
37    ///
38    /// # Returns
39    /// Default [`HttpTimeoutOptions`].
40    fn default() -> Self {
41        Self {
42            connect_timeout: Duration::from_secs(DEFAULT_CONNECT_TIMEOUT_SECS),
43            read_timeout: Duration::from_secs(DEFAULT_READ_TIMEOUT_SECS),
44            write_timeout: Duration::from_secs(DEFAULT_WRITE_TIMEOUT_SECS),
45            request_timeout: None,
46        }
47    }
48}
49
50struct TimeoutConfigInput {
51    connect_timeout: Option<Duration>,
52    read_timeout: Option<Duration>,
53    write_timeout: Option<Duration>,
54    request_timeout: Option<Duration>,
55}
56
57fn read_timeout_config<R>(config: &R) -> ConfigResult<TimeoutConfigInput>
58where
59    R: ConfigReader + ?Sized,
60{
61    Ok(TimeoutConfigInput {
62        connect_timeout: config.get_optional("connect_timeout")?,
63        read_timeout: config.get_optional("read_timeout")?,
64        write_timeout: config.get_optional("write_timeout")?,
65        request_timeout: config.get_optional("request_timeout")?,
66    })
67}
68
69impl HttpTimeoutOptions {
70    /// Validates timeout bounds.
71    ///
72    /// # Returns
73    /// `Ok(())` when all configured durations are strictly greater than zero.
74    pub fn validate(&self) -> Result<(), HttpConfigError> {
75        validate_positive_duration("connect_timeout", self.connect_timeout)?;
76        validate_positive_duration("read_timeout", self.read_timeout)?;
77        validate_positive_duration("write_timeout", self.write_timeout)?;
78        if let Some(request_timeout) = self.request_timeout {
79            validate_positive_duration("request_timeout", request_timeout)?;
80        }
81        Ok(())
82    }
83
84    /// Reads timeout settings from `config` using **relative** keys.
85    ///
86    /// # Parameters
87    /// - `config`: Any [`ConfigReader`] (e.g. root [`qubit_config::Config`] or
88    ///   `config.prefix_view("timeouts")`).
89    ///
90    /// Keys read (all optional; missing keys keep their defaults):
91    /// - `connect_timeout`
92    /// - `read_timeout`
93    /// - `write_timeout`
94    /// - `request_timeout`
95    ///
96    /// # Returns
97    /// Populated [`HttpTimeoutOptions`] or [`HttpConfigError`] on type conversion
98    /// failure.
99    pub fn from_config<R>(config: &R) -> Result<Self, HttpConfigError>
100    where
101        R: ConfigReader + ?Sized,
102    {
103        let raw = read_timeout_config(config).map_err(HttpConfigError::from)?;
104
105        let mut opts = HttpTimeoutOptions::default();
106        if let Some(d) = raw.connect_timeout {
107            opts.connect_timeout = d;
108        }
109        if let Some(d) = raw.read_timeout {
110            opts.read_timeout = d;
111        }
112        if let Some(d) = raw.write_timeout {
113            opts.write_timeout = d;
114        }
115        opts.request_timeout = raw.request_timeout;
116        opts.validate()?;
117        Ok(opts)
118    }
119}
120
121fn validate_positive_duration(path: &str, value: Duration) -> Result<(), HttpConfigError> {
122    if value.is_zero() {
123        return Err(HttpConfigError::invalid_value(
124            path,
125            "Timeout value must be greater than 0",
126        ));
127    }
128    Ok(())
129}