fob_cli/config/
validation.rs

1use crate::config::FobConfig;
2use crate::error::{ConfigError, Result};
3
4/// Validate global name follows JavaScript identifier rules.
5pub fn validate_global_name(name: &str) -> Result<()> {
6    if name.is_empty() {
7        return Err(ConfigError::InvalidValue {
8            field: "globalName".to_string(),
9            value: "".to_string(),
10            hint: "Global name cannot be empty".to_string(),
11        }
12        .into());
13    }
14
15    let first = name.chars().next().unwrap();
16    if !first.is_alphabetic() && first != '_' && first != '$' {
17        return Err(ConfigError::InvalidValue {
18            field: "globalName".to_string(),
19            value: name.to_string(),
20            hint: format!(
21                "Must start with letter, underscore, or dollar sign (got '{}')",
22                first
23            ),
24        }
25        .into());
26    }
27
28    for c in name.chars() {
29        if !c.is_alphanumeric() && c != '_' && c != '$' {
30            return Err(ConfigError::InvalidValue {
31                field: "globalName".to_string(),
32                value: name.to_string(),
33                hint: format!("Invalid character '{}' in identifier", c),
34            }
35            .into());
36        }
37    }
38
39    Ok(())
40}
41
42impl FobConfig {
43    /// Validate configuration for logical consistency.
44    pub fn validate(&self) -> Result<()> {
45        if self.entry.is_empty() {
46            return Err(ConfigError::MissingField {
47                field: "entry".to_string(),
48                hint: "Provide at least one entry point".to_string(),
49            }
50            .into());
51        }
52
53        if self.format == crate::config::types::Format::Iife && self.global_name.is_none() {
54            return Err(ConfigError::MissingField {
55                field: "globalName".to_string(),
56                hint: "IIFE format requires a global variable name".to_string(),
57            }
58            .into());
59        }
60
61        if self.dts_bundle == Some(true) && !self.dts {
62            return Err(ConfigError::InvalidValue {
63                field: "dtsBundle".to_string(),
64                value: "true".to_string(),
65                hint: "Bundling declarations requires dts: true".to_string(),
66            }
67            .into());
68        }
69
70        // Code splitting requires bundling
71        if self.splitting && !self.bundle {
72            return Err(ConfigError::InvalidValue {
73                field: "splitting".to_string(),
74                value: "true".to_string(),
75                hint: "Code splitting requires bundle: true".to_string(),
76            }
77            .into());
78        }
79
80        if let Some(ref name) = self.global_name {
81            validate_global_name(name)?;
82        }
83
84        Ok(())
85    }
86}