tinc_build/
prost_explore.rs

1use std::collections::BTreeMap;
2
3use anyhow::Context;
4use convert_case::{Case, Casing};
5use indexmap::IndexMap;
6use prost_reflect::prost_types::source_code_info::Location;
7use prost_reflect::{
8    DescriptorPool, EnumDescriptor, ExtensionDescriptor, FileDescriptor, Kind, MessageDescriptor, ServiceDescriptor,
9};
10use quote::format_ident;
11use tinc_cel::{CelEnum, CelValueConv};
12
13use crate::codegen::cel::{CelExpression, CelExpressions};
14use crate::codegen::prost_sanatize::{strip_enum_prefix, to_upper_camel};
15use crate::types::{
16    Comments, ProtoEnumOptions, ProtoEnumType, ProtoEnumVariant, ProtoEnumVariantOptions, ProtoFieldOptions,
17    ProtoFieldSerdeOmittable, ProtoMessageField, ProtoMessageOptions, ProtoMessageType, ProtoModifiedValueType,
18    ProtoOneOfField, ProtoOneOfOptions, ProtoOneOfType, ProtoPath, ProtoService, ProtoServiceMethod,
19    ProtoServiceMethodEndpoint, ProtoServiceMethodIo, ProtoServiceOptions, ProtoType, ProtoTypeRegistry, ProtoValueType,
20    ProtoVisibility, Tagged,
21};
22
23pub(crate) struct Extension<T> {
24    name: &'static str,
25    descriptor: Option<ExtensionDescriptor>,
26    _marker: std::marker::PhantomData<T>,
27}
28
29impl<T> Extension<T> {
30    fn new(name: &'static str, pool: &DescriptorPool) -> Self {
31        Self {
32            name,
33            descriptor: pool.get_extension_by_name(name),
34            _marker: std::marker::PhantomData,
35        }
36    }
37
38    fn descriptor(&self) -> Option<&ExtensionDescriptor> {
39        self.descriptor.as_ref()
40    }
41
42    fn decode(&self, incoming: &T::Incoming) -> anyhow::Result<Option<T>>
43    where
44        T: ProstExtension,
45    {
46        let mut messages = self.decode_all(incoming)?;
47        Ok(if messages.is_empty() {
48            None
49        } else {
50            Some(messages.swap_remove(0))
51        })
52    }
53
54    fn decode_all(&self, incoming: &T::Incoming) -> anyhow::Result<Vec<T>>
55    where
56        T: ProstExtension,
57    {
58        let extension = match &self.descriptor {
59            Some(ext) => ext,
60            None => return Ok(Vec::new()),
61        };
62
63        let descriptor = match T::get_options(incoming) {
64            Some(desc) => desc,
65            None => return Ok(Vec::new()),
66        };
67
68        let message = descriptor.get_extension(extension);
69        match message.as_ref() {
70            prost_reflect::Value::Message(message) => {
71                if message.fields().next().is_some() {
72                    let message = message
73                        .transcode_to::<T>()
74                        .with_context(|| format!("{} is not a valid {}", self.name, std::any::type_name::<T>()))?;
75                    Ok(vec![message])
76                } else {
77                    Ok(Vec::new())
78                }
79            }
80            prost_reflect::Value::List(list) => list
81                .iter()
82                .map(|value| {
83                    let message = value.as_message().context("expected a message")?;
84                    message.transcode_to::<T>().context("transcoding failed")
85                })
86                .collect(),
87            _ => anyhow::bail!("expected a message or list of messages"),
88        }
89    }
90}
91
92trait ProstExtension: prost::Message + Default {
93    type Incoming;
94    fn get_options(incoming: &Self::Incoming) -> Option<prost_reflect::DynamicMessage>;
95}
96
97impl ProstExtension for tinc_pb_prost::MessageOptions {
98    type Incoming = prost_reflect::MessageDescriptor;
99
100    fn get_options(incoming: &Self::Incoming) -> Option<prost_reflect::DynamicMessage> {
101        Some(incoming.options())
102    }
103}
104
105impl ProstExtension for tinc_pb_prost::FieldOptions {
106    type Incoming = prost_reflect::FieldDescriptor;
107
108    fn get_options(incoming: &Self::Incoming) -> Option<prost_reflect::DynamicMessage> {
109        Some(incoming.options())
110    }
111}
112
113impl ProstExtension for tinc_pb_prost::PredefinedConstraints {
114    type Incoming = prost_reflect::FieldDescriptor;
115
116    fn get_options(incoming: &Self::Incoming) -> Option<prost_reflect::DynamicMessage> {
117        Some(incoming.options())
118    }
119}
120
121impl ProstExtension for tinc_pb_prost::EnumOptions {
122    type Incoming = prost_reflect::EnumDescriptor;
123
124    fn get_options(incoming: &Self::Incoming) -> Option<prost_reflect::DynamicMessage> {
125        Some(incoming.options())
126    }
127}
128
129impl ProstExtension for tinc_pb_prost::EnumVariantOptions {
130    type Incoming = prost_reflect::EnumValueDescriptor;
131
132    fn get_options(incoming: &Self::Incoming) -> Option<prost_reflect::DynamicMessage> {
133        Some(incoming.options())
134    }
135}
136
137impl ProstExtension for tinc_pb_prost::MethodOptions {
138    type Incoming = prost_reflect::MethodDescriptor;
139
140    fn get_options(incoming: &Self::Incoming) -> Option<prost_reflect::DynamicMessage> {
141        Some(incoming.options())
142    }
143}
144
145impl ProstExtension for tinc_pb_prost::ServiceOptions {
146    type Incoming = prost_reflect::ServiceDescriptor;
147
148    fn get_options(incoming: &Self::Incoming) -> Option<prost_reflect::DynamicMessage> {
149        Some(incoming.options())
150    }
151}
152
153impl ProstExtension for tinc_pb_prost::OneofOptions {
154    type Incoming = prost_reflect::OneofDescriptor;
155
156    fn get_options(incoming: &Self::Incoming) -> Option<prost_reflect::DynamicMessage> {
157        Some(incoming.options())
158    }
159}
160
161fn rename_field(field: &str, style: tinc_pb_prost::RenameAll) -> Option<String> {
162    match style {
163        tinc_pb_prost::RenameAll::LowerCase => Some(field.to_lowercase()),
164        tinc_pb_prost::RenameAll::UpperCase => Some(field.to_uppercase()),
165        tinc_pb_prost::RenameAll::PascalCase => Some(field.to_case(Case::Pascal)),
166        tinc_pb_prost::RenameAll::CamelCase => Some(field.to_case(Case::Camel)),
167        tinc_pb_prost::RenameAll::SnakeCase => Some(field.to_case(Case::Snake)),
168        tinc_pb_prost::RenameAll::KebabCase => Some(field.to_case(Case::Kebab)),
169        tinc_pb_prost::RenameAll::ScreamingSnakeCase => Some(field.to_case(Case::UpperSnake)),
170        tinc_pb_prost::RenameAll::ScreamingKebabCase => Some(field.to_case(Case::UpperKebab)),
171        tinc_pb_prost::RenameAll::Unspecified => None,
172    }
173}
174
175pub(crate) struct Extensions<'a> {
176    pool: &'a DescriptorPool,
177    // Message extensions.
178    ext_message: Extension<tinc_pb_prost::MessageOptions>,
179    ext_field: Extension<tinc_pb_prost::FieldOptions>,
180    ext_oneof: Extension<tinc_pb_prost::OneofOptions>,
181    ext_predefined: Extension<tinc_pb_prost::PredefinedConstraints>,
182
183    // Enum extensions.
184    ext_enum: Extension<tinc_pb_prost::EnumOptions>,
185    ext_variant: Extension<tinc_pb_prost::EnumVariantOptions>,
186
187    // Service extensions.
188    ext_method: Extension<tinc_pb_prost::MethodOptions>,
189    ext_service: Extension<tinc_pb_prost::ServiceOptions>,
190}
191
192impl<'a> Extensions<'a> {
193    pub(crate) fn new(pool: &'a DescriptorPool) -> Self {
194        Self {
195            pool,
196            ext_message: Extension::new("tinc.message", pool),
197            ext_field: Extension::new("tinc.field", pool),
198            ext_predefined: Extension::new("tinc.predefined", pool),
199            ext_enum: Extension::new("tinc.enum", pool),
200            ext_variant: Extension::new("tinc.variant", pool),
201            ext_method: Extension::new("tinc.method", pool),
202            ext_service: Extension::new("tinc.service", pool),
203            ext_oneof: Extension::new("tinc.oneof", pool),
204        }
205    }
206
207    pub(crate) fn process(&self, registry: &mut ProtoTypeRegistry) -> anyhow::Result<()> {
208        self.pool
209            .files()
210            .map(|file| FileWalker::new(file, self))
211            .try_for_each(|file| {
212                anyhow::ensure!(
213                    !file.file.package_name().is_empty(),
214                    "you must provide a proto package for file: {}",
215                    file.file.name()
216                );
217
218                file.process(registry)
219            })
220    }
221}
222
223struct FileWalker<'a> {
224    file: FileDescriptor,
225    extensions: &'a Extensions<'a>,
226    locations: Vec<Location>,
227}
228
229impl<'a> FileWalker<'a> {
230    fn new(file: FileDescriptor, extensions: &'a Extensions) -> Self {
231        Self {
232            extensions,
233            locations: file
234                .file_descriptor_proto()
235                .source_code_info
236                .clone()
237                .map(|mut si| {
238                    si.location.retain(|l| {
239                        let len = l.path.len();
240                        len > 0 && len % 2 == 0
241                    });
242
243                    si.location.sort_by(|a, b| a.path.cmp(&b.path));
244
245                    si.location
246                })
247                .unwrap_or_default(),
248            file,
249        }
250    }
251
252    fn location(&self, path: &[i32]) -> Option<&Location> {
253        let idx = self
254            .locations
255            .binary_search_by_key(&path, |location| location.path.as_slice())
256            .ok()?;
257        Some(&self.locations[idx])
258    }
259
260    fn process(&self, registry: &mut ProtoTypeRegistry) -> anyhow::Result<()> {
261        for message in self.file.messages() {
262            // FileDescriptorProto.message_type = 4
263            self.process_message(&message, registry)
264                .with_context(|| format!("message {}", message.full_name()))?;
265        }
266
267        for enum_ in self.file.enums() {
268            // FileDescriptorProto.enum_type = 5
269            self.process_enum(&enum_, registry)
270                .with_context(|| format!("enum {}", enum_.full_name()))?;
271        }
272
273        for service in self.file.services() {
274            // FileDescriptorProto.service = 6
275            self.process_service(&service, registry)
276                .with_context(|| format!("service {}", service.full_name()))?;
277        }
278
279        Ok(())
280    }
281
282    fn process_service(&self, service: &ServiceDescriptor, registry: &mut ProtoTypeRegistry) -> anyhow::Result<()> {
283        if registry.get_service(service.full_name()).is_some() {
284            return Ok(());
285        }
286
287        let mut methods = IndexMap::new();
288
289        let opts = self.extensions.ext_service.decode(service)?.unwrap_or_default();
290        let service_full_name = ProtoPath::new(service.full_name());
291
292        for method in service.methods() {
293            let input = method.input();
294            let output = method.output();
295
296            let method_input = ProtoValueType::from_proto_path(input.full_name());
297            let method_output = ProtoValueType::from_proto_path(output.full_name());
298
299            let opts = self
300                .extensions
301                .ext_method
302                .decode(&method)
303                .with_context(|| format!("method {}", method.full_name()))?
304                .unwrap_or_default();
305
306            let mut endpoints = Vec::new();
307            for endpoint in opts.endpoint {
308                let Some(method) = endpoint.method else {
309                    continue;
310                };
311
312                endpoints.push(ProtoServiceMethodEndpoint {
313                    method,
314                    request: endpoint.request,
315                    response: endpoint.response,
316                });
317            }
318
319            methods.insert(
320                method.name().to_owned(),
321                ProtoServiceMethod {
322                    full_name: ProtoPath::new(method.full_name()),
323                    service: service_full_name.clone(),
324                    comments: self.location(method.path()).map(location_to_comments).unwrap_or_default(),
325                    input: if method.is_client_streaming() {
326                        ProtoServiceMethodIo::Stream(method_input)
327                    } else {
328                        ProtoServiceMethodIo::Single(method_input)
329                    },
330                    output: if method.is_server_streaming() {
331                        ProtoServiceMethodIo::Stream(method_output)
332                    } else {
333                        ProtoServiceMethodIo::Single(method_output)
334                    },
335                    endpoints,
336                    cel: opts
337                        .cel
338                        .into_iter()
339                        .map(|expr| CelExpression {
340                            expression: expr.expression,
341                            jsonschemas: expr.jsonschemas,
342                            message: expr.message,
343                            this: None,
344                        })
345                        .collect(),
346                },
347            );
348        }
349
350        registry.register_service(ProtoService {
351            full_name: ProtoPath::new(service.full_name()),
352            comments: self.location(service.path()).map(location_to_comments).unwrap_or_default(),
353            package: ProtoPath::new(service.package_name()),
354            options: ProtoServiceOptions { prefix: opts.prefix },
355            methods,
356        });
357
358        Ok(())
359    }
360
361    fn process_message(&self, message: &MessageDescriptor, registry: &mut ProtoTypeRegistry) -> anyhow::Result<()> {
362        let opts = self.extensions.ext_message.decode(message)?;
363
364        let fields = message
365            .fields()
366            .map(|field| {
367                let opts = self
368                    .extensions
369                    .ext_field
370                    .decode(&field)
371                    .with_context(|| field.full_name().to_owned())?;
372                Ok((field, opts.unwrap_or_default()))
373            })
374            .collect::<anyhow::Result<Vec<_>>>()?;
375
376        let opts = opts.unwrap_or_default();
377        let message_full_name = ProtoPath::new(message.full_name());
378        let rename_all = opts.rename_all.and_then(|v| tinc_pb_prost::RenameAll::try_from(v).ok());
379
380        let mut message_type = ProtoMessageType {
381            full_name: message_full_name.clone(),
382            comments: self.location(message.path()).map(location_to_comments).unwrap_or_default(),
383            package: ProtoPath::new(message.package_name()),
384            fields: IndexMap::new(),
385            options: ProtoMessageOptions {
386                cel: opts
387                    .cel
388                    .into_iter()
389                    .map(|cel| CelExpression {
390                        expression: cel.expression,
391                        jsonschemas: cel.jsonschemas,
392                        message: cel.message,
393                        this: None,
394                    })
395                    .collect(),
396            },
397        };
398
399        for (field, opts) in fields {
400            // This means the field is nullable, and can be omitted from the payload.
401            let proto3_optional = field.field_descriptor_proto().proto3_optional();
402            let visibility = ProtoVisibility::from_pb(opts.visibility());
403
404            let field_opts = ProtoFieldOptions {
405                serde_omittable: ProtoFieldSerdeOmittable::from_prost_pb(opts.json_omittable(), proto3_optional),
406                nullable: proto3_optional,
407                visibility,
408                flatten: opts.flatten(),
409                serde_name: opts
410                    .rename
411                    .or_else(|| rename_field(field.name(), rename_all?))
412                    .unwrap_or_else(|| field.name().to_owned()),
413                cel_exprs: gather_cel_expressions(&self.extensions.ext_predefined, &field.options())
414                    .context("gathering cel expressions")?,
415            };
416
417            let Some(Some(oneof)) = (!proto3_optional).then(|| field.containing_oneof()) else {
418                message_type.fields.insert(
419                    field.name().to_owned(),
420                    ProtoMessageField {
421                        full_name: ProtoPath::new(field.full_name()),
422                        message: message_full_name.clone(),
423                        comments: self.location(field.path()).map(location_to_comments).unwrap_or_default(),
424                        ty: match field.kind() {
425                            Kind::Message(message) if field.is_map() => ProtoType::Modified(ProtoModifiedValueType::Map(
426                                ProtoValueType::from_pb(&message.map_entry_key_field().kind()),
427                                ProtoValueType::from_pb(&message.map_entry_value_field().kind()),
428                            )),
429                            // Prost will generate messages as optional even if they are not optional in the proto.
430                            kind if field.is_list() => {
431                                ProtoType::Modified(ProtoModifiedValueType::Repeated(ProtoValueType::from_pb(&kind)))
432                            }
433                            kind if proto3_optional || matches!(kind, Kind::Message(_)) => {
434                                ProtoType::Modified(ProtoModifiedValueType::Optional(ProtoValueType::from_pb(&kind)))
435                            }
436                            kind => ProtoType::Value(ProtoValueType::from_pb(&kind)),
437                        },
438                        options: field_opts,
439                    },
440                );
441                continue;
442            };
443
444            let opts = self.extensions.ext_oneof.decode(&oneof)?.unwrap_or_default();
445            let mut entry = message_type.fields.entry(oneof.name().to_owned());
446            let oneof = match entry {
447                indexmap::map::Entry::Occupied(ref mut entry) => entry.get_mut(),
448                indexmap::map::Entry::Vacant(entry) => {
449                    let visibility = ProtoVisibility::from_pb(opts.visibility());
450                    let json_omittable = ProtoFieldSerdeOmittable::from_prost_pb(opts.json_omittable(), false);
451
452                    entry.insert(ProtoMessageField {
453                        full_name: ProtoPath::new(oneof.full_name()),
454                        message: message_full_name.clone(),
455                        comments: self.location(oneof.path()).map(location_to_comments).unwrap_or_default(),
456                        options: ProtoFieldOptions {
457                            flatten: opts.flatten(),
458                            nullable: json_omittable.is_true(),
459                            serde_omittable: json_omittable,
460                            serde_name: opts
461                                .rename
462                                .or_else(|| rename_field(oneof.name(), rename_all?))
463                                .unwrap_or_else(|| oneof.name().to_owned()),
464                            visibility,
465                            cel_exprs: CelExpressions::default(),
466                        },
467                        ty: ProtoType::Modified(ProtoModifiedValueType::OneOf(ProtoOneOfType {
468                            full_name: ProtoPath::new(oneof.full_name()),
469                            message: message_full_name.clone(),
470                            fields: IndexMap::new(),
471                            options: ProtoOneOfOptions {
472                                tagged: opts.tagged.clone().map(|tagged| Tagged {
473                                    content: tagged.content,
474                                    tag: tagged.tag,
475                                }),
476                            },
477                        })),
478                    })
479                }
480            };
481
482            let ProtoType::Modified(ProtoModifiedValueType::OneOf(ProtoOneOfType {
483                ref full_name,
484                ref mut fields,
485                ..
486            })) = oneof.ty
487            else {
488                panic!("field type is not a oneof but is being added to a oneof");
489            };
490
491            let field_ty = ProtoValueType::from_pb(&field.kind());
492
493            fields.insert(
494                field.name().to_owned(),
495                ProtoOneOfField {
496                    // This is because the field name should contain the oneof name, by
497                    // default the `field.full_name()` just has the field name on the message
498                    // instead of through the oneof.
499                    full_name: ProtoPath::new(format!("{full_name}.{}", field.name())),
500                    message: message_full_name.clone(),
501                    comments: self.location(field.path()).map(location_to_comments).unwrap_or_default(),
502                    ty: field_ty.clone(),
503                    options: field_opts,
504                },
505            );
506        }
507
508        registry.register_message(message_type);
509
510        for child in message.child_messages() {
511            if child.is_map_entry() {
512                continue;
513            }
514
515            self.process_message(&child, registry)?;
516        }
517
518        for child in message.child_enums() {
519            self.process_enum(&child, registry)?;
520        }
521
522        Ok(())
523    }
524
525    fn process_enum(&self, enum_: &EnumDescriptor, registry: &mut ProtoTypeRegistry) -> anyhow::Result<()> {
526        let opts = self.extensions.ext_enum.decode(enum_)?;
527
528        let values = enum_
529            .values()
530            .map(|value| {
531                let opts = self
532                    .extensions
533                    .ext_variant
534                    .decode(&value)
535                    .with_context(|| value.full_name().to_owned())?;
536                Ok((value, opts))
537            })
538            .collect::<anyhow::Result<Vec<_>>>()?;
539
540        let opts = opts.unwrap_or_default();
541        let rename_all = opts
542            .rename_all
543            .and_then(|v| tinc_pb_prost::RenameAll::try_from(v).ok())
544            .unwrap_or(tinc_pb_prost::RenameAll::ScreamingSnakeCase);
545
546        let mut enum_opts = ProtoEnumType {
547            full_name: ProtoPath::new(enum_.full_name()),
548            comments: self.location(enum_.path()).map(location_to_comments).unwrap_or_default(),
549            package: ProtoPath::new(enum_.package_name()),
550            variants: IndexMap::new(),
551            options: ProtoEnumOptions {
552                repr_enum: opts.repr_enum(),
553            },
554        };
555
556        for (variant, opts) in values {
557            let opts = opts.unwrap_or_default();
558
559            let visibility = ProtoVisibility::from_pb(opts.visibility());
560
561            let name = strip_enum_prefix(&to_upper_camel(enum_.name()), &to_upper_camel(variant.name()));
562
563            enum_opts.variants.insert(
564                variant.name().to_owned(),
565                ProtoEnumVariant {
566                    comments: self.location(variant.path()).map(location_to_comments).unwrap_or_default(),
567                    // This is not the same as variant.full_name() because that strips the enum name.
568                    full_name: ProtoPath::new(format!("{}.{}", enum_.full_name(), variant.name())),
569                    value: variant.number(),
570                    rust_ident: format_ident!("{name}"),
571                    options: ProtoEnumVariantOptions {
572                        visibility,
573                        serde_name: opts.rename.or_else(|| rename_field(&name, rename_all)).unwrap_or(name),
574                    },
575                },
576            );
577        }
578
579        registry.register_enum(enum_opts);
580
581        Ok(())
582    }
583}
584
585#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
586enum CelInput {
587    Root,
588    MapKey,
589    MapValue,
590    RepeatedItem,
591}
592
593pub(crate) fn gather_cel_expressions(
594    extension: &Extension<tinc_pb_prost::PredefinedConstraints>,
595    field_options: &prost_reflect::DynamicMessage,
596) -> anyhow::Result<CelExpressions> {
597    let Some(extension) = extension.descriptor() else {
598        return Ok(CelExpressions::default());
599    };
600
601    let mut results = BTreeMap::new();
602    let mut input = CelInput::Root;
603
604    if field_options.has_extension(extension) {
605        let value = field_options.get_extension(extension);
606        let predef = value
607            .as_message()
608            .context("expected message")?
609            .transcode_to::<tinc_pb_prost::PredefinedConstraints>()
610            .context("invalid predefined constraint")?;
611        match predef.r#type() {
612            tinc_pb_prost::predefined_constraints::Type::Unspecified => {}
613            tinc_pb_prost::predefined_constraints::Type::CustomExpression => {}
614            tinc_pb_prost::predefined_constraints::Type::WrapperMapKey => {
615                input = CelInput::MapKey;
616            }
617            tinc_pb_prost::predefined_constraints::Type::WrapperMapValue => {
618                input = CelInput::MapValue;
619            }
620            tinc_pb_prost::predefined_constraints::Type::WrapperRepeatedItem => {
621                input = CelInput::RepeatedItem;
622            }
623        }
624    }
625
626    for (ext, value) in field_options.extensions() {
627        if &ext == extension {
628            continue;
629        }
630
631        if let Some(message) = value.as_message() {
632            explore_fields(extension, input, message, &mut results)?;
633        }
634    }
635
636    Ok(CelExpressions {
637        field: results.remove(&CelInput::Root).unwrap_or_default(),
638        map_key: results.remove(&CelInput::MapKey).unwrap_or_default(),
639        map_value: results.remove(&CelInput::MapValue).unwrap_or_default(),
640        repeated_item: results.remove(&CelInput::RepeatedItem).unwrap_or_default(),
641    })
642}
643
644fn explore_fields(
645    extension: &prost_reflect::ExtensionDescriptor,
646    input: CelInput,
647    value: &prost_reflect::DynamicMessage,
648    results: &mut BTreeMap<CelInput, Vec<CelExpression>>,
649) -> anyhow::Result<()> {
650    for (field, value) in value.fields() {
651        let options = field.options();
652        let mut input = input;
653        if options.has_extension(extension) {
654            let message = options.get_extension(extension);
655            let predef = message
656                .as_message()
657                .unwrap()
658                .transcode_to::<tinc_pb_prost::PredefinedConstraints>()
659                .unwrap();
660            match predef.r#type() {
661                tinc_pb_prost::predefined_constraints::Type::Unspecified => {}
662                tinc_pb_prost::predefined_constraints::Type::CustomExpression => {
663                    if let Some(list) = value.as_list() {
664                        results.entry(input).or_default().extend(
665                            list.iter()
666                                .filter_map(|item| item.as_message())
667                                .filter_map(|msg| msg.transcode_to::<tinc_pb_prost::CelExpression>().ok())
668                                .map(|expr| CelExpression {
669                                    expression: expr.expression,
670                                    jsonschemas: expr.jsonschemas,
671                                    message: expr.message,
672                                    this: None,
673                                }),
674                        );
675                    }
676                    continue;
677                }
678                tinc_pb_prost::predefined_constraints::Type::WrapperMapKey => {
679                    input = CelInput::MapKey;
680                }
681                tinc_pb_prost::predefined_constraints::Type::WrapperMapValue => {
682                    input = CelInput::MapValue;
683                }
684                tinc_pb_prost::predefined_constraints::Type::WrapperRepeatedItem => {
685                    input = CelInput::RepeatedItem;
686                }
687            }
688
689            results
690                .entry(input)
691                .or_default()
692                .extend(predef.cel.into_iter().map(|expr| CelExpression {
693                    expression: expr.expression,
694                    jsonschemas: expr.jsonschemas,
695                    message: expr.message,
696                    this: Some(prost_to_cel(value, &field.kind())),
697                }));
698        }
699
700        let Some(message) = value.as_message() else {
701            continue;
702        };
703
704        explore_fields(extension, input, message, results)?;
705    }
706
707    Ok(())
708}
709
710fn prost_to_cel(value: &prost_reflect::Value, kind: &Kind) -> tinc_cel::CelValue<'static> {
711    match value {
712        prost_reflect::Value::String(s) => tinc_cel::CelValue::String(s.clone().into()),
713        prost_reflect::Value::Message(msg) => tinc_cel::CelValue::Map(
714            msg.fields()
715                .map(|(field, value)| {
716                    (
717                        tinc_cel::CelValue::String(field.name().to_owned().into()),
718                        prost_to_cel(value, &field.kind()),
719                    )
720                })
721                .collect(),
722        ),
723        prost_reflect::Value::EnumNumber(value) => tinc_cel::CelValue::Enum(CelEnum::new(
724            kind.as_enum().expect("enum").full_name().to_owned().into(),
725            *value,
726        )),
727        prost_reflect::Value::Bool(v) => v.conv(),
728        prost_reflect::Value::I32(v) => v.conv(),
729        prost_reflect::Value::I64(v) => v.conv(),
730        prost_reflect::Value::U32(v) => v.conv(),
731        prost_reflect::Value::U64(v) => v.conv(),
732        prost_reflect::Value::Bytes(b) => tinc_cel::CelValue::Bytes(b.into()),
733        prost_reflect::Value::F32(v) => v.conv(),
734        prost_reflect::Value::F64(v) => v.conv(),
735        prost_reflect::Value::List(list) => {
736            tinc_cel::CelValue::List(list.iter().map(|item| prost_to_cel(item, kind)).collect())
737        }
738        prost_reflect::Value::Map(map) => tinc_cel::CelValue::Map(
739            map.iter()
740                .map(|(key, value)| {
741                    let key = match key {
742                        prost_reflect::MapKey::Bool(v) => v.conv(),
743                        prost_reflect::MapKey::I32(v) => v.conv(),
744                        prost_reflect::MapKey::I64(v) => v.conv(),
745                        prost_reflect::MapKey::U32(v) => v.conv(),
746                        prost_reflect::MapKey::U64(v) => v.conv(),
747                        prost_reflect::MapKey::String(s) => tinc_cel::CelValue::String(s.clone().into()),
748                    };
749
750                    let v = prost_to_cel(value, &kind.as_message().expect("map").map_entry_value_field().kind());
751                    (key, v)
752                })
753                .collect(),
754        ),
755    }
756}
757
758fn location_to_comments(location: &Location) -> Comments {
759    Comments {
760        leading: location.leading_comments.as_deref().map(Into::into),
761        detached: location.leading_detached_comments.iter().map(|s| s.as_str().into()).collect(),
762        trailing: location.trailing_comments.as_deref().map(Into::into),
763    }
764}
765
766impl ProtoFieldSerdeOmittable {
767    pub(crate) fn from_prost_pb(value: tinc_pb_prost::JsonOmittable, nullable: bool) -> Self {
768        match value {
769            tinc_pb_prost::JsonOmittable::Unspecified => {
770                if nullable {
771                    Self::TrueButStillSerialize
772                } else {
773                    Self::False
774                }
775            }
776            tinc_pb_prost::JsonOmittable::True => Self::True,
777            tinc_pb_prost::JsonOmittable::False => Self::False,
778            tinc_pb_prost::JsonOmittable::TrueButStillSerialize => Self::TrueButStillSerialize,
779        }
780    }
781}