rstest_bdd_patterns/pattern/
compiler.rs1use crate::errors::{PatternError, placeholder_error};
4use crate::hint::get_type_pattern;
5
6use super::lexer::{Token, lex_pattern};
7
8pub fn build_regex_from_pattern(pat: &str) -> Result<String, PatternError> {
22 let tokens = lex_pattern(pat)?;
23 let mut regex = String::with_capacity(pat.len().saturating_mul(2) + 2);
24 regex.push('^');
25 let mut stray_depth = 0usize;
26
27 for token in tokens {
28 match token {
29 Token::Literal(text) => regex.push_str(®ex::escape(&text)),
30 Token::Placeholder { hint, .. } => {
31 regex.push('(');
32 regex.push_str(get_type_pattern(hint.as_deref()));
33 regex.push(')');
34 }
35 Token::OpenBrace { .. } => {
36 stray_depth = stray_depth.saturating_add(1);
37 regex.push_str(®ex::escape("{"));
38 }
39 Token::CloseBrace { index } => {
40 if stray_depth == 0 {
41 return Err(placeholder_error(
42 "unmatched closing brace '}' in step pattern",
43 index,
44 None,
45 ));
46 }
47 stray_depth -= 1;
48 regex.push_str(®ex::escape("}"));
49 }
50 }
51 }
52
53 if stray_depth != 0 {
54 return Err(placeholder_error(
55 "unbalanced braces in step pattern",
56 pat.len(),
57 None,
58 ));
59 }
60
61 regex.push('$');
62 Ok(regex)
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn builds_regex_for_placeholder_patterns() {
71 let regex = build_regex_from_pattern("I have {count:u32} cukes")
72 .unwrap_or_else(|err| panic!("pattern should compile: {err}"));
73 assert_eq!(regex, r"^I have (\d+) cukes$");
74 }
75
76 #[test]
77 fn errors_when_closing_brace_unmatched() {
78 let Err(err) = build_regex_from_pattern("broken}") else {
79 panic!("should fail");
80 };
81 assert!(
82 err.to_string()
83 .contains("unmatched closing brace '}' in step pattern")
84 );
85 }
86
87 #[test]
88 fn errors_when_open_braces_remain() {
89 let Err(err) = build_regex_from_pattern("{open") else {
90 panic!("should fail");
91 };
92 assert!(
93 err.to_string()
94 .contains("missing closing '}' for placeholder")
95 );
96 }
97}