#![allow(clippy::expect_used)]
use polyplug_codegen::error::PolyplugcError;
use polyplugc::parser::{parse_api_str, parse_bundle_str};
fn api_err(toml: &str) -> PolyplugcError {
parse_api_str(toml).expect_err("expected parse error but got Ok")
}
fn bundle_err(toml: &str) -> PolyplugcError {
parse_bundle_str(toml).expect_err("expected parse error but got Ok")
}
#[test]
fn missing_closing_bracket_single_table() {
let err: PolyplugcError = bundle_err("[bundle\nname = \"x\"\nversion = \"1.0\"");
assert!(
matches!(err, PolyplugcError::TomlParseError { .. }),
"expected TomlParseError for missing `]` in table header, got {err:?}",
);
}
#[test]
fn missing_closing_bracket_array_of_tables() {
let err: PolyplugcError = api_err("[[contract\nname = \"x\"\nversion = \"1.0\"");
assert!(
matches!(err, PolyplugcError::TomlParseError { .. }),
"expected TomlParseError for missing `]]` in array-of-tables header, got {err:?}",
);
}
#[test]
fn missing_closing_bracket_nested_subtable() {
let err: PolyplugcError = api_err(concat!(
"[[contract]]\nname = \"x\"\nversion = \"1.0\"\n\n",
"[[contract.functions\nname = \"f\"\n",
));
assert!(
matches!(err, PolyplugcError::TomlParseError { .. }),
"expected TomlParseError for missing `]]` in sub-array header, got {err:?}",
);
}
#[test]
fn unclosed_inline_array_value() {
let err: PolyplugcError = bundle_err(concat!(
"[bundle]\nname = \"b\"\nversion = \"1.0\"\n\n",
"[[plugin]]\nname = \"p\"\nimplements = [\"x\"\n",
));
assert!(
matches!(err, PolyplugcError::TomlParseError { .. }),
"expected TomlParseError for unclosed inline array, got {err:?}",
);
}
#[test]
fn unclosed_inline_table_value() {
let err: PolyplugcError = api_err("[[contract]]\nname = {value = \"x\"\nversion = \"1.0\"");
assert!(
matches!(err, PolyplugcError::TomlParseError { .. }),
"expected TomlParseError for unclosed inline table, got {err:?}",
);
}
#[test]
fn invalid_escape_sequence_backslash_q() {
let err: PolyplugcError = api_err("[[contract]]\nname = \"bad\\qescape\"\nversion = \"1.0\"");
assert!(
matches!(err, PolyplugcError::TomlParseError { .. }),
"expected TomlParseError for `\\q` escape, got {err:?}",
);
}
#[test]
fn invalid_escape_sequence_backslash_a() {
let err: PolyplugcError = bundle_err("[bundle]\nname = \"bad\\aescape\"\nversion = \"1.0\"");
assert!(
matches!(err, PolyplugcError::TomlParseError { .. }),
"expected TomlParseError for `\\a` escape, got {err:?}",
);
}
#[test]
fn invalid_escape_sequence_lone_backslash() {
let err: PolyplugcError = api_err("[[contract]]\nname = \"trailing\\\"\nversion = \"1.0\"");
assert!(
matches!(err, PolyplugcError::TomlParseError { .. }),
"expected TomlParseError for lone trailing backslash, got {err:?}",
);
}
#[test]
fn valid_escape_sequences_accepted() {
let result: Result<polyplugc::ir::ValidatedIr, PolyplugcError> = parse_api_str(
"[[contract]]\nname = \"esc\\\\slash\\\"quote\\nnewline\\ttab\"\nversion = \"1.0\"",
);
assert!(
matches!(result, Err(PolyplugcError::InvalidIdentifier { .. })),
"expected TOML escapes to lex (then fail identifier validation), got {result:?}"
);
}
#[test]
fn mixed_table_and_array_of_tables_same_key() {
let err: PolyplugcError = api_err(concat!(
"[contract]\nname = \"single\"\nversion = \"1.0\"\n\n",
"[[contract]]\nname = \"array\"\nversion = \"1.0\"\n",
));
assert!(
matches!(err, PolyplugcError::TomlParseError { .. }),
"expected TomlParseError for mixed `[contract]` + `[[contract]]`, got {err:?}",
);
}
#[test]
fn redefining_existing_table_header() {
let err: PolyplugcError = bundle_err(concat!(
"[bundle]\nname = \"first\"\nversion = \"1.0\"\n\n",
"[bundle]\nname = \"second\"\nversion = \"2.0\"\n",
));
assert!(
matches!(err, PolyplugcError::TomlParseError { .. }),
"expected TomlParseError for duplicate `[bundle]` header, got {err:?}",
);
}
#[test]
fn array_element_defined_before_array_header() {
let err: PolyplugcError = api_err(concat!(
"contract.name = \"dotted\"\n\n",
"[[contract]]\nname = \"array\"\nversion = \"1.0\"\n",
));
assert!(
matches!(err, PolyplugcError::TomlParseError { .. }),
"expected TomlParseError for dotted-key vs array-of-tables conflict, got {err:?}",
);
}
#[test]
fn empty_string_is_valid_api_toml() {
let result: Result<polyplugc::ir::ValidatedIr, PolyplugcError> = parse_api_str("");
assert!(
result.is_ok(),
"expected empty string to parse as empty API, got {result:?}"
);
let ir: polyplugc::ir::ValidatedIr = result.expect("parse");
assert_eq!(ir.contracts.len(), 0, "expected zero contracts");
assert_eq!(ir.types.len(), 0, "expected zero types");
assert_eq!(ir.enums.len(), 0, "expected zero enums");
}
#[test]
fn empty_string_is_invalid_bundle_toml() {
let err: PolyplugcError = bundle_err("");
assert!(
matches!(err, PolyplugcError::TomlParseError { .. }),
"expected TomlParseError for empty bundle TOML, got {err:?}",
);
}
#[test]
fn newlines_only_is_valid_api_toml() {
let result: Result<polyplugc::ir::ValidatedIr, PolyplugcError> = parse_api_str("\n\n\n");
assert!(
result.is_ok(),
"expected newlines-only to parse as empty API, got {result:?}"
);
}
#[test]
fn comments_only_is_valid_api_toml() {
let toml: &str = "# polyplug api schema\n# no contracts defined yet\n";
let result: Result<polyplugc::ir::ValidatedIr, PolyplugcError> = parse_api_str(toml);
assert!(
result.is_ok(),
"expected comments-only API TOML to succeed, got {result:?}"
);
}
#[test]
fn comments_only_is_invalid_bundle_toml() {
let err: PolyplugcError = bundle_err("# just a comment\n# another comment\n");
assert!(
matches!(err, PolyplugcError::TomlParseError { .. }),
"expected TomlParseError for comments-only bundle TOML, got {err:?}",
);
}
#[test]
fn comment_after_value_is_valid() {
let toml: &str = concat!(
"[[contract]] # define a contract\n",
"name = \"svc.foo\" # the name\n",
"version = \"1.0\" # the version\n",
);
let result: Result<polyplugc::ir::ValidatedIr, PolyplugcError> = parse_api_str(toml);
assert!(
result.is_ok(),
"expected inline comments after values to be accepted, got {result:?}"
);
}
#[test]
fn comment_mid_key_value_is_invalid() {
let err: PolyplugcError = api_err("[[contract]]\nname # comment = \"x\"\nversion = \"1.0\"");
assert!(
matches!(err, PolyplugcError::TomlParseError { .. }),
"expected TomlParseError for `#` between key and `=`, got {err:?}",
);
}
#[test]
fn unicode_escape_with_too_few_hex_digits() {
let err: PolyplugcError = api_err("[[contract]]\nname = \"bad\\u004\"\nversion = \"1.0\"");
assert!(
matches!(err, PolyplugcError::TomlParseError { .. }),
"expected TomlParseError for `\\u` with 3 hex digits, got {err:?}",
);
}
#[test]
fn unicode_escape_with_non_hex_digit() {
let err: PolyplugcError = api_err("[[contract]]\nname = \"bad\\u000G\"\nversion = \"1.0\"");
assert!(
matches!(err, PolyplugcError::TomlParseError { .. }),
"expected TomlParseError for `\\u000G` non-hex digit, got {err:?}",
);
}
#[test]
fn unicode_escape_surrogate_pair_rejected() {
let err: PolyplugcError = api_err("[[contract]]\nname = \"\\uD800\"\nversion = \"1.0\"");
assert!(
matches!(err, PolyplugcError::TomlParseError { .. }),
"expected TomlParseError for lone surrogate `\\uD800`, got {err:?}",
);
}
#[test]
fn unicode_escape_valid_codepoint_accepted() {
let result: Result<polyplugc::ir::ValidatedIr, PolyplugcError> =
parse_api_str("[[contract]]\nname = \"\\u0041BC\"\nversion = \"1.0\"");
assert!(
result.is_ok(),
"expected valid \\u0041 Unicode escape to be accepted, got {result:?}"
);
}
#[test]
fn long_unicode_escape_u_uppercase_valid() {
let result: Result<polyplugc::ir::ValidatedIr, PolyplugcError> =
parse_api_str("[[contract]]\nname = \"emoji\\U0001F600end\"\nversion = \"1.0\"");
assert!(
matches!(result, Err(PolyplugcError::InvalidIdentifier { .. })),
"expected \\U0001F600 long Unicode escape to lex (then fail identifier validation), got {result:?}"
);
}
#[test]
fn very_long_name_value_accepted() {
let long_name: String = "a".repeat(10_000);
let toml: String = format!("[[contract]]\nname = \"{long_name}\"\nversion = \"1.0\"\n");
let result: Result<polyplugc::ir::ValidatedIr, PolyplugcError> = parse_api_str(&toml);
assert!(
result.is_ok(),
"expected very long name string to be accepted, got {result:?}"
);
let ir: polyplugc::ir::ValidatedIr = result.expect("parse");
assert_eq!(ir.contracts[0].name.len(), 10_000);
}
#[test]
fn very_long_comment_line_accepted() {
let long_comment: String = format!("# {}", "x".repeat(50_000));
let toml: String = format!("{long_comment}\n[[contract]]\nname = \"svc\"\nversion = \"1.0\"\n");
let result: Result<polyplugc::ir::ValidatedIr, PolyplugcError> = parse_api_str(&toml);
assert!(
result.is_ok(),
"expected very long comment line to be accepted, got {result:?}"
);
}
#[test]
fn very_long_key_name_invalid() {
let long_key: String = format!("{} extra", "a".repeat(1_000));
let toml: String = format!("[[contract]]\n{long_key} = \"v\"\nversion = \"1.0\"\n");
let err: PolyplugcError = api_err(&toml);
assert!(
matches!(err, PolyplugcError::TomlParseError { .. }),
"expected TomlParseError for key with embedded space, got {err:?}",
);
}
#[test]
fn deeply_nested_dotted_keys_accepted() {
let toml: &str = concat!(
"[bundle]\n",
"name = \"nested-bundle\"\n",
"version = \"1.0\"\n",
"extra.deep.key = \"ignored\"\n",
);
let _result: Result<polyplugc::ir::ValidatedIr, PolyplugcError> = parse_bundle_str(toml);
}
#[test]
fn nested_inline_table_in_array_is_malformed() {
let err: PolyplugcError = bundle_err(concat!(
"[bundle]\nname = \"b\"\nversion = \"1.0\"\n\n",
"[[plugin]]\nname = \"p\"\n",
"implements = [{contract = \"svc\"\n",
));
assert!(
matches!(err, PolyplugcError::TomlParseError { .. }),
"expected TomlParseError for unclosed inline table in array, got {err:?}",
);
}
#[test]
fn super_table_key_conflict_after_dotted_key() {
let err: PolyplugcError = bundle_err(concat!(
"bundle.name = \"first\"\n\n",
"[bundle]\nname = \"second\"\nversion = \"1.0\"\n",
));
assert!(
matches!(err, PolyplugcError::TomlParseError { .. }),
"expected TomlParseError for dotted-key then [bundle] name conflict, got {err:?}",
);
}
#[test]
fn contract_functions_params_nested_array_round_trip() {
let toml: &str = concat!(
"[[contract]]\n",
"name = \"math.ops\"\n",
"version = \"1.0\"\n\n",
"[[contract.functions]]\n",
"name = \"add\"\n\n",
"[[contract.functions.params]]\n",
"name = \"a\"\n",
"type = \"u32\"\n\n",
"[[contract.functions.params]]\n",
"name = \"b\"\n",
"type = \"u32\"\n",
);
let result: Result<polyplugc::ir::ValidatedIr, PolyplugcError> = parse_api_str(toml);
assert!(
result.is_ok(),
"expected deeply-nested array-of-tables round-trip to succeed, got {result:?}"
);
let ir: polyplugc::ir::ValidatedIr = result.expect("parse");
assert_eq!(ir.contracts[0].functions[0].params.len(), 2);
}