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 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 ext_enum: Extension<tinc_pb_prost::EnumOptions>,
185 ext_variant: Extension<tinc_pb_prost::EnumVariantOptions>,
186
187 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 self.process_message(&message, registry)
264 .with_context(|| format!("message {}", message.full_name()))?;
265 }
266
267 for enum_ in self.file.enums() {
268 self.process_enum(&enum_, registry)
270 .with_context(|| format!("enum {}", enum_.full_name()))?;
271 }
272
273 for service in self.file.services() {
274 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 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 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 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 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}