shopify_approver_opencode/
config.rs

1//! Configuration for OpenCode client
2
3use crate::error::{OpenCodeError, Result};
4use serde::{Deserialize, Serialize};
5use std::time::Duration;
6
7/// Configuration for OpenCode client
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct OpenCodeConfig {
10    /// Base URL of the OpenCode service
11    pub base_url: String,
12
13    /// API key for authentication (optional)
14    pub api_key: Option<String>,
15
16    /// Request timeout
17    #[serde(with = "humantime_serde", default = "default_timeout")]
18    pub timeout: Duration,
19
20    /// Maximum file size to include (in bytes)
21    #[serde(default = "default_max_file_size")]
22    pub max_file_size: usize,
23
24    /// Maximum total upload size (in bytes)
25    #[serde(default = "default_max_upload_size")]
26    pub max_upload_size: usize,
27
28    /// Timeout for indexing to complete
29    #[serde(with = "humantime_serde", default = "default_indexing_timeout")]
30    pub indexing_timeout: Duration,
31
32    /// Poll interval when waiting for indexing
33    #[serde(with = "humantime_serde", default = "default_poll_interval")]
34    pub poll_interval: Duration,
35}
36
37fn default_timeout() -> Duration {
38    Duration::from_secs(30)
39}
40
41fn default_max_file_size() -> usize {
42    1024 * 1024 // 1MB per file
43}
44
45fn default_max_upload_size() -> usize {
46    100 * 1024 * 1024 // 100MB total
47}
48
49fn default_indexing_timeout() -> Duration {
50    Duration::from_secs(300) // 5 minutes
51}
52
53fn default_poll_interval() -> Duration {
54    Duration::from_secs(2)
55}
56
57impl OpenCodeConfig {
58    /// Create a new configuration with the given base URL
59    pub fn new(base_url: impl Into<String>) -> Self {
60        Self {
61            base_url: base_url.into(),
62            api_key: None,
63            timeout: default_timeout(),
64            max_file_size: default_max_file_size(),
65            max_upload_size: default_max_upload_size(),
66            indexing_timeout: default_indexing_timeout(),
67            poll_interval: default_poll_interval(),
68        }
69    }
70
71    /// Create configuration from environment variables
72    pub fn from_env() -> Result<Self> {
73        let base_url = std::env::var("OPENCODE_URL")
74            .map_err(|_| OpenCodeError::InvalidConfig("OPENCODE_URL not set".to_string()))?;
75
76        let api_key = std::env::var("OPENCODE_API_KEY").ok();
77
78        let timeout = std::env::var("OPENCODE_TIMEOUT")
79            .ok()
80            .and_then(|s| s.parse().ok())
81            .map(Duration::from_secs)
82            .unwrap_or_else(default_timeout);
83
84        let indexing_timeout = std::env::var("OPENCODE_INDEXING_TIMEOUT")
85            .ok()
86            .and_then(|s| s.parse().ok())
87            .map(Duration::from_secs)
88            .unwrap_or_else(default_indexing_timeout);
89
90        Ok(Self {
91            base_url,
92            api_key,
93            timeout,
94            max_file_size: default_max_file_size(),
95            max_upload_size: default_max_upload_size(),
96            indexing_timeout,
97            poll_interval: default_poll_interval(),
98        })
99    }
100
101    /// Set API key
102    pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
103        self.api_key = Some(api_key.into());
104        self
105    }
106
107    /// Set request timeout
108    pub fn with_timeout(mut self, timeout: Duration) -> Self {
109        self.timeout = timeout;
110        self
111    }
112
113    /// Set indexing timeout
114    pub fn with_indexing_timeout(mut self, timeout: Duration) -> Self {
115        self.indexing_timeout = timeout;
116        self
117    }
118
119    /// Validate configuration
120    pub fn validate(&self) -> Result<()> {
121        if self.base_url.is_empty() {
122            return Err(OpenCodeError::InvalidConfig("base_url cannot be empty".to_string()));
123        }
124
125        // Validate URL format
126        url::Url::parse(&self.base_url)
127            .map_err(|e| OpenCodeError::InvalidConfig(format!("Invalid base_url: {}", e)))?;
128
129        Ok(())
130    }
131}
132
133impl Default for OpenCodeConfig {
134    fn default() -> Self {
135        Self::new("http://localhost:3000")
136    }
137}
138
139// Module for humantime serialization
140mod humantime_serde {
141    use serde::{Deserialize, Deserializer, Serializer};
142    use std::time::Duration;
143
144    pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
145    where
146        S: Serializer,
147    {
148        serializer.serialize_u64(duration.as_secs())
149    }
150
151    pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
152    where
153        D: Deserializer<'de>,
154    {
155        let secs = u64::deserialize(deserializer)?;
156        Ok(Duration::from_secs(secs))
157    }
158}