sdl-parser 0.21.2

The SDL parser is a Rust tool designed for parsing Scenario Defined Language files.
Documentation
use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

use crate::{
    common::{HelperSource, Source},
    condition::Condition,
    helpers::Connection,
    inject::Inject,
    Formalize,
};

#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
pub struct Event {
    #[serde(default, alias = "Name", alias = "NAME")]
    pub name: Option<String>,
    #[serde(
        default,
        rename = "source",
        alias = "Source",
        alias = "SOURCE",
        skip_serializing
    )]
    _source_helper: Option<HelperSource>,
    #[serde(default, skip_deserializing)]
    pub source: Option<Source>,
    #[serde(alias = "Conditions", alias = "CONDITIONS")]
    pub conditions: Option<Vec<String>>,
    #[serde(alias = "Injects", alias = "INJECTS")]
    pub injects: Option<Vec<String>>,
    #[serde(alias = "Description", alias = "DESCRIPTION")]
    pub description: Option<String>,
}

pub type Events = HashMap<String, Event>;

impl Formalize for Event {
    fn formalize(&mut self) -> Result<()> {
        if self._source_helper.is_some() {
            if let Some(helper_source) = &self._source_helper {
                self.source = Some(helper_source.to_owned().into());
            } else {
                return Err(anyhow!("Event missing Source field"));
            }
        }

        Ok(())
    }
}
impl Connection<Condition> for (&String, &Event) {
    fn validate_connections(&self, potential_condition_names: &Option<Vec<String>>) -> Result<()> {
        if self.1.conditions.is_some() && potential_condition_names.is_none() {
            return Err(anyhow!(
                "Event \"{event_name}\" has Conditions but none found under Scenario",
                event_name = self.0
            ));
        }

        if let Some(required_conditions) = &self.1.conditions {
            if let Some(condition_names) = potential_condition_names {
                for event_condition_name in required_conditions {
                    if !condition_names.contains(event_condition_name) {
                        return Err(anyhow!(
                            "Condition \"{event_condition_name}\" not found under Scenario"
                        ));
                    }
                }
            }
        }

        Ok(())
    }
}

impl Connection<Inject> for (&String, &Event) {
    fn validate_connections(&self, potential_inject_names: &Option<Vec<String>>) -> Result<()> {
        if self.1.injects.is_some() && potential_inject_names.is_none() {
            return Err(anyhow!(
                "Event \"{event_name}\" has Injects but none found under Scenario",
                event_name = self.0
            ));
        }

        if let Some(required_injects) = &self.1.injects {
            if let Some(inject_names) = potential_inject_names {
                for event_inject_name in required_injects {
                    if !inject_names.contains(event_inject_name) {
                        return Err(anyhow!(
                            "Inject \"{event_inject_name}\" not found under Scenario",
                            event_inject_name = event_inject_name
                        ));
                    }
                }
            }
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::parse_sdl;

    #[test]
    fn parses_sdl_with_events() {
        let sdl = r#"
            name: test-scenario
            description: some description
            conditions:
                condition-1:
                    command: executable/path.sh
                    interval: 30
            injects:
                my-cool-inject:
                    source: inject-package
            events:
                my-cool-event:
                    source: event-package
                    conditions:
                        - condition-1
                    injects:
                        - my-cool-inject
        "#;
        let schema = parse_sdl(sdl).unwrap();

        insta::with_settings!({sort_maps => true}, {
                insta::assert_yaml_snapshot!(schema);
        });
    }

    #[test]
    fn parses_single_event() {
        let event = r#"
            source: 
                name: event-package
                version: 1.0.0
            conditions:
                - condition-1
            injects:
                - my-cool-inject
      "#;
        serde_yaml::from_str::<Event>(event).unwrap();
    }

    #[test]
    #[should_panic(expected = "Condition \"condition-1\" not found under Scenario")]
    fn fails_on_missing_scenario_condition() {
        let sdl = r#"
                name: test-scenario
                description: some description
                conditions:
                    condition-3000:
                        source: digital-library-package
                injects:
                    my-cool-inject:
                        source: inject-package
                events:
                    my-cool-event:
                        conditions:
                            - condition-1
                        injects:
                            - my-cool-inject
            "#;
        parse_sdl(sdl).unwrap();
    }

    #[test]
    #[should_panic(
        expected = "Event \"my-cool-event\" has Conditions but none found under Scenario"
    )]
    fn fails_on_missing_conditions() {
        let sdl = r#"
                name: test-scenario
                description: some description
                injects:
                    my-cool-inject:
                        source: inject-package
                events:
                    my-cool-event:
                        conditions:
                            - condition-1
                        injects:
                            - my-cool-inject
            "#;
        parse_sdl(sdl).unwrap();
    }

    #[test]
    #[should_panic(expected = "Event \"my-cool-event\" has Injects but none found under Scenario")]
    fn fails_no_injects_under_scenario() {
        let sdl = r#"
                name: test-scenario
                description: some description
                events:
                    my-cool-event:
                        injects:
                            - my-cool-inject
            "#;
        parse_sdl(sdl).unwrap();
    }

    #[test]
    #[should_panic(expected = "Inject \"my-cool-inject\" not found under Scenario")]
    fn fails_on_missing_inject() {
        let sdl = r#"
                name: test-scenario
                description: some description
                conditions:
                    condition-1:
                        command: executable/path.sh
                        interval: 30
                injects:
                    inject-1:
                        source: inject-package
                events:
                    my-cool-event:
                        injects:
                            - my-cool-inject
            "#;
        parse_sdl(sdl).unwrap();
    }
}