tinc_build/codegen/cel/functions/
string.rs1use 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(®istry);
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(), ])), @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(®istry);
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}