Skip to main content

gcop_rs/config/structs/
app.rs

1//! Top-level application configuration and remaining command structures.
2
3use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7use crate::error::{GcopError, Result};
8
9use super::commit::CommitConfig;
10use super::llm::LLMConfig;
11use super::network::NetworkConfig;
12
13/// Application configuration.
14///
15/// Top-level runtime configuration for `gcop-rs`.
16///
17/// Effective configuration is merged from multiple sources (low to high):
18/// 1. Rust defaults (`Default` + `serde(default)`)
19/// 2. User-level config file (platform-specific config directory)
20/// 3. Project-level config (`.gcop/config.toml`, discovered from repository root)
21/// 4. `GCOP__*` environment variables
22/// 5. CI mode overrides (`CI=1` + `GCOP_CI_*`)
23///
24/// # Configuration File Locations
25/// - Linux: `~/.config/gcop/config.toml`
26/// - macOS: `~/Library/Application Support/gcop/config.toml`
27/// - Windows: `%APPDATA%\gcop\config\config.toml`
28/// - Project level (optional): `<repo>/.gcop/config.toml`
29///
30/// # Example
31/// ```toml
32/// [llm]
33/// default_provider = "claude"
34/// fallback_providers = ["openai"]
35///
36/// [llm.providers.claude]
37/// api_key = "sk-ant-..."
38/// model = "claude-sonnet-4-5-20250929"
39///
40/// [commit]
41/// max_retries = 10
42/// show_diff_preview = true
43///
44/// [ui]
45/// colored = true
46/// ```
47#[derive(Debug, Clone, Deserialize, Serialize, Default)]
48pub struct AppConfig {
49    /// LLM provider and prompt settings.
50    #[serde(default)]
51    pub llm: LLMConfig,
52
53    /// Commit command behavior.
54    #[serde(default)]
55    pub commit: CommitConfig,
56
57    /// Review command behavior.
58    #[serde(default)]
59    pub review: ReviewConfig,
60
61    /// Terminal UI behavior.
62    #[serde(default)]
63    pub ui: UIConfig,
64
65    /// HTTP timeout and retry settings.
66    #[serde(default)]
67    pub network: NetworkConfig,
68
69    /// File I/O limits.
70    #[serde(default)]
71    pub file: FileConfig,
72
73    /// Workspace detection and scope inference (monorepo support).
74    #[serde(default)]
75    pub workspace: WorkspaceConfig,
76}
77
78impl AppConfig {
79    /// Validates configuration consistency.
80    pub fn validate(&self) -> Result<()> {
81        // Ensure the configured default provider exists.
82        if !self.llm.providers.is_empty()
83            && !self.llm.providers.contains_key(&self.llm.default_provider)
84        {
85            return Err(GcopError::Config(format!(
86                "default_provider '{}' not found in [llm.providers]",
87                self.llm.default_provider
88            )));
89        }
90
91        // Ensure all configured fallback providers exist.
92        for name in &self.llm.fallback_providers {
93            if !self.llm.providers.contains_key(name) {
94                return Err(GcopError::Config(format!(
95                    "fallback_providers: '{}' not found in [llm.providers]",
96                    name
97                )));
98            }
99        }
100
101        for (name, provider) in &self.llm.providers {
102            provider.validate(name)?;
103        }
104        self.network.validate()?;
105        Ok(())
106    }
107}
108
109/// Review command configuration.
110///
111/// Controls code-review behavior.
112///
113/// # Fields
114/// - `min_severity`: minimum issue severity shown in text output (`"info"`, `"warning"`, `"critical"`)
115/// - `custom_prompt`: review system prompt override (optional; JSON constraints are always appended)
116///
117/// # Example
118/// ```toml
119/// [review]
120/// min_severity = "warning"
121/// custom_prompt = "Focus on security issues"
122/// ```
123#[derive(Debug, Clone, Deserialize, Serialize)]
124pub struct ReviewConfig {
125    /// Minimum issue severity displayed in text output.
126    ///
127    /// Note: this filter currently applies only to `review --format text`.
128    /// `json` and `markdown` output keep the full issue list.
129    #[serde(default = "default_severity")]
130    pub min_severity: String,
131
132    /// Review system prompt override.
133    ///
134    /// The provided text replaces the default review system prompt.
135    /// JSON output constraints are always appended automatically.
136    ///
137    /// No placeholder substitution is performed (`{diff}` is passed literally).
138    #[serde(default)]
139    pub custom_prompt: Option<String>,
140}
141
142impl Default for ReviewConfig {
143    fn default() -> Self {
144        Self {
145            min_severity: "info".to_string(),
146            custom_prompt: None,
147        }
148    }
149}
150
151/// UI configuration.
152///
153/// Controls terminal display behavior.
154///
155/// # Fields
156/// - `colored`: enable colored output (default: `true`)
157/// - `streaming`: enable streaming output (typewriter effect, default: `true`)
158/// - `language`: UI language in BCP 47 format (for example `"en"`, `"zh-CN"`), auto-detected by default
159///
160/// # Example
161/// ```toml
162/// [ui]
163/// colored = true
164/// streaming = true
165/// language = "zh-CN"
166/// ```
167#[derive(Debug, Clone, Deserialize, Serialize)]
168pub struct UIConfig {
169    /// Whether to enable color output.
170    #[serde(default = "default_true")]
171    pub colored: bool,
172
173    /// Whether to enable streaming output (real-time typing effect).
174    #[serde(default = "default_true")]
175    pub streaming: bool,
176
177    /// UI language in BCP 47 format (for example `"en"`, `"zh-CN"`).
178    /// `None` means auto-detect from system locale.
179    #[serde(default)]
180    pub language: Option<String>,
181}
182
183impl Default for UIConfig {
184    fn default() -> Self {
185        Self {
186            colored: true,
187            streaming: true,
188            language: None,
189        }
190    }
191}
192
193/// File configuration.
194///
195/// Controls local file-read limits.
196///
197/// # Fields
198/// - `max_size`: max file size in bytes (default: 10 MiB)
199///   Used by `review file <PATH>` when reading workspace files.
200///
201/// # Example
202/// ```toml
203/// [file]
204/// max_size = 10485760  # 10MB
205/// ```
206#[derive(Debug, Clone, Deserialize, Serialize)]
207pub struct FileConfig {
208    /// Maximum file size in bytes.
209    ///
210    /// Current read limit for `review file <PATH>`.
211    #[serde(default = "default_max_file_size")]
212    pub max_size: u64,
213}
214
215impl Default for FileConfig {
216    fn default() -> Self {
217        Self {
218            max_size: default_max_file_size(),
219        }
220    }
221}
222
223/// Workspace configuration (monorepo support).
224///
225/// Controls workspace detection and scope inference.
226/// Auto-detection is enabled by default; this section is for manual overrides.
227///
228/// # Example
229/// ```toml
230/// [workspace]
231/// enabled = true
232/// members = ["packages/*", "apps/*"]
233/// scope_mappings = { "packages/core" = "core", "packages/ui" = "ui" }
234/// ```
235#[derive(Debug, Clone, Deserialize, Serialize)]
236pub struct WorkspaceConfig {
237    /// Whether workspace detection is enabled (default: `true`).
238    #[serde(default = "default_true")]
239    pub enabled: bool,
240
241    /// Manual scope mapping: package path -> scope name.
242    ///
243    /// Overrides automatically inferred package short names.
244    #[serde(default)]
245    pub scope_mappings: HashMap<String, String>,
246
247    /// Explicit workspace member globs.
248    ///
249    /// When set, auto-detection is skipped and this list is used directly.
250    #[serde(default)]
251    pub members: Option<Vec<String>>,
252}
253
254impl Default for WorkspaceConfig {
255    fn default() -> Self {
256        Self {
257            enabled: true,
258            scope_mappings: HashMap::new(),
259            members: None,
260        }
261    }
262}
263
264fn default_true() -> bool {
265    true
266}
267
268fn default_severity() -> String {
269    "info".to_string()
270}
271
272fn default_max_file_size() -> u64 {
273    10 * 1024 * 1024 // 10MB
274}