diff --git a/Cargo.lock b/Cargo.lock index 9a347f08..0586678a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -909,12 +909,13 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" dependencies = [ "crc32fast", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -951,12 +952,12 @@ dependencies = [ [[package]] name = "fraction" -version = "0.15.4" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076045bb43dac435333ed5f04caf35c7463631d0dae2deb2638d94dd0a5b872" +checksum = "e246562084dde8ebbcc943b261c406ce4f68e5032ec28029a251a47d6a295500" dependencies = [ - "lazy_static", "num", + "num-bigint", ] [[package]] @@ -1586,9 +1587,9 @@ dependencies = [ [[package]] name = "jsonschema" -version = "0.49.9" +version = "0.55.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ec8a241beed129f06114aa68007e905ca350e7baeb6e17a7631bb7978d91b2" +checksum = "b68339c3d874e48151d74ffe256d93a58cffa240983cb0967d3cbaea083a44fe" dependencies = [ "ahash", "bytecount", @@ -1597,7 +1598,6 @@ dependencies = [ "fancy-regex", "fraction", "getrandom 0.3.4", - "idna", "itoa", "jsonschema-regex", "jsonschema-value", @@ -1615,25 +1615,27 @@ dependencies = [ [[package]] name = "jsonschema-regex" -version = "0.49.9" +version = "0.55.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91994f45017ed5e66aa8e59b8415f4cb033a6380d7200387b7cf117595fbdf85" +checksum = "6307b5b51216ec9b941b52244c74043fa0b1d6b657b56199f57cb1416d3641c5" dependencies = [ "regex-syntax", ] [[package]] name = "jsonschema-value" -version = "0.49.9" +version = "0.55.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ec7637f83e510868ae6ed625f7ebfbbde4554ee8ce49854caa5126a8b9b9ecb" +checksum = "0230ac05e09c6111e96c147b75c390579f5cbd45654b980c68ac60fe17b3f129" dependencies = [ "ahash", "bytecount", "fraction", + "getrandom 0.3.4", "num-cmp", "num-traits", "serde_json", + "zmij", ] [[package]] @@ -1945,9 +1947,9 @@ checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74" [[package]] name = "miniz_oxide" -version = "0.8.9" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" dependencies = [ "adler2", "simd-adler32", @@ -2001,9 +2003,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" dependencies = [ "num-integer", "num-traits", @@ -2351,9 +2353,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b" dependencies = [ "memchr", ] @@ -2533,9 +2535,9 @@ dependencies = [ [[package]] name = "referencing" -version = "0.49.9" +version = "0.55.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6efa2154ea6f5ce0fdecdd2a8d18f2fa1a39a8fbba91564f555a592e4dce8278" +checksum = "a196a5b4a8a12f46b6353174df865a05d41a6055aff212ec30877492788618b6" dependencies = [ "ahash", "fluent-uri", @@ -3979,11 +3981,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zlib-rs" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" + [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zvariant" diff --git a/Cargo.toml b/Cargo.toml index fa80cf4d..6e876729 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ getrandom = "0.4" glob = "0.3" globset = "0.4" iana-time-zone = "0.1" -jsonschema = { version = "0.49", default-features = false } +jsonschema = { version = "0.55", default-features = false } # OS credential store for the token `mergify auth login` mints. The # default feature set is the one that keeps the Linux build free of C: # `zbus-secret-service-keyring-store` talks to the Secret Service over @@ -49,7 +49,7 @@ jsonschema = { version = "0.49", default-features = false } keyring = "4" opentelemetry-proto = { version = "0.32", default-features = false, features = ["gen-tonic-messages", "trace"] } prost = "0.14" -quick-xml = "0.41" +quick-xml = "0.42" regex = "1" # `form`: the OAuth device grant is `application/x-www-form-urlencoded` # on the wire, as RFC 6749 §4 requires. Nothing else in the CLI sends a diff --git a/crates/mergify-ci/src/junit_process/junit.rs b/crates/mergify-ci/src/junit_process/junit.rs index 3e9882a5..b34423c3 100644 --- a/crates/mergify-ci/src/junit_process/junit.rs +++ b/crates/mergify-ci/src/junit_process/junit.rs @@ -219,26 +219,22 @@ pub fn parse(xml: &[u8]) -> Result { // `GeneralRef` event below; entity resolution // happens in the `GeneralRef` arm. // - // `xml10_content()` (vs the plainer `decode()`) is - // important on Windows: when git checks fixtures - // out with `core.autocrlf` enabled the file's line - // endings are `\r\n`, and the XML 1.0 spec - // requires those to be normalized to `\n` before - // they reach element text. Without this, failure - // stacktraces ship `\r\n` and tests that diff on - // the assembled stacktrace fail only on Windows. - let s = e.xml10_content().map_err(|err| InvalidJunitXml { - details: format!("invalid UTF-8 in element text: {err}"), - })?; + // `xml10_content()` (vs the plain `Deref` content) is important on Windows: when git + // checks fixtures out with `core.autocrlf` enabled + // the file's line endings are `\r\n`, and the XML + // 1.0 spec requires those to be normalized to `\n` + // before they reach element text. Without this, + // failure stacktraces ship `\r\n` and tests that + // diff on the assembled stacktrace fail only on + // Windows. + let s = e.xml10_content(); state.append_failure_text(&s); } Ok(Event::CData(e)) => { - // CDATA bodies are by definition not entity-escaped, - // so plain UTF-8 decoding is correct here. - let text = std::str::from_utf8(e.as_ref()).map_err(|err| InvalidJunitXml { - details: format!("invalid UTF-8 in CDATA: {err}"), - })?; - state.append_failure_text(text); + // quick-xml validates UTF-8 up front, so the event + // already derefs to `str` — no decoding step needed. + state.append_failure_text(e.as_ref()); } Ok(Event::GeneralRef(e)) => { // quick-xml emits a separate `GeneralRef` event for @@ -258,10 +254,8 @@ pub fn parse(xml: &[u8]) -> Result { state.append_failure_text(ch.encode_utf8(&mut tmp)); } } else { - let name = e.decode().map_err(|err| InvalidJunitXml { - details: format!("invalid UTF-8 in entity reference: {err}"), - })?; - let resolved = match name.as_ref() { + let name = e.as_ref(); + let resolved = match name { "lt" => "<", "gt" => ">", "amp" => "&", @@ -294,14 +288,13 @@ pub fn parse(xml: &[u8]) -> Result { /// `testsuite` or `ns:testsuite` (XML namespaces); Python's /// `findall(".//{*}testsuite")` ignores the namespace prefix /// entirely, so we do the same. -fn local_name<'a>(name: QName<'a>) -> &'a [u8] { - // `QName::local_name` would return a wrapped type; we want - // raw bytes here. `into_inner()` (`QName: Deref` - // via `&'a [u8]`) gives the input-borrowed slice back so the - // returned `&[u8]` stays tied to `'a`, not the temporary - // function frame. - let raw: &'a [u8] = name.into_inner(); - raw.rsplit(|b| *b == b':').next().unwrap_or(raw) +fn local_name<'a>(name: QName<'a>) -> &'a str { + // `QName::local_name` would return a wrapped type; we want the + // raw string here. `into_inner()` gives the input-borrowed + // `&'a str` back so the return stays tied to `'a`, not the + // temporary function frame. + let raw: &'a str = name.into_inner(); + raw.rsplit(':').next().unwrap_or(raw) } /// Decode an attribute value to a `String`, resolving XML entity @@ -383,11 +376,11 @@ impl ParserState { let name = local_name(e.name()); if !self.saw_valid_root { match name { - b"testsuites" => { + "testsuites" => { self.saw_valid_root = true; return Ok(()); } - b"testsuite" => { + "testsuite" => { self.saw_valid_root = true; self.saw_any_testsuite = true; let suite_name = read_suite_name(e)?; @@ -404,13 +397,13 @@ impl ParserState { } match name { - b"testsuite" => { + "testsuite" => { self.saw_any_testsuite = true; let suite_name = read_suite_name(e)?; self.suite_names.push(suite_name.clone()); self.suite_stack.push(suite_name); } - b"testcase" => { + "testcase" => { if self.in_progress.is_some() { return Err(InvalidJunitXml { details: "nested not allowed".to_string(), @@ -424,11 +417,11 @@ impl ParserState { self.in_progress = Some(read_testcase(e, &suite_name)?); self.failure_captured = false; } - b"failure" | b"error" => { + "failure" | "error" => { if let Some(tc) = self.in_progress.as_mut() && !self.failure_captured { - tc.status = if name == b"failure" { + tc.status = if name == "failure" { TestStatus::Failed } else { TestStatus::Errored @@ -439,7 +432,7 @@ impl ParserState { self.failure_text_buf.clear(); } } - b"skipped" => { + "skipped" => { if let Some(tc) = self.in_progress.as_mut() { // `` wins over `` only when // the failure wasn't already recorded — Python @@ -465,7 +458,7 @@ impl ParserState { // mirror the work `on_start` does and just skip the // matching `on_end` since there's no body to track. match name { - b"testsuite" => { + "testsuite" => { self.saw_any_testsuite = true; if !self.saw_valid_root { self.saw_valid_root = true; @@ -477,7 +470,7 @@ impl ParserState { self.suite_names.push(read_suite_name(e)?); // Empty testsuite contributes nothing to `cases`. } - b"testcase" => { + "testcase" => { if !self.saw_valid_root { return Err(InvalidJunitXml { details: " outside ".to_string(), @@ -491,18 +484,18 @@ impl ParserState { let tc = read_testcase(e, &suite_name)?; self.output.push(tc); } - b"skipped" => { + "skipped" => { if let Some(tc) = self.in_progress.as_mut() && !self.failure_captured { tc.status = TestStatus::Skipped; } } - b"failure" | b"error" => { + "failure" | "error" => { if let Some(tc) = self.in_progress.as_mut() && !self.failure_captured { - tc.status = if name == b"failure" { + tc.status = if name == "failure" { TestStatus::Failed } else { TestStatus::Errored @@ -524,16 +517,16 @@ impl ParserState { fn on_end(&mut self, e: &quick_xml::events::BytesEnd<'_>) { let name = local_name(e.name()); match name { - b"testsuite" => { + "testsuite" => { self.suite_stack.pop(); } - b"testcase" => { + "testcase" => { if let Some(tc) = self.in_progress.take() { self.output.push(tc); } self.failure_captured = false; } - b"failure" | b"error" if self.in_failure => { + "failure" | "error" if self.in_failure => { // Flush the accumulated body text into the // testcase's failure record. Trimming once at the // close keeps the wire format identical to @@ -569,7 +562,7 @@ fn read_suite_name(e: &quick_xml::events::BytesStart<'_>) -> Result classname = Some(attr_value(&attr)?), - b"name" => name = Some(attr_value(&attr)?), - b"time" => { + "classname" => classname = Some(attr_value(&attr)?), + "name" => name = Some(attr_value(&attr)?), + "time" => { let raw = attr_value(&attr)?; if !raw.is_empty() { time = raw.parse::().ok(); } } - b"file" => file = Some(attr_value(&attr)?), - b"line" => line = Some(attr_value(&attr)?), + "file" => file = Some(attr_value(&attr)?), + "line" => line = Some(attr_value(&attr)?), _ => {} } } @@ -628,8 +621,8 @@ fn read_failure(e: &quick_xml::events::BytesStart<'_>) -> Result failure.kind = Some(attr_value(&attr)?), - b"message" => failure.message = Some(attr_value(&attr)?), + "type" => failure.kind = Some(attr_value(&attr)?), + "message" => failure.message = Some(attr_value(&attr)?), _ => {} } }