1use proc_macro2::TokenStream;
2use quote::{ToTokens, quote};
3use syn::parse_quote;
4use tinc_cel::CelValue;
5
6use super::Function;
7use crate::codegen::cel::compiler::{CompileError, CompiledExpr, CompilerCtx, ConstantCompiledExpr, RuntimeCompiledExpr};
8use crate::codegen::cel::types::CelType;
9use crate::types::{ProtoModifiedValueType, ProtoType, ProtoValueType};
10
11#[derive(Debug, Clone, Default)]
12pub(crate) struct Exists;
13
14fn native_impl(iter: TokenStream, item_ident: syn::Ident, compare: impl ToTokens) -> syn::Expr {
15 parse_quote!({
16 let mut iter = (#iter).into_iter();
17 loop {
18 let Some(#item_ident) = iter.next() else {
19 break false;
20 };
21
22 if #compare {
23 break true;
24 }
25 }
26 })
27}
28
29impl Function for Exists {
31 fn name(&self) -> &'static str {
32 "exists"
33 }
34
35 fn syntax(&self) -> &'static str {
36 "<this>.exists(<ident>, <expr>)"
37 }
38
39 fn compile(&self, mut ctx: CompilerCtx) -> Result<CompiledExpr, CompileError> {
40 let Some(this) = ctx.this.take() else {
41 return Err(CompileError::syntax("missing this", self));
42 };
43
44 if ctx.args.len() != 2 {
45 return Err(CompileError::syntax("invalid number of args", self));
46 }
47
48 let cel_parser::Expression::Ident(variable) = &ctx.args[0] else {
49 return Err(CompileError::syntax("first argument must be an ident", self));
50 };
51
52 match this {
53 CompiledExpr::Runtime(RuntimeCompiledExpr { expr, ty }) => {
54 let mut child_ctx = ctx.child();
55
56 match &ty {
57 CelType::CelValue => {
58 child_ctx.add_variable(variable, CompiledExpr::runtime(CelType::CelValue, parse_quote!(item)));
59 }
60 CelType::Proto(ProtoType::Modified(
61 ProtoModifiedValueType::Repeated(ty) | ProtoModifiedValueType::Map(ty, _),
62 )) => {
63 child_ctx.add_variable(
64 variable,
65 CompiledExpr::runtime(CelType::Proto(ProtoType::Value(ty.clone())), parse_quote!(item)),
66 );
67 }
68 v => {
69 return Err(CompileError::TypeConversion {
70 ty: Box::new(v.clone()),
71 message: "type cannot be iterated over".to_string(),
72 });
73 }
74 };
75
76 let arg = child_ctx.resolve(&ctx.args[1])?.into_bool(&child_ctx);
77
78 Ok(CompiledExpr::runtime(
79 CelType::Proto(ProtoType::Value(ProtoValueType::Bool)),
80 match &ty {
81 CelType::CelValue => parse_quote! {
82 ::tinc::__private::cel::CelValue::cel_exists(#expr, |item| {
83 ::core::result::Result::Ok(
84 #arg
85 )
86 })?
87 },
88 CelType::Proto(ProtoType::Modified(ProtoModifiedValueType::Map(_, _))) => {
89 native_impl(quote!((#expr).keys()), parse_quote!(item), arg)
90 }
91 CelType::Proto(ProtoType::Modified(ProtoModifiedValueType::Repeated(_))) => {
92 native_impl(quote!((#expr).iter()), parse_quote!(item), arg)
93 }
94 _ => unreachable!(),
95 },
96 ))
97 }
98 CompiledExpr::Constant(ConstantCompiledExpr {
99 value: value @ (CelValue::List(_) | CelValue::Map(_)),
100 }) => {
101 let compile_val = |value: CelValue<'static>| {
102 let mut child_ctx = ctx.child();
103
104 child_ctx.add_variable(variable, CompiledExpr::constant(value));
105
106 child_ctx.resolve(&ctx.args[1]).map(|v| v.into_bool(&child_ctx))
107 };
108
109 let collected: Result<Vec<_>, _> = match value {
110 CelValue::List(item) => item.iter().cloned().map(compile_val).collect(),
111 CelValue::Map(item) => item.iter().map(|(key, _)| key).cloned().map(compile_val).collect(),
112 _ => unreachable!(),
113 };
114
115 let collected = collected?;
116 if collected.iter().any(|c| matches!(c, CompiledExpr::Runtime(_))) {
117 Ok(CompiledExpr::runtime(
118 CelType::Proto(ProtoType::Value(ProtoValueType::Bool)),
119 native_impl(quote!([#(#collected),*]), parse_quote!(item), quote!(item)),
120 ))
121 } else {
122 Ok(CompiledExpr::constant(CelValue::Bool(collected.into_iter().any(
123 |c| match c {
124 CompiledExpr::Constant(ConstantCompiledExpr { value }) => value.to_bool(),
125 _ => unreachable!("all values must be constant"),
126 },
127 ))))
128 }
129 }
130 CompiledExpr::Constant(ConstantCompiledExpr { value }) => Err(CompileError::TypeConversion {
131 ty: Box::new(CelType::CelValue),
132 message: format!("{value:?} cannot be iterated over"),
133 }),
134 }
135 }
136}
137
138#[cfg(test)]
139#[cfg(feature = "prost")]
140#[cfg_attr(coverage_nightly, coverage(off))]
141mod tests {
142 use quote::quote;
143 use syn::parse_quote;
144 use tinc_cel::{CelValue, CelValueConv};
145
146 use crate::codegen::cel::compiler::{CompiledExpr, Compiler, CompilerCtx};
147 use crate::codegen::cel::functions::{Exists, Function};
148 use crate::codegen::cel::types::CelType;
149 use crate::types::{ProtoModifiedValueType, ProtoType, ProtoTypeRegistry, ProtoValueType};
150
151 #[test]
152 fn test_exists_syntax() {
153 let registry = ProtoTypeRegistry::new(crate::Mode::Prost, crate::extern_paths::ExternPaths::new(crate::Mode::Prost));
154 let compiler = Compiler::new(®istry);
155 insta::assert_debug_snapshot!(Exists.compile(CompilerCtx::new(compiler.child(), None, &[])), @r#"
156 Err(
157 InvalidSyntax {
158 message: "missing this",
159 syntax: "<this>.exists(<ident>, <expr>)",
160 },
161 )
162 "#);
163
164 insta::assert_debug_snapshot!(Exists.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::constant(CelValue::String("hi".into()))), &[])), @r#"
165 Err(
166 InvalidSyntax {
167 message: "invalid number of args",
168 syntax: "<this>.exists(<ident>, <expr>)",
169 },
170 )
171 "#);
172
173 insta::assert_debug_snapshot!(Exists.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::constant(CelValue::String("hi".into()))), &[
174 cel_parser::parse("x").unwrap(),
175 cel_parser::parse("dyn(x >= 1)").unwrap(),
176 ])), @r#"
177 Err(
178 TypeConversion {
179 ty: CelValue,
180 message: "String(Borrowed(\"hi\")) cannot be iterated over",
181 },
182 )
183 "#);
184
185 insta::assert_debug_snapshot!(Exists.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::runtime(CelType::Proto(ProtoType::Value(ProtoValueType::Bool)), parse_quote!(input))), &[
186 cel_parser::parse("x").unwrap(),
187 cel_parser::parse("dyn(x >= 1)").unwrap(),
188 ])), @r#"
189 Err(
190 TypeConversion {
191 ty: Proto(
192 Value(
193 Bool,
194 ),
195 ),
196 message: "type cannot be iterated over",
197 },
198 )
199 "#);
200
201 insta::assert_debug_snapshot!(Exists.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::constant(CelValue::List(Default::default()))), &[
202 cel_parser::parse("x").unwrap(),
203 cel_parser::parse("x == 'value'").unwrap(),
204 ])), @r"
205 Ok(
206 Constant(
207 ConstantCompiledExpr {
208 value: Bool(
209 false,
210 ),
211 },
212 ),
213 )
214 ");
215 }
216
217 #[test]
218 #[cfg(not(valgrind))]
219 fn test_exists_runtime_map() {
220 let registry = ProtoTypeRegistry::new(crate::Mode::Prost, crate::extern_paths::ExternPaths::new(crate::Mode::Prost));
221 let compiler = Compiler::new(®istry);
222
223 let string_value = CompiledExpr::runtime(
224 CelType::Proto(ProtoType::Modified(ProtoModifiedValueType::Map(
225 ProtoValueType::String,
226 ProtoValueType::Bool,
227 ))),
228 parse_quote!(input),
229 );
230
231 let output = Exists
232 .compile(CompilerCtx::new(
233 compiler.child(),
234 Some(string_value),
235 &[cel_parser::parse("x").unwrap(), cel_parser::parse("x == 'value'").unwrap()],
236 ))
237 .unwrap();
238
239 insta::assert_snapshot!(postcompile::compile_str!(
240 postcompile::config! {
241 test: true,
242 dependencies: vec![
243 postcompile::Dependency::version("tinc", "*"),
244 ],
245 },
246 quote! {
247 fn exists(input: &std::collections::HashMap<String, bool>) -> Result<bool, ::tinc::__private::cel::CelError<'_>> {
248 Ok(#output)
249 }
250
251 #[test]
252 fn test_contains() {
253 assert_eq!(exists(&{
254 let mut map = std::collections::HashMap::new();
255 map.insert("value".to_string(), true);
256 map
257 }).unwrap(), true);
258 assert_eq!(exists(&{
259 let mut map = std::collections::HashMap::new();
260 map.insert("not_value".to_string(), true);
261 map
262 }).unwrap(), false);
263 assert_eq!(exists(&{
264 let mut map = std::collections::HashMap::new();
265 map.insert("xd".to_string(), true);
266 map.insert("value".to_string(), true);
267 map
268 }).unwrap(), true);
269 }
270 },
271 ));
272 }
273
274 #[test]
275 #[cfg(not(valgrind))]
276 fn test_exists_runtime_repeated() {
277 let registry = ProtoTypeRegistry::new(crate::Mode::Prost, crate::extern_paths::ExternPaths::new(crate::Mode::Prost));
278 let compiler = Compiler::new(®istry);
279
280 let string_value = CompiledExpr::runtime(
281 CelType::Proto(ProtoType::Modified(ProtoModifiedValueType::Repeated(ProtoValueType::String))),
282 parse_quote!(input),
283 );
284
285 let output = Exists
286 .compile(CompilerCtx::new(
287 compiler.child(),
288 Some(string_value),
289 &[cel_parser::parse("x").unwrap(), cel_parser::parse("x == 'value'").unwrap()],
290 ))
291 .unwrap();
292
293 insta::assert_snapshot!(postcompile::compile_str!(
294 postcompile::config! {
295 test: true,
296 dependencies: vec![
297 postcompile::Dependency::version("tinc", "*"),
298 ],
299 },
300 quote! {
301 fn exists(input: &Vec<String>) -> Result<bool, ::tinc::__private::cel::CelError<'_>> {
302 Ok(#output)
303 }
304
305 #[test]
306 fn test_exists() {
307 assert_eq!(exists(&vec!["value".into()]).unwrap(), true);
308 assert_eq!(exists(&vec!["not_value".into()]).unwrap(), false);
309 assert_eq!(exists(&vec!["xd".into(), "value".into()]).unwrap(), true);
310 assert_eq!(exists(&vec!["xd".into(), "value".into(), "value".into()]).unwrap(), true);
311 }
312 },
313 ));
314 }
315
316 #[test]
317 #[cfg(not(valgrind))]
318 fn test_exists_runtime_cel_value() {
319 let registry = ProtoTypeRegistry::new(crate::Mode::Prost, crate::extern_paths::ExternPaths::new(crate::Mode::Prost));
320 let compiler = Compiler::new(®istry);
321
322 let string_value = CompiledExpr::runtime(CelType::CelValue, parse_quote!(input));
323
324 let output = Exists
325 .compile(CompilerCtx::new(
326 compiler.child(),
327 Some(string_value),
328 &[cel_parser::parse("x").unwrap(), cel_parser::parse("x == 'value'").unwrap()],
329 ))
330 .unwrap();
331
332 insta::assert_snapshot!(postcompile::compile_str!(
333 postcompile::config! {
334 test: true,
335 dependencies: vec![
336 postcompile::Dependency::version("tinc", "*"),
337 ],
338 },
339 quote! {
340 fn exists<'a>(input: &'a ::tinc::__private::cel::CelValue<'a>) -> Result<bool, ::tinc::__private::cel::CelError<'a>> {
341 Ok(#output)
342 }
343
344 #[test]
345 fn test_exists() {
346 assert_eq!(exists(&tinc::__private::cel::CelValue::List([
347 tinc::__private::cel::CelValueConv::conv("value"),
348 ].into_iter().collect())).unwrap(), true);
349 assert_eq!(exists(&tinc::__private::cel::CelValue::List([
350 tinc::__private::cel::CelValueConv::conv("not_value"),
351 ].into_iter().collect())).unwrap(), false);
352 assert_eq!(exists(&tinc::__private::cel::CelValue::List([
353 tinc::__private::cel::CelValueConv::conv("xd"),
354 tinc::__private::cel::CelValueConv::conv("value"),
355 ].into_iter().collect())).unwrap(), true);
356 assert_eq!(exists(&tinc::__private::cel::CelValue::List([
357 tinc::__private::cel::CelValueConv::conv("xd"),
358 tinc::__private::cel::CelValueConv::conv("value"),
359 tinc::__private::cel::CelValueConv::conv("value"),
360 ].into_iter().collect())).unwrap(), true);
361 }
362 },
363 ));
364 }
365
366 #[test]
367 #[cfg(not(valgrind))]
368 fn test_exists_const_requires_runtime() {
369 let registry = ProtoTypeRegistry::new(crate::Mode::Prost, crate::extern_paths::ExternPaths::new(crate::Mode::Prost));
370 let compiler = Compiler::new(®istry);
371
372 let list_value = CompiledExpr::constant(CelValue::List(
373 [CelValueConv::conv(5), CelValueConv::conv(0), CelValueConv::conv(1)]
374 .into_iter()
375 .collect(),
376 ));
377
378 let output = Exists
379 .compile(CompilerCtx::new(
380 compiler.child(),
381 Some(list_value),
382 &[cel_parser::parse("x").unwrap(), cel_parser::parse("dyn(x >= 1)").unwrap()],
383 ))
384 .unwrap();
385
386 insta::assert_snapshot!(postcompile::compile_str!(
387 postcompile::config! {
388 test: true,
389 dependencies: vec![
390 postcompile::Dependency::version("tinc", "*"),
391 ],
392 },
393 quote! {
394 fn exists_one() -> Result<bool, ::tinc::__private::cel::CelError<'static>> {
395 Ok(#output)
396 }
397
398 #[test]
399 fn test_filter() {
400 assert_eq!(exists_one().unwrap(), true);
401 }
402 },
403 ));
404 }
405}