Skip to main content

netspeed_cli/config/
source.rs

1//! Raw CLI input types that bridge the CLI layer to the config layer.
2//!
3//! These structs carry unmerged, unvalidated values from [`crate::cli::Args`].
4//! The only place that touches `Args` directly is `ConfigSource::from_args`;
5//! everything downstream depends on these source types, not on the CLI crate.
6
7use super::Format;
8
9/// Raw CLI input values for output settings.
10///
11/// Groups output-related fields from [`crate::cli::Args`] into a cohesive unit
12/// matching the structure of `OutputConfig`.
13///
14/// # Example
15///
16/// ```
17/// use netspeed_cli::config::{Format, OutputSource};
18///
19/// let src = OutputSource {
20///     format: Some(Format::Json),
21///     quiet: Some(true),
22///     ..Default::default()
23/// };
24///
25/// assert_eq!(src.format, Some(Format::Json));
26/// assert_eq!(src.csv_delimiter, ','); // business-logic default preserved
27/// assert_eq!(src.theme, "dark");       // business-logic default preserved
28/// ```
29#[derive(Debug, Clone)]
30pub struct OutputSource {
31    /// Display values in bytes instead of bits
32    pub bytes: Option<bool>,
33    /// Suppress verbose output (deprecated, use format)
34    pub simple: Option<bool>,
35    /// Output in CSV format (deprecated, use format)
36    pub csv: Option<bool>,
37    /// CSV field delimiter
38    pub csv_delimiter: char,
39    /// Include CSV headers
40    pub csv_header: Option<bool>,
41    /// Output in JSON format (deprecated, use format)
42    pub json: Option<bool>,
43    /// Display server list and exit
44    pub list: bool,
45    /// Suppress all progress output
46    pub quiet: Option<bool>,
47    /// Minimal ASCII-only output
48    pub minimal: Option<bool>,
49    /// User profile for customized output
50    pub profile: Option<String>,
51    /// Color theme name
52    pub theme: String,
53    /// Output format (supersedes legacy flags)
54    pub format: Option<Format>,
55}
56
57impl Default for OutputSource {
58    fn default() -> Self {
59        Self {
60            bytes: None,
61            simple: None,
62            csv: None,
63            csv_delimiter: ',',
64            csv_header: None,
65            json: None,
66            list: false,
67            quiet: None,
68            minimal: None,
69            profile: None,
70            theme: "dark".to_string(),
71            format: None,
72        }
73    }
74}
75
76/// Raw CLI input values for test execution settings.
77///
78/// # Example
79///
80/// ```
81/// use netspeed_cli::config::TestSource;
82///
83/// let src = TestSource {
84///     no_download: Some(true),
85///     single: Some(true),
86///     ..Default::default()
87/// };
88///
89/// assert_eq!(src.no_download, Some(true));
90/// assert!(src.no_upload.is_none()); // unset fields default to None
91/// ```
92#[derive(Debug, Clone, Default)]
93pub struct TestSource {
94    /// Do not perform download test
95    pub no_download: Option<bool>,
96    /// Do not perform upload test
97    pub no_upload: Option<bool>,
98    /// Use single connection instead of multiple
99    pub single: Option<bool>,
100}
101
102/// Raw CLI input values for network/transport settings.
103///
104/// # Example
105///
106/// ```
107/// use netspeed_cli::config::NetworkSource;
108///
109/// let src = NetworkSource {
110///     timeout: 30,
111///     tls_version: Some("1.3".to_string()),
112///     ..Default::default()
113/// };
114///
115/// assert_eq!(src.timeout, 30);
116/// assert!(src.source.is_none()); // unset fields default to None
117/// ```
118#[derive(Debug, Clone)]
119pub struct NetworkSource {
120    /// Source IP address to bind to
121    pub source: Option<String>,
122    /// HTTP request timeout in seconds
123    pub timeout: u64,
124    /// Path to custom CA certificate for TLS
125    pub ca_cert: Option<String>,
126    /// Minimum TLS version (1.2 or 1.3)
127    pub tls_version: Option<String>,
128    /// Restrict TLS connections to speedtest.net and ookla.com domains.
129    pub pin_certs: Option<bool>,
130}
131
132impl Default for NetworkSource {
133    fn default() -> Self {
134        Self {
135            source: None,
136            timeout: 10,
137            ca_cert: None,
138            tls_version: None,
139            pin_certs: None,
140        }
141    }
142}
143
144/// Raw CLI input values for server selection settings.
145///
146/// # Example
147///
148/// ```
149/// use netspeed_cli::config::ServerSource;
150///
151/// let src = ServerSource {
152///     server_ids: vec!["1234".to_string()],
153///     ..Default::default()
154/// };
155///
156/// assert_eq!(src.server_ids, vec!["1234"]);
157/// assert!(src.exclude_ids.is_empty()); // unset fields default to empty
158/// ```
159#[derive(Debug, Clone, Default)]
160pub struct ServerSource {
161    /// Specific server IDs to use (empty = auto-select)
162    pub server_ids: Vec<String>,
163    /// Server IDs to exclude from selection
164    pub exclude_ids: Vec<String>,
165}
166
167/// Raw CLI input values extracted from parsed command-line arguments.
168///
169/// The sole bridge between [`crate::cli::Args`] and the config layer.
170/// All downstream config code depends on these source types, not on `Args`.
171///
172/// # Example
173///
174/// ```no_run
175/// use netspeed_cli::config::{
176///     Config, ConfigSource, Format, NetworkSource, OutputSource, TestSource,
177/// };
178///
179/// let source = ConfigSource {
180///     output: OutputSource {
181///         format: Some(Format::Dashboard),
182///         profile: Some("gamer".to_string()),
183///         ..Default::default()
184///     },
185///     test: TestSource {
186///         no_upload: Some(true),
187///         ..Default::default()
188///     },
189///     network: NetworkSource {
190///         timeout: 60,
191///         ..Default::default()
192///     },
193///     ..Default::default()
194/// };
195///
196/// let config = Config::from_source(&source);
197/// assert_eq!(config.timeout(), 60);
198/// assert!(config.no_upload());
199/// ```
200#[derive(Debug, Clone, Default)]
201pub struct ConfigSource {
202    /// Output and display settings
203    pub output: OutputSource,
204    /// Test execution controls
205    pub test: TestSource,
206    /// Network and transport settings
207    pub network: NetworkSource,
208    /// Server selection criteria
209    pub servers: ServerSource,
210    /// Enable strict config validation mode
211    pub strict_config: Option<bool>,
212}
213
214impl ConfigSource {
215    /// Extract config-relevant values from parsed CLI arguments.
216    ///
217    /// This is the **only** method in the config layer that touches
218    /// [`crate::cli::Args`]. All downstream code uses [`ConfigSource`].
219    #[must_use]
220    #[allow(deprecated)] // accesses deprecated --simple/--csv/--json fields for backward compat
221    pub(crate) fn from_args(args: &crate::cli::Args) -> Self {
222        Self {
223            output: OutputSource {
224                bytes: args.bytes,
225                simple: args.simple,
226                csv: args.csv,
227                csv_delimiter: args.csv_delimiter,
228                csv_header: args.csv_header,
229                json: args.json,
230                list: args.list,
231                quiet: args.quiet,
232                minimal: args.minimal,
233                profile: args.profile.clone(),
234                theme: args.theme.clone(),
235                format: args.format.map(Format::from_cli_type),
236            },
237            test: TestSource {
238                no_download: args.no_download,
239                no_upload: args.no_upload,
240                single: args.single,
241            },
242            network: NetworkSource {
243                source: args.source.clone(),
244                timeout: args.timeout,
245                ca_cert: args.ca_cert.clone(),
246                tls_version: args.tls_version.clone(),
247                pin_certs: args.pin_certs,
248            },
249            servers: ServerSource {
250                server_ids: args.server.clone(),
251                exclude_ids: args.exclude.clone(),
252            },
253            strict_config: args.strict_config,
254        }
255    }
256}