mavinspect 0.6.6

Library for parsing MAVLink XML definitions
Documentation
use mavinspect::parser::errors::XmlInspectionError;
use mavinspect::parser::Inspector;
use mavinspect::protocol::Microservices;

fn default_dialect_paths() -> Vec<&'static str> {
    vec![
        "./message_definitions/standard",
        "./message_definitions/extra",
    ]
}

fn extra_dialect_paths() -> Vec<&'static str> {
    vec!["./message_definitions/extra"]
}

#[test]
fn naming_collisions_are_avoided() {
    use mavinspect::errors::Error;

    let inspector = Inspector::builder()
        .set_sources(&["./message_definitions/colliding"])
        .build();

    assert!(
        inspector.is_err(),
        "XMLInspector should recognize naming collisions"
    );
    assert!(matches!(
        inspector,
        Err(Error::Inspection(
            XmlInspectionError::NamingCollision { .. }
        ))
    ))
}

#[test]
fn wrong_paths_do_not_cause_panic() {
    use mavinspect::errors::Error;

    let paths = vec![
        "./message_definitions/invalid", // Non-existing
        "./message_definitions/standard",
    ];
    let inspector = Inspector::builder().set_sources(&paths).build();
    assert!(
        inspector.is_err(),
        "XMLInspector should return error for non-existing paths"
    );
    assert!(matches!(inspector, Err(Error::Io(_))))
}

#[test]
fn empty_paths_do_not_cause_errors() {
    let inspector = Inspector::builder()
        .set_sources(&[
            "./tests", // Clearly don't have any definitions
            "./message_definitions/standard",
        ])
        .build();

    assert!(
        inspector.is_ok(),
        "XMLInspector should non return error for paths without definitions"
    );
}

#[test]
fn xml_definitions_are_loaded() {
    let inspector = Inspector::builder()
        .set_sources(&default_dialect_paths())
        .build();
    assert!(inspector.is_ok(), "failed to instantiate XMLInspector");

    let inspector = inspector.unwrap();
    assert!(!inspector.definitions().is_empty(), "no definitions loaded");
}

#[test]
fn default_message_definitions_are_parsed() {
    let inspector = Inspector::builder()
        .set_sources(&default_dialect_paths())
        .build()
        .unwrap();
    let protocol = inspector.parse();

    assert!(
        protocol.is_ok(),
        "failed to instantiate XMLInspector: {:?}",
        protocol
    );
    let protocol = protocol.unwrap();

    // Check that some dialects are parsed
    assert!(!protocol.dialects_are_empty(), "no dialects parsed");
    // Check that the `minimal` dialect is parsed
    assert!(
        protocol.get_dialect_by_canonical_name("minimal").is_some(),
        "`minimal` dialect should be parsed"
    );
    // Check that the `common` dialect is parsed
    assert!(
        protocol.get_dialect_by_canonical_name("common").is_some(),
        "`minimal` dialect should be parsed"
    );
}

#[test]
fn default_minimal_dialect_is_parsed_correctly() {
    let inspector = Inspector::builder()
        .set_sources(&default_dialect_paths())
        .build()
        .unwrap();
    let protocol = inspector.parse().unwrap();

    let minimal = protocol.get_dialect_by_canonical_name("minimal").unwrap();

    assert_eq!(minimal.name(), "minimal", "incorrect dialect name");

    assert!(
        minimal.contains_enum_with_name("MAV_AUTOPILOT"),
        "`minimal` dialect should contain `MAV_AUTOPILOT` enum"
    );

    assert!(
        minimal.contains_message_with_id(0u32),
        "`minimal` dialect should contain `HEARTBEAT` message"
    );
    assert_eq!(
        minimal.get_message_by_id(0u32).unwrap().name(),
        "HEARTBEAT",
        "`minimal` dialect should contain `HEARTBEAT` message"
    );

    assert!(
        minimal.get_message_by_id(300u32).is_some(),
        "`minimal` dialect should contain `PROTOCOL_VERSION` message"
    );
    assert_eq!(
        minimal.get_message_by_id(300u32).unwrap().name(),
        "PROTOCOL_VERSION",
        "`minimal` dialect should contain `PROTOCOL_VERSION` message"
    );
}

