tinc_build/codegen/
serde.rs

1use anyhow::Context;
2use quote::{ToTokens, quote};
3use syn::parse_quote;
4
5use super::Package;
6use super::cel::compiler::{CompiledExpr, Compiler};
7use super::cel::types::CelType;
8use super::cel::{CelExpression, eval_message_fmt, functions};
9use crate::types::{
10    ProtoEnumType, ProtoFieldOptions, ProtoFieldSerdeOmittable, ProtoMessageField, ProtoMessageType, ProtoModifiedValueType,
11    ProtoOneOfType, ProtoType, ProtoTypeRegistry, ProtoValueType, ProtoVisibility, Tagged,
12};
13
14fn handle_oneof(
15    package: &mut Package,
16    field_name: &str,
17    oneof: &ProtoOneOfType,
18    registry: &ProtoTypeRegistry,
19    visibility: ProtoVisibility,
20) -> anyhow::Result<()> {
21    let message_config = package.message_config(&oneof.message);
22    message_config.field_attribute(field_name, parse_quote!(#[tinc(oneof)]));
23
24    let oneof_config = message_config.oneof_config(field_name);
25
26    if visibility.has_output() {
27        oneof_config.attribute(parse_quote!(#[derive(::tinc::reexports::serde_derive::Serialize)]));
28    }
29
30    oneof_config.attribute(parse_quote!(#[derive(::tinc::__private::Tracker)]));
31
32    let variant_identifier_ident = quote::format_ident!("___identifier");
33    let mut oneof_identifier_for_ident = variant_identifier_ident.clone();
34    let mut variant_idents = Vec::new();
35    let mut variant_name_fn = Vec::new();
36    let mut variant_from_str_fn = Vec::new();
37    let mut variant_fields = Vec::new();
38    let mut variant_enum_ident = Vec::new();
39    let mut deserializer_impl_fn = Vec::new();
40    let mut validate_message_impl = Vec::new();
41
42    let tagged_impl = if let Some(Tagged { tag, content }) = &oneof.options.tagged {
43        oneof_config.attribute(parse_quote!(#[serde(tag = #tag, content = #content)]));
44        oneof_config.attribute(parse_quote!(#[tinc(tagged)]));
45        oneof_identifier_for_ident = quote::format_ident!("___tagged_identifier");
46        quote! {
47            #[derive(
48                ::std::fmt::Debug,
49                ::std::clone::Clone,
50                ::core::marker::Copy,
51                ::core::cmp::PartialEq,
52                ::core::cmp::Eq,
53                ::core::hash::Hash,
54                ::core::cmp::Ord,
55                ::core::cmp::PartialOrd,
56            )]
57            #[allow(non_camel_case_types)]
58            pub enum #oneof_identifier_for_ident {
59                ___tag,
60                ___content,
61            }
62
63            impl ::tinc::__private::Identifier for #oneof_identifier_for_ident {
64                const OPTIONS: &'static [&'static str] = &[
65                    #tag,
66                    #content,
67                ];
68
69                fn name(&self) -> &'static str {
70                    match self {
71                        #oneof_identifier_for_ident::___tag => #tag,
72                        #oneof_identifier_for_ident::___content => #content,
73                    }
74                }
75            }
76
77            impl ::core::str::FromStr for #oneof_identifier_for_ident {
78                type Err = ();
79
80                fn from_str(s: &str) -> Result<Self, Self::Err> {
81                    ::tinc::__tinc_field_from_str!(s,
82                        #tag => #oneof_identifier_for_ident::___tag,
83                        #content => #oneof_identifier_for_ident::___content,
84                    )
85                }
86            }
87
88            impl ::tinc::__private::TaggedOneOfIdentifier for #oneof_identifier_for_ident {
89                const TAG: Self = #oneof_identifier_for_ident::___tag;
90                const CONTENT: Self = #oneof_identifier_for_ident::___content;
91            }
92        }
93    } else {
94        quote! {}
95    };
96
97    for (field_name, field) in &oneof.fields {
98        anyhow::ensure!(!field.options.flatten, "oneof fields cannot be flattened");
99
100        let ident = quote::format_ident!("__field_{field_name}");
101        let serde_name = &field.options.serde_name;
102
103        oneof_config.field_attribute(field_name, parse_quote!(#[serde(rename = #serde_name)]));
104        if visibility.has_output() && !field.options.visibility.has_output() {
105            oneof_config.field_attribute(field_name, parse_quote!(#[serde(skip_serializing)]));
106        }
107
108        if field.options.visibility.has_input() {
109            variant_idents.push(ident.clone());
110            variant_name_fn.push(quote! {
111                #variant_identifier_ident::#ident => #serde_name
112            });
113            variant_from_str_fn.push(quote! {
114                #serde_name => #variant_identifier_ident::#ident
115            });
116            variant_fields.push(quote! {
117                #serde_name
118            });
119            let enum_ident = field.rust_ident();
120            variant_enum_ident.push(enum_ident.clone());
121            deserializer_impl_fn.push(quote! {
122                #variant_identifier_ident::#ident => {
123                    let tracker = match tracker {
124                        ::core::option::Option::None => {
125                            let ___Tracker::#enum_ident(tracker) = tracker.get_or_insert_with(|| ___Tracker::#enum_ident(Default::default())) else {
126                                ::core::unreachable!()
127                            };
128
129                            tracker
130                        },
131                        ::core::option::Option::Some(___Tracker::#enum_ident(tracker)) => {
132                            if !::tinc::__private::tracker_allow_duplicates(Some(tracker)) {
133                                return ::tinc::__private::report_tracked_error(
134                                    ::tinc::__private::TrackedError::duplicate_field(),
135                                );
136                            }
137
138                            tracker
139                        },
140                        ::core::option::Option::Some(tracker) => {
141                            return ::core::result::Result::Err(
142                                ::tinc::reexports::serde::de::Error::invalid_type(
143                                    ::tinc::reexports::serde::de::Unexpected::Other(
144                                        ::tinc::__private::Identifier::name(&Self::tracker_to_identifier(tracker)),
145                                    ),
146                                    &::tinc::__private::Identifier::name(&#variant_identifier_ident::#ident),
147                                ),
148                            );
149                        }
150                    };
151
152                    let value = match value.get_or_insert_with(|| Self::#enum_ident(Default::default())) {
153                        Self::#enum_ident(value) => value,
154                        value => {
155                            return ::core::result::Result::Err(
156                                ::tinc::reexports::serde::de::Error::invalid_type(
157                                    ::tinc::reexports::serde::de::Unexpected::Other(
158                                        ::tinc::__private::Identifier::name(&Self::value_to_identifier(value)),
159                                    ),
160                                    &::tinc::__private::Identifier::name(&#variant_identifier_ident::#ident),
161                                ),
162                            );
163                        }
164                    };
165
166                    ::tinc::__private::TrackerDeserializer::deserialize(
167                        tracker,
168                        value,
169                        deserializer,
170                    )?;
171                }
172            });
173
174            let cel_validation_fn = cel_expressions(
175                registry,
176                &ProtoType::Value(field.ty.clone()),
177                &field.full_name,
178                &field.options,
179                quote!(*value),
180                quote!(tracker),
181            )?;
182
183            let serde_name = if let Some(tagged) = &oneof.options.tagged {
184                tagged.content.as_str()
185            } else {
186                field.options.serde_name.as_str()
187            };
188
189            validate_message_impl.push(quote! {
190                (Self::#enum_ident(value)) => {
191                    let _token = ::tinc::__private::ProtoPathToken::push_field(#field_name);
192                    let _token = ::tinc::__private::SerdePathToken::push_field(#serde_name);
193                    let tracker = match tracker {
194                        ::core::option::Option::Some(___Tracker::#enum_ident(tracker)) => ::core::option::Option::Some(tracker),
195                        ::core::option::Option::Some(t) => return ::core::result::Result::Err(
196                            ::tinc::reexports::serde::de::Error::custom(format!(
197                                "tracker and value do not match: {:?} != {:?}",
198                                ::tinc::__private::Identifier::name(&<Self as ::tinc::__private::TrackedOneOfDeserializer<'_>>::tracker_to_identifier(t)),
199                                ::tinc::__private::Identifier::name(&<Self as ::tinc::__private::TrackedOneOfDeserializer<'_>>::value_to_identifier(self)),
200                            )),
201                        ),
202                        ::core::option::Option::None => ::core::option::Option::None,
203                    };
204                    #(#cel_validation_fn)*
205                }
206            });
207        }
208
209        match &field.ty {
210            ProtoValueType::Enum(path) => {
211                let path_str = registry
212                    .resolve_rust_path(&oneof.message, path)
213                    .expect("enum not found")
214                    .to_token_stream()
215                    .to_string();
216
217                if field.options.visibility.has_output() {
218                    let serialize_with = format!("::tinc::__private::serialize_enum::<{path_str}, _, _>");
219                    oneof_config.field_attribute(field_name, parse_quote!(#[serde(serialize_with = #serialize_with)]));
220                }
221
222                oneof_config.field_attribute(field_name, parse_quote!(#[tinc(enum = #path_str)]));
223            }
224            ProtoValueType::WellKnown(_) | ProtoValueType::Bytes => {
225                if field.options.visibility.has_output() {
226                    oneof_config.field_attribute(
227                        field_name,
228                        parse_quote!(#[serde(serialize_with = "::tinc::__private::serialize_well_known")]),
229                    );
230                }
231            }
232            ProtoValueType::Float | ProtoValueType::Double => {
233                if registry.support_non_finite_vals(&field.full_name) {
234                    if field.options.visibility.has_output() {
235                        oneof_config.field_attribute(
236                            field_name,
237                            parse_quote!(#[serde(serialize_with = "::tinc::__private::serialize_floats_with_non_finite")]),
238                        );
239                    }
240                    if field.options.visibility.has_input() {
241                        oneof_config.field_attribute(field_name, parse_quote!(#[tinc(with_non_finite_values)]));
242                    }
243                }
244            }
245            _ => {}
246        }
247    }
248
249    let message = registry.get_message(&oneof.message).expect("message not found");
250
251    let oneof_path = oneof.rust_path(&message.package);
252    let oneof_ident = oneof_path.segments.last().unwrap().ident.clone();
253    let bug_message = quote::quote!(::core::unreachable!(
254        "oneof has no valid variants, this should never happen, please report this as a bug in tinc"
255    ));
256
257    package.push_item(parse_quote! {
258        #[allow(clippy::all, dead_code, unused_imports, unused_variables, unused_parens, unreachable_patterns, unreachable_code)]
259        const _: () = {
260            #tagged_impl
261
262            #[derive(
263                ::std::fmt::Debug,
264                ::std::clone::Clone,
265                ::core::marker::Copy,
266                ::core::cmp::PartialEq,
267                ::core::cmp::Eq,
268                ::core::hash::Hash,
269                ::core::cmp::Ord,
270                ::core::cmp::PartialOrd,
271            )]
272            #[allow(non_camel_case_types)]
273            pub enum #variant_identifier_ident {
274                #(#variant_idents),*
275            }
276
277            impl ::tinc::__private::Identifier for #variant_identifier_ident {
278                const OPTIONS: &'static [&'static str] = &[#(#variant_fields),*];
279
280                fn name(&self) -> &'static str {
281                    match self {
282                        #(#variant_name_fn,)*
283                        _ => #bug_message,
284                    }
285                }
286            }
287
288            impl ::core::str::FromStr for #variant_identifier_ident {
289                type Err = ();
290
291                fn from_str(s: &str) -> Result<Self, Self::Err> {
292                    ::tinc::__tinc_field_from_str!(s, #(#variant_from_str_fn),*)
293                }
294            }
295
296            impl ::tinc::__private::Expected for #oneof_path {
297                fn expecting(formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
298                    write!(formatter, stringify!(#oneof_ident))
299                }
300            }
301
302            impl ::tinc::__private::IdentifierFor for #oneof_path {
303                const NAME: &'static str = stringify!(#oneof_ident);
304                type Identifier = #oneof_identifier_for_ident;
305            }
306
307            impl ::tinc::__private::TrackedOneOfVariant for #oneof_path {
308                type Variant = #variant_identifier_ident;
309            }
310
311            type ___Tracker = <<#oneof_path as ::tinc::__private::TrackerFor>::Tracker as ::tinc::__private::TrackerWrapper>::Tracker;
312
313            impl<'de> ::tinc::__private::TrackedOneOfDeserializer<'de> for #oneof_path {
314                fn deserialize<D>(
315                    value: &mut ::core::option::Option<#oneof_path>,
316                    variant: #variant_identifier_ident,
317                    tracker: &mut ::core::option::Option<___Tracker>,
318                    deserializer: D,
319                ) -> ::core::result::Result<(), D::Error>
320                where
321                    D: ::tinc::__private::DeserializeContent<'de>
322                {
323                    match variant {
324                        #(#deserializer_impl_fn,)*
325                        _ => #bug_message,
326                    }
327
328                    ::core::result::Result::Ok(())
329                }
330
331                fn tracker_to_identifier(v: &___Tracker) -> #variant_identifier_ident {
332                    match v {
333                        #(___Tracker::#variant_enum_ident(_) => #variant_identifier_ident::#variant_idents,)*
334                        _ => #bug_message,
335                    }
336                }
337
338                fn value_to_identifier(v: &#oneof_path) -> #variant_identifier_ident {
339                    match v {
340                        #(#oneof_path::#variant_enum_ident(_) => #variant_identifier_ident::#variant_idents,)*
341                        _ => #bug_message,
342                    }
343                }
344            }
345
346            impl ::tinc::__private::TincValidate for #oneof_path {
347                fn validate(&self, tracker: Option<&<#oneof_path as ::tinc::__private::TrackerFor>::Tracker>) -> ::core::result::Result<(), ::tinc::__private::ValidationError> {
348                    let tracker = tracker.and_then(|t| t.as_ref());
349                    match self {
350                        #(#validate_message_impl,)*
351                        _ => #bug_message,
352                    }
353
354                    ::core::result::Result::Ok(())
355                }
356            }
357        };
358    });
359
360    Ok(())
361}
362
363struct FieldBuilder<'a> {
364    deserializer_fields: &'a mut Vec<proc_macro2::TokenStream>,
365    field_enum_variants: &'a mut Vec<proc_macro2::TokenStream>,
366    field_enum_name_fn: &'a mut Vec<proc_macro2::TokenStream>,
367    field_enum_from_str_fn: &'a mut Vec<proc_macro2::TokenStream>,
368    field_enum_from_str_flattened_fn: &'a mut Vec<proc_macro2::TokenStream>,
369    deserializer_fn: &'a mut Vec<proc_macro2::TokenStream>,
370    cel_validation_fn: &'a mut Vec<proc_macro2::TokenStream>,
371}
372
373fn handle_message_field(
374    package: &mut Package,
375    field_name: &str,
376    field: &ProtoMessageField,
377    field_builder: FieldBuilder<'_>,
378    field_enum_ident: &syn::Ident,
379    registry: &ProtoTypeRegistry,
380) -> anyhow::Result<()> {
381    let serde_name = &field.options.serde_name;
382
383    let message_config = package.message_config(&field.message);
384
385    message_config.field_attribute(field_name, parse_quote!(#[serde(rename = #serde_name)]));
386
387    let message = registry.get_message(&field.message).expect("message not found");
388
389    let ident = quote::format_ident!("__field_{field_name}");
390    if field.options.flatten {
391        let flattened_ty_path = match &field.ty {
392            ProtoType::Modified(ProtoModifiedValueType::Optional(ProtoValueType::Message(path)))
393            | ProtoType::Value(ProtoValueType::Message(path)) => {
394                registry.resolve_rust_path(&message.package, path).expect("message not found")
395            }
396            ProtoType::Modified(ProtoModifiedValueType::OneOf(oneof)) => oneof.rust_path(&message.package),
397            _ => anyhow::bail!("flattened fields must be messages or oneofs"),
398        };
399
400        if field.options.visibility.has_output() {
401            message_config.field_attribute(field_name, parse_quote!(#[serde(flatten)]));
402        }
403
404        if field.options.visibility.has_input() {
405            let flattened_identifier = quote! {
406                <#flattened_ty_path as ::tinc::__private::IdentifierFor>::Identifier
407            };
408
409            field_builder.deserializer_fields.push(quote! {
410                <#flattened_identifier as ::tinc::__private::Identifier>::OPTIONS
411            });
412            field_builder.field_enum_variants.push(quote! {
413                #ident(#flattened_identifier)
414            });
415            field_builder.field_enum_name_fn.push(quote! {
416                #field_enum_ident::#ident(flatten) => ::tinc::__private::Identifier::name(flatten)
417            });
418            field_builder.field_enum_from_str_flattened_fn.push(quote! {
419                #ident
420            });
421        }
422    } else if field.options.visibility.has_input() {
423        field_builder.deserializer_fields.push(quote! {
424            &[#serde_name]
425        });
426        field_builder.field_enum_variants.push(quote! {
427            #ident
428        });
429        field_builder.field_enum_name_fn.push(quote! {
430            #field_enum_ident::#ident => #serde_name
431        });
432        field_builder.field_enum_from_str_fn.push(quote! {
433            #serde_name => #field_enum_ident::#ident
434        });
435    }
436
437    if field.options.visibility.has_output() {
438        if matches!(field.options.serde_omittable, ProtoFieldSerdeOmittable::True) {
439            message_config.field_attribute(
440                field_name,
441                parse_quote!(#[serde(skip_serializing_if = "::tinc::__private::serde_ser_skip_default")]),
442            );
443        }
444    } else {
445        message_config.field_attribute(field_name, parse_quote!(#[serde(skip_serializing)]));
446    }
447
448    match field.ty.value_type() {
449        Some(ProtoValueType::Enum(path)) => {
450            let path_str = registry
451                .resolve_rust_path(message.full_name.trim_last_segment(), path)
452                .expect("enum not found")
453                .to_token_stream()
454                .to_string();
455
456            if field.options.visibility.has_output() {
457                let serialize_with = format!("::tinc::__private::serialize_enum::<{path_str}, _, _>");
458                message_config.field_attribute(field_name, parse_quote!(#[serde(serialize_with = #serialize_with)]));
459            }
460
461            message_config.field_attribute(field_name, parse_quote!(#[tinc(enum = #path_str)]));
462        }
463        Some(ProtoValueType::WellKnown(_) | ProtoValueType::Bytes) => {
464            if field.options.visibility.has_output() {
465                message_config.field_attribute(
466                    field_name,
467                    parse_quote!(#[serde(serialize_with = "::tinc::__private::serialize_well_known")]),
468                );
469            }
470        }
471        Some(ProtoValueType::Float | ProtoValueType::Double) => {
472            if registry.support_non_finite_vals(&field.full_name) {
473                if field.options.visibility.has_output() {
474                    message_config.field_attribute(
475                        field_name,
476                        parse_quote!(#[serde(serialize_with = "::tinc::__private::serialize_floats_with_non_finite")]),
477                    );
478                }
479                if field.options.visibility.has_input() {
480                    message_config.field_attribute(field_name, parse_quote!(#[tinc(with_non_finite_values)]));
481                }
482            }
483        }
484        _ => {}
485    }
486
487    if let ProtoType::Modified(ProtoModifiedValueType::OneOf(oneof)) = &field.ty {
488        handle_oneof(package, field_name, oneof, registry, field.options.visibility)?;
489    }
490
491    let field_ident = field.rust_ident();
492
493    let mut tracker = quote! {
494        &mut tracker.#field_ident
495    };
496
497    let mut value = quote! {
498        &mut self.#field_ident
499    };
500
501    // When a field is not nullable but prost generates an option<T>, we need to
502    // remove the option before deserializing otherwise null will be a valid input.
503    if matches!(field.ty, ProtoType::Modified(ProtoModifiedValueType::Optional(_)))
504        && (!field.options.nullable || field.options.flatten)
505    {
506        tracker = quote! {
507            (#tracker).get_or_insert_default()
508        };
509
510        value = quote! {
511            (#value).get_or_insert_default()
512        };
513    }
514
515    if field.options.visibility.has_input() {
516        if field.options.flatten {
517            field_builder.deserializer_fn.push(quote! {
518                #field_enum_ident::#ident(field) => {
519                    if let Err(error) = ::tinc::__private::TrackerDeserializeIdentifier::<'de>::deserialize(
520                        (#tracker).get_or_insert_default(),
521                        #value,
522                        field,
523                        deserializer,
524                    ) {
525                        return ::tinc::__private::report_de_error(error);
526                    }
527                }
528            });
529        } else {
530            field_builder.deserializer_fn.push(quote! {
531                #field_enum_ident::#ident => {
532                    let tracker = #tracker;
533
534                    if !::tinc::__private::tracker_allow_duplicates(tracker.as_ref()) {
535                        return ::tinc::__private::report_tracked_error(
536                            ::tinc::__private::TrackedError::duplicate_field(),
537                        );
538                    }
539
540                    if let Err(error) = ::tinc::__private::TrackerDeserializer::deserialize(
541                        tracker.get_or_insert_default(),
542                        #value,
543                        deserializer,
544                    ) {
545                        return ::tinc::__private::report_de_error(error);
546                    }
547                }
548            });
549        }
550    }
551
552    let push_field_token = if !field.options.flatten {
553        quote! {
554            let _token = ::tinc::__private::SerdePathToken::push_field(
555                ::tinc::__private::Identifier::name(&#field_enum_ident::#ident),
556            );
557        }
558    } else {
559        quote! {}
560    };
561
562    let missing = if matches!(field.options.serde_omittable, ProtoFieldSerdeOmittable::False) && !field.options.flatten {
563        quote! {
564            ::tinc::__private::report_tracked_error(
565                ::tinc::__private::TrackedError::missing_field(),
566            )?;
567        }
568    } else {
569        quote! {}
570    };
571
572    let mut tracker_access = quote!(tracker.and_then(|t| t.#field_ident.as_ref()));
573    if matches!(field.ty, ProtoType::Modified(ProtoModifiedValueType::Optional(_))) {
574        tracker_access = quote!(#tracker_access.and_then(|t| t.as_ref()))
575    }
576
577    if field.options.visibility.has_input() {
578        let cel_validation_fn = cel_expressions(
579            registry,
580            &field.ty,
581            &field.full_name,
582            &field.options,
583            quote!(self.#field_ident),
584            tracker_access,
585        )?;
586
587        field_builder.cel_validation_fn.push(quote!({
588            let _token = ::tinc::__private::ProtoPathToken::push_field(#field_name);
589            #push_field_token
590
591            // TODO: this seems wrong. I feel as if we should validate it even if omittable is true.
592            if tracker.is_none_or(|t| t.#field_ident.is_some()) {
593                #(#cel_validation_fn)*
594            } else {
595                #missing
596            }
597        }));
598    }
599
600    Ok(())
601}
602
603fn cel_expressions(
604    registry: &ProtoTypeRegistry,
605    ty: &ProtoType,
606    field_full_name: &str,
607    options: &ProtoFieldOptions,
608    value_accessor: proc_macro2::TokenStream,
609    tracker_accessor: proc_macro2::TokenStream,
610) -> anyhow::Result<Vec<proc_macro2::TokenStream>> {
611    let compiler = Compiler::new(registry);
612    let mut cel_validation_fn = Vec::new();
613
614    let evaluate_expr = |ctx: &Compiler, expr: &CelExpression| {
615        let mut ctx = ctx.child();
616        if let Some(this) = expr.this.clone() {
617            ctx.add_variable("this", CompiledExpr::constant(this));
618        }
619        let parsed = cel_parser::parse(&expr.expression).context("expression parse")?;
620        let resolved = ctx.resolve(&parsed).context("cel expression")?;
621        let expr_str = &expr.expression;
622        let message = eval_message_fmt(field_full_name, &expr.message, &ctx).context("message")?;
623
624        anyhow::Ok(quote! {
625            if !::tinc::__private::cel::to_bool({
626                (|| {
627                    ::core::result::Result::Ok::<_, ::tinc::__private::cel::CelError>(#resolved)
628                })().map_err(|err| {
629                    ::tinc::__private::ValidationError::Expression {
630                        error: err.to_string().into_boxed_str(),
631                        field: #field_full_name,
632                        expression: #expr_str,
633                    }
634                })?
635            }) {
636                ::tinc::__private::report_tracked_error(
637                    ::tinc::__private::TrackedError::invalid_field(#message)
638                )?;
639            }
640        })
641    };
642
643    {
644        let mut compiler = compiler.child();
645        let (value_match, field_type) = match ty {
646            ProtoType::Modified(ProtoModifiedValueType::Optional(ty)) => (quote!(Some(value)), ProtoType::Value(ty.clone())),
647            ty @ ProtoType::Modified(ProtoModifiedValueType::OneOf(_)) => (quote!(Some(value)), ty.clone()),
648            _ => (quote!(value), ty.clone()),
649        };
650
651        if let ProtoType::Value(ProtoValueType::Enum(path))
652        | ProtoType::Modified(ProtoModifiedValueType::Optional(ProtoValueType::Enum(path))) = ty
653        {
654            compiler.register_function(functions::Enum(Some(path.clone())));
655        }
656
657        let recursive_validate = matches!(
658            field_type,
659            ProtoType::Value(ProtoValueType::Message(_)) | ProtoType::Modified(ProtoModifiedValueType::OneOf(_))
660        );
661
662        compiler.add_variable(
663            "input",
664            CompiledExpr::runtime(CelType::Proto(field_type), parse_quote!(value)),
665        );
666        let mut exprs = options
667            .cel_exprs
668            .field
669            .iter()
670            .map(|expr| evaluate_expr(&compiler, expr))
671            .collect::<anyhow::Result<Vec<_>>>()?;
672
673        if recursive_validate {
674            exprs.push(quote! {
675                ::tinc::__private::TincValidate::validate(value, #tracker_accessor)?;
676            })
677        }
678
679        if !exprs.is_empty() {
680            cel_validation_fn.push(quote! {{
681                #[allow(irrefutable_let_patterns)]
682                if let #value_match = &#value_accessor {
683                    #(#exprs)*
684                }
685            }});
686        }
687
688        if !options.nullable
689            && matches!(
690                &ty,
691                ProtoType::Modified(ProtoModifiedValueType::Optional(_) | ProtoModifiedValueType::OneOf(_))
692            )
693        {
694            cel_validation_fn.push(quote! {{
695                if #value_accessor.is_none() {
696                    ::tinc::__private::report_tracked_error(
697                        ::tinc::__private::TrackedError::missing_field()
698                    )?;
699                }
700            }})
701        }
702    }
703
704    match ty {
705        ProtoType::Modified(ProtoModifiedValueType::Map(key, value))
706            if !options.cel_exprs.map_key.is_empty()
707                || !options.cel_exprs.map_value.is_empty()
708                || matches!(value, ProtoValueType::Message(_)) =>
709        {
710            let key_exprs = {
711                let mut compiler = compiler.child();
712
713                if let ProtoValueType::Enum(path) = key {
714                    compiler.register_function(functions::Enum(Some(path.clone())));
715                }
716
717                compiler.add_variable(
718                    "input",
719                    CompiledExpr::runtime(CelType::Proto(ProtoType::Value(key.clone())), parse_quote!(key)),
720                );
721                options
722                    .cel_exprs
723                    .map_key
724                    .iter()
725                    .map(|expr| evaluate_expr(&compiler, expr))
726                    .collect::<anyhow::Result<Vec<_>>>()?
727            };
728
729            let is_message = matches!(value, ProtoValueType::Message(_));
730
731            let mut value_exprs = {
732                let mut compiler = compiler.child();
733                if let ProtoValueType::Enum(path) = value {
734                    compiler.register_function(functions::Enum(Some(path.clone())));
735                }
736                compiler.add_variable(
737                    "input",
738                    CompiledExpr::runtime(CelType::Proto(ProtoType::Value(value.clone())), parse_quote!(value)),
739                );
740                options
741                    .cel_exprs
742                    .map_value
743                    .iter()
744                    .map(|expr| evaluate_expr(&compiler, expr))
745                    .collect::<anyhow::Result<Vec<_>>>()?
746            };
747
748            if is_message {
749                value_exprs.push(quote!({
750                    let tracker = match #tracker_accessor {
751                        ::core::option::Option::Some(t) => Some(t.get(key).expect("map tracker state missing item, this is a bug report it.")),
752                        ::core::option::Option::None => None
753                    };
754                    ::tinc::__private::TincValidate::validate(value, tracker)?;
755                }));
756            }
757
758            cel_validation_fn.push(quote! {{
759                for (key, value) in &#value_accessor {
760                    let _token = ::tinc::__private::ProtoPathToken::push_field(key);
761                    let _token = ::tinc::__private::SerdePathToken::push_field(key);
762                    #(#key_exprs)*
763                    #(#value_exprs)*
764                }
765            }});
766        }
767        ProtoType::Modified(ProtoModifiedValueType::Repeated(item))
768            if !options.cel_exprs.repeated_item.is_empty() || matches!(item, ProtoValueType::Message(_)) =>
769        {
770            let is_message = matches!(item, ProtoValueType::Message(_));
771            let mut compiler = compiler.child();
772            if let ProtoValueType::Enum(path) = item {
773                compiler.register_function(functions::Enum(Some(path.clone())));
774            }
775            compiler.add_variable(
776                "input",
777                CompiledExpr::runtime(CelType::Proto(ProtoType::Value(item.clone())), parse_quote!(item)),
778            );
779
780            let mut exprs = options
781                .cel_exprs
782                .repeated_item
783                .iter()
784                .map(|expr| evaluate_expr(&compiler, expr))
785                .collect::<anyhow::Result<Vec<_>>>()?;
786
787            if is_message {
788                exprs.push(quote!({
789                    let tracker = match #tracker_accessor {
790                        ::core::option::Option::Some(t) => Some(t.get(idx).expect("repeated tracker state missing item, this is a bug report it.")),
791                        ::core::option::Option::None => None
792                    };
793                    ::tinc::__private::TincValidate::validate(item, tracker)?;
794                }));
795            }
796
797            cel_validation_fn.push(quote! {{
798                for (idx, item) in #value_accessor.iter().enumerate() {
799                    let _token = ::tinc::__private::ProtoPathToken::push_index(idx);
800                    let _token = ::tinc::__private::SerdePathToken::push_index(idx);
801                    #(#exprs)*
802                }
803            }});
804        }
805        _ => {}
806    }
807
808    Ok(cel_validation_fn)
809}
810
811pub(super) fn handle_message(
812    message: &ProtoMessageType,
813    package: &mut Package,
814    registry: &ProtoTypeRegistry,
815) -> anyhow::Result<()> {
816    let message_config = package.message_config(&message.full_name);
817
818    message_config.attribute(parse_quote!(#[derive(::tinc::reexports::serde_derive::Serialize)]));
819    message_config.attribute(parse_quote!(#[serde(crate = "::tinc::reexports::serde")]));
820    message_config.attribute(parse_quote!(#[derive(::tinc::__private::Tracker)]));
821
822    let field_enum_ident = quote::format_ident!("___field_enum");
823
824    let mut field_enum_variants = Vec::new();
825    let mut field_enum_name_fn = Vec::new();
826    let mut field_enum_from_str_fn = Vec::new();
827    let mut field_enum_from_str_flattened_fn = Vec::new();
828    let mut deserializer_fields = Vec::new();
829    let mut deserializer_fn = Vec::new();
830    let mut cel_validation_fn = Vec::new();
831
832    for (field_name, field) in message.fields.iter() {
833        handle_message_field(
834            package,
835            field_name,
836            field,
837            FieldBuilder {
838                deserializer_fields: &mut deserializer_fields,
839                field_enum_variants: &mut field_enum_variants,
840                field_enum_name_fn: &mut field_enum_name_fn,
841                field_enum_from_str_fn: &mut field_enum_from_str_fn,
842                field_enum_from_str_flattened_fn: &mut field_enum_from_str_flattened_fn,
843                deserializer_fn: &mut deserializer_fn,
844                cel_validation_fn: &mut cel_validation_fn,
845            },
846            &field_enum_ident,
847            registry,
848        )?;
849    }
850
851    let bug_message = quote::quote!(::core::unreachable!(
852        "message has no fields, this should never happen, please report this as a bug in tinc"
853    ));
854
855    let message_path = registry
856        .resolve_rust_path(&message.package, &message.full_name)
857        .expect("message not found");
858    let message_ident = message_path.segments.last().unwrap().ident.clone();
859
860    package.push_item(parse_quote! {
861        #[allow(clippy::all, dead_code, unused_imports, unused_variables, unused_parens, unreachable_patterns, unreachable_code)]
862        const _: () = {
863            #[derive(
864                ::std::fmt::Debug,
865                ::std::clone::Clone,
866                ::core::marker::Copy,
867                ::core::cmp::PartialEq,
868                ::core::cmp::Eq,
869                ::core::hash::Hash,
870                ::core::cmp::Ord,
871                ::core::cmp::PartialOrd,
872            )]
873            #[allow(non_camel_case_types)]
874            pub enum #field_enum_ident {
875                #(#field_enum_variants),*
876            }
877
878            impl ::tinc::__private::Identifier for #field_enum_ident {
879                const OPTIONS: &'static [&'static str] = ::tinc::__private_const_concat_str_array!(#(#deserializer_fields),*);
880
881                fn name(&self) -> &'static str {
882                    match self {
883                        #(#field_enum_name_fn,)*
884                        _ => #bug_message,
885                    }
886                }
887            }
888
889            impl ::core::str::FromStr for #field_enum_ident {
890                type Err = ();
891
892                fn from_str(s: &str) -> Result<Self, Self::Err> {
893                    ::tinc::__tinc_field_from_str!(s, #(#field_enum_from_str_fn),*, flattened: [#(#field_enum_from_str_flattened_fn),*])
894                }
895            }
896
897            impl ::tinc::__private::IdentifierFor for #message_path {
898                const NAME: &'static str = stringify!(#message_ident);
899                type Identifier = #field_enum_ident;
900            }
901
902            type ___Tracker = <<#message_path as ::tinc::__private::TrackerFor>::Tracker as ::tinc::__private::TrackerWrapper>::Tracker;
903
904            impl<'de> ::tinc::__private::TrackedStructDeserializer<'de> for #message_path {
905                #[allow(unused_mut, dead_code)]
906                fn deserialize<D>(
907                    &mut self,
908                    field: Self::Identifier,
909                    mut tracker: &mut ___Tracker,
910                    deserializer: D,
911                ) -> Result<(), D::Error>
912                where
913                    D: ::tinc::__private::DeserializeContent<'de>,
914                {
915                    match field {
916                        #(#deserializer_fn,)*
917                        _ => #bug_message,
918                    }
919
920                    ::core::result::Result::Ok(())
921                }
922            }
923
924            impl ::tinc::__private::Expected for #message_path {
925                fn expecting(formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
926                    write!(formatter, stringify!(#message_ident))
927                }
928            }
929
930            impl ::tinc::__private::TincValidate for #message_path {
931                fn validate(&self, tracker: Option<&<#message_path as ::tinc::__private::TrackerFor>::Tracker>) -> ::core::result::Result<(), ::tinc::__private::ValidationError> {
932                    let tracker = tracker.map(|t| &**t);
933                    #(#cel_validation_fn)*
934                    ::core::result::Result::Ok(())
935                }
936            }
937        };
938    });
939
940    Ok(())
941}
942
943pub(super) fn handle_enum(enum_: &ProtoEnumType, package: &mut Package, registry: &ProtoTypeRegistry) -> anyhow::Result<()> {
944    let enum_path = registry
945        .resolve_rust_path(&enum_.package, &enum_.full_name)
946        .expect("enum not found");
947    let enum_ident = enum_path.segments.last().unwrap().ident.clone();
948    let enum_config = package.enum_config(&enum_.full_name);
949
950    if enum_.options.repr_enum {
951        enum_config.attribute(parse_quote!(#[derive(::tinc::reexports::serde_repr::Serialize_repr)]));
952        enum_config.attribute(parse_quote!(#[derive(::tinc::reexports::serde_repr::Deserialize_repr)]));
953    } else {
954        enum_config.attribute(parse_quote!(#[derive(::tinc::reexports::serde_derive::Serialize)]));
955        enum_config.attribute(parse_quote!(#[derive(::tinc::reexports::serde_derive::Deserialize)]));
956    }
957
958    enum_config.attribute(parse_quote!(#[serde(crate = "::tinc::reexports::serde")]));
959
960    let mut to_serde_matchers = if !enum_.options.repr_enum {
961        Vec::new()
962    } else {
963        vec![quote! {
964            item => ::tinc::__private::cel::CelValueConv::conv(item as i32)
965        }]
966    };
967
968    for (name, variant) in &enum_.variants {
969        if !enum_.options.repr_enum {
970            let serde_name = &variant.options.serde_name;
971            enum_config.variant_attribute(name, parse_quote!(#[serde(rename = #serde_name)]));
972            let ident = &variant.rust_ident;
973            to_serde_matchers.push(quote! {
974                #enum_path::#ident => ::tinc::__private::cel::CelValueConv::conv(#serde_name)
975            })
976        }
977
978        match variant.options.visibility {
979            ProtoVisibility::InputOnly => {
980                enum_config.variant_attribute(name, parse_quote!(#[serde(skip_serializing)]));
981            }
982            ProtoVisibility::OutputOnly => {
983                enum_config.variant_attribute(name, parse_quote!(#[serde(skip_deserializing)]));
984            }
985            ProtoVisibility::Skip => {
986                enum_config.variant_attribute(name, parse_quote!(#[serde(skip)]));
987            }
988            _ => {}
989        }
990    }
991
992    let proto_path = enum_.full_name.as_ref();
993    let bug_message = quote::quote!(::core::unreachable!(
994        "enum has no variants, this should never happen, please report this as a bug in tinc"
995    ));
996
997    package.push_item(parse_quote! {
998        #[allow(clippy::all, dead_code, unused_imports, unused_variables, unused_parens, unreachable_patterns, unreachable_code)]
999        const _: () = {
1000            impl ::tinc::__private::Expected for #enum_path {
1001                fn expecting(formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1002                    write!(formatter, "an enum of `")?;
1003                    write!(formatter, stringify!(#enum_ident))?;
1004                    write!(formatter, "`")
1005                }
1006            }
1007
1008            #[::tinc::reexports::linkme::distributed_slice(::tinc::__private::cel::TINC_CEL_ENUM_VTABLE)]
1009            #[linkme(crate = ::tinc::reexports::linkme)]
1010            static ENUM_VTABLE: ::tinc::__private::cel::EnumVtable = ::tinc::__private::cel::EnumVtable {
1011                proto_path: #proto_path,
1012                is_valid: |tag| {
1013                    <#enum_path as std::convert::TryFrom<i32>>::try_from(tag).is_ok()
1014                },
1015                to_serde: |tag| {
1016                    match <#enum_path as std::convert::TryFrom<i32>>::try_from(tag) {
1017                        Ok(value) => match value {
1018                            #(#to_serde_matchers,)*
1019                            _ => #bug_message,
1020                        }
1021                        Err(_) => ::tinc::__private::cel::CelValue::Null,
1022                    }
1023                },
1024                to_proto: |tag| {
1025                    match <#enum_path as std::convert::TryFrom<i32>>::try_from(tag) {
1026                        Ok(value) => ::tinc::__private::cel::CelValue::String(::tinc::__private::cel::CelString::Borrowed(value.as_str_name())),
1027                        Err(_) => ::tinc::__private::cel::CelValue::Null,
1028                    }
1029                }
1030            };
1031        };
1032    });
1033
1034    Ok(())
1035}