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::types::{ProtoType, ProtoTypeRegistry, ProtoValueType};
86
87    #[test]
88    fn test_enum_syntax() {
89        let registry = ProtoTypeRegistry::new(crate::Mode::Prost, crate::extern_paths::ExternPaths::new(crate::Mode::Prost));
90        let compiler = Compiler::new(&registry);
91        let enum_ = Enum(None);
92        insta::assert_debug_snapshot!(enum_.compile(CompilerCtx::new(compiler.child(), None, &[])), @r#"
93        Err(
94            InvalidSyntax {
95                message: "missing this",
96                syntax: "<this>.enum() | <this>.enum(<path>)",
97            },
98        )
99        "#);
100
101        insta::assert_debug_snapshot!(enum_.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::constant(5)), &[])), @r#"
102        Err(
103            InvalidSyntax {
104                message: "unable to determine enum type, try providing an explicit path",
105                syntax: "<this>.enum() | <this>.enum(<path>)",
106            },
107        )
108        "#);
109
110        insta::assert_debug_snapshot!(enum_.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::constant(5)), &[
111            cel_parser::parse("'some.Enum'").unwrap(),
112        ])), @r#"
113        Ok(
114            Constant(
115                ConstantCompiledExpr {
116                    value: Enum(
117                        CelEnum {
118                            tag: Owned(
119                                "some.Enum",
120                            ),
121                            value: 5,
122                        },
123                    ),
124                },
125            ),
126        )
127        "#);
128    }
129
130    #[test]
131    #[cfg(not(valgrind))]
132    fn test_enum_runtime() {
133        let registry = ProtoTypeRegistry::new(crate::Mode::Prost, crate::extern_paths::ExternPaths::new(crate::Mode::Prost));
134        let compiler = Compiler::new(&registry);
135
136        let string_value =
137            CompiledExpr::runtime(CelType::Proto(ProtoType::Value(ProtoValueType::Int32)), parse_quote!(input));
138
139        let output = Enum(None)
140            .compile(CompilerCtx::new(
141                compiler.child(),
142                Some(string_value),
143                &[cel_parser::parse("'some.Enum'").unwrap()],
144            ))
145            .unwrap();
146
147        insta::assert_snapshot!(postcompile::compile_str!(
148            postcompile::config! {
149                test: true,
150                dependencies: vec![
151                    postcompile::Dependency::version("tinc", "*"),
152                ],
153            },
154            quote::quote! {
155                fn to_enum(input: i32) -> Result<::tinc::__private::cel::CelValue<'static>, ::tinc::__private::cel::CelError<'static>> {
156                    Ok(#output)
157                }
158
159                #[test]
160                fn test_to_enum() {
161                    #[::tinc::reexports::linkme::distributed_slice(::tinc::__private::cel::TINC_CEL_ENUM_VTABLE)]
162                    #[linkme(crate = ::tinc::reexports::linkme)]
163                    static ENUM_VTABLE: ::tinc::__private::cel::EnumVtable = ::tinc::__private::cel::EnumVtable {
164                        proto_path: "some.Enum",
165                        is_valid: |_| {
166                            true
167                        },
168                        to_serde: |_| {
169                            ::tinc::__private::cel::CelValue::String(::tinc::__private::cel::CelString::Borrowed("SERDE"))
170                        },
171                        to_proto: |_| {
172                            ::tinc::__private::cel::CelValue::String(::tinc::__private::cel::CelString::Borrowed("PROTO"))
173                        }
174                    };
175
176                    ::tinc::__private::cel::CelMode::Serde.set();
177                    assert_eq!(to_enum(1).unwrap().to_string(), "SERDE");
178                    ::tinc::__private::cel::CelMode::Proto.set();
179                    assert_eq!(to_enum(1).unwrap().to_string(), "PROTO");
180                }
181            },
182        ));
183    }
184}