Skip to main content

opencode_sdk_rs/
config.rs

1use std::{collections::HashMap, time::Duration};
2
3use http::HeaderMap;
4
5/// Default base URL matching the JS SDK.
6pub const DEFAULT_BASE_URL: &str = "http://localhost:54321";
7
8/// Default request timeout (60 seconds), matching the JS SDK.
9pub const DEFAULT_TIMEOUT: Duration = Duration::from_mins(1);
10
11/// Default maximum number of retries, matching the JS SDK.
12pub const DEFAULT_MAX_RETRIES: u32 = 2;
13
14/// Environment variable name for the base URL override.
15pub const ENV_BASE_URL: &str = "OPENCODE_BASE_URL";
16
17/// Configuration options for building an [`crate::client::Opencode`] client.
18///
19/// All fields are optional; unset fields fall back to defaults that match the
20/// JS SDK behaviour.  The `Default` implementation reads `OPENCODE_BASE_URL`
21/// from the environment when available.
22#[derive(Debug, Clone)]
23pub struct ClientOptions {
24    /// Base URL of the `OpenCode` server.
25    pub base_url: Option<String>,
26
27    /// Per-request timeout.
28    pub timeout: Option<Duration>,
29
30    /// Maximum number of automatic retries for retryable errors.
31    pub max_retries: Option<u32>,
32
33    /// Headers sent with every request.
34    pub default_headers: Option<HeaderMap>,
35
36    /// Query parameters appended to every request URL.
37    pub default_query: Option<HashMap<String, String>>,
38}
39
40impl Default for ClientOptions {
41    fn default() -> Self {
42        let base_url = std::env::var(ENV_BASE_URL).ok();
43
44        Self {
45            base_url,
46            timeout: None,
47            max_retries: None,
48            default_headers: None,
49            default_query: None,
50        }
51    }
52}
53
54impl ClientOptions {
55    /// Create a new `ClientOptions` with all fields set to `None` (no env
56    /// lookup).  Useful when you want to set every field explicitly.
57    #[must_use]
58    pub const fn empty() -> Self {
59        Self {
60            base_url: None,
61            timeout: None,
62            max_retries: None,
63            default_headers: None,
64            default_query: None,
65        }
66    }
67
68    /// Resolve each option to its concrete value, falling back to the
69    /// compile-time defaults.
70    #[must_use]
71    pub(crate) fn resolve_base_url(&self) -> &str {
72        self.base_url.as_deref().unwrap_or(DEFAULT_BASE_URL)
73    }
74
75    #[must_use]
76    pub(crate) fn resolve_timeout(&self) -> Duration {
77        self.timeout.unwrap_or(DEFAULT_TIMEOUT)
78    }
79
80    #[must_use]
81    pub(crate) fn resolve_max_retries(&self) -> u32 {
82        self.max_retries.unwrap_or(DEFAULT_MAX_RETRIES)
83    }
84
85    #[must_use]
86    pub(crate) fn resolve_default_headers(&self) -> HeaderMap {
87        self.default_headers.clone().unwrap_or_default()
88    }
89
90    #[must_use]
91    pub(crate) fn resolve_default_query(&self) -> HashMap<String, String> {
92        self.default_query.clone().unwrap_or_default()
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn empty_has_all_none() {
102        let opts = ClientOptions::empty();
103        assert!(opts.base_url.is_none());
104        assert!(opts.timeout.is_none());
105        assert!(opts.max_retries.is_none());
106        assert!(opts.default_headers.is_none());
107        assert!(opts.default_query.is_none());
108    }
109
110    #[test]
111    fn resolve_falls_back_to_defaults() {
112        let opts = ClientOptions::empty();
113        assert_eq!(opts.resolve_base_url(), DEFAULT_BASE_URL);
114        assert_eq!(opts.resolve_timeout(), DEFAULT_TIMEOUT);
115        assert_eq!(opts.resolve_max_retries(), DEFAULT_MAX_RETRIES);
116        assert!(opts.resolve_default_headers().is_empty());
117        assert!(opts.resolve_default_query().is_empty());
118    }
119
120    #[test]
121    fn resolve_uses_provided_values() {
122        let opts = ClientOptions {
123            base_url: Some("http://example.com".to_owned()),
124            timeout: Some(Duration::from_secs(30)),
125            max_retries: Some(5),
126            default_headers: None,
127            default_query: None,
128        };
129        assert_eq!(opts.resolve_base_url(), "http://example.com");
130        assert_eq!(opts.resolve_timeout(), Duration::from_secs(30));
131        assert_eq!(opts.resolve_max_retries(), 5);
132    }
133}