shopify_approver_opencode/
config.rs1use crate::error::{OpenCodeError, Result};
4use serde::{Deserialize, Serialize};
5use std::time::Duration;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct OpenCodeConfig {
10 pub base_url: String,
12
13 pub api_key: Option<String>,
15
16 #[serde(with = "humantime_serde", default = "default_timeout")]
18 pub timeout: Duration,
19
20 #[serde(default = "default_max_file_size")]
22 pub max_file_size: usize,
23
24 #[serde(default = "default_max_upload_size")]
26 pub max_upload_size: usize,
27
28 #[serde(with = "humantime_serde", default = "default_indexing_timeout")]
30 pub indexing_timeout: Duration,
31
32 #[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 }
44
45fn default_max_upload_size() -> usize {
46 100 * 1024 * 1024 }
48
49fn default_indexing_timeout() -> Duration {
50 Duration::from_secs(300) }
52
53fn default_poll_interval() -> Duration {
54 Duration::from_secs(2)
55}
56
57impl OpenCodeConfig {
58 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 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 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 pub fn with_timeout(mut self, timeout: Duration) -> Self {
109 self.timeout = timeout;
110 self
111 }
112
113 pub fn with_indexing_timeout(mut self, timeout: Duration) -> Self {
115 self.indexing_timeout = timeout;
116 self
117 }
118
119 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 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
139mod 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}