tinc_build/codegen/cel/functions/
contains.rs

1use 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
13// this.contains(arg)
14// arg in this
15impl 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(&registry);
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(&registry);
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(&registry);
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(&registry);
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}