tinc_build/codegen/cel/functions/
size.rs

1use syn::parse_quote;
2use tinc_cel::CelValue;
3
4use super::Function;
5use crate::codegen::cel::compiler::{CompileError, CompiledExpr, CompilerCtx, ConstantCompiledExpr, RuntimeCompiledExpr};
6use crate::codegen::cel::types::CelType;
7use crate::types::{ProtoModifiedValueType, ProtoType, ProtoValueType};
8
9#[derive(Debug, Clone, Default)]
10pub(crate) struct Size;
11
12impl Function for Size {
13    fn name(&self) -> &'static str {
14        "size"
15    }
16
17    fn syntax(&self) -> &'static str {
18        "<this>.size()"
19    }
20
21    fn compile(&self, ctx: CompilerCtx) -> Result<CompiledExpr, CompileError> {
22        let Some(this) = ctx.this else {
23            return Err(CompileError::syntax("missing this", self));
24        };
25
26        if !ctx.args.is_empty() {
27            return Err(CompileError::syntax("takes no arguments", self));
28        }
29
30        if let CompiledExpr::Runtime(RuntimeCompiledExpr {
31            expr,
32            ty: CelType::Proto(ProtoType::Modified(ProtoModifiedValueType::Repeated(_) | ProtoModifiedValueType::Map(_, _))),
33        }) = &this
34        {
35            return Ok(CompiledExpr::runtime(
36                CelType::Proto(ProtoType::Value(ProtoValueType::UInt64)),
37                parse_quote! {
38                    ((#expr).len() as u64)
39                },
40            ));
41        }
42
43        match this.into_cel()? {
44            CompiledExpr::Constant(ConstantCompiledExpr { value }) => Ok(CompiledExpr::constant(CelValue::cel_size(value)?)),
45            CompiledExpr::Runtime(RuntimeCompiledExpr { expr, .. }) => Ok(CompiledExpr::runtime(
46                CelType::Proto(ProtoType::Value(ProtoValueType::UInt64)),
47                parse_quote!(::tinc::__private::cel::CelValue::cel_size(#expr)?),
48            )),
49        }
50    }
51}
52
53#[cfg(test)]
54#[cfg(feature = "prost")]
55#[cfg_attr(coverage_nightly, coverage(off))]
56mod tests {
57    use syn::parse_quote;
58    use tinc_cel::CelValue;
59
60    use crate::codegen::cel::compiler::{CompiledExpr, Compiler, CompilerCtx};
61    use crate::codegen::cel::functions::{Function, Size};
62    use crate::codegen::cel::types::CelType;
63    use crate::extern_paths::ExternPaths;
64    use crate::path_set::PathSet;
65    use crate::types::{ProtoModifiedValueType, ProtoType, ProtoTypeRegistry, ProtoValueType};
66
67    #[test]
68    fn test_size_syntax() {
69        let registry = ProtoTypeRegistry::new(crate::Mode::Prost, ExternPaths::new(crate::Mode::Prost), PathSet::default());
70        let compiler = Compiler::new(&registry);
71        insta::assert_debug_snapshot!(Size.compile(CompilerCtx::new(compiler.child(), None, &[])), @r#"
72        Err(
73            InvalidSyntax {
74                message: "missing this",
75                syntax: "<this>.size()",
76            },
77        )
78        "#);
79
80        insta::assert_debug_snapshot!(Size.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::constant(CelValue::String("13".into()))), &[])), @r"
81        Ok(
82            Constant(
83                ConstantCompiledExpr {
84                    value: Number(
85                        U64(
86                            2,
87                        ),
88                    ),
89                },
90            ),
91        )
92        ");
93
94        insta::assert_debug_snapshot!(Size.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::constant(CelValue::List(Default::default()))), &[
95            cel_parser::parse("1 + 1").unwrap(), // not an ident
96        ])), @r#"
97        Err(
98            InvalidSyntax {
99                message: "takes no arguments",
100                syntax: "<this>.size()",
101            },
102        )
103        "#);
104    }
105
106    #[test]
107    #[cfg(not(valgrind))]
108    fn test_size_runtime() {
109        let registry = ProtoTypeRegistry::new(crate::Mode::Prost, ExternPaths::new(crate::Mode::Prost), PathSet::default());
110        let compiler = Compiler::new(&registry);
111
112        let string_value =
113            CompiledExpr::runtime(CelType::Proto(ProtoType::Value(ProtoValueType::String)), parse_quote!(input));
114
115        let output = Size
116            .compile(CompilerCtx::new(compiler.child(), Some(string_value), &[]))
117            .unwrap();
118
119        insta::assert_snapshot!(postcompile::compile_str!(
120            postcompile::config! {
121                test: true,
122                dependencies: vec![
123                    postcompile::Dependency::version("tinc", "*"),
124                ],
125            },
126            quote::quote! {
127                fn size(input: &str) -> Result<u64, ::tinc::__private::cel::CelError<'_>> {
128                    Ok(#output)
129                }
130
131                #[test]
132                fn test_size() {
133                    assert_eq!(size("55").unwrap(), 2);
134                }
135            },
136        ));
137    }
138
139    #[test]
140    #[cfg(not(valgrind))]
141    fn test_size_runtime_map() {
142        let registry = ProtoTypeRegistry::new(crate::Mode::Prost, ExternPaths::new(crate::Mode::Prost), PathSet::default());
143        let compiler = Compiler::new(&registry);
144
145        let input = CompiledExpr::runtime(
146            CelType::Proto(ProtoType::Modified(ProtoModifiedValueType::Map(
147                ProtoValueType::String,
148                ProtoValueType::Bool,
149            ))),
150            parse_quote!(input),
151        );
152
153        let output = Size.compile(CompilerCtx::new(compiler.child(), Some(input), &[])).unwrap();
154
155        insta::assert_snapshot!(postcompile::compile_str!(
156            postcompile::config! {
157                test: true,
158                dependencies: vec![
159                    postcompile::Dependency::version("tinc", "*"),
160                ],
161            },
162            quote::quote! {
163                #![allow(unused_parens)]
164
165                fn size(input: &std::collections::HashMap<String, bool>) -> Result<u64, ::tinc::__private::cel::CelError<'_>> {
166                    Ok(#output)
167                }
168
169                #[test]
170                fn test_contains() {
171                    assert_eq!(size(&{
172                        let mut map = std::collections::HashMap::new();
173                        map.insert("value".to_string(), true);
174                        map
175                    }).unwrap(), 1);
176                    assert_eq!(size(&std::collections::HashMap::new()).unwrap(), 0);
177                    assert_eq!(size(&{
178                        let mut map = std::collections::HashMap::new();
179                        map.insert("xd".to_string(), true);
180                        map.insert("value".to_string(), true);
181                        map
182                    }).unwrap(), 2);
183                }
184            },
185        ));
186    }
187
188    #[test]
189    #[cfg(not(valgrind))]
190    fn test_size_runtime_repeated() {
191        let registry = ProtoTypeRegistry::new(crate::Mode::Prost, ExternPaths::new(crate::Mode::Prost), PathSet::default());
192        let compiler = Compiler::new(&registry);
193
194        let string_value = CompiledExpr::runtime(
195            CelType::Proto(ProtoType::Modified(ProtoModifiedValueType::Repeated(ProtoValueType::String))),
196            parse_quote!(input),
197        );
198
199        let output = Size
200            .compile(CompilerCtx::new(compiler.child(), Some(string_value), &[]))
201            .unwrap();
202
203        insta::assert_snapshot!(postcompile::compile_str!(
204            postcompile::config! {
205                test: true,
206                dependencies: vec![
207                    postcompile::Dependency::version("tinc", "*"),
208                ],
209            },
210            quote::quote! {
211                #![allow(unused_parens)]
212
213                fn size(input: &Vec<String>) -> Result<u64, ::tinc::__private::cel::CelError<'_>> {
214                    Ok(#output)
215                }
216
217                #[test]
218                fn test_contains() {
219                    assert_eq!(size(&vec!["value".into()]).unwrap(), 1);
220                    assert_eq!(size(&vec![]).unwrap(), 0);
221                    assert_eq!(size(&vec!["xd".into(), "value".into()]).unwrap(), 2);
222                }
223            },
224        ));
225    }
226}