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::extern_paths::ExternPaths;
159 use crate::path_set::PathSet;
160 use crate::types::{ProtoModifiedValueType, ProtoType, ProtoTypeRegistry, ProtoValueType};
161
162 #[test]
163 fn test_exists_one_syntax() {
164 let registry = ProtoTypeRegistry::new(crate::Mode::Prost, ExternPaths::new(crate::Mode::Prost), PathSet::default());
165 let compiler = Compiler::new(®istry);
166 insta::assert_debug_snapshot!(ExistsOne.compile(CompilerCtx::new(compiler.child(), None, &[])), @r#"
167 Err(
168 InvalidSyntax {
169 message: "missing this",
170 syntax: "<this>.existsOne(<ident>, <expr>)",
171 },
172 )
173 "#);
174
175 insta::assert_debug_snapshot!(ExistsOne.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::constant(CelValue::String("hi".into()))), &[])), @r#"
176 Err(
177 InvalidSyntax {
178 message: "invalid number of args",
179 syntax: "<this>.existsOne(<ident>, <expr>)",
180 },
181 )
182 "#);
183
184 insta::assert_debug_snapshot!(ExistsOne.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::constant(CelValue::String("hi".into()))), &[
185 cel_parser::parse("x").unwrap(),
186 cel_parser::parse("dyn(x >= 1)").unwrap(),
187 ])), @r#"
188 Err(
189 TypeConversion {
190 ty: CelValue,
191 message: "String(Borrowed(\"hi\")) cannot be iterated over",
192 },
193 )
194 "#);
195
196 insta::assert_debug_snapshot!(ExistsOne.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::runtime(CelType::Proto(ProtoType::Value(ProtoValueType::Bool)), parse_quote!(input))), &[
197 cel_parser::parse("x").unwrap(),
198 cel_parser::parse("dyn(x >= 1)").unwrap(),
199 ])), @r#"
200 Err(
201 TypeConversion {
202 ty: Proto(
203 Value(
204 Bool,
205 ),
206 ),
207 message: "type cannot be iterated over",
208 },
209 )
210 "#);
211
212 insta::assert_debug_snapshot!(ExistsOne.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::constant(CelValue::List([
213 CelValueConv::conv("value")
214 ].into_iter().collect()))), &[
215 cel_parser::parse("x").unwrap(),
216 cel_parser::parse("x == 'value'").unwrap(),
217 ])), @r"
218 Ok(
219 Constant(
220 ConstantCompiledExpr {
221 value: Bool(
222 true,
223 ),
224 },
225 ),
226 )
227 ");
228
229 let input = CompiledExpr::constant(CelValue::Map(
230 [(CelValueConv::conv("value"), CelValueConv::conv(1))].into_iter().collect(),
231 ));
232 let mut ctx = compiler.child();
233 ctx.add_variable("input", input.clone());
234
235 insta::assert_debug_snapshot!(ExistsOne.compile(CompilerCtx::new(ctx, Some(input), &[
236 cel_parser::parse("x").unwrap(),
237 cel_parser::parse("input[x] > 0").unwrap(),
238 ])), @r"
239 Ok(
240 Constant(
241 ConstantCompiledExpr {
242 value: Bool(
243 true,
244 ),
245 },
246 ),
247 )
248 ");
249 }
250
251 #[test]
252 #[cfg(not(valgrind))]
253 fn test_exists_one_runtime_map() {
254 let registry = ProtoTypeRegistry::new(crate::Mode::Prost, ExternPaths::new(crate::Mode::Prost), PathSet::default());
255 let compiler = Compiler::new(®istry);
256
257 let string_value = CompiledExpr::runtime(
258 CelType::Proto(ProtoType::Modified(ProtoModifiedValueType::Map(
259 ProtoValueType::String,
260 ProtoValueType::Bool,
261 ))),
262 parse_quote!(input),
263 );
264
265 let output = ExistsOne
266 .compile(CompilerCtx::new(
267 compiler.child(),
268 Some(string_value),
269 &[cel_parser::parse("x").unwrap(), cel_parser::parse("x == 'value'").unwrap()],
270 ))
271 .unwrap();
272
273 insta::assert_snapshot!(postcompile::compile_str!(
274 postcompile::config! {
275 test: true,
276 dependencies: vec![
277 postcompile::Dependency::version("tinc", "*"),
278 ],
279 },
280 quote! {
281 fn exists_one(input: &std::collections::HashMap<String, bool>) -> Result<bool, ::tinc::__private::cel::CelError<'_>> {
282 Ok(#output)
283 }
284
285 #[test]
286 fn test_exists_one() {
287 assert_eq!(exists_one(&{
288 let mut map = std::collections::HashMap::new();
289 map.insert("value".to_string(), true);
290 map
291 }).unwrap(), true);
292 assert_eq!(exists_one(&{
293 let mut map = std::collections::HashMap::new();
294 map.insert("not_value".to_string(), true);
295 map
296 }).unwrap(), false);
297 assert_eq!(exists_one(&{
298 let mut map = std::collections::HashMap::new();
299 map.insert("xd".to_string(), true);
300 map.insert("value".to_string(), true);
301 map
302 }).unwrap(), true);
303 }
304 },
305 ));
306 }
307
308 #[test]
309 #[cfg(not(valgrind))]
310 fn test_exists_one_runtime_repeated() {
311 let registry = ProtoTypeRegistry::new(crate::Mode::Prost, ExternPaths::new(crate::Mode::Prost), PathSet::default());
312 let compiler = Compiler::new(®istry);
313
314 let string_value = CompiledExpr::runtime(
315 CelType::Proto(ProtoType::Modified(ProtoModifiedValueType::Repeated(ProtoValueType::String))),
316 parse_quote!(input),
317 );
318
319 let output = ExistsOne
320 .compile(CompilerCtx::new(
321 compiler.child(),
322 Some(string_value),
323 &[cel_parser::parse("x").unwrap(), cel_parser::parse("x == 'value'").unwrap()],
324 ))
325 .unwrap();
326
327 insta::assert_snapshot!(postcompile::compile_str!(
328 postcompile::config! {
329 test: true,
330 dependencies: vec![
331 postcompile::Dependency::version("tinc", "*"),
332 ],
333 },
334 quote! {
335 fn contains(input: &Vec<String>) -> Result<bool, ::tinc::__private::cel::CelError<'_>> {
336 Ok(#output)
337 }
338
339 #[test]
340 fn test_contains() {
341 assert_eq!(contains(&vec!["value".into()]).unwrap(), true);
342 assert_eq!(contains(&vec!["not_value".into()]).unwrap(), false);
343 assert_eq!(contains(&vec!["xd".into(), "value".into()]).unwrap(), true);
344 assert_eq!(contains(&vec!["xd".into(), "value".into(), "value".into()]).unwrap(), false);
345 }
346 },
347 ));
348 }
349
350 #[test]
351 #[cfg(not(valgrind))]
352 fn test_exists_one_runtime_cel_value() {
353 let registry = ProtoTypeRegistry::new(crate::Mode::Prost, ExternPaths::new(crate::Mode::Prost), PathSet::default());
354 let compiler = Compiler::new(®istry);
355
356 let string_value = CompiledExpr::runtime(CelType::CelValue, parse_quote!(input));
357
358 let output = ExistsOne
359 .compile(CompilerCtx::new(
360 compiler.child(),
361 Some(string_value),
362 &[cel_parser::parse("x").unwrap(), cel_parser::parse("x == 'value'").unwrap()],
363 ))
364 .unwrap();
365
366 insta::assert_snapshot!(postcompile::compile_str!(
367 postcompile::config! {
368 test: true,
369 dependencies: vec![
370 postcompile::Dependency::version("tinc", "*"),
371 ],
372 },
373 quote! {
374 fn exists_one<'a>(input: &'a ::tinc::__private::cel::CelValue<'a>) -> Result<bool, ::tinc::__private::cel::CelError<'a>> {
375 Ok(#output)
376 }
377
378 #[test]
379 fn test_exists_one() {
380 assert_eq!(exists_one(&tinc::__private::cel::CelValue::List([
381 tinc::__private::cel::CelValueConv::conv("value"),
382 ].into_iter().collect())).unwrap(), true);
383 assert_eq!(exists_one(&tinc::__private::cel::CelValue::List([
384 tinc::__private::cel::CelValueConv::conv("not_value"),
385 ].into_iter().collect())).unwrap(), false);
386 assert_eq!(exists_one(&tinc::__private::cel::CelValue::List([
387 tinc::__private::cel::CelValueConv::conv("xd"),
388 tinc::__private::cel::CelValueConv::conv("value"),
389 ].into_iter().collect())).unwrap(), true);
390 assert_eq!(exists_one(&tinc::__private::cel::CelValue::List([
391 tinc::__private::cel::CelValueConv::conv("xd"),
392 tinc::__private::cel::CelValueConv::conv("value"),
393 tinc::__private::cel::CelValueConv::conv("value"),
394 ].into_iter().collect())).unwrap(), false);
395 }
396 },
397 ));
398 }
399
400 #[test]
401 #[cfg(not(valgrind))]
402 fn test_exists_one_const_requires_runtime() {
403 let registry = ProtoTypeRegistry::new(crate::Mode::Prost, ExternPaths::new(crate::Mode::Prost), PathSet::default());
404 let compiler = Compiler::new(®istry);
405
406 let list_value = CompiledExpr::constant(CelValue::List(
407 [CelValueConv::conv(5), CelValueConv::conv(0), CelValueConv::conv(1)]
408 .into_iter()
409 .collect(),
410 ));
411
412 let output = ExistsOne
413 .compile(CompilerCtx::new(
414 compiler.child(),
415 Some(list_value),
416 &[cel_parser::parse("x").unwrap(), cel_parser::parse("dyn(x >= 1)").unwrap()],
417 ))
418 .unwrap();
419
420 insta::assert_snapshot!(postcompile::compile_str!(
421 postcompile::config! {
422 test: true,
423 dependencies: vec![
424 postcompile::Dependency::version("tinc", "*"),
425 ],
426 },
427 quote! {
428 fn exists_one() -> Result<bool, ::tinc::__private::cel::CelError<'static>> {
429 Ok(#output)
430 }
431
432 #[test]
433 fn test_filter() {
434 assert_eq!(exists_one().unwrap(), false);
435 }
436 },
437 ));
438 }
439}