use alloc::vec::Vec;
use super::*;
use crate::error::{ParseError, ParseErrorKind, PayloadKind};
use crate::limits::DEFAULT_MAX_PAYLOAD_LEN;
use crate::source::{SourceLineNumber, SourcePosition};
use crate::test_support::{
TestFailure, TestResult, ensure_eq, ensure_matches, expect_error_position, source_column,
source_line_number,
};
fn validated_runtime_byte(byte: u8) -> Result<RuntimeByte, TestFailure> {
RuntimeInputByte::validate(byte, 0)
.map(RuntimeInputByte::into_runtime_byte)
.map_err(TestFailure::from)
}
fn compact_byte(byte: u8, line: usize, column: usize) -> Result<CompactByte, TestFailure> {
let position = SourcePosition::new(source_line_number(line)?, source_column(column)?);
let executable = ExecutableCodeByte::validate(byte, position)?;
Ok(CompactByte::from_executable(executable))
}
fn parse_payload_error(
input: &[CompactByte],
line_number: SourceLineNumber,
payload_kind: PayloadKind,
) -> Result<ParseError, TestFailure> {
match PayloadSyntax::check(input, line_number, payload_kind, DEFAULT_MAX_PAYLOAD_LEN)?
.validate()
{
Ok(_) => Err(TestFailure::message("invalid payload bytes were accepted")),
Err(error) => Ok(error),
}
}
fn parse_payload(
input: &[CompactByte],
line_number: SourceLineNumber,
payload_kind: PayloadKind,
) -> Result<Payload, TestFailure> {
Ok(
PayloadSyntax::check(input, line_number, payload_kind, DEFAULT_MAX_PAYLOAD_LEN)?
.validate()?,
)
}
#[test]
fn payload_rejects_every_reserved_syntax_byte_even_if_payload_parser_is_called_directly()
-> TestResult {
for reserved in [b'=', b'#', b'(', b')'] {
let compact = [compact_byte(reserved, 1, 1)?];
let error =
parse_payload_error(&compact, source_line_number(1)?, PayloadKind::RightSideData)?;
expect_error_position(&error, 1, 1)?;
ensure_matches(
matches!(
error.kind(),
ParseErrorKind::ReservedSyntaxInPayload { byte, .. }
if byte.get() == reserved
),
"expected concrete reserved syntax byte",
)?;
ensure_matches(
matches!(
error.kind(),
ParseErrorKind::ReservedSyntaxInPayload {
payload_kind: PayloadKind::RightSideData,
..
}
),
"expected reserved syntax payload error",
)?;
}
Ok(())
}
#[test]
fn executable_code_byte_validation_precedes_payload_parsing() -> TestResult {
let position = SourcePosition::new(source_line_number(1)?, source_column(1)?);
let Err(error) = ExecutableCodeByte::validate(0xff, position) else {
return Err(TestFailure::message(
"non-ASCII executable byte should be rejected",
));
};
ensure_matches(
matches!(error.kind(), ParseErrorKind::NonAsciiInCode { .. }),
"expected non-ASCII parse error",
)?;
let position = SourcePosition::new(source_line_number(1)?, source_column(2)?);
let Err(error) = ExecutableCodeByte::validate(b' ', position) else {
return Err(TestFailure::message(
"non-printable executable byte should be rejected",
));
};
expect_error_position(&error, 1, 2)?;
ensure_matches(
matches!(error.kind(), ParseErrorKind::NonPrintableAsciiInCode { .. }),
"expected non-printable parse error",
)?;
Ok(())
}
#[test]
fn payload_exposes_validated_bytes_without_leaking_the_internal_domain_type() -> TestResult {
let compact = [compact_byte(b'a', 1, 1)?, compact_byte(b'b', 1, 2)?];
let payload = parse_payload(&compact, source_line_number(1)?, PayloadKind::LeftSideData)?;
ensure_eq!(payload.bytes().collect::<Vec<_>>(), b"ab".to_vec())?;
let PayloadNeedle::NonEmpty(needle) = payload.needle() else {
return Err(TestFailure::message("expected non-empty payload needle"));
};
ensure_eq!(needle.first_byte().get(), b'a')?;
Ok(())
}
#[test]
fn runtime_input_classifies_program_constructible_and_opaque_ascii_separately() -> TestResult {
let parsed = validated_runtime_byte(b'a')?;
ensure_matches(
matches!(parsed, RuntimeByte::ProgramConstructible(byte) if byte.get() == b'a'),
"expected program-constructible input byte",
)?;
ensure_eq!(parsed.materialize(), b'a')?;
for byte in [0x00, b' ', b'=', b'#', b'(', b')'] {
let parsed = validated_runtime_byte(byte)?;
ensure_eq!(parsed.materialize(), byte)?;
ensure_matches(
matches!(parsed, RuntimeByte::Opaque(_)),
"expected opaque input byte",
)?;
}
Ok(())
}
#[test]
fn runtime_input_validation_has_no_reconstruction_invariant() -> TestResult {
let parsed = validated_runtime_byte(b'a')?;
ensure_matches(
matches!(parsed, RuntimeByte::ProgramConstructible(byte) if byte.get() == b'a'),
"expected ASCII runtime input to validate normally",
)
}