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	let mut issues = Vec::new();
109
110	// Check description
111	if profile.description.is_none() {
112		warnings.push("Profile has no description".to_string());
113	}
114
115	// Check workbench
116	if profile.workbench.is_none() {
117		issues.push("Profile has no workbench type specified".to_string());
118	}
119
120	// Check environment variables
121	if profile.env.is_none() || profile.env.as_ref().unwrap().is_empty() {
122		warnings.push("Profile has no environment variables defined".to_string());
123	}
124
125	// Check run_config if present
126	if let Some(run_config) = &profile.run_config {
127		if run_config.live_reload_port == 0 {
128			issues.push("Live-reload port cannot be 0".to_string());
129		}
130	}
131
132	(warnings, issues)
133}
134
135/// Merges two profiles, with the second overriding the first.
136///
137/// # Arguments
138///
139/// * `base` - The base profile
140/// * `override_profile` - The overriding profile
141///
142/// # Returns
143///
144/// A merged profile
145pub fn merge(base:&Profile, override_profile:&Profile) -> Profile {
146	Profile {
147		name:base.name.clone(),
148		description:override_profile.description.clone().or_else(|| base.description.clone()),
149		workbench:override_profile.workbench.clone().or_else(|| base.workbench.clone()),
150		env:{
151			let mut env = base.env.clone().unwrap_or_default();
152			if let Some(override_env) = &override_profile.env {
153				for (key, value) in override_env {
154					env.insert(key.clone(), value.clone());
155				}
156			}
157			if env.is_empty() { None } else { Some(env) }
158		},
159		run_config:override_profile.run_config.clone().or_else(|| base.run_config.clone()),
160	}
161}
162
163/// Creates a profile from environment variables.
164///
165/// # Arguments
166///
167/// * `name` - Profile name
168/// * `env_vars` - Environment variables to use
169///
170/// # Returns
171///
172/// A new Profile instance
173pub fn from_env(name:&str, env_vars:HashMap<String, String>) -> Profile {
174	Profile {
175		name:name.to_string(),
176		description:Some("Environment-based profile".to_string()),
177		workbench:env_vars.get("Workbench").cloned(),
178		env:Some(env_vars),
179		run_config:None,
180	}
181}
182
183/// Gets the run configuration for a profile.
184///
185/// # Arguments
186///
187/// * `profile` - The profile to get config from
188///
189/// # Returns
190///
191/// The run configuration, or default if not specified
192pub fn get_run_config(profile:&Profile) -> RunProfileConfig {
193	profile
194		.run_config
195		.clone()
196		.unwrap_or_else(|| RunProfileConfig { hot_reload:true, watch:true, live_reload_port:3001, features:None })
197}