krabka-java-regex 1.0.7

Java's java.util.regex semantics in Rust: Pattern.matches, replaceAll and replaceFirst as JDK 25 reads them
Documentation

krabka-java-regex

Crates.io Docs.rs

Java's java.util.regex in Rust: Pattern.compile, Matcher.matches(), String.replaceAll and String.replaceFirst, answered as JDK 25 answers them.

Part of Krabka, a Rust implementation of Apache Kafka.

Overview

Three Apache Kafka configs take a Java regular expression: ssl.principal.mapping.rules (KIP-371), the Kerberos auth_to_local rules, and the match patterns of a KIP-714 CLIENT_METRICS subscription. A rule that matches differently from Java maps a principal to another name, or selects other clients, so krabka-broker needs Java's answer exactly. This crate gives it. It rewrites the Java pattern for fancy-regex, and runs the constructs fancy-regex reads otherwise on a backtracking matcher of its own.

Features

  • JavaPattern::matches is Matcher.matches(): some way of matching spans the whole input, through backtracking and lazy quantifiers.
  • JavaPattern::replace is replaceAll or replaceFirst. Its matches are Matcher.find's, empty matches and \G included, and its replacement syntax is appendReplacement's: $n, ${name} and \x.
  • Java's reading of the syntax:
    • the ASCII \w, \d, \s, \b and POSIX classes, and their Unicode forms under (?U)
    • ., $, ^ and \Z with Java's line terminators, under (?s), (?m) and (?d)
    • \h, \v and \R
    • ASCII case folding under (?i), and Java's own Unicode case folding under (?iu)
    • class intersection with &&
    • \Q…\E and (?x) comments
  • Java's matcher, where fancy-regex differs:
    • backreferences under a case flag
    • \R giving back the \n of \r\n
    • groups that keep their text after a look-around, an atomic group, a possessive quantifier or an iteration of a repeated group
    • empty iterations of a repeated group: kept by Java's Loop, and skipped by its GroupCurly past the least count
    • \G after an empty match
    • a look-behind over a char past U+FFFF, whose starts Java counts in UTF-16 units, one of them between the two units of the char
  • Java's patterns that fancy-regex cannot compile, answered by the backtracking matcher: a look-behind that is not of one length with \R, a look-around, an atomic group or a group in it, such as (?<=\R?x)y, and a counted repeat too large for the regex crate to build, such as x{1,1000000000}, which the matcher counts.
  • JavaPattern::compile refuses what Pattern.compile refuses: duplicate group names, a dangling quantifier, a backwards repeat range, an unknown property name, a look-behind with no obvious maximum length, and the rest. It also refuses the few patterns it cannot read as Java does, rather than match them another way: Unicode blocks, \X, \N{name}, a quantified look-around, a backreference to a group the pattern does not have, and a look-behind whose maximum length overflows Java's int so that it tries fewer starts. The crate docs list them under Limits, with the input limit that a few such look-behinds take.
  • A search takes at most 1,000,000 backtracking steps. One past the limit is an error, not a no match, so a mapping rule cannot fall through to the next rule when Java would have applied it. The backtracking matcher keeps its own stack, so a long input cannot overflow the thread's.

Usage

use krabka_java_regex::JavaPattern;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let rule = JavaPattern::compile("CN=([^,]+),OU=(?i)ops.*")?;
    // `Matcher.matches()`: the whole input, so `OU=OPS-EU` matches.
    if rule.matches("CN=alice,OU=OPS-EU")? {
        // `String.replaceAll`: prints `alice`.
        println!("{}", rule.replace("CN=alice,OU=OPS-EU", "$1", true)?);
    }
    Ok(())
}

Documentation

License

Apache-2.0. Derivative work of Apache Kafka; see NOTICE.