distri_types/
client_config.rs1use std::path::PathBuf;
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6pub(crate) const DEFAULT_BASE_URL: &str = "https://api.distri.dev/v1";
8
9pub(crate) const ENV_BASE_URL: &str = "DISTRI_BASE_URL";
11
12pub(crate) const ENV_API_KEY: &str = "DISTRI_API_KEY";
14
15pub(crate) const ENV_WORKSPACE_ID: &str = "DISTRI_WORKSPACE_ID";
17
18const CONFIG_DIR_NAME: &str = ".distri";
19const CONFIG_FILE_NAME: &str = "config";
20
21#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
23pub struct DistriConfig {
24 pub base_url: String,
26
27 #[serde(default, skip_serializing_if = "Option::is_none")]
29 pub api_key: Option<String>,
30
31 #[serde(default, skip_serializing_if = "Option::is_none")]
33 pub workspace_id: Option<String>,
34
35 #[serde(default = "default_timeout")]
37 pub timeout_secs: u64,
38
39 #[serde(default = "default_retries")]
41 pub retry_attempts: u32,
42}
43
44fn default_timeout() -> u64 {
45 30
46}
47
48fn default_retries() -> u32 {
49 3
50}
51
52#[derive(Debug, Deserialize, Default)]
53struct FileConfig {
54 base_url: Option<String>,
55 api_key: Option<String>,
56 workspace_id: Option<String>,
57}
58
59fn normalize_optional(value: String) -> Option<String> {
60 let trimmed = value.trim();
61 if trimmed.is_empty() {
62 None
63 } else {
64 Some(trimmed.to_string())
65 }
66}
67
68fn normalize_base_url(value: String) -> Option<String> {
69 normalize_optional(value).map(|s| s.trim_end_matches('/').to_string())
70}
71
72impl FileConfig {
73 fn normalized(self) -> Self {
74 Self {
75 base_url: self.base_url.and_then(normalize_base_url),
76 api_key: self.api_key.and_then(normalize_optional),
77 workspace_id: self.workspace_id.and_then(normalize_optional),
78 }
79 }
80}
81
82impl Default for DistriConfig {
83 fn default() -> Self {
84 Self {
85 base_url: DEFAULT_BASE_URL.to_string(),
86 api_key: None,
87 workspace_id: None,
88 timeout_secs: default_timeout(),
89 retry_attempts: default_retries(),
90 }
91 }
92}
93
94impl DistriConfig {
95 pub fn config_path() -> Option<PathBuf> {
97 let home = std::env::var_os("HOME").or_else(|| std::env::var_os("USERPROFILE"))?;
98 let mut path = PathBuf::from(home);
99 path.push(CONFIG_DIR_NAME);
100 path.push(CONFIG_FILE_NAME);
101 Some(path)
102 }
103
104 pub fn new(base_url: impl Into<String>) -> Self {
106 Self {
107 base_url: base_url.into().trim_end_matches('/').to_string(),
108 ..Default::default()
109 }
110 }
111
112 pub fn from_env() -> Self {
121 let file_config = Self::config_path()
122 .and_then(|path| std::fs::read_to_string(path).ok())
123 .and_then(|contents| toml::from_str::<FileConfig>(&contents).ok())
124 .map(|cfg| cfg.normalized())
125 .unwrap_or_default();
126
127 let env_base_url = std::env::var(ENV_BASE_URL)
128 .ok()
129 .and_then(normalize_base_url);
130 let env_api_key = std::env::var(ENV_API_KEY).ok().and_then(normalize_optional);
131 let env_workspace_id = std::env::var(ENV_WORKSPACE_ID)
132 .ok()
133 .and_then(normalize_optional);
134
135 let base_url = env_base_url
136 .or(file_config.base_url)
137 .unwrap_or_else(|| DEFAULT_BASE_URL.to_string());
138 let api_key = env_api_key.or(file_config.api_key);
139 let workspace_id = env_workspace_id.or(file_config.workspace_id);
140
141 Self {
142 base_url,
143 api_key,
144 workspace_id,
145 ..Default::default()
146 }
147 }
148
149 pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
151 self.api_key = Some(api_key.into());
152 self
153 }
154
155 pub fn with_workspace_id(mut self, workspace_id: impl Into<String>) -> Self {
157 self.workspace_id = Some(workspace_id.into());
158 self
159 }
160
161 pub fn with_timeout(mut self, timeout_secs: u64) -> Self {
163 self.timeout_secs = timeout_secs;
164 self
165 }
166
167 pub fn with_retries(mut self, retry_attempts: u32) -> Self {
169 self.retry_attempts = retry_attempts;
170 self
171 }
172
173 pub fn is_local(&self) -> bool {
175 self.base_url.contains("localhost") || self.base_url.contains("127.0.0.1")
176 }
177
178 pub fn has_auth(&self) -> bool {
180 self.api_key.is_some()
181 }
182}