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