Skip to main content

mabi_scenario/
parser.rs

1//! Scenario parser.
2
3use std::path::Path;
4
5use crate::schema::Scenario;
6use crate::{ScenarioError, ScenarioResult};
7
8/// Scenario parser.
9pub struct ScenarioParser;
10
11impl ScenarioParser {
12    /// Load scenario from YAML file.
13    pub async fn load_yaml(path: impl AsRef<Path>) -> ScenarioResult<Scenario> {
14        let content = tokio::fs::read_to_string(path).await?;
15        let scenario: Scenario = serde_yaml::from_str(&content)?;
16        Ok(scenario)
17    }
18
19    /// Load scenario from JSON file.
20    pub async fn load_json(path: impl AsRef<Path>) -> ScenarioResult<Scenario> {
21        let content = tokio::fs::read_to_string(path).await?;
22        let scenario: Scenario = serde_json::from_str(&content)?;
23        Ok(scenario)
24    }
25
26    /// Load scenario from file (auto-detect format).
27    pub async fn load(path: impl AsRef<Path>) -> ScenarioResult<Scenario> {
28        let path = path.as_ref();
29        let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
30
31        match ext {
32            "yaml" | "yml" => Self::load_yaml(path).await,
33            "json" => Self::load_json(path).await,
34            _ => Err(ScenarioError::Parse(format!(
35                "Unknown file extension: {}",
36                ext
37            ))),
38        }
39    }
40
41    /// Parse scenario from YAML string.
42    pub fn parse_yaml(content: &str) -> ScenarioResult<Scenario> {
43        let scenario: Scenario = serde_yaml::from_str(content)?;
44        Ok(scenario)
45    }
46
47    /// Parse scenario from JSON string.
48    pub fn parse_json(content: &str) -> ScenarioResult<Scenario> {
49        let scenario: Scenario = serde_json::from_str(content)?;
50        Ok(scenario)
51    }
52
53    /// Validate scenario.
54    pub fn validate(scenario: &Scenario) -> ScenarioResult<()> {
55        if scenario.name.is_empty() {
56            return Err(ScenarioError::Parse("Scenario name is required".into()));
57        }
58
59        if scenario.time_scale <= 0.0 {
60            return Err(ScenarioError::Parse("Time scale must be positive".into()));
61        }
62
63        for point in &scenario.points {
64            if point.device_id.is_empty() {
65                return Err(ScenarioError::Parse(format!(
66                    "Point {} has empty device_id",
67                    point.id
68                )));
69            }
70        }
71
72        Ok(())
73    }
74}