use crate::e2e::codegen::field_skip::nested_wildcard_skip_line;
use crate::e2e::escape::escape_csharp;
use crate::e2e::field_access::FieldResolver;
use crate::e2e::fixture::Assertion;
use std::fmt::Write as FmtWrite;
use std::hash::{Hash, Hasher};
fn wildcard_lambda_param(assertion: &Assertion) -> String {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
assertion.assertion_type.hash(&mut hasher);
assertion.field.hash(&mut hasher);
assertion
.value
.as_ref()
.map(std::string::ToString::to_string)
.unwrap_or_default()
.hash(&mut hasher);
format!("e{:x}", hasher.finish() & 0xffff_ffff)
}
pub(super) struct WildcardPath<'a> {
pub(super) result_expr: &'a str,
pub(super) field_resolver: &'a FieldResolver,
pub(super) field: &'a str,
pub(super) array_part: &'a str,
pub(super) elem_part: &'a str,
}
impl<'a> WildcardPath<'a> {
pub(super) fn new(
result_expr: &'a str,
field_resolver: &'a FieldResolver,
field: &'a str,
array_part: &'a str,
elem_part: &'a str,
) -> Self {
Self {
result_expr,
field_resolver,
field,
array_part,
elem_part,
}
}
}
struct WildcardExprs {
array_accessor: String,
param: String,
elem_accessor: String,
text_accessor: String,
null_guard: String,
}
impl WildcardExprs {
fn new(assertion: &Assertion, path: &WildcardPath<'_>) -> Self {
let array_accessor = if path.array_part.is_empty() {
path.result_expr.to_string()
} else {
path.field_resolver
.accessor(path.array_part, "csharp", path.result_expr)
};
let param = wildcard_lambda_param(assertion);
let elem_accessor = path.field_resolver.element_accessor(path.elem_part, "csharp", ¶m);
let enum_leaf = path.field_resolver.is_enum(path.field);
let text_accessor = if enum_leaf {
let options = super::super::UNESCAPED_JSON_SERIALIZER_OPTIONS;
format!("System.Text.Json.JsonSerializer.Serialize({elem_accessor}, {options}).Trim('\"')")
} else {
format!("Convert.ToString({elem_accessor})!")
};
let null_guard = if enum_leaf {
format!("{elem_accessor} is {{ }} && ")
} else {
String::new()
};
Self {
array_accessor,
param,
elem_accessor,
text_accessor,
null_guard,
}
}
fn any_expr(&self, value: &serde_json::Value) -> Option<(String, String)> {
let serde_json::Value::String(s) = value else {
return None;
};
let escaped = escape_csharp(s);
let Self {
array_accessor,
param,
text_accessor,
null_guard,
..
} = self;
Some((
format!("{array_accessor}?.Any({param} => {null_guard}{text_accessor}.Contains(\"{escaped}\")) == true"),
escaped,
))
}
}
fn render_single_contains(out: &mut String, assertion: &Assertion, exprs: &WildcardExprs, field: &str) {
let value = assertion.value.as_ref().expect("guarded by the match arm");
let Some((expr, escaped)) = exprs.any_expr(value) else {
let _ = writeln!(
out,
" // skipped: non-string value for '{field}' traversal assertion"
);
return;
};
let verb = if assertion.assertion_type == "contains" {
"True"
} else {
"False"
};
let _ = writeln!(
out,
" Assert.{verb}({expr}, \"element of '{field}': {escaped}\");"
);
}
fn render_multi_contains(out: &mut String, assertion: &Assertion, exprs: &WildcardExprs, field: &str) {
let Some(values) = &assertion.values else {
let _ = writeln!(out, " // skipped: '{field}' traversal assertion has no values");
return;
};
let verb = if assertion.assertion_type == "not_contains" {
"False"
} else {
"True"
};
for value in values {
let Some((expr, escaped)) = exprs.any_expr(value) else {
continue;
};
let _ = writeln!(
out,
" Assert.{verb}({expr}, \"element of '{field}': {escaped}\");"
);
}
}
pub(super) fn render_wildcard_assertion(out: &mut String, assertion: &Assertion, path: &WildcardPath<'_>) {
let field = path.field;
if let Some(line) = nested_wildcard_skip_line(" ", "//", field, path.elem_part) {
let _ = writeln!(out, "{line}");
return;
}
let exprs = WildcardExprs::new(assertion, path);
match assertion.assertion_type.as_str() {
"contains" | "not_contains" if assertion.value.is_some() => {
render_single_contains(out, assertion, &exprs, field)
}
"contains" | "contains_all" | "not_contains" => render_multi_contains(out, assertion, &exprs, field),
"not_empty" => {
let WildcardExprs {
array_accessor,
param,
elem_accessor,
..
} = &exprs;
let _ = writeln!(
out,
" Assert.True({array_accessor}?.Any({param} => \
!string.IsNullOrEmpty(Convert.ToString({elem_accessor}))) == true, \
\"expected some element of '{field}' to be non-empty\");"
);
}
other => {
let _ = writeln!(
out,
" // skipped: unsupported traversal assertion '{other}' on '{field}'"
);
}
}
}