#![allow(clippy::missing_panics_doc, reason = "tests are allowed to panic")]
#![allow(clippy::panic, reason = "tests are allowed panic")]
#![allow(
clippy::arithmetic_side_effects,
reason = "tests are allowed have arithmetic_side_effects"
)]
use std::fmt;
use crate::test::Expectation;
use super::{
match_path_node, parser::Span, ElemId, Element, Field, FieldsAsExt as _, LineCol, PathNode,
PathNodeRef, UnexpectedFields, Value,
};
impl<'buf> Element<'buf> {
pub(crate) fn into_value(self) -> Value<'buf> {
self.value
}
pub(crate) fn into_parts(self) -> (ElemId, PathNodeRef<'buf>, Span, Value<'buf>) {
let Self {
id,
path_node: path,
span,
value,
} = self;
(id, path, span, value)
}
pub(crate) fn find_field(&self, key: &str) -> Option<&Field<'buf>> {
self.as_object_fields()
.and_then(|fields| fields.find_field(key))
}
}
impl Value<'_> {
pub(crate) fn is_array(&self) -> bool {
matches!(self, Value::Array(_))
}
pub(crate) fn is_object(&self) -> bool {
matches!(self, Value::Object(_))
}
}
impl<'buf> Field<'buf> {
pub fn id(&self) -> ElemId {
self.0.id()
}
pub fn into_parts(self) -> (ElemId, PathNodeRef<'buf>, Span, Value<'buf>) {
self.0.into_parts()
}
}
impl<'buf> UnexpectedFields<'buf> {
pub(super) fn into_inner(self) -> Vec<PathNodeRef<'buf>> {
self.0
}
fn filter_matches(&mut self, glob: &PathGlob) {
self.0.retain(|path| !glob.matches(path));
}
}
pub struct LineHighlighter {
json: String,
pos: LineCol,
}
impl LineHighlighter {
pub fn from_value(json: &serde_json::Value, pos: LineCol) -> Self {
let json = serde_json::to_string_pretty(json).unwrap();
Self { json, pos }
}
}
impl fmt::Display for LineHighlighter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Ok(target_line) = usize::try_from(self.pos.line) else {
return Ok(());
};
for (index, line) in self.json.lines().enumerate() {
let line_no = index + 1;
if index == target_line {
writeln!(f, "{line_no:3}| {line}")?;
} else {
writeln!(f, " | {line}")?;
}
}
Ok(())
}
}
#[derive(Debug)]
pub(crate) struct PathGlob(String);
impl PathGlob {
pub(crate) fn matches(&self, path: &PathNode<'_>) -> bool {
const WILDCARD: &str = "*";
match_path_node(path, &self.0, |s| {
s == WILDCARD
})
}
}
impl From<usize> for ElemId {
fn from(value: usize) -> Self {
Self(value)
}
}
impl<'a> From<&'a str> for PathGlob {
fn from(s: &'a str) -> Self {
Self(s.into())
}
}
impl From<String> for PathGlob {
fn from(s: String) -> Self {
Self(s)
}
}
impl<'de> serde::Deserialize<'de> for PathGlob {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
let s = <String as ::serde::Deserialize>::deserialize(deserializer)?;
Ok(Self(s))
}
}
#[track_caller]
pub(crate) fn expect_no_unexpected_fields(
expect_file_name: &str,
unexpected_fields: &UnexpectedFields<'_>,
) {
if !unexpected_fields.is_empty() {
const MAX_FIELD_DISPLAY: usize = 20;
if unexpected_fields.len() > MAX_FIELD_DISPLAY {
let truncated_fields = unexpected_fields
.iter()
.take(MAX_FIELD_DISPLAY)
.map(|path| path.to_string())
.collect::<Vec<_>>();
panic!(
"The expect file `{expect_file_name}` didn't expect `{}` unexpected fields;\n\
displaying the first ({}):\n{}\n... and {} more",
unexpected_fields.len(),
truncated_fields.len(),
truncated_fields.join(",\n"),
unexpected_fields.len() - truncated_fields.len(),
)
} else {
panic!(
"The expect file `{expect_file_name}` didn't expect `{}` unexpected fields:\n{}",
unexpected_fields.len(),
unexpected_fields.to_strings().join(",\n")
)
};
}
}
#[track_caller]
pub(crate) fn expect_unexpected_fields(
expect_file_name: &str,
unexpected_fields: &mut UnexpectedFields<'_>,
expected: Expectation<Vec<PathGlob>>,
) {
if let Expectation::Present(expectation) = expected {
let unexpected_fields_expect = expectation.expect_value();
for expect_glob in unexpected_fields_expect {
unexpected_fields.filter_matches(&expect_glob);
}
expect_no_unexpected_fields(expect_file_name, unexpected_fields);
} else {
expect_no_unexpected_fields(expect_file_name, unexpected_fields);
}
}