tinc_build/codegen/cel/functions/
matches.rs1use syn::parse_quote;
2use tinc_cel::CelValue;
3
4use super::Function;
5use crate::codegen::cel::compiler::{CompileError, CompiledExpr, CompilerCtx, ConstantCompiledExpr};
6use crate::codegen::cel::types::CelType;
7use crate::types::{ProtoType, ProtoValueType};
8
9#[derive(Debug, Clone, Default)]
10pub(crate) struct Matches;
11
12impl Function for Matches {
14 fn name(&self) -> &'static str {
15 "matches"
16 }
17
18 fn syntax(&self) -> &'static str {
19 "<this>.matches(<const regex>)"
20 }
21
22 fn compile(&self, ctx: CompilerCtx) -> Result<CompiledExpr, CompileError> {
23 let Some(this) = &ctx.this else {
24 return Err(CompileError::syntax("missing this", self));
25 };
26
27 if ctx.args.len() != 1 {
28 return Err(CompileError::syntax("takes exactly one argument", self));
29 }
30
31 let CompiledExpr::Constant(ConstantCompiledExpr {
32 value: CelValue::String(regex),
33 }) = ctx.resolve(&ctx.args[0])?.into_cel()?
34 else {
35 return Err(CompileError::syntax("regex must be known at compile time string", self));
36 };
37
38 let regex = regex.as_ref();
39 if regex.is_empty() {
40 return Err(CompileError::syntax("regex cannot be an empty string", self));
41 }
42
43 let re = regex::Regex::new(regex).map_err(|err| CompileError::syntax(format!("bad regex {err}"), self))?;
44
45 let this = this.clone().into_cel()?;
46
47 match this {
48 CompiledExpr::Constant(ConstantCompiledExpr { value }) => {
49 Ok(CompiledExpr::constant(CelValue::cel_matches(value, &re)?))
50 }
51 this => Ok(CompiledExpr::runtime(
52 CelType::Proto(ProtoType::Value(ProtoValueType::Bool)),
53 parse_quote! {{
54 static REGEX: ::std::sync::LazyLock<::tinc::reexports::regex::Regex> = ::std::sync::LazyLock::new(|| {
55 ::tinc::reexports::regex::Regex::new(#regex).expect("failed to compile regex this is a bug in tinc")
56 });
57
58 ::tinc::__private::cel::CelValue::cel_matches(
59 #this,
60 &*REGEX,
61 )?
62 }},
63 )),
64 }
65 }
66}
67
68#[cfg(test)]
69#[cfg(feature = "prost")]
70#[cfg_attr(coverage_nightly, coverage(off))]
71mod tests {
72 use quote::quote;
73 use syn::parse_quote;
74 use tinc_cel::CelValue;
75
76 use crate::codegen::cel::compiler::{CompiledExpr, Compiler, CompilerCtx};
77 use crate::codegen::cel::functions::{Function, Matches};
78 use crate::codegen::cel::types::CelType;
79 use crate::types::{ProtoType, ProtoTypeRegistry, ProtoValueType};
80
81 #[test]
82 fn test_matches_syntax() {
83 let registry = ProtoTypeRegistry::new(crate::Mode::Prost, crate::extern_paths::ExternPaths::new(crate::Mode::Prost));
84 let compiler = Compiler::new(®istry);
85 insta::assert_debug_snapshot!(Matches.compile(CompilerCtx::new(compiler.child(), None, &[])), @r#"
86 Err(
87 InvalidSyntax {
88 message: "missing this",
89 syntax: "<this>.matches(<const regex>)",
90 },
91 )
92 "#);
93
94 insta::assert_debug_snapshot!(Matches.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::constant(CelValue::String("hi".into()))), &[])), @r#"
95 Err(
96 InvalidSyntax {
97 message: "takes exactly one argument",
98 syntax: "<this>.matches(<const regex>)",
99 },
100 )
101 "#);
102
103 insta::assert_debug_snapshot!(Matches.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::constant(CelValue::String("hi".into()))), &[
104 cel_parser::parse("dyn('^h')").unwrap(),
105 ])), @r#"
106 Err(
107 InvalidSyntax {
108 message: "regex must be known at compile time string",
109 syntax: "<this>.matches(<const regex>)",
110 },
111 )
112 "#);
113
114 insta::assert_debug_snapshot!(Matches.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::constant(CelValue::String("hi".into()))), &[
115 cel_parser::parse("'^h'").unwrap(),
116 ])), @r"
117 Ok(
118 Constant(
119 ConstantCompiledExpr {
120 value: Bool(
121 true,
122 ),
123 },
124 ),
125 )
126 ");
127 }
128
129 #[test]
130 #[cfg(not(valgrind))]
131 fn test_matches_runtime_string() {
132 let registry = ProtoTypeRegistry::new(crate::Mode::Prost, crate::extern_paths::ExternPaths::new(crate::Mode::Prost));
133 let compiler = Compiler::new(®istry);
134
135 let string_value =
136 CompiledExpr::runtime(CelType::Proto(ProtoType::Value(ProtoValueType::String)), parse_quote!(input));
137
138 let output = Matches
139 .compile(CompilerCtx::new(
140 compiler.child(),
141 Some(string_value),
142 &[cel_parser::parse("'\\\\d+'").unwrap()],
143 ))
144 .unwrap();
145
146 insta::assert_snapshot!(postcompile::compile_str!(
147 postcompile::config! {
148 test: true,
149 dependencies: vec![
150 postcompile::Dependency::version("tinc", "*"),
151 ],
152 },
153 quote! {
154 fn matches(input: &String) -> Result<bool, ::tinc::__private::cel::CelError<'_>> {
155 Ok(#output)
156 }
157
158 #[test]
159 fn test_matches() {
160 assert_eq!(matches(&"in2dastring".into()).unwrap(), true);
161 assert_eq!(matches(&"in3dastring".into()).unwrap(), true);
162 assert_eq!(matches(&"xd".into()).unwrap(), false);
163 }
164 },
165 ));
166 }
167}