#[test]
fn enums_are_parsed_correctly() {
    let inspector = Inspector::builder()
        .set_sources(&default_dialect_paths())
        .build()
        .unwrap();
    let protocol = inspector.parse().unwrap();

    let minimal = protocol.get_dialect_by_canonical_name("minimal").unwrap();

    let mav_autopilot = minimal
        .get_enum_by_name("MAV_AUTOPILOT")
        .expect("`MAV_AUTOPILOT` enum not found in `minimal` dialect");
    assert!(
        mav_autopilot.entries().len() > 10,
        "incorrect number of entries in `MAV_AUTOPILOT` enum"
    );
    // First entry
    assert_eq!(
        mav_autopilot.entries().first().unwrap().name(),
        "MAV_AUTOPILOT_GENERIC"
    );
    assert_eq!(mav_autopilot.entries().first().unwrap().value(), 0);
    // 9th entry
    assert_eq!(
        mav_autopilot.entries().get(8).unwrap().name(),
        "MAV_AUTOPILOT_INVALID"
    );
    assert_eq!(mav_autopilot.entries().get(8).unwrap().value(), 8);

    let mav_inspect_test = protocol.get_dialect_by_canonical_name("mav_inspect_test").unwrap();

    let large_enum = mav_inspect_test
        .get_enum_by_name("LARGE_ENUM")
        .expect("`LARGE_ENUM` enum not found in `MAVInspect_test` dialect");

    assert_eq!(large_enum.entries().len(), 4, "incorrect number of entries in `LARGE_ENUM` enum");
    assert_eq!(
        large_enum.entries().first().unwrap().name(),
        "LARGE_ENUM_FIRST"
    );
    assert_eq!(large_enum.entries().first().unwrap().value(), 0);
    assert_eq!(
        large_enum.entries().get(1).unwrap().name(),
        "LARGE_ENUM_SECOND"
    );
    assert_eq!(large_enum.entries().get(1).unwrap().value(), 256);
}

#[test]
fn custom_dialect() {
    let inspector = Inspector::builder()
        .set_sources(&extra_dialect_paths())
        .set_include(&["crazy_flight"])
        .build()
        .unwrap();
    let protocol = inspector.parse().unwrap();
    let dialect = protocol
        .get_dialect_by_canonical_name("crazy_flight")
        .unwrap();

    assert!(dialect.version().is_some());
    assert!(matches!(dialect.version(), Some(3)));

    assert!(dialect.contains_enum_with_name("CRAZYFLIGHT_INSANITY_LEVEL"));
    assert!(dialect.contains_enum_with_name("CRAZYFLIGHT_DEED_FLAGS"));

    let mav_enum = dialect
        .get_enum_by_name("CRAZYFLIGHT_INSANITY_LEVEL")
        .unwrap();
    assert_eq!(mav_enum.entries().len(), 3);

    let mav_bitmask = dialect.get_enum_by_name("CRAZYFLIGHT_DEED_FLAGS").unwrap();
    assert_eq!(mav_bitmask.entries().len(), 3);
    assert!(mav_bitmask.bitmask());
}

#[test]
fn microservices_flags() {
    let inspector = Inspector::builder()
        .set_sources(&default_dialect_paths())
        .set_include(&["crazy_flight", "common"])
        .build()
        .unwrap();
    let protocol = inspector.parse().unwrap();

    let dialect = protocol
        .get_dialect_by_canonical_name("crazy_flight")
        .unwrap();
    assert!(dialect.microservices().contains(Microservices::HEARTBEAT));

    let dialect = protocol.get_dialect_by_canonical_name("common").unwrap();
    assert!(dialect.microservices().contains(Microservices::HEARTBEAT));
    assert!(dialect.microservices().contains(Microservices::COMMAND));
    assert!(dialect.microservices().contains(Microservices::MISSION));
    assert!(dialect.microservices().contains(Microservices::PARAMETER));
}

#[test]
fn test_default_dialects() {
    let inspector = Inspector::builder()
        .set_sources(&default_dialect_paths())
        .set_include(&["crazy_flight", "common"])
        .build()
        .unwrap();
    let protocol = inspector.parse().unwrap().with_default_dialect("common");

    let default_dialect = protocol.default_dialect().unwrap();
    assert_eq!(default_dialect.name(), "common");
}

#[test]
fn test_dialect_names_discovery() {
    let dialect_names = Inspector::discover_dialect_names(&default_dialect_paths()).unwrap();

    assert!(dialect_names.contains(&"common".to_string()));
    assert!(dialect_names.contains(&"ASLUAV".to_string()));
    assert!(dialect_names.contains(&"MAVInspect_test".to_string()));
}

#[test]
fn test_dialect_canonical_names_discovery() {
    let dialect_names =
        Inspector::discover_dialect_canonical_names(&default_dialect_paths()).unwrap();

    assert!(dialect_names.contains(&"common".to_string()));
    assert!(dialect_names.contains(&"asluav".to_string()));
    assert!(dialect_names.contains(&"mav_inspect_test".to_string()));
}