sails-idl-parser-v2 1.0.0-beta.5

IDL v2 parser for the Sails framework
Documentation
use sails_idl_parser_v2::ast::TypeDef;
use sails_idl_parser_v2::parse_idl;

#[test]
fn validate_named_types_fails_on_unknown_type() {
    let src = include_str!("idls/post_process_unknown_type.idl");
    let err = parse_idl(src).expect_err("Should have failed due to unknown type");
    assert!(err.to_string().contains("Unknown type 'UnknownType'"));
}

#[test]
fn validate_named_types_fails_on_unknown_generic_param() {
    let src = include_str!("idls/post_process_unknown_generic.idl");
    let err = parse_idl(src).expect_err("Should have failed due to unknown generic parameter");
    assert!(err.to_string().contains("Unknown type 'UnknownGeneric'"));
}

#[test]
fn validate_named_types_fails_on_service_type_in_program() {
    let src = include_str!("idls/post_process_service_type_in_program.idl");
    let err =
        parse_idl(src).expect_err("Should have failed due to service type used in program scope");
    assert!(err.to_string().contains("Unknown type 'ServiceType'"));
}

#[test]

fn validate_scoping_fails_on_sibling_service_type_usage() {
    let src = include_str!("idls/post_process_advanced_scoping.idl");

    let result = parse_idl(src);
    println!("{result:?}");
    let err = result.expect_err("Should have failed due to sibling service type usage");
    assert!(err.to_string().contains("Unknown type 'TypeX'"));
}

#[test]
fn validate_named_types_fails_on_program_type_in_service() {
    let src = r#"
        program P {
            types {
                struct ProgramType { val: u32 }
            }
            services {
                S: S,
            }
        }

        service S {
            functions {
                UseProgramType(input: ProgramType);
            }
        }
    "#;

    let err =
        parse_idl(src).expect_err("Should have failed due to program type used in service scope");
    assert!(err.to_string().contains("Unknown type 'ProgramType'"));
}

#[test]
fn validate_mixed_fields_fails() {
    let src = include_str!("idls/post_process_mixed_fields.idl");
    let err = parse_idl(src).expect_err("Should have failed due to mixed fields");
    assert!(
        err.to_string().contains(
            "Mixing named and unnamed fields in a struct or enum variant is not allowed."
        )
    );
}

#[test]
fn validate_duplicate_interface_id_fails() {
    let src = r#"
        service S1 {
            functions {
                Do();
            }
        }
        service S2 {
            functions {
                Do();
            }
        }
        program P {
            services {
                S1: S1,
                S2: S2,
            }
        }
    "#;
    let err = parse_idl(src).expect_err("Should have failed due to duplicate interface_id");
    let err_str = err.to_string();
    assert!(err_str.contains("duplicate interface_id"));
    assert!(err_str.contains("S1"));
    assert!(err_str.contains("S2"));
}

#[test]
fn validate_invalid_entry_id_fails_for_regular_service() {
    let src = r#"
        service Canvas {
            functions {
                @entry_id: foo
                Draw();
            }
        }
    "#;
    let err = parse_idl(src).expect_err("Should have failed due to invalid @entry_id");
    assert!(
        err.to_string().contains(
            "service `Canvas`: function `Draw` has invalid `@entry_id` value `foo` (expected a u16)"
        ),
        "unexpected error: {err}"
    );
}

#[test]
fn validate_invalid_entry_id_fails_for_constructor() {
    let src = r#"
        program Demo {
            constructors {
                @entry_id: nope
                New();
            }
        }
    "#;
    let err = parse_idl(src).expect_err("Should have failed due to invalid constructor @entry_id");
    assert!(
        err.to_string().contains(
            "program `Demo`: constructor `New` has invalid `@entry_id` value `nope` (expected a u16)"
        ),
        "unexpected error: {err}"
    );
}

#[test]
fn parse_doc_annotation_works() {
    const SRC: &str = include_str!("../tests/idls/doc_annotation.idl");
    let doc = parse_idl(SRC).expect("parse idl");

    let program = doc.program.as_ref().unwrap();
    assert_eq!(
        program.docs,
        vec![
            "1. This is a program doc comment.",
            "2. This is also a program doc comment."
        ]
    );

    let ctor = &program.ctors[0];
    assert_eq!(
        ctor.docs,
        vec![
            "1. This is a ctor doc comment.",
            "2. This is also a ctor doc comment."
        ]
    );

    let ty = &program.types[0];
    assert_eq!(
        ty.docs,
        vec![
            "1. This is a struct doc comment.",
            "2. This is also a struct doc comment."
        ]
    );

    let field = match &ty.def {
        TypeDef::Struct(s) => &s.fields[0],
        _ => panic!("Expected struct"),
    };
    assert_eq!(
        field.docs,
        vec![
            "1. This is a field doc comment.",
            "2. This is also a field doc comment."
        ]
    );
}

#[test]
fn codec_annotation_is_preserved_in_service_func() {
    let src = r#"
        service CodecAnn {
            functions {
                @entry_id: 0
                @codec: scale,ethabi
                Foo() -> bool;
            }
        }
    "#;
    let doc = parse_idl(src).expect("parse idl");
    let func = &doc.services[0].funcs[0];
    assert!(
        func.annotations
            .iter()
            .any(|(k, v)| k == "codec" && v.as_deref() == Some("scale,ethabi")),
        "expected @codec annotation to be preserved, got: {:?}",
        func.annotations
    );
}