Skip to main content

mecha10_cli_core/services/
config.rs

1#![allow(dead_code)]
2
3//! Configuration service for managing project configuration
4//!
5//! Centralized interface for loading, validating, and working with mecha10.json files.
6
7use crate::paths;
8use crate::types::ProjectConfig;
9use anyhow::{Context, Result};
10use std::path::{Path, PathBuf};
11
12pub struct ConfigService;
13
14impl ConfigService {
15    /// Load project configuration from a specific mecha10.json path.
16    ///
17    /// Errors if the file is missing, unreadable, or fails to parse/validate.
18    pub async fn load_from(path: &Path) -> Result<ProjectConfig> {
19        if !path.exists() {
20            anyhow::bail!(
21                "Project configuration not found at {}. Run 'mecha10 init' first.",
22                path.display()
23            );
24        }
25
26        let content = tokio::fs::read_to_string(path)
27            .await
28            .with_context(|| format!("Failed to read configuration file: {}", path.display()))?;
29
30        let config: ProjectConfig = serde_json::from_str(&content)
31            .with_context(|| format!("Failed to parse configuration file: {}", path.display()))?;
32
33        Ok(config)
34    }
35
36    /// Find mecha10.json by searching upward from the current working directory.
37    pub fn find_config() -> Result<PathBuf> {
38        Self::find_config_from(&std::env::current_dir()?)
39    }
40
41    /// Find mecha10.json by searching upward from `start_dir`.
42    pub fn find_config_from(start_dir: &Path) -> Result<PathBuf> {
43        let mut current_dir = start_dir.to_path_buf();
44
45        loop {
46            let config_path = current_dir.join(paths::PROJECT_CONFIG);
47            if config_path.exists() {
48                return Ok(config_path);
49            }
50
51            match current_dir.parent() {
52                Some(parent) => current_dir = parent.to_path_buf(),
53                None => {
54                    anyhow::bail!(
55                        "No mecha10.json found in {} or any parent directory.\n\n\
56                         Run 'mecha10 init' to create a new project.",
57                        start_dir.display()
58                    )
59                }
60            }
61        }
62    }
63
64    /// Check if `dir` contains a mecha10.json
65    pub fn is_initialized(dir: &Path) -> bool {
66        dir.join(paths::PROJECT_CONFIG).exists()
67    }
68
69    /// Check if a project is initialized in the current directory
70    pub fn is_initialized_here() -> bool {
71        PathBuf::from(paths::PROJECT_CONFIG).exists()
72    }
73
74    /// Load just the robot ID from the mecha10.json at `path`.
75    pub async fn load_robot_id(path: &Path) -> Result<String> {
76        let config = Self::load_from(path).await?;
77        Ok(config.robot.id)
78    }
79
80    /// Validate the configuration file at `path` against the mecha10-core schema.
81    pub fn validate(path: &Path) -> Result<()> {
82        use mecha10_core::schema_validation::validate_project_config;
83
84        if !path.exists() {
85            anyhow::bail!(
86                "Configuration file not found: {}\n\nRun 'mecha10 init' to create a new project.",
87                path.display()
88            );
89        }
90
91        validate_project_config(path).context("Configuration validation failed")
92    }
93
94    /// Resolve the config file to validate and validate it -- shared by `mecha10 config validate`
95    /// and `mecha10 lint`. `None` validates the project's `mecha10.json` plus every node config
96    /// it references; `Some` validates exactly that one file.
97    pub fn resolve_and_validate(path: Option<&Path>) -> Result<PathBuf> {
98        use mecha10_core::schema_validation::{validate_config_file, validate_project_config};
99
100        let target = match path {
101            Some(p) => p.to_path_buf(),
102            None => Self::find_config()?,
103        };
104
105        if !target.exists() {
106            anyhow::bail!("Configuration file not found: {}", target.display());
107        }
108
109        let result = match path {
110            Some(_) => validate_config_file(&target),
111            None => validate_project_config(&target),
112        };
113
114        result.map_err(|e| anyhow::anyhow!("Configuration validation failed for {}: {}", target.display(), e))?;
115
116        Ok(target)
117    }
118}