#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use zerodds_ast::{Ast, Extensibility, MemberSpec, PrimitiveKind, TypeKind, TypeNode};
use zerodds_ast_core::{Id, Ref};
use zerodds_ast_layout::{Encoding, Endianness};
use zerodds_float128::Float128;
use zerodds_wire_codec::encode::ProbeError;
use zerodds_wire_codec::source::Source;
use zerodds_wire_codec::value::ProbeRequest;
use zerodds_wire_codec::{MAX_VALUE_DEPTH, decode, encode};
fn node_model() -> (Ast, Id<TypeNode>) {
let mut ast = Ast::new();
let root = ast.root_module();
let long = ast
.add_type(root, "L", TypeKind::Primitive(PrimitiveKind::Int32))
.expect("long");
let node = ast
.add_type(
root,
"Node",
TypeKind::Structure {
base: None,
members: Vec::new(),
},
)
.expect("Node");
let any = ast.any_of_type(node).expect("any");
ast.facets_mut(any)
.expect("facets")
.xtypes_mut()
.extensibility = Some(Extensibility::Mutable);
ast.add_member(node, MemberSpec::new("value", Ref::Resolved(long)))
.expect("value");
let next = ast
.add_member(node, MemberSpec::new("next", Ref::Resolved(node)))
.expect("next");
let any = ast.any_of_member(next).expect("any");
let facets = ast.facets_mut(any).expect("facets");
facets.xtypes_mut().is_optional = true;
facets.xtypes_mut().is_external = true;
(ast, node)
}
fn request() -> ProbeRequest {
ProbeRequest::new("Node", Encoding::PlCdr1, Endianness::Big)
}
enum Sample {
Number,
Node(Option<Box<Self>>),
Cyclic,
}
static LEAF: Sample = Sample::Number;
fn chain(levels: usize) -> Sample {
let mut node = Sample::Node(None);
for _ in 1..levels {
node = Sample::Node(Some(Box::new(node)));
}
node
}
impl Source for Sample {
fn kind(&self) -> &'static str {
match self {
Self::Number => "a number",
Self::Node(_) | Self::Cyclic => "an object",
}
}
fn is_null(&self) -> bool {
false
}
fn is_object(&self) -> bool {
!matches!(self, Self::Number)
}
fn as_bool(&self) -> Option<bool> {
None
}
fn as_text(&self) -> Option<&str> {
None
}
fn as_float(&self) -> Option<f64> {
None
}
fn as_items(&self) -> Option<&[Self]> {
None
}
fn field(&self, name: &str) -> Option<&Self> {
match (self, name) {
(Self::Node(_) | Self::Cyclic, "value") => Some(&LEAF),
(Self::Node(next), "next") => next.as_deref(),
(Self::Cyclic, "next") => Some(self),
_ => None,
}
}
fn entries(&self) -> Vec<(&str, &Self)> {
match self {
Self::Number => Vec::new(),
Self::Node(None) => vec![("value", &LEAF)],
Self::Node(Some(next)) => vec![("value", &LEAF), ("next", next.as_ref())],
Self::Cyclic => vec![("value", &LEAF), ("next", self)],
}
}
fn as_integer(&self, path: &str) -> Result<i128, ProbeError> {
match self {
Self::Number => Ok(1),
_ => Err(ProbeError::Mismatch {
path: String::from(path),
expected: String::from("a number"),
found: String::from("a Node"),
}),
}
}
fn as_float128(&self, path: &str) -> Result<Float128, ProbeError> {
Err(ProbeError::Mismatch {
path: String::from(path),
expected: String::from("a long double"),
found: String::from(self.kind()),
})
}
fn render(&self) -> String {
String::from("<a Node>")
}
}
#[test]
fn a_cyclic_value_is_refused_by_name() {
let (ast, _) = node_model();
match encode(&ast, &request(), &Sample::Cyclic) {
Err(ProbeError::ValueTooDeep { limit, .. }) => assert_eq!(limit, MAX_VALUE_DEPTH),
other => panic!("a cyclic object graph was not refused: {other:?}"),
}
}
#[test]
fn the_refusal_names_the_clause() {
let (ast, _) = node_model();
let message = match encode(&ast, &request(), &Sample::Cyclic) {
Err(error) => format!("{error}"),
Ok(bytes) => panic!("a cyclic object graph produced {} bytes", bytes.len()),
};
assert!(message.contains("cycle"), "{message}");
assert!(message.contains("7.3.1.2.1.4"), "{message}");
}
#[test]
fn the_encoder_writes_up_to_the_limit_and_not_past_it() {
let (ast, _) = node_model();
let at_the_limit = encode(&ast, &request(), &chain(MAX_VALUE_DEPTH));
assert!(at_the_limit.is_ok(), "{at_the_limit:?}");
match encode(&ast, &request(), &chain(MAX_VALUE_DEPTH + 1)) {
Err(ProbeError::ValueTooDeep { limit, .. }) => assert_eq!(limit, MAX_VALUE_DEPTH),
other => panic!("a chain past the limit was not refused: {other:?}"),
}
}
fn nested_stream(levels: usize) -> Vec<u8> {
let value_member = [0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01];
let sentinel = [0x7f, 0x02, 0x00, 0x00];
let mut body: Vec<u8> = Vec::new();
body.extend_from_slice(&value_member);
body.extend_from_slice(&sentinel);
for _ in 1..levels {
let mut outer: Vec<u8> = Vec::new();
outer.extend_from_slice(&value_member);
outer.extend_from_slice(&[0x00, 0x01]);
outer.extend_from_slice(
&u16::try_from(body.len())
.expect("a length the short header holds")
.to_be_bytes(),
);
outer.extend_from_slice(&body);
outer.extend_from_slice(&sentinel);
body = outer;
}
body
}
#[test]
fn a_stream_that_nests_past_the_limit_is_refused() {
let (ast, _) = node_model();
let bytes = nested_stream(MAX_VALUE_DEPTH + 1);
match decode::<serde_json::Value>(&ast, &request(), &bytes) {
Err(ProbeError::ValueTooDeep { limit, .. }) => assert_eq!(limit, MAX_VALUE_DEPTH),
other => panic!("a stream nesting past the limit was not refused: {other:?}"),
}
}
#[test]
fn a_stream_at_the_limit_reads_to_the_bottom() {
let (ast, _) = node_model();
let bytes = nested_stream(MAX_VALUE_DEPTH);
let value: serde_json::Value = decode(&ast, &request(), &bytes).expect("a stream at the limit");
assert_eq!(levels_of(&value), MAX_VALUE_DEPTH);
}
#[test]
fn the_two_directions_agree_at_the_limit() {
let (ast, _) = node_model();
let bytes = encode(&ast, &request(), &chain(MAX_VALUE_DEPTH)).expect("a chain at the limit");
let value: serde_json::Value = decode(&ast, &request(), &bytes).expect("the same bytes back");
assert_eq!(levels_of(&value), MAX_VALUE_DEPTH);
}
fn levels_of(value: &serde_json::Value) -> usize {
let mut levels = 1;
let mut at = value;
while let Some(next) = at.get("next") {
levels += 1;
at = next;
}
levels
}