Maintain/Run/
Profile.rs

1//=============================================================================//
2// File Path: Element/Maintain/Source/Run/Profile.rs
3//=============================================================================//
4// Module: Profile
5//
6// Brief Description: Profile resolution and management for run operations.
7//
8// RESPONSIBILITIES:
9// ================
10//
11// Primary:
12// - Load and parse run profiles from configuration
13// - Resolve profile names and aliases
14// - Validate profile configurations
15//
16// Secondary:
17// - Provide profile metadata
18// - Support profile inheritance
19//
20// ARCHITECTURAL ROLE:
21// ===================
22//
23// Position:
24// - Infrastructure/Profile management layer
25// - Profile resolution
26//
27// Dependencies (What this module requires):
28// - External crates: serde, std
29// - Internal modules: Constant, Definition, Error
30// - Traits implemented: None
31//
32// Dependents (What depends on this module):
33// - Run CLI module
34// - Run Process module
35//
36//=============================================================================//
37// IMPLEMENTATION
38//=============================================================================//
39
40use crate::Run::Constant::ProfileDefault;
41use crate::Run::Definition::{Profile, RunProfileConfig};
42use crate::Run::Error::{Error, Result};
43
44use std::collections::HashMap;
45use std::path::Path;
46
47/// Loads profiles from a configuration file.
48///
49/// # Arguments
50///
51/// * `config_path` - Path to the configuration file
52///
53/// # Returns
54///
55/// A HashMap of profile names to Profile instances
56pub fn load_profiles(config_path: &Path) -> Result<HashMap<String, Profile>> {
57    let content = std::fs::read_to_string(config_path)
58        .map_err(|e| Error::ConfigNotFound(config_path.to_path_buf()))?;
59
60    let config: serde_json::Value = serde_json::from_str(&content)
61        .map_err(|e| Error::ConfigParse(e.to_string()))?;
62
63    let mut profiles = HashMap::new();
64
65    if let Some(profiles_obj) = config.get("profiles").and_then(|v| v.as_object()) {
66        for (name, value) in profiles_obj {
67            if let Ok(profile) = serde_json::from_value::<Profile>(value.clone()) {
68                profiles.insert(name.clone(), profile);
69            }
70        }
71    }
72
73    Ok(profiles)
74}
75
76/// Resolves a profile name, handling aliases.
77///
78/// # Arguments
79///
80/// * `name` - The profile name or alias to resolve
81/// * `aliases` - Map of alias -> target profile
82///
83/// # Returns
84///
85/// The resolved profile name
86pub fn resolve_name(name: &str, aliases: &HashMap<String, String>) -> String {
87    aliases.get(name).cloned().unwrap_or_else(|| name.to_string())
88}
89
90/// Gets the default profile name.
91///
92/// # Returns
93///
94/// The default profile name
95pub fn default_name() -> String {
96    ProfileDefault.to_string()
97}
98
99/// Validates a profile configuration.
100///
101/// # Arguments
102///
103/// * `profile` - The profile to validate
104///
105/// # Returns
106///
107/// A list of validation warnings and issues
108pub fn validate(profile: &Profile) -> (Vec<String>, Vec<String>) {
109    let mut warnings = Vec::new();
110    let mut issues = Vec::new();
111
112    // Check description
113    if profile.description.is_none() {
114        warnings.push("Profile has no description".to_string());
115    }
116
117    // Check workbench
118    if profile.workbench.is_none() {
119        issues.push("Profile has no workbench type specified".to_string());
120    }
121
122    // Check environment variables
123    if profile.env.is_none() || profile.env.as_ref().unwrap().is_empty() {
124        warnings.push("Profile has no environment variables defined".to_string());
125    }
126
127    // Check run_config if present
128    if let Some(run_config) = &profile.run_config {
129        if run_config.live_reload_port == 0 {
130            issues.push("Live-reload port cannot be 0".to_string());
131        }
132    }
133
134    (warnings, issues)
135}
136
137/// Merges two profiles, with the second overriding the first.
138///
139/// # Arguments
140///
141/// * `base` - The base profile
142/// * `override_profile` - The overriding profile
143///
144/// # Returns
145///
146/// A merged profile
147pub fn merge(base: &Profile, override_profile: &Profile) -> Profile {
148    Profile {
149        name: base.name.clone(),
150        description: override_profile.description.clone().or_else(|| base.description.clone()),
151        workbench: override_profile.workbench.clone().or_else(|| base.workbench.clone()),
152        env: {
153            let mut env = base.env.clone().unwrap_or_default();
154            if let Some(override_env) = &override_profile.env {
155                for (key, value) in override_env {
156                    env.insert(key.clone(), value.clone());
157                }
158            }
159            if env.is_empty() {
160                None
161            } else {
162                Some(env)
163            }
164        },
165        run_config: override_profile.run_config.clone().or_else(|| base.run_config.clone()),
166    }
167}
168
169/// Creates a profile from environment variables.
170///
171/// # Arguments
172///
173/// * `name` - Profile name
174/// * `env_vars` - Environment variables to use
175///
176/// # Returns
177///
178/// A new Profile instance
179pub fn from_env(name: &str, env_vars: HashMap<String, String>) -> Profile {
180    Profile {
181        name: name.to_string(),
182        description: Some("Environment-based profile".to_string()),
183        workbench: env_vars.get("Workbench").cloned(),
184        env: Some(env_vars),
185        run_config: None,
186    }
187}
188
189/// Gets the run configuration for a profile.
190///
191/// # Arguments
192///
193/// * `profile` - The profile to get config from
194///
195/// # Returns
196///
197/// The run configuration, or default if not specified
198pub fn get_run_config(profile: &Profile) -> RunProfileConfig {
199    profile.run_config.clone().unwrap_or_else(|| RunProfileConfig {
200        hot_reload: true,
201        watch: true,
202        live_reload_port: 3001,
203        features: None,
204    })
205}