tinc_build/codegen/cel/functions/
contains.rs1use quote::quote;
2use syn::parse_quote;
3use tinc_cel::CelValue;
4
5use super::Function;
6use crate::codegen::cel::compiler::{CompileError, CompiledExpr, CompilerCtx, ConstantCompiledExpr, RuntimeCompiledExpr};
7use crate::codegen::cel::types::CelType;
8use crate::types::{ProtoModifiedValueType, ProtoType, ProtoValueType};
9
10#[derive(Debug, Clone, Default)]
11pub(crate) struct Contains;
12
13impl Function for Contains {
16 fn name(&self) -> &'static str {
17 "contains"
18 }
19
20 fn syntax(&self) -> &'static str {
21 "<this>.contains(<arg>)"
22 }
23
24 fn compile(&self, mut ctx: CompilerCtx) -> Result<CompiledExpr, CompileError> {
25 let Some(this) = ctx.this.take() else {
26 return Err(CompileError::syntax("missing this", self));
27 };
28
29 if ctx.args.len() != 1 {
30 return Err(CompileError::syntax("takes exactly one argument", self));
31 }
32
33 let arg = ctx.resolve(&ctx.args[0])?.into_cel()?;
34
35 if let CompiledExpr::Runtime(RuntimeCompiledExpr {
36 expr,
37 ty:
38 ty @ CelType::Proto(ProtoType::Modified(
39 ProtoModifiedValueType::Repeated(item) | ProtoModifiedValueType::Map(item, _),
40 )),
41 }) = &this
42 && !matches!(item, ProtoValueType::Message { .. } | ProtoValueType::Enum(_))
43 {
44 let op = match &ty {
45 CelType::Proto(ProtoType::Modified(ProtoModifiedValueType::Repeated(_))) => {
46 quote! { array_contains }
47 }
48 CelType::Proto(ProtoType::Modified(ProtoModifiedValueType::Map(_, _))) => {
49 quote! { map_contains }
50 }
51 _ => unreachable!(),
52 };
53
54 return Ok(CompiledExpr::runtime(
55 CelType::Proto(ProtoType::Value(ProtoValueType::Bool)),
56 parse_quote! {
57 ::tinc::__private::cel::#op(
58 #expr,
59 #arg,
60 )
61 },
62 ));
63 }
64
65 let this = this.clone().into_cel()?;
66
67 match (this, arg) {
68 (
69 CompiledExpr::Constant(ConstantCompiledExpr { value: this }),
70 CompiledExpr::Constant(ConstantCompiledExpr { value: arg }),
71 ) => Ok(CompiledExpr::constant(CelValue::cel_contains(this, arg)?)),
72 (this, arg) => Ok(CompiledExpr::runtime(
73 CelType::Proto(ProtoType::Value(ProtoValueType::Bool)),
74 parse_quote! {
75 ::tinc::__private::cel::CelValue::cel_contains(
76 #this,
77 #arg,
78 )?
79 },
80 )),
81 }
82 }
83}
84
85#[cfg(test)]
86#[cfg(feature = "prost")]
87#[cfg_attr(coverage_nightly, coverage(off))]
88mod tests {
89 use quote::quote;
90 use syn::parse_quote;
91 use tinc_cel::CelValue;
92
93 use crate::codegen::cel::compiler::{CompiledExpr, Compiler, CompilerCtx};
94 use crate::codegen::cel::functions::{Contains, Function};
95 use crate::codegen::cel::types::CelType;
96 use crate::types::{ProtoModifiedValueType, ProtoType, ProtoTypeRegistry, ProtoValueType};
97
98 #[test]
99 fn test_contains_syntax() {
100 let registry = ProtoTypeRegistry::new(crate::Mode::Prost, crate::extern_paths::ExternPaths::new(crate::Mode::Prost));
101 let compiler = Compiler::new(®istry);
102 insta::assert_debug_snapshot!(Contains.compile(CompilerCtx::new(compiler.child(), None, &[])), @r#"
103 Err(
104 InvalidSyntax {
105 message: "missing this",
106 syntax: "<this>.contains(<arg>)",
107 },
108 )
109 "#);
110
111 insta::assert_debug_snapshot!(Contains.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::constant(CelValue::String("hi".into()))), &[])), @r#"
112 Err(
113 InvalidSyntax {
114 message: "takes exactly one argument",
115 syntax: "<this>.contains(<arg>)",
116 },
117 )
118 "#);
119
120 insta::assert_debug_snapshot!(Contains.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::constant(CelValue::List(Default::default()))), &[
121 cel_parser::parse("1 + 1").unwrap(),
122 ])), @r"
123 Ok(
124 Constant(
125 ConstantCompiledExpr {
126 value: Bool(
127 false,
128 ),
129 },
130 ),
131 )
132 ");
133 }
134
135 #[test]
136 #[cfg(not(valgrind))]
137 fn test_contains_runtime_string() {
138 let registry = ProtoTypeRegistry::new(crate::Mode::Prost, crate::extern_paths::ExternPaths::new(crate::Mode::Prost));
139 let compiler = Compiler::new(®istry);
140
141 let string_value =
142 CompiledExpr::runtime(CelType::Proto(ProtoType::Value(ProtoValueType::String)), parse_quote!(input));
143
144 let output = Contains
145 .compile(CompilerCtx::new(
146 compiler.child(),
147 Some(string_value),
148 &[cel_parser::parse("(1 + 1).string()").unwrap()],
149 ))
150 .unwrap();
151
152 insta::assert_snapshot!(postcompile::compile_str!(
153 postcompile::config! {
154 test: true,
155 dependencies: vec![
156 postcompile::Dependency::version("tinc", "*"),
157 ],
158 },
159 quote! {
160 fn contains(input: &String) -> Result<bool, ::tinc::__private::cel::CelError<'_>> {
161 Ok(#output)
162 }
163
164 #[test]
165 fn test_contains() {
166 assert_eq!(contains(&"in2dastring".into()).unwrap(), true);
167 assert_eq!(contains(&"in3dastring".into()).unwrap(), false);
168 }
169 },
170 ));
171 }
172
173 #[test]
174 #[cfg(not(valgrind))]
175 fn test_contains_runtime_map() {
176 let registry = ProtoTypeRegistry::new(crate::Mode::Prost, crate::extern_paths::ExternPaths::new(crate::Mode::Prost));
177 let compiler = Compiler::new(®istry);
178
179 let string_value = CompiledExpr::runtime(
180 CelType::Proto(ProtoType::Modified(ProtoModifiedValueType::Map(
181 ProtoValueType::String,
182 ProtoValueType::Bool,
183 ))),
184 parse_quote!(input),
185 );
186
187 let output = Contains
188 .compile(CompilerCtx::new(
189 compiler.child(),
190 Some(string_value),
191 &[cel_parser::parse("'value'").unwrap()],
192 ))
193 .unwrap();
194
195 insta::assert_snapshot!(postcompile::compile_str!(
196 postcompile::config! {
197 test: true,
198 dependencies: vec![
199 postcompile::Dependency::version("tinc", "*"),
200 ],
201 },
202 quote! {
203 fn contains(input: &std::collections::HashMap<String, bool>) -> Result<bool, ::tinc::__private::cel::CelError<'_>> {
204 Ok(#output)
205 }
206
207 #[test]
208 fn test_contains() {
209 assert_eq!(contains(&{
210 let mut map = std::collections::HashMap::new();
211 map.insert("value".to_string(), true);
212 map
213 }).unwrap(), true);
214 assert_eq!(contains(&{
215 let mut map = std::collections::HashMap::new();
216 map.insert("not_value".to_string(), true);
217 map
218 }).unwrap(), false);
219 assert_eq!(contains(&{
220 let mut map = std::collections::HashMap::new();
221 map.insert("xd".to_string(), true);
222 map.insert("value".to_string(), true);
223 map
224 }).unwrap(), true);
225 }
226 },
227 ));
228 }
229
230 #[test]
231 #[cfg(not(valgrind))]
232 fn test_contains_runtime_repeated() {
233 let registry = ProtoTypeRegistry::new(crate::Mode::Prost, crate::extern_paths::ExternPaths::new(crate::Mode::Prost));
234 let compiler = Compiler::new(®istry);
235
236 let string_value = CompiledExpr::runtime(
237 CelType::Proto(ProtoType::Modified(ProtoModifiedValueType::Repeated(ProtoValueType::String))),
238 parse_quote!(input),
239 );
240
241 let output = Contains
242 .compile(CompilerCtx::new(
243 compiler.child(),
244 Some(string_value),
245 &[cel_parser::parse("'value'").unwrap()],
246 ))
247 .unwrap();
248
249 insta::assert_snapshot!(postcompile::compile_str!(
250 postcompile::config! {
251 test: true,
252 dependencies: vec![
253 postcompile::Dependency::version("tinc", "*"),
254 ],
255 },
256 quote! {
257 fn contains(input: &Vec<String>) -> Result<bool, ::tinc::__private::cel::CelError<'_>> {
258 Ok(#output)
259 }
260
261 #[test]
262 fn test_contains() {
263 assert_eq!(contains(&vec!["value".into()]).unwrap(), true);
264 assert_eq!(contains(&vec!["not_value".into()]).unwrap(), false);
265 assert_eq!(contains(&vec!["xd".into(), "value".into()]).unwrap(), true);
266 }
267 },
268 ));
269 }
270}