1use std::path::Path;
4
5use crate::schema::Scenario;
6use crate::{ScenarioError, ScenarioResult};
7
8pub struct ScenarioParser;
10
11impl ScenarioParser {
12 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 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 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 pub fn parse_yaml(content: &str) -> ScenarioResult<Scenario> {
43 let scenario: Scenario = serde_yaml::from_str(content)?;
44 Ok(scenario)
45 }
46
47 pub fn parse_json(content: &str) -> ScenarioResult<Scenario> {
49 let scenario: Scenario = serde_json::from_str(content)?;
50 Ok(scenario)
51 }
52
53 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}