Skip to main content

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 std::{collections::HashMap, path::Path};
41
42use crate::Run::{
43	Constant::ProfileDefault,
44	Definition::{Profile, RunProfileConfig},
45	Error::{Error, Result},
46};
47
48/// Loads profiles from a configuration file.
49///
50/// # Arguments
51///
52/// * `config_path` - Path to the configuration file
53///
54/// # Returns
55///
56/// A HashMap of profile names to Profile instances
57pub fn load_profiles(config_path:&Path) -> Result<HashMap<String, Profile>> {
58	let content =
59		std::fs::read_to_string(config_path).map_err(|_e| Error::ConfigNotFound(config_path.to_path_buf()))?;
60
61	let config:serde_json::Value = serde_json::from_str(&content).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 { ProfileDefault.to_string() }
96
97/// Validates a profile configuration.
98///
99/// # Arguments
100///
101/// * `profile` - The profile to validate
102///
103/// # Returns
104///
105/// A list of validation warnings and issues
106pub fn validate(profile:&Profile) -> (Vec<String>, Vec<String>) {
107	let mut warnings = Vec::new();
108
109	let mut issues = Vec::new();
110
111	// Check description
112	if profile.description.is_none() {
113		warnings.push("Profile has no description".to_string());
114	}
115
116	// Check workbench
117	if profile.workbench.is_none() {
118		issues.push("Profile has no workbench type specified".to_string());
119	}
120
121	// Check environment variables
122	if profile.env.is_none() || profile.env.as_ref().unwrap().is_empty() {
123		warnings.push("Profile has no environment variables defined".to_string());
124	}
125
126	// Check run_config if present
127	if let Some(run_config) = &profile.run_config {
128		if run_config.live_reload_port == 0 {
129			issues.push("Live-reload port cannot be 0".to_string());
130		}
131	}
132
133	(warnings, issues)
134}
135
136/// Merges two profiles, with the second overriding the first.
137///
138/// # Arguments
139///
140/// * `base` - The base profile
141/// * `override_profile` - The overriding profile
142///
143/// # Returns
144///
145/// A merged profile
146pub fn merge(base:&Profile, override_profile:&Profile) -> Profile {
147	Profile {
148		name:base.name.clone(),
149
150		description:override_profile.description.clone().or_else(|| base.description.clone()),
151
152		workbench:override_profile.workbench.clone().or_else(|| base.workbench.clone()),
153
154		env:{
155			let mut env = base.env.clone().unwrap_or_default();
156
157			if let Some(override_env) = &override_profile.env {
158				for (key, value) in override_env {
159					env.insert(key.clone(), value.clone());
160				}
161			}
162
163			if env.is_empty() { None } else { Some(env) }
164		},
165
166		run_config:override_profile.run_config.clone().or_else(|| base.run_config.clone()),
167	}
168}
169
170/// Creates a profile from environment variables.
171///
172/// # Arguments
173///
174/// * `name` - Profile name
175/// * `env_vars` - Environment variables to use
176///
177/// # Returns
178///
179/// A new Profile instance
180pub fn from_env(name:&str, env_vars:HashMap<String, String>) -> Profile {
181	Profile {
182		name:name.to_string(),
183
184		description:Some("Environment-based profile".to_string()),
185
186		workbench:env_vars.get("Workbench").cloned(),
187
188		env:Some(env_vars),
189
190		run_config:None,
191	}
192}
193
194/// Gets the run configuration for a profile.
195///
196/// # Arguments
197///
198/// * `profile` - The profile to get config from
199///
200/// # Returns
201///
202/// The run configuration, or default if not specified
203pub fn get_run_config(profile:&Profile) -> RunProfileConfig {
204	profile
205		.run_config
206		.clone()
207		.unwrap_or_else(|| RunProfileConfig { hot_reload:true, watch:true, live_reload_port:3001, features:None })
208}