#![allow(
clippy::float_cmp,
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::similar_names,
clippy::doc_markdown,
clippy::needless_raw_string_hashes,
clippy::too_many_lines
)]
use std::sync::OnceLock;
const SARIF_SCHEMA_JSON: &str = include_str!("../../../tests/fixtures/sarif-2.1.0.json");
pub fn validate_sarif(json_text: &str) -> Result<(), Vec<String>> {
static VALIDATOR: OnceLock<jsonschema::Validator> = OnceLock::new();
let validator = VALIDATOR.get_or_init(|| {
let schema: serde_json::Value = serde_json::from_str(SARIF_SCHEMA_JSON)
.expect("vendored SARIF schema is valid JSON; refresh tests/fixtures/sarif-2.1.0.json");
jsonschema::draft7::new(&schema).expect("vendored SARIF schema is a valid Draft-07 schema")
});
let instance: serde_json::Value = match serde_json::from_str(json_text) {
Ok(v) => v,
Err(e) => return Err(vec![format!("SARIF output is not valid JSON: {e}")]),
};
let errors: Vec<String> = validator
.iter_errors(&instance)
.map(|err| format!("{err} (at {})", err.instance_path()))
.collect();
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
pub fn assert_checkstyle_well_formed_and_structural(xml_text: &str) {
use quick_xml::events::Event;
use quick_xml::reader::Reader;
let mut reader = Reader::from_str(xml_text);
reader.config_mut().trim_text(true);
let mut buf = Vec::new();
let mut saw_root = false;
let mut depth = 0usize;
loop {
let pos = reader.buffer_position();
let evt = match reader.read_event_into(&mut buf) {
Ok(e) => e,
Err(e) => panic!("checkstyle parse error at byte {pos}: {e}"),
};
match evt {
Event::Decl(_)
| Event::Comment(_)
| Event::DocType(_)
| Event::CData(_)
| Event::Text(_) => {}
Event::Start(start) => {
check_element(&start, &mut saw_root, pos);
depth += 1;
}
Event::Empty(start) => {
check_element(&start, &mut saw_root, pos);
}
Event::End(end) => {
let name_bytes = end.name();
let name = std::str::from_utf8(name_bytes.as_ref()).unwrap_or_else(|_| {
panic!("checkstyle end-element name is not UTF-8 at byte {pos}")
});
assert!(
matches!(name, "checkstyle" | "file" | "error" | "exception"),
"checkstyle: unexpected end-element </{name}> at byte {pos}"
);
depth = depth.saturating_sub(1);
}
Event::Eof => break,
other => panic!("checkstyle: unexpected event {other:?} at byte {pos}"),
}
buf.clear();
}
assert!(
saw_root,
"checkstyle: document did not contain a <checkstyle> root element"
);
assert!(
depth == 0,
"checkstyle: unbalanced element depth {depth} at EOF"
);
}
fn check_element(start: &quick_xml::events::BytesStart<'_>, saw_root: &mut bool, pos: u64) {
let name_bytes = start.name();
let name = std::str::from_utf8(name_bytes.as_ref())
.unwrap_or_else(|_| panic!("checkstyle element name is not UTF-8 at byte {pos}"));
match name {
"checkstyle" => {
assert!(
!*saw_root,
"checkstyle: unexpected second <checkstyle> at byte {pos}"
);
*saw_root = true;
require_attr(start, "version", "checkstyle", pos);
}
"file" => require_attr(start, "name", "file", pos),
"error" => {
require_attr(start, "line", "error", pos);
require_attr(start, "severity", "error", pos);
require_attr(start, "message", "error", pos);
require_attr(start, "source", "error", pos);
let sev = attr_value(start, "severity").expect("checked by require_attr above");
assert!(
matches!(sev.as_str(), "error" | "warning" | "info"),
"checkstyle: <error> severity={sev:?} not in XSD enum {{error, warning, info}} at byte {pos}"
);
let line = attr_value(start, "line").expect("checked by require_attr above");
assert_positive_integer(&line, "line", pos);
if let Some(col) = attr_value(start, "column") {
assert_positive_integer(&col, "column", pos);
}
}
"exception" => { }
other => panic!("checkstyle: unexpected element <{other}> at byte {pos}"),
}
}
fn assert_positive_integer(value: &str, attr: &str, pos: u64) {
let n: u32 = value.parse().unwrap_or_else(|_| {
panic!("checkstyle: <error> {attr}={value:?} is not an unsigned integer at byte {pos}")
});
assert!(
n != 0,
"checkstyle: <error> {attr}=0 violates xs:positiveInteger at byte {pos}"
);
}
fn require_attr(start: &quick_xml::events::BytesStart<'_>, attr: &str, elem: &str, pos: u64) {
assert!(
attr_value(start, attr).is_some(),
"checkstyle: <{elem}> missing required attribute `{attr}` at byte {pos}"
);
}
fn attr_value(start: &quick_xml::events::BytesStart<'_>, name: &str) -> Option<String> {
use std::borrow::Cow;
for attr in start.attributes().with_checks(false).flatten() {
if attr.key.as_ref() == name.as_bytes() {
return Some(attr.unescape_value().map_or_else(
|_| String::from_utf8_lossy(&attr.value).into_owned(),
Cow::into_owned,
));
}
}
None
}
pub fn assert_html_well_formed(html_text: &str) {
use quick_xml::events::Event;
use quick_xml::reader::Reader;
let body = extract_body(html_text);
let stripped = strip_block(body, "style");
let stripped = strip_block(&stripped, "script");
let wrapped = format!("<root>{stripped}</root>");
let mut reader = Reader::from_str(&wrapped);
reader.config_mut().trim_text(false);
let mut buf = Vec::new();
loop {
let pos = reader.buffer_position();
match reader.read_event_into(&mut buf) {
Ok(Event::Eof) => break,
Ok(_) => {}
Err(e) => {
let snippet_start = (pos as usize).saturating_sub(40);
let snippet_end = ((pos as usize) + 40).min(wrapped.len());
let snippet = &wrapped[snippet_start..snippet_end];
panic!("html: not well-formed at byte {pos}: {e}\n near: {snippet:?}");
}
}
buf.clear();
}
}
fn extract_body(html: &str) -> &str {
let Some(body_open_pos) = find_element_start(html, "body") else {
panic!("html: no <body> element in document");
};
let Some(rel_close) = html[body_open_pos..].find('>') else {
panic!("html: <body has no closing > at byte {body_open_pos}");
};
let body_open_end = body_open_pos + rel_close + 1;
let Some(body_close_pos) = html.rfind("</body>") else {
panic!("html: no </body> in document (open was at byte {body_open_pos})");
};
assert!(
body_close_pos >= body_open_end,
"html: </body> at byte {body_close_pos} precedes <body> at byte {body_open_pos}"
);
&html[body_open_end..body_close_pos]
}
fn find_element_start(text: &str, tag: &str) -> Option<usize> {
let needle = format!("<{tag}");
let mut cursor = 0;
while let Some(rel) = text[cursor..].find(&needle) {
let abs = cursor + rel;
let after = abs + needle.len();
match text.as_bytes().get(after) {
Some(b) if matches!(b, b'>' | b'/') || b.is_ascii_whitespace() => return Some(abs),
None => return None,
_ => cursor = after,
}
}
None
}
fn strip_block(text: &str, tag: &str) -> String {
let end_tag = format!("</{tag}>");
let mut out = String::with_capacity(text.len());
let mut cursor = 0;
while cursor < text.len() {
let Some(start_abs) = find_element_start(&text[cursor..], tag).map(|p| cursor + p) else {
out.push_str(&text[cursor..]);
break;
};
let Some(start_close_rel) = text[start_abs..].find('>') else {
out.push_str(&text[cursor..]);
break;
};
let start_close_abs = start_abs + start_close_rel + 1;
out.push_str(&text[cursor..start_close_abs]);
let Some(end_rel) = text[start_close_abs..].find(&end_tag) else {
out.push_str(&text[start_close_abs..]);
break;
};
out.push_str(&end_tag);
cursor = start_close_abs + end_rel + end_tag.len();
}
out
}