use beamr::native::ProcessContext;
use beamr::term::Term;
use super::engine_nifs::{decode_string_arg, error_result_term, ok_result_term};
const MAX_CATEGORY_BYTES: usize = 64;
const MAX_MESSAGE_BYTES: usize = 2048;
pub(super) fn report_query_diagnostic(
args: &[Term],
ctx: &mut ProcessContext,
) -> Result<Term, Term> {
if args.len() > u8::MAX.into() {
return Err(Term::NIL);
}
if args.len() != 2 {
let message = format!(
"report_query_diagnostic: expected 2 arguments, got {}",
args.len()
);
return Ok(result_or_nil(error_result_term(ctx, &message)));
}
let category = match decode_string_arg(args[0]) {
Ok(value) => value,
Err(error) => {
return Ok(result_or_nil(error_result_term(
ctx,
&format!("report_query_diagnostic category: {error}"),
)));
}
};
let message = match decode_string_arg(args[1]) {
Ok(value) => value,
Err(error) => {
return Ok(result_or_nil(error_result_term(
ctx,
&format!("report_query_diagnostic message: {error}"),
)));
}
};
tracing::warn!(
workflow_pid = ?ctx.pid(),
category = bounded_text(&category, MAX_CATEGORY_BYTES),
diagnostic = bounded_text(&message, MAX_MESSAGE_BYTES),
"workflow query pump diagnostic"
);
Ok(result_or_nil(ok_result_term(ctx, "reported")))
}
fn result_or_nil(term: Option<Term>) -> Term {
match term {
Some(value) => value,
None => Term::NIL,
}
}
fn bounded_text(value: &str, max_bytes: usize) -> &str {
if value.len() <= max_bytes {
return value;
}
let mut end = max_bytes;
while !value.is_char_boundary(end) {
end -= 1;
}
&value[..end]
}
#[cfg(test)]
mod tests {
use super::{MAX_MESSAGE_BYTES, bounded_text};
type TestResult = Result<(), Box<dyn std::error::Error>>;
#[test]
fn diagnostic_bound_preserves_utf8_boundaries() -> TestResult {
let value = format!("{}é", "a".repeat(MAX_MESSAGE_BYTES - 1));
let bounded = bounded_text(&value, MAX_MESSAGE_BYTES);
let validated = std::str::from_utf8(bounded.as_bytes())?;
assert_eq!(validated.len(), MAX_MESSAGE_BYTES - 1);
assert!(bounded.chars().all(|character| character == 'a'));
Ok(())
}
}