tinc_build/codegen/cel/functions/
enum_.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, ProtoPath, ProtoType, ProtoValueType};
8
9#[derive(Debug, Clone, Default)]
10pub(crate) struct Enum(pub Option<ProtoPath>);
11
12impl Function for Enum {
13    fn name(&self) -> &'static str {
14        "enum"
15    }
16
17    fn syntax(&self) -> &'static str {
18        "<this>.enum() | <this>.enum(<path>)"
19    }
20
21    fn compile(&self, ctx: CompilerCtx) -> Result<CompiledExpr, CompileError> {
22        let Some(this) = ctx.this.as_ref() else {
23            return Err(CompileError::syntax("missing this", self));
24        };
25
26        if ctx.args.len() > 1 {
27            return Err(CompileError::syntax("invalid number of arguments", self));
28        }
29
30        let enum_path = if let Some(arg) = ctx.args.first() {
31            ctx.resolve(arg)?
32        } else {
33            match (&this, &self.0) {
34                (
35                    CompiledExpr::Runtime(RuntimeCompiledExpr {
36                        ty:
37                            CelType::Proto(ProtoType::Modified(ProtoModifiedValueType::Optional(ProtoValueType::Enum(path))))
38                            | CelType::Proto(ProtoType::Value(ProtoValueType::Enum(path))),
39                        ..
40                    }),
41                    _,
42                )
43                | (_, Some(path)) => CompiledExpr::Constant(ConstantCompiledExpr {
44                    value: CelValue::String(path.0.clone().into()),
45                }),
46                _ => {
47                    return Err(CompileError::syntax(
48                        "unable to determine enum type, try providing an explicit path",
49                        self,
50                    ));
51                }
52            }
53        };
54
55        let this = this.clone().into_cel()?;
56        let enum_path = enum_path.into_cel()?;
57
58        match (this, enum_path) {
59            (
60                CompiledExpr::Constant(ConstantCompiledExpr { value: this }),
61                CompiledExpr::Constant(ConstantCompiledExpr { value: enum_path }),
62            ) => Ok(CompiledExpr::constant(CelValue::cel_to_enum(this, enum_path)?)),
63            (this, enum_path) => Ok(CompiledExpr::runtime(
64                CelType::CelValue,
65                parse_quote! {
66                    ::tinc::__private::cel::CelValue::cel_to_enum(
67                        #this,
68                        #enum_path,
69                    )?
70                },
71            )),
72        }
73    }
74}
75
76#[cfg(test)]
77#[cfg(feature = "prost")]
78#[cfg_attr(coverage_nightly, coverage(off))]
79mod tests {
80    use syn::parse_quote;
81
82    use crate::codegen::cel::compiler::{CompiledExpr, Compiler, CompilerCtx};
83    use crate::codegen::cel::functions::{Enum, Function};
84    use crate::codegen::cel::types::CelType;
85    use crate::extern_paths::ExternPaths;
86    use crate::path_set::PathSet;
87    use crate::types::{ProtoType, ProtoTypeRegistry, ProtoValueType};
88
89    #[test]
90    fn test_enum_syntax() {
91        let registry = ProtoTypeRegistry::new(crate::Mode::Prost, ExternPaths::new(crate::Mode::Prost), PathSet::default());
92        let compiler = Compiler::new(&registry);
93        let enum_ = Enum(None);
94        insta::assert_debug_snapshot!(enum_.compile(CompilerCtx::new(compiler.child(), None, &[])), @r#"
95        Err(
96            InvalidSyntax {
97                message: "missing this",
98                syntax: "<this>.enum() | <this>.enum(<path>)",
99            },
100        )
101        "#);
102
103        insta::assert_debug_snapshot!(enum_.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::constant(5)), &[])), @r#"
104        Err(
105            InvalidSyntax {
106                message: "unable to determine enum type, try providing an explicit path",
107                syntax: "<this>.enum() | <this>.enum(<path>)",
108            },
109        )
110        "#);
111
112        insta::assert_debug_snapshot!(enum_.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::constant(5)), &[
113            cel_parser::parse("'some.Enum'").unwrap(),
114        ])), @r#"
115        Ok(
116            Constant(
117                ConstantCompiledExpr {
118                    value: Enum(
119                        CelEnum {
120                            tag: Owned(
121                                "some.Enum",
122                            ),
123                            value: 5,
124                        },
125                    ),
126                },
127            ),
128        )
129        "#);
130    }
131
132    #[test]
133    #[cfg(not(valgrind))]
134    fn test_enum_runtime() {
135        let registry = ProtoTypeRegistry::new(crate::Mode::Prost, ExternPaths::new(crate::Mode::Prost), PathSet::default());
136        let compiler = Compiler::new(&registry);
137
138        let string_value =
139            CompiledExpr::runtime(CelType::Proto(ProtoType::Value(ProtoValueType::Int32)), parse_quote!(input));
140
141        let output = Enum(None)
142            .compile(CompilerCtx::new(
143                compiler.child(),
144                Some(string_value),
145                &[cel_parser::parse("'some.Enum'").unwrap()],
146            ))
147            .unwrap();
148
149        insta::assert_snapshot!(postcompile::compile_str!(
150            postcompile::config! {
151                test: true,
152                dependencies: vec![
153                    postcompile::Dependency::version("tinc", "*"),
154                ],
155            },
156            quote::quote! {
157                fn to_enum(input: i32) -> Result<::tinc::__private::cel::CelValue<'static>, ::tinc::__private::cel::CelError<'static>> {
158                    Ok(#output)
159                }
160
161                #[test]
162                fn test_to_enum() {
163                    #[::tinc::reexports::linkme::distributed_slice(::tinc::__private::cel::TINC_CEL_ENUM_VTABLE)]
164                    #[linkme(crate = ::tinc::reexports::linkme)]
165                    static ENUM_VTABLE: ::tinc::__private::cel::EnumVtable = ::tinc::__private::cel::EnumVtable {
166                        proto_path: "some.Enum",
167                        is_valid: |_| {
168                            true
169                        },
170                        to_serde: |_| {
171                            ::tinc::__private::cel::CelValue::String(::tinc::__private::cel::CelString::Borrowed("SERDE"))
172                        },
173                        to_proto: |_| {
174                            ::tinc::__private::cel::CelValue::String(::tinc::__private::cel::CelString::Borrowed("PROTO"))
175                        }
176                    };
177
178                    ::tinc::__private::cel::CelMode::Serde.set();
179                    assert_eq!(to_enum(1).unwrap().to_string(), "SERDE");
180                    ::tinc::__private::cel::CelMode::Proto.set();
181                    assert_eq!(to_enum(1).unwrap().to_string(), "PROTO");
182                }
183            },
184        ));
185    }
186}