#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use zerodds_ast_layout::{Encoding, Endianness};
use zerodds_wire_probe::canonical::text;
use zerodds_wire_probe::decode::decode;
use zerodds_wire_probe::encode::{ProbeError, encode, model_from_idl};
use zerodds_wire_probe::value::ProbeRequest;
const MODES: [(Encoding, Endianness); 4] = [
(Encoding::PlainCdr2, Endianness::Little),
(Encoding::PlainCdr2, Endianness::Big),
(Encoding::PlainCdr1, Endianness::Little),
(Encoding::PlainCdr1, Endianness::Big),
];
fn narrowed(writer_idl: &str, reader_idl: &str, type_name: &str, values: &str) -> String {
let writer = model_from_idl(writer_idl, "writer.idl").expect("the writer's model");
let reader = model_from_idl(reader_idl, "reader.idl").expect("the reader's model");
let sample: serde_json::Value = serde_json::from_str(values).expect("the sample");
let mut answers: Vec<String> = Vec::new();
for (encoding, endianness) in MODES {
let request = ProbeRequest::new(type_name, encoding, endianness);
let bytes = encode(&writer, &request, &sample).expect("the writer's bytes");
answers.push(match decode(&reader, &request, &bytes) {
Ok(value) => text(&value),
Err(error @ ProbeError::Discarded { .. }) => format!("discarded: {error}"),
Err(other) => format!("refused: {other}"),
});
}
let first = answers[0].clone();
for (index, answer) in answers.iter().enumerate() {
assert_eq!(
*answer, first,
"mode {index} disagreed with the first: {answer}"
);
}
first
}
fn string_pair(annotation: &str) -> String {
narrowed(
"@mutable struct S { long a; string<32> m; long b; };",
&format!("@mutable struct S {{ long a; {annotation} string<5> m; long b; }};"),
"S",
r#"{"a": 7, "m": "Hello World!", "b": 9}"#,
)
}
#[test]
fn an_unannotated_member_discards_the_sample() {
let answer = string_pair("");
assert!(answer.starts_with("discarded:"), "{answer}");
assert!(answer.contains("at most 5 octets"), "{answer}");
}
#[test]
fn use_default_substitutes_table_nines_value_and_keeps_the_sample() {
assert_eq!(
string_pair("@try_construct(USE_DEFAULT)"),
r#"{"a":7,"b":9,"m":""}"#
);
}
#[test]
fn trim_cuts_the_string_to_the_bound_and_keeps_the_sample() {
assert_eq!(
string_pair("@try_construct(TRIM)"),
r#"{"a":7,"b":9,"m":"Hello"}"#
);
}
#[test]
fn a_bare_annotation_is_use_default_and_not_discard() {
assert_eq!(string_pair("@try_construct"), r#"{"a":7,"b":9,"m":""}"#);
assert!(string_pair("").starts_with("discarded:"));
}
fn sequence_pair(annotation: &str) -> String {
narrowed(
"@mutable struct S { long a; sequence<long, 32> m; long b; };",
&format!("@mutable struct S {{ long a; {annotation} sequence<long, 4> m; long b; }};"),
"S",
r#"{"a": 7, "m": [1, 2, 3, 4, 5, 6, 7, 8], "b": 9}"#,
)
}
#[test]
fn trim_cuts_a_sequence_to_its_bound_keeping_the_first_elements() {
assert_eq!(
sequence_pair("@try_construct(TRIM)"),
r#"{"a":7,"b":9,"m":[1,2,3,4]}"#
);
assert_eq!(
sequence_pair("@try_construct(USE_DEFAULT)"),
r#"{"a":7,"b":9,"m":[]}"#
);
assert!(sequence_pair("").starts_with("discarded:"));
}
#[test]
fn an_element_annotation_trims_the_element_and_not_the_collection() {
assert_eq!(
narrowed(
"typedef string<16> String16;
@mutable struct S { long a; sequence<String16, 10> m; long b; };",
"typedef string<5> String5;
@mutable struct S { long a; sequence<@try_construct(TRIM) String5, 10> m; long b; };",
"S",
r#"{"a": 7, "m": ["Hello World"], "b": 9}"#,
),
r#"{"a":7,"b":9,"m":["Hello"]}"#
);
}
#[test]
fn an_enumerator_the_reader_does_not_declare_is_a_construction_failure() {
let pair = |annotation: &str| {
narrowed(
"@appendable enum K { K1, @default_literal K2, K3 };
@mutable struct S { long a; K m; long b; };",
&format!(
"@appendable enum K {{ K1, @default_literal K2 }};
@mutable struct S {{ long a; {annotation} K m; long b; }};"
),
"S",
r#"{"a": 7, "m": "K3", "b": 9}"#,
)
};
assert!(pair("").starts_with("discarded:"));
assert_eq!(
pair("@try_construct(USE_DEFAULT)"),
r#"{"a":7,"b":9,"m":"K2"}"#
);
}
#[test]
fn a_defaulted_discriminator_defaults_the_member_it_now_selects() {
let pair = |annotation: &str| {
narrowed(
"@appendable enum K { K1, @default_literal K2, K3 };
@mutable union U switch (K) { case K1: long v1; case K2: long v2; case K3: long v3; };",
&format!(
"@appendable enum K {{ K1, @default_literal K2 }};
@mutable union U switch ({annotation} K) {{ case K1: long v1; case K2: long v2; }};"
),
"U",
r#"{"_d": "K3", "v3": 42}"#,
)
};
assert!(pair("").starts_with("discarded:"));
assert_eq!(pair("@try_construct"), r#"{"_d":"K2","v2":0}"#);
}
#[test]
fn a_map_value_trims_and_the_entry_survives() {
assert_eq!(
narrowed(
"@mutable struct S { long a; map<long, string<32>, 10> m; long b; };",
"@mutable struct S { long a; map<long, @try_construct(TRIM) string<5>, 10> m; \
long b; };",
"S",
r#"{"a": 7, "m": [[1, "Hello World!"]], "b": 9}"#,
),
r#"{"a":7,"b":9,"m":[[1,"Hello"]]}"#
);
}
#[test]
fn an_alias_borne_behaviour_reaches_a_member_that_wrote_none() {
assert_eq!(
narrowed(
"@mutable struct S { long a; string<32> m; long b; };",
"@try_construct(TRIM) typedef string<5> String5;
@mutable struct S { long a; String5 m; long b; };",
"S",
r#"{"a": 7, "m": "Hello World!", "b": 9}"#,
),
r#"{"a":7,"b":9,"m":"Hello"}"#
);
}
#[test]
fn a_trim_stops_on_a_character_boundary() {
let answer = narrowed(
"@mutable struct S { long a; string<32> m; long b; };",
"@mutable struct S { long a; @try_construct(TRIM) string<5> m; long b; };",
"S",
r#"{"a": 7, "m": "äöüäö", "b": 9}"#,
);
assert_eq!(answer, "{\"a\":7,\"b\":9,\"m\":\"\u{e4}\u{f6}\"}");
}
#[test]
fn a_discard_and_a_malformed_stream_are_not_the_same_error() {
let reader = model_from_idl(
"@mutable struct S { long a; string<5> m; long b; };",
"r.idl",
)
.unwrap();
let writer = model_from_idl(
"@mutable struct S { long a; string<32> m; long b; };",
"w.idl",
)
.unwrap();
let request = ProbeRequest::new("S", Encoding::PlainCdr2, Endianness::Little);
let sample: serde_json::Value =
serde_json::from_str(r#"{"a": 7, "m": "Hello World!", "b": 9}"#).unwrap();
let bytes = encode(&writer, &request, &sample).unwrap();
assert!(matches!(
decode(&reader, &request, &bytes),
Err(ProbeError::Discarded { .. })
));
let short = &bytes[..bytes.len() - 4];
assert!(!matches!(
decode(&reader, &request, short),
Err(ProbeError::Discarded { .. }) | Ok(_)
));
}