Skip to content

Compile a pattern that came from a user

Compile it through compile_bounded

use rtb_app::regex_util::compile_bounded;

let re = compile_bounded(pattern_from_config)?;
if re.is_match(line) {
    // …
}

Any pattern that did not come from a string literal in your source goes through this function: config files, CLI flags, TUI input, HTTP payloads, message queues.

Know when you do not need it

A pattern that is a build-time literal in your own source is trusted, and regex::Regex::new is fine — usually wrapped in a LazyLock or once_cell so it compiles once:

static SEMVER: LazyLock<Regex> =
    LazyLock::new(|| Regex::new(r"^\d+\.\d+\.\d+$").expect("literal pattern"));

The distinction is where the pattern came from, not where the input came from. Matching untrusted text against a literal pattern is not a risk here.

Report the failure usefully

compile_bounded returns RegexCompileError, which is a miette::Diagnostic, so propagating it with ? from a miette::Result gives a rendered diagnostic for free:

async fn run(&self, app: App) -> miette::Result<()> {
    let re = compile_bounded(&pattern)?;
    Ok(())
}

If you want to tell the user which problem they have, match on the variant:

match compile_bounded(&pattern) {
    Ok(re) => { /* … */ }
    Err(RegexCompileError::TooLong { len }) => {
        eprintln!("that pattern is {len} bytes; the limit is 1024");
    }
    Err(RegexCompileError::Compile(source)) => {
        eprintln!("that pattern will not compile: {source}");
    }
}

Note the second arm covers two different problems: a syntax error and a pattern whose compiled program would exceed the memory bounds. Both arrive as Compile. To distinguish them you have to inspect the wrapped regex::Error — the enum variant will not tell you, and the top-level message (regex failed to compile within memory bounds) reads as a size problem even when the pattern was simply malformed.

Do not try to raise the limits

MAX_PATTERN_LEN, SIZE_LIMIT and DFA_SIZE_LIMIT are pub const and fixed at 1 KiB, 1 MiB and 8 MiB. There is no options struct, no builder and no per-call override. A tool that genuinely needs different bounds has to construct its own regex::RegexBuilder and takes on the denial-of-service surface that compile_bounded exists to close.

The constants are public so you can quote them at the user:

use rtb_app::regex_util::MAX_PATTERN_LEN;
eprintln!("patterns are limited to {MAX_PATTERN_LEN} bytes");