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::extern_paths::ExternPaths;
146    use crate::path_set::PathSet;
147    use crate::types::{ProtoType, ProtoTypeRegistry, ProtoValueType};
148
149    #[test]
150    fn test_string_syntax() {
151        let registry = ProtoTypeRegistry::new(crate::Mode::Prost, ExternPaths::new(crate::Mode::Prost), PathSet::default());
152        let compiler = Compiler::new(&registry);
153        insta::assert_debug_snapshot!(String.compile(CompilerCtx::new(compiler.child(), None, &[])), @r#"
154        Err(
155            InvalidSyntax {
156                message: "missing this",
157                syntax: "<this>.string()",
158            },
159        )
160        "#);
161
162        insta::assert_debug_snapshot!(String.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::constant(CelValue::String("13".into()))), &[])), @r#"
163        Ok(
164            Constant(
165                ConstantCompiledExpr {
166                    value: String(
167                        Borrowed(
168                            "13",
169                        ),
170                    ),
171                },
172            ),
173        )
174        "#);
175
176        insta::assert_debug_snapshot!(String.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::constant(CelValue::List(Default::default()))), &[
177            cel_parser::parse("1 + 1").unwrap(), // not an ident
178        ])), @r#"
179        Err(
180            InvalidSyntax {
181                message: "takes no arguments",
182                syntax: "<this>.string()",
183            },
184        )
185        "#);
186    }
187
188    #[test]
189    #[cfg(not(valgrind))]
190    fn test_string_runtime() {
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 =
195            CompiledExpr::runtime(CelType::Proto(ProtoType::Value(ProtoValueType::String)), parse_quote!(input));
196
197        let output = String
198            .compile(CompilerCtx::new(compiler.child(), Some(string_value), &[]))
199            .unwrap();
200
201        insta::assert_snapshot!(postcompile::compile_str!(
202            postcompile::config! {
203                test: true,
204                dependencies: vec![
205                    postcompile::Dependency::version("tinc", "*"),
206                ],
207            },
208            quote::quote! {
209                fn to_string(input: &str) -> Result<::tinc::__private::cel::CelValue<'_>, ::tinc::__private::cel::CelError<'_>> {
210                    Ok(#output)
211                }
212
213                #[test]
214                fn test_to_int() {
215                    assert_eq!(to_string("55").unwrap(), ::tinc::__private::cel::CelValueConv::conv("55"));
216                }
217            },
218        ));
219    }
220}