tinc_build/codegen/cel/functions/
string.rs

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