Skip to main content

aperture_cli/config/
models.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, Deserialize, Serialize)]
5pub struct GlobalConfig {
6    #[serde(default = "default_timeout_secs_value")]
7    pub default_timeout_secs: u64,
8    #[serde(default)]
9    pub agent_defaults: AgentDefaults,
10    /// Default retry configuration for transient failures
11    #[serde(default)]
12    pub retry_defaults: RetryDefaults,
13    /// Per-API configuration overrides
14    #[serde(default)]
15    pub api_configs: HashMap<String, ApiConfig>,
16}
17
18const fn default_timeout_secs_value() -> u64 {
19    30
20}
21
22#[derive(Debug, Clone, Deserialize, Serialize, Default)]
23pub struct AgentDefaults {
24    #[serde(default)]
25    pub json_errors: bool,
26}
27
28/// Default retry configuration for API requests.
29///
30/// These settings control automatic retry behavior for transient failures.
31/// Set `max_attempts` to 0 to disable retries (the default).
32///
33/// Retryable status codes are determined by the `is_retryable_status` function
34/// in the resilience module (408, 429, 500-504 excluding 501/505).
35#[derive(Debug, Clone, Deserialize, Serialize)]
36pub struct RetryDefaults {
37    /// Maximum number of retry attempts (0 = disabled, 1-10 recommended)
38    #[serde(default)]
39    pub max_attempts: u32,
40    /// Initial delay between retries in milliseconds (default: 500ms)
41    #[serde(default = "default_initial_delay_ms")]
42    pub initial_delay_ms: u64,
43    /// Maximum delay cap in milliseconds (default: 30000ms = 30s)
44    #[serde(default = "default_max_delay_ms")]
45    pub max_delay_ms: u64,
46}
47
48const fn default_initial_delay_ms() -> u64 {
49    500
50}
51
52const fn default_max_delay_ms() -> u64 {
53    30_000
54}
55
56impl Default for RetryDefaults {
57    fn default() -> Self {
58        Self {
59            max_attempts: 0, // Disabled by default
60            initial_delay_ms: default_initial_delay_ms(),
61            max_delay_ms: default_max_delay_ms(),
62        }
63    }
64}
65
66impl Default for GlobalConfig {
67    fn default() -> Self {
68        Self {
69            default_timeout_secs: 30,
70            agent_defaults: AgentDefaults::default(),
71            retry_defaults: RetryDefaults::default(),
72            api_configs: HashMap::new(),
73        }
74    }
75}
76
77/// Per-API configuration for base URLs and environment-specific settings
78#[derive(Debug, Deserialize, Serialize, Clone)]
79pub struct ApiConfig {
80    /// Override base URL for this API
81    pub base_url_override: Option<String>,
82    /// Environment-specific base URLs (e.g., "dev", "staging", "prod")
83    #[serde(default)]
84    pub environment_urls: HashMap<String, String>,
85    /// Whether this spec was added with --strict flag (preserved for reinit)
86    #[serde(default)]
87    pub strict_mode: bool,
88    /// Secret configurations for security schemes (overrides x-aperture-secret extensions)
89    #[serde(default)]
90    pub secrets: HashMap<String, ApertureSecret>,
91    /// Custom command tree mapping (rename groups, operations, add aliases, hide commands)
92    #[serde(default)]
93    pub command_mapping: Option<CommandMapping>,
94}
95
96impl ApiConfig {
97    /// Returns true if this config entry has no meaningful user-configured data
98    /// and can safely be removed from the global config.
99    ///
100    /// `strict_mode` is not considered because it is only meaningful when other
101    /// config (like the spec itself) exists.
102    #[must_use]
103    pub fn is_empty(&self) -> bool {
104        self.base_url_override.is_none()
105            && self.environment_urls.is_empty()
106            && self.secrets.is_empty()
107            && self.command_mapping.is_none()
108    }
109}
110
111/// Custom command tree mapping for an API specification.
112///
113/// Allows users to customize the CLI command tree structure generated from an
114/// `OpenAPI` spec without modifying the original specification. This is especially
115/// useful for third-party specs with verbose or awkward naming.
116///
117/// Config-based mappings take precedence over default tag/operationId naming.
118#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialEq, Eq)]
119pub struct CommandMapping {
120    /// Tag group renames: original tag name → custom display name.
121    /// The original tag (as it appears in the `OpenAPI` spec) is the key,
122    /// and the desired CLI group name is the value.
123    #[serde(default)]
124    pub groups: HashMap<String, String>,
125    /// Per-operation mappings keyed by the original operationId from the `OpenAPI` spec.
126    #[serde(default)]
127    pub operations: HashMap<String, OperationMapping>,
128}
129
130/// Mapping overrides for a single API operation.
131#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialEq, Eq)]
132pub struct OperationMapping {
133    /// Override the subcommand name (replaces the kebab-cased operationId)
134    #[serde(default)]
135    pub name: Option<String>,
136    /// Override the command group (replaces the tag-based group)
137    #[serde(default)]
138    pub group: Option<String>,
139    /// Additional subcommand aliases
140    #[serde(default)]
141    pub aliases: Vec<String>,
142    /// Whether this command is hidden from help output
143    #[serde(default)]
144    pub hidden: bool,
145}
146
147#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
148#[serde(rename_all = "camelCase")]
149pub struct ApertureSecret {
150    pub source: SecretSource,
151    pub name: String,
152}
153
154#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
155#[serde(rename_all = "camelCase")]
156pub enum SecretSource {
157    Env,
158    // Keychain, // Future option
159}