1use std::collections::{HashMap, HashSet};
4
5use crate::features::{self, ResolvedFeatures};
6use crate::generated::descriptor::{DescriptorProto, EnumDescriptorProto, FileDescriptorProto};
7use crate::oneof::to_snake_case;
8use crate::CodeGenConfig;
9
10pub const SENTINEL_MOD: &str = "__buffa";
17
18#[derive(Debug, Clone)]
24pub struct SplitPath {
25 pub to_package: String,
33 pub within_package: String,
36 pub is_extern: bool,
39}
40
41pub struct CodeGenContext<'a> {
47 pub files: &'a [FileDescriptorProto],
49 pub config: &'a CodeGenConfig,
51 pub type_map: HashMap<String, String>,
57 package_of: HashMap<String, String>,
62 enum_closedness: HashMap<String, bool>,
70 comment_map: HashMap<String, String>,
83 nested_module_names: HashMap<String, String>,
93 unboxed_oneof_variants: HashSet<String>,
98 warnings: std::cell::RefCell<Vec<crate::CodeGenWarning>>,
110 imports: std::cell::RefCell<crate::imports::ImportsPhase>,
122}
123
124fn child_package_segments(package: &str, all_packages: &HashSet<String>) -> HashSet<String> {
131 let prefix = if package.is_empty() {
132 String::new()
133 } else {
134 format!("{package}.")
135 };
136 all_packages
137 .iter()
138 .filter_map(|p| {
139 let rest = if package.is_empty() {
140 Some(p.as_str())
141 } else {
142 p.strip_prefix(&prefix)
143 };
144 rest.filter(|r| !r.is_empty())
145 .map(|r| r.split('.').next().unwrap_or(r).to_string())
146 })
147 .collect()
148}
149
150fn deconflict_package_modules(message_names: &[String], children: &HashSet<String>) -> Vec<String> {
170 let bases: Vec<String> = message_names.iter().map(|n| to_snake_case(n)).collect();
171 let mut taken: HashSet<String> = children.clone();
174 taken.insert(SENTINEL_MOD.to_string());
175 taken.extend(bases.iter().cloned());
176
177 let mut out = bases.clone();
182 let mut order: Vec<usize> = (0..bases.len()).collect();
183 order.sort_by(|&a, &b| bases[a].cmp(&bases[b]));
184 for i in order {
185 if !children.contains(&bases[i]) {
186 continue;
187 }
188 let mut candidate = format!("{}_", bases[i]);
189 while taken.contains(&candidate) {
190 candidate.push('_');
191 }
192 taken.insert(candidate.clone());
193 out[i] = candidate;
194 }
195 out
196}
197
198impl<'a> CodeGenContext<'a> {
199 pub fn new(
211 files: &'a [FileDescriptorProto],
212 config: &'a CodeGenConfig,
213 effective_extern_paths: &[(String, String)],
214 ) -> Self {
215 Self::with_extern_resolution(files, config, effective_extern_paths, &[])
216 }
217
218 pub(crate) fn with_extern_resolution(
241 files: &'a [FileDescriptorProto],
242 config: &'a CodeGenConfig,
243 effective_extern_paths: &[(String, String)],
244 file_extern_paths: &[(String, String)],
245 ) -> Self {
246 let mut type_map = HashMap::new();
247 let mut package_of = HashMap::new();
248 let mut enum_closedness = HashMap::new();
249 let mut comment_map = HashMap::new();
250 let mut nested_module_names = HashMap::new();
251 let unboxed_oneof_variants =
252 crate::oneof::resolve_unboxed_variants(files, &config.unboxed_oneof_fields);
253
254 let mut all_packages: HashSet<String> = HashSet::new();
261 let mut pkg_message_names: HashMap<String, Vec<String>> = HashMap::new();
262 for file in files {
263 let package = file.package.as_deref().unwrap_or("");
264 all_packages.insert(package.to_string());
265 for msg in &file.message_type {
266 if let Some(name) = &msg.name {
267 let fqn = if package.is_empty() {
271 format!(".{name}")
272 } else {
273 format!(".{package}.{name}")
274 };
275 if effective_extern_paths
276 .iter()
277 .any(|(proto, _)| proto == &fqn)
278 {
279 continue;
280 }
281 pkg_message_names
282 .entry(package.to_string())
283 .or_default()
284 .push(name.clone());
285 }
286 }
287 }
288
289 for (package, names) in &pkg_message_names {
294 let children = child_package_segments(package, &all_packages);
295 let modules = deconflict_package_modules(names, &children);
296 for (name, module) in names.iter().zip(modules) {
297 let fqn = if package.is_empty() {
298 format!(".{name}")
299 } else {
300 format!(".{package}.{name}")
301 };
302 nested_module_names.insert(fqn, module);
303 }
304 }
305
306 for file in files {
307 comment_map.extend(crate::comments::fqn_comments(file));
308 let package = file.package.as_deref().unwrap_or("");
309 let file_features = features::for_file(file);
310 let proto_prefix = if package.is_empty() {
311 String::from(".")
312 } else {
313 format!(".{}.", package)
314 };
315
316 let file_root = file
326 .name
327 .as_deref()
328 .and_then(|n| resolve_file_extern(n, file_extern_paths));
329 let local_module = package.replace('.', "::");
330
331 for msg in &file.message_type {
333 if let Some(name) = &msg.name {
334 let fqn = format!("{}{}", proto_prefix, name);
335 let (rust_path, is_extern) = resolve_type_path(
336 &fqn,
337 name,
338 file_root,
339 &local_module,
340 effective_extern_paths,
341 &config.type_name_prefix,
342 );
343
344 let parent_mod = if is_extern {
352 let snake = nested_module_names
353 .get(&fqn)
354 .cloned()
355 .unwrap_or_else(|| to_snake_case(name));
356 match rust_path.rsplit_once("::") {
357 Some((parent, _)) => format!("{parent}::{snake}"),
358 None => snake,
359 }
360 } else {
361 let snake = nested_module_names
362 .get(&fqn)
363 .cloned()
364 .unwrap_or_else(|| to_snake_case(name));
365 join_mod(&local_module, &snake)
366 };
367
368 type_map.insert(fqn.clone(), rust_path);
369 package_of.insert(fqn.clone(), package.to_string());
370 register_nested_types(
371 &mut type_map,
372 &mut package_of,
373 NestedRegistrationCtx {
374 package,
375 extern_paths: effective_extern_paths,
376 type_name_prefix: &config.type_name_prefix,
377 },
378 &fqn,
379 &parent_mod,
380 msg,
381 );
382 register_nested_enum_closedness(
383 &mut enum_closedness,
384 &fqn,
385 &file_features,
386 msg,
387 );
388 }
389 }
390
391 for enum_type in &file.enum_type {
393 if let Some(name) = &enum_type.name {
394 let fqn = format!("{}{}", proto_prefix, name);
395 let (rust_path, _) = resolve_type_path(
396 &fqn,
397 name,
398 file_root,
399 &local_module,
400 effective_extern_paths,
401 &config.type_name_prefix,
402 );
403 type_map.insert(fqn.clone(), rust_path);
404 package_of.insert(fqn.clone(), package.to_string());
405 register_enum_closedness(&mut enum_closedness, &fqn, &file_features, enum_type);
406 }
407 }
408 }
409
410 Self {
411 files,
412 config,
413 type_map,
414 package_of,
415 enum_closedness,
416 comment_map,
417 nested_module_names,
418 unboxed_oneof_variants,
419 warnings: std::cell::RefCell::new(Vec::new()),
420 imports: std::cell::RefCell::new(crate::imports::ImportsPhase::Off),
421 }
422 }
423
424 pub(crate) fn warn(&self, warning: crate::CodeGenWarning) {
426 self.warnings.borrow_mut().push(warning);
427 }
428
429 pub(crate) fn take_warnings(&self) -> Vec<crate::CodeGenWarning> {
435 self.warnings.take()
436 }
437
438 pub(crate) fn truncate_warnings(&self, len: usize) {
443 self.warnings.borrow_mut().truncate(len);
444 }
445
446 pub(crate) fn warnings_len(&self) -> usize {
449 self.warnings.borrow().len()
450 }
451
452 pub(crate) fn imports_begin_collecting(&self) {
457 *self.imports.borrow_mut() =
458 crate::imports::ImportsPhase::Collecting(std::collections::BTreeSet::new());
459 }
460
461 pub(crate) fn imports_take_collected(&self) -> std::collections::BTreeSet<String> {
463 match self.imports.replace(crate::imports::ImportsPhase::Off) {
464 crate::imports::ImportsPhase::Collecting(set) => set,
465 _ => std::collections::BTreeSet::new(),
466 }
467 }
468
469 pub(crate) fn imports_set_resolving(&self, imports: crate::imports::RootImports) {
471 *self.imports.borrow_mut() = crate::imports::ImportsPhase::Resolving(imports);
472 }
473
474 pub(crate) fn imports_reset(&self) {
479 *self.imports.borrow_mut() = crate::imports::ImportsPhase::Off;
480 }
481
482 pub(crate) fn imports_use_block(&self) -> proc_macro2::TokenStream {
485 match &*self.imports.borrow() {
486 crate::imports::ImportsPhase::Resolving(imports) => imports.use_items(),
487 _ => proc_macro2::TokenStream::new(),
488 }
489 }
490
491 fn root_path(&self, path: String, nesting: usize) -> String {
498 if nesting != 0 {
499 return path;
500 }
501 match &mut *self.imports.borrow_mut() {
502 crate::imports::ImportsPhase::Off => path,
503 crate::imports::ImportsPhase::Collecting(set) => {
504 if crate::imports::shortenable(&path) {
505 set.insert(path.clone());
506 }
507 path
508 }
509 crate::imports::ImportsPhase::Resolving(imports) => match imports.resolve(&path) {
510 Some(short) => short.to_string(),
511 None => path,
512 },
513 }
514 }
515
516 pub(crate) fn root_runtime_path(
522 &self,
523 canonical: &str,
524 nesting: usize,
525 ) -> proc_macro2::TokenStream {
526 crate::idents::rust_path_to_tokens(&self.root_path(canonical.to_string(), nesting))
527 }
528
529 pub fn nested_module_name(&self, package: &str, name: &str) -> String {
538 let fqn = if package.is_empty() {
539 format!(".{name}")
540 } else {
541 format!(".{package}.{name}")
542 };
543 self.nested_module_names
544 .get(&fqn)
545 .cloned()
546 .unwrap_or_else(|| to_snake_case(name))
547 }
548
549 pub fn for_generate(
563 files: &'a [FileDescriptorProto],
564 files_to_generate: &[String],
565 config: &'a CodeGenConfig,
566 ) -> Self {
567 let paths = crate::effective_extern_paths(files, files_to_generate, config);
568 let file_paths = crate::effective_file_extern_paths(files_to_generate, config);
569 Self::with_extern_resolution(files, config, &paths, &file_paths)
570 }
571
572 pub fn rust_type(&self, proto_fqn: &str) -> Option<&str> {
574 self.type_map.get(proto_fqn).map(|s| s.as_str())
575 }
576
577 pub fn comment(&self, fqn: &str) -> Option<&str> {
586 self.comment_map.get(fqn).map(|s| s.as_str())
587 }
588
589 pub fn is_enum_closed(&self, proto_fqn: &str) -> Option<bool> {
596 self.enum_closedness.get(proto_fqn).copied()
597 }
598
599 pub fn rust_type_relative(
614 &self,
615 proto_fqn: &str,
616 current_package: &str,
617 nesting: usize,
618 ) -> Option<String> {
619 let full_path = self.type_map.get(proto_fqn)?;
620
621 if full_path.starts_with("::") || full_path.starts_with("crate::") {
624 return Some(self.root_path(full_path.clone(), nesting));
625 }
626
627 let target_package = self
628 .package_of
629 .get(proto_fqn)
630 .map(|s| s.as_str())
631 .unwrap_or("");
632
633 let target_rust_module = target_package.replace('.', "::");
636 let type_suffix = if target_rust_module.is_empty() {
637 full_path.as_str()
638 } else {
639 full_path
640 .strip_prefix(&format!("{}::", target_rust_module))
641 .unwrap_or(full_path)
642 };
643
644 if current_package == target_package {
645 if nesting == 0 {
647 return Some(type_suffix.to_string());
648 }
649 let supers = (0..nesting).map(|_| "super").collect::<Vec<_>>().join("::");
650 return Some(format!("{}::{}", supers, type_suffix));
651 }
652
653 let current_parts: Vec<&str> = if current_package.is_empty() {
655 vec![]
656 } else {
657 current_package.split('.').collect()
658 };
659 let target_parts: Vec<&str> = if target_package.is_empty() {
660 vec![]
661 } else {
662 target_package.split('.').collect()
663 };
664
665 let common_len = current_parts
667 .iter()
668 .zip(&target_parts)
669 .take_while(|(a, b)| a == b)
670 .count();
671
672 let up_count = (current_parts.len() - common_len) + nesting;
675
676 let down_parts = &target_parts[common_len..];
678
679 let mut segments: Vec<&str> = vec!["super"; up_count];
680 segments.extend_from_slice(down_parts);
681
682 let mut result = segments.join("::");
684 if !result.is_empty() {
685 result.push_str("::");
686 }
687 result.push_str(type_suffix);
688
689 Some(self.root_path(result, nesting))
690 }
691
692 pub fn rust_type_relative_split(
704 &self,
705 proto_fqn: &str,
706 current_package: &str,
707 nesting: usize,
708 ) -> Option<SplitPath> {
709 let full_path = self.type_map.get(proto_fqn)?;
710
711 let target_package = self
712 .package_of
713 .get(proto_fqn)
714 .map(|s| s.as_str())
715 .unwrap_or("");
716
717 let target_rust_module = if full_path.starts_with("::") || full_path.starts_with("crate::")
723 {
724 let fqn_no_dot = proto_fqn.strip_prefix('.').unwrap_or(proto_fqn);
733 let within_proto = if target_package.is_empty() {
734 fqn_no_dot
735 } else {
736 fqn_no_dot
737 .strip_prefix(target_package)
738 .and_then(|s| s.strip_prefix('.'))
739 .unwrap_or(fqn_no_dot)
740 };
741 let within_segs = within_proto.split('.').count();
755 let full_segs: Vec<&str> = full_path.split("::").collect();
756 let cut = full_segs.len().saturating_sub(within_segs);
757 full_segs[..cut].join("::")
758 } else {
759 target_package.replace('.', "::")
760 };
761
762 let type_suffix = if target_rust_module.is_empty() {
763 full_path.as_str()
764 } else {
765 full_path
766 .strip_prefix(&format!("{}::", target_rust_module))
767 .unwrap_or(full_path)
768 };
769
770 if full_path.starts_with("::") || full_path.starts_with("crate::") {
772 return Some(SplitPath {
773 to_package: target_rust_module,
774 within_package: type_suffix.to_string(),
775 is_extern: true,
776 });
777 }
778
779 if current_package == target_package {
780 let to_package = if nesting == 0 {
781 String::new()
782 } else {
783 (0..nesting).map(|_| "super").collect::<Vec<_>>().join("::")
784 };
785 return Some(SplitPath {
786 to_package,
787 within_package: type_suffix.to_string(),
788 is_extern: false,
789 });
790 }
791
792 let current_parts: Vec<&str> = if current_package.is_empty() {
794 vec![]
795 } else {
796 current_package.split('.').collect()
797 };
798 let target_parts: Vec<&str> = if target_package.is_empty() {
799 vec![]
800 } else {
801 target_package.split('.').collect()
802 };
803 let common_len = current_parts
804 .iter()
805 .zip(&target_parts)
806 .take_while(|(a, b)| a == b)
807 .count();
808 let up_count = (current_parts.len() - common_len) + nesting;
809 let down_parts = &target_parts[common_len..];
810
811 let mut segments: Vec<&str> = vec!["super"; up_count];
812 segments.extend_from_slice(down_parts);
813
814 Some(SplitPath {
815 to_package: segments.join("::"),
816 within_package: type_suffix.to_string(),
817 is_extern: false,
818 })
819 }
820
821 pub(crate) fn matching_attributes(
834 attrs: &[(String, String)],
835 fqn: &str,
836 ) -> Result<proc_macro2::TokenStream, crate::CodeGenError> {
837 if attrs.is_empty() {
838 return Ok(proc_macro2::TokenStream::new());
839 }
840 let fqn_dotted = format!(".{fqn}");
841 let mut tokens = proc_macro2::TokenStream::new();
842 for (prefix, attr_str) in attrs {
843 if matches_proto_prefix(prefix, &fqn_dotted) {
844 let parsed =
845 syn::parse_str::<proc_macro2::TokenStream>(attr_str).map_err(|err| {
846 crate::CodeGenError::InvalidCustomAttribute {
847 path: prefix.clone(),
848 attribute: attr_str.clone(),
849 detail: err.to_string(),
850 }
851 })?;
852 tokens.extend(parsed);
853 }
854 }
855 Ok(tokens)
856 }
857
858 pub fn bytes_repr(&self, field_fqn: &str) -> crate::BytesRepr {
869 self.config
870 .bytes_fields
871 .iter()
872 .rev()
873 .find(|(prefix, _)| matches_proto_prefix(prefix, field_fqn))
874 .map_or(crate::BytesRepr::default(), |(_, repr)| repr.clone())
875 }
876
877 pub fn oneof_unboxed(&self, variant_fqn: &str) -> bool {
887 self.unboxed_oneof_variants.contains(variant_fqn)
888 }
889
890 pub fn string_repr(&self, field_fqn: &str) -> crate::StringRepr {
900 self.config
901 .string_fields
902 .iter()
903 .rev()
904 .find(|(prefix, _)| matches_proto_prefix(prefix, field_fqn))
905 .map_or(crate::StringRepr::default(), |(_, repr)| repr.clone())
906 }
907
908 pub fn map_repr(&self, field_fqn: &str) -> crate::MapRepr {
918 self.config
919 .map_fields
920 .iter()
921 .rev()
922 .find(|(prefix, _)| matches_proto_prefix(prefix, field_fqn))
923 .map_or(crate::MapRepr::default(), |(_, repr)| repr.clone())
924 }
925
926 pub fn pointer_repr(&self, field_fqn: &str) -> crate::PointerRepr {
931 self.config
932 .pointer_fields
933 .iter()
934 .rev()
935 .find(|(prefix, _)| matches_proto_prefix(prefix, field_fqn))
936 .map_or(crate::PointerRepr::default(), |(_, repr)| repr.clone())
937 }
938
939 pub fn repeated_repr(&self, field_fqn: &str) -> crate::RepeatedRepr {
949 self.config
950 .repeated_fields
951 .iter()
952 .rev()
953 .find(|(prefix, _)| matches_proto_prefix(prefix, field_fqn))
954 .map_or(crate::RepeatedRepr::default(), |(_, repr)| repr.clone())
955 }
956}
957
958#[derive(Clone, Copy)]
965pub(crate) struct MessageScope<'a> {
966 pub ctx: &'a CodeGenContext<'a>,
968 pub current_package: &'a str,
970 pub proto_fqn: &'a str,
973 pub features: &'a ResolvedFeatures,
975 pub nesting: usize,
979}
980
981impl<'a> MessageScope<'a> {
982 pub fn nested(&self, proto_fqn: &'a str, features: &'a ResolvedFeatures) -> MessageScope<'a> {
984 MessageScope {
985 ctx: self.ctx,
986 current_package: self.current_package,
987 proto_fqn,
988 features,
989 nesting: self.nesting + 1,
990 }
991 }
992}
993
994#[derive(Debug, Clone, Copy, PartialEq, Eq)]
999pub(crate) enum AncillaryKind {
1000 Oneof,
1002 View,
1004 ViewOneof,
1006 LazyView,
1008}
1009
1010impl AncillaryKind {
1011 fn path_segments(self) -> &'static [&'static str] {
1012 match self {
1013 Self::Oneof => &["oneof"],
1014 Self::View => &["view"],
1015 Self::ViewOneof => &["view", "oneof"],
1016 Self::LazyView => &["lazy_view"],
1017 }
1018 }
1019}
1020
1021pub(crate) fn ancillary_prefix(
1036 kind: AncillaryKind,
1037 current_package: &str,
1038 proto_fqn: &str,
1039 from_nesting: usize,
1040) -> proc_macro2::TokenStream {
1041 use crate::idents::make_field_ident;
1042 use quote::quote;
1043
1044 debug_assert!(
1045 !proto_fqn.starts_with('.'),
1046 "ancillary_prefix expects dotless FQN, got {proto_fqn:?}"
1047 );
1048
1049 let mut supers_tokens = proc_macro2::TokenStream::new();
1050 for _ in 0..from_nesting {
1051 supers_tokens.extend(quote! { super:: });
1052 }
1053
1054 let sentinel = make_field_ident(SENTINEL_MOD);
1055 let kind_segs: Vec<_> = kind
1056 .path_segments()
1057 .iter()
1058 .map(|s| make_field_ident(s))
1059 .collect();
1060
1061 let within_pkg = if current_package.is_empty() {
1063 proto_fqn
1064 } else {
1065 proto_fqn
1066 .strip_prefix(current_package)
1067 .and_then(|s| s.strip_prefix('.'))
1068 .unwrap_or(proto_fqn)
1069 };
1070 let msg_segs: Vec<_> = within_pkg
1071 .split('.')
1072 .filter(|s| !s.is_empty())
1073 .map(|name| make_field_ident(&to_snake_case(name)))
1074 .collect();
1075
1076 quote! { #supers_tokens #sentinel :: #(#kind_segs ::)* #(#msg_segs ::)* }
1077}
1078
1079pub(crate) fn matches_proto_prefix(prefix: &str, fqn_dotted: &str) -> bool {
1084 prefix == "."
1085 || prefix == fqn_dotted
1086 || (fqn_dotted.starts_with(prefix)
1087 && fqn_dotted.as_bytes().get(prefix.len()) == Some(&b'.'))
1088}
1089
1090fn resolve_file_extern<'p>(
1099 file_name: &str,
1100 file_extern_paths: &'p [(String, String)],
1101) -> Option<&'p str> {
1102 file_extern_paths
1103 .iter()
1104 .find(|(name, _)| name == file_name)
1105 .map(|(_, rust)| rust.as_str())
1106}
1107
1108pub(crate) fn resolve_extern_prefix(
1119 package: &str,
1120 extern_paths: &[(String, String)],
1121) -> Option<String> {
1122 let dotted = format!(".{}", package);
1123
1124 let mut best: Option<(&str, &str, usize)> = None;
1127
1128 for (proto_prefix, rust_prefix) in extern_paths {
1129 if dotted == *proto_prefix {
1130 return Some(rust_prefix.clone());
1132 }
1133 if let Some(rest) = dotted.strip_prefix(proto_prefix.as_str()) {
1134 if proto_prefix == "." || rest.starts_with('.') {
1136 let prefix_len = proto_prefix.len();
1137 if best.map_or(true, |(_, _, best_len)| prefix_len > best_len) {
1139 best = Some((proto_prefix, rust_prefix, prefix_len));
1140 }
1141 }
1142 }
1143 }
1144
1145 let (proto_prefix, rust_prefix, _) = best?;
1146 let rest = dotted.strip_prefix(proto_prefix)?;
1147 let rest = rest.strip_prefix('.').unwrap_or(rest);
1148 let suffix = rest
1149 .split('.')
1150 .map(to_snake_case)
1151 .collect::<Vec<_>>()
1152 .join("::");
1153 Some(format!("{}::{}", rust_prefix, suffix))
1154}
1155
1156fn resolve_extern_type(fqn: &str, extern_paths: &[(String, String)]) -> Option<String> {
1175 if let Some((_, rust)) = extern_paths.iter().find(|(proto, _)| proto == fqn) {
1177 return Some(rust.clone());
1178 }
1179
1180 let mut best: Option<(&str, &str, usize)> = None;
1182 for (proto_prefix, rust_prefix) in extern_paths {
1183 let matches = proto_prefix == "."
1184 || fqn
1185 .strip_prefix(proto_prefix.as_str())
1186 .is_some_and(|rest| rest.starts_with('.'));
1187 if matches && best.map_or(true, |(_, _, best_len)| proto_prefix.len() > best_len) {
1189 best = Some((proto_prefix, rust_prefix, proto_prefix.len()));
1190 }
1191 }
1192
1193 let (proto_prefix, rust_prefix, _) = best?;
1194 let rest = if proto_prefix == "." {
1197 fqn.strip_prefix('.').unwrap_or(fqn)
1198 } else {
1199 fqn.strip_prefix(proto_prefix)
1200 .and_then(|r| r.strip_prefix('.'))
1201 .unwrap_or("")
1202 };
1203 let mut segments = rest.split('.').collect::<Vec<_>>();
1204 let type_name = segments.pop()?;
1206 let mut path = rust_prefix.to_string();
1207 for module in segments {
1208 path.push_str("::");
1209 path.push_str(&to_snake_case(module));
1210 }
1211 path.push_str("::");
1212 path.push_str(type_name);
1213 Some(path)
1214}
1215
1216fn join_mod(module: &str, name: &str) -> String {
1219 if module.is_empty() {
1220 name.to_string()
1221 } else {
1222 format!("{module}::{name}")
1223 }
1224}
1225
1226fn resolve_type_path(
1239 fqn: &str,
1240 name: &str,
1241 file_root: Option<&str>,
1242 local_module: &str,
1243 extern_paths: &[(String, String)],
1244 type_name_prefix: &str,
1245) -> (String, bool) {
1246 if let Some((_, rust)) = extern_paths.iter().find(|(proto, _)| proto == fqn) {
1256 (rust.clone(), true)
1257 } else if let Some(root) = file_root {
1258 (join_mod(root, name), true)
1259 } else if let Some(path) = resolve_extern_type(fqn, extern_paths) {
1260 (path, true)
1261 } else {
1262 (
1263 join_mod(local_module, &format!("{type_name_prefix}{name}")),
1264 false,
1265 )
1266 }
1267}
1268
1269#[derive(Clone, Copy)]
1272struct NestedRegistrationCtx<'a> {
1273 package: &'a str,
1274 extern_paths: &'a [(String, String)],
1275 type_name_prefix: &'a str,
1276}
1277
1278fn register_nested_types(
1288 type_map: &mut HashMap<String, String>,
1289 package_of: &mut HashMap<String, String>,
1290 reg: NestedRegistrationCtx<'_>,
1291 parent_fqn: &str,
1292 parent_mod: &str,
1293 msg: &crate::generated::descriptor::DescriptorProto,
1294) {
1295 let NestedRegistrationCtx {
1296 package,
1297 extern_paths,
1298 type_name_prefix,
1299 } = reg;
1300 for nested in &msg.nested_type {
1301 if let Some(name) = &nested.name {
1302 let fqn = format!("{}.{}", parent_fqn, name);
1303 let (rust_path, child_mod) = match extern_paths.iter().find(|(proto, _)| proto == &fqn)
1309 {
1310 Some((_, rust)) => {
1311 let child = match rust.rsplit_once("::") {
1312 Some((parent, _)) => format!("{parent}::{}", to_snake_case(name)),
1313 None => to_snake_case(name),
1314 };
1315 (rust.clone(), child)
1316 }
1317 None => (
1318 format!("{parent_mod}::{type_name_prefix}{name}"),
1319 format!("{parent_mod}::{}", to_snake_case(name)),
1320 ),
1321 };
1322 type_map.insert(fqn.clone(), rust_path);
1323 package_of.insert(fqn.clone(), package.to_string());
1324
1325 register_nested_types(type_map, package_of, reg, &fqn, &child_mod, nested);
1327 }
1328 }
1329
1330 for enum_type in &msg.enum_type {
1331 if let Some(name) = &enum_type.name {
1332 let fqn = format!("{}.{}", parent_fqn, name);
1333 let rust_path = extern_paths
1334 .iter()
1335 .find(|(proto, _)| proto == &fqn)
1336 .map(|(_, rust)| rust.clone())
1337 .unwrap_or_else(|| format!("{parent_mod}::{type_name_prefix}{name}"));
1338 type_map.insert(fqn.clone(), rust_path);
1339 package_of.insert(fqn, package.to_string());
1340 }
1341 }
1342}
1343
1344fn register_enum_closedness(
1346 map: &mut HashMap<String, bool>,
1347 fqn: &str,
1348 parent_features: &ResolvedFeatures,
1349 enum_desc: &EnumDescriptorProto,
1350) {
1351 let resolved = features::resolve_child(parent_features, features::enum_features(enum_desc));
1352 let closed = resolved.enum_type == features::EnumType::Closed;
1353 map.insert(fqn.to_string(), closed);
1354}
1355
1356fn register_nested_enum_closedness(
1359 map: &mut HashMap<String, bool>,
1360 parent_fqn: &str,
1361 parent_features: &ResolvedFeatures,
1362 msg: &DescriptorProto,
1363) {
1364 let msg_features = features::resolve_child(parent_features, features::message_features(msg));
1365 for enum_type in &msg.enum_type {
1366 if let Some(name) = &enum_type.name {
1367 let fqn = format!("{}.{}", parent_fqn, name);
1368 register_enum_closedness(map, &fqn, &msg_features, enum_type);
1369 }
1370 }
1371 for nested in &msg.nested_type {
1372 if let Some(name) = &nested.name {
1373 let fqn = format!("{}.{}", parent_fqn, name);
1374 register_nested_enum_closedness(map, &fqn, &msg_features, nested);
1375 }
1376 }
1377}
1378
1379#[cfg(test)]
1380mod tests {
1381 use super::*;
1382 use crate::generated::descriptor::{DescriptorProto, EnumDescriptorProto, FileDescriptorProto};
1383
1384 fn children(segs: &[&str]) -> HashSet<String> {
1385 segs.iter().map(|s| s.to_string()).collect()
1386 }
1387
1388 fn names(ns: &[&str]) -> Vec<String> {
1389 ns.iter().map(|s| s.to_string()).collect()
1390 }
1391
1392 #[test]
1393 fn deconflict_no_collision_keeps_base() {
1394 let out = deconflict_package_modules(&names(&["Oof", "Bar"]), &children(&["other"]));
1396 assert_eq!(out, vec!["oof".to_string(), "bar".to_string()]);
1397 }
1398
1399 #[test]
1400 fn deconflict_single_collision_appends_underscore() {
1401 let out = deconflict_package_modules(&names(&["Oof"]), &children(&["oof"]));
1402 assert_eq!(out, vec!["oof_".to_string()]);
1403 }
1404
1405 #[test]
1406 fn deconflict_repeated_append_when_underscore_slot_also_taken() {
1407 let out = deconflict_package_modules(&names(&["Oof"]), &children(&["oof", "oof_"]));
1409 assert_eq!(out, vec!["oof__".to_string()]);
1410 }
1411
1412 #[test]
1413 fn deconflict_two_messages_racing_to_same_slot_stay_distinct() {
1414 let out = deconflict_package_modules(&names(&["Oof", "Oof_"]), &children(&["oof", "oof_"]));
1417 assert_eq!(out, vec!["oof__".to_string(), "oof___".to_string()]);
1418 let set: HashSet<&String> = out.iter().collect();
1420 assert_eq!(set.len(), out.len());
1421 assert!(!out.contains(&"oof".to_string()) && !out.contains(&"oof_".to_string()));
1422 }
1423
1424 #[test]
1425 fn deconflict_is_independent_of_declaration_order() {
1426 let ch = children(&["oof", "oof_"]);
1428 let fwd = deconflict_package_modules(&names(&["Oof", "Oof_"]), &ch);
1429 let rev = deconflict_package_modules(&names(&["Oof_", "Oof"]), &ch);
1430 assert_eq!(fwd, vec!["oof__".to_string(), "oof___".to_string()]);
1432 assert_eq!(rev, vec!["oof___".to_string(), "oof__".to_string()]);
1433 }
1434
1435 #[test]
1436 fn deconflict_avoids_other_messages_raw_base() {
1437 let out = deconflict_package_modules(&names(&["Oof", "Oof_"]), &children(&["oof"]));
1440 assert_eq!(out, vec!["oof__".to_string(), "oof_".to_string()]);
1442 }
1443
1444 #[test]
1445 fn deconflict_never_yields_the_sentinel() {
1446 let out = deconflict_package_modules(&names(&["Buffa"]), &children(&["__buffa", "buffa"]));
1447 assert_eq!(out, vec!["buffa_".to_string()]);
1449 assert_ne!(out[0], SENTINEL_MOD);
1450 }
1451
1452 #[test]
1453 fn child_package_segments_extracts_immediate_segment() {
1454 let pkgs = children(&["foo", "foo.oof", "foo.bar.baz", "foobar"]);
1455 let mut got: Vec<String> = child_package_segments("foo", &pkgs).into_iter().collect();
1456 got.sort();
1457 assert_eq!(got, vec!["bar".to_string(), "oof".to_string()]);
1459 }
1460
1461 fn make_file(
1462 name: &str,
1463 package: &str,
1464 messages: Vec<DescriptorProto>,
1465 enums: Vec<EnumDescriptorProto>,
1466 ) -> FileDescriptorProto {
1467 FileDescriptorProto {
1468 name: Some(name.to_string()),
1469 package: if package.is_empty() {
1470 None
1471 } else {
1472 Some(package.to_string())
1473 },
1474 message_type: messages,
1475 enum_type: enums,
1476 ..Default::default()
1477 }
1478 }
1479
1480 fn msg(name: &str) -> DescriptorProto {
1481 DescriptorProto {
1482 name: Some(name.to_string()),
1483 ..Default::default()
1484 }
1485 }
1486
1487 fn msg_with_nested(name: &str, nested: Vec<DescriptorProto>) -> DescriptorProto {
1488 DescriptorProto {
1489 name: Some(name.to_string()),
1490 nested_type: nested,
1491 ..Default::default()
1492 }
1493 }
1494
1495 fn msg_with_nested_and_enums(
1496 name: &str,
1497 nested: Vec<DescriptorProto>,
1498 enums: Vec<EnumDescriptorProto>,
1499 ) -> DescriptorProto {
1500 DescriptorProto {
1501 name: Some(name.to_string()),
1502 nested_type: nested,
1503 enum_type: enums,
1504 ..Default::default()
1505 }
1506 }
1507
1508 fn enum_desc(name: &str) -> EnumDescriptorProto {
1509 EnumDescriptorProto {
1510 name: Some(name.to_string()),
1511 ..Default::default()
1512 }
1513 }
1514
1515 fn enum_with_closed_feature(name: &str) -> EnumDescriptorProto {
1516 use crate::generated::descriptor::{feature_set, EnumOptions, FeatureSet};
1517 EnumDescriptorProto {
1518 name: Some(name.to_string()),
1519 options: buffa::MessageField::some(EnumOptions {
1520 features: buffa::MessageField::some(FeatureSet {
1521 enum_type: Some(feature_set::EnumType::CLOSED),
1522 ..Default::default()
1523 }),
1524 ..Default::default()
1525 }),
1526 ..Default::default()
1527 }
1528 }
1529
1530 fn editions_file(
1531 name: &str,
1532 package: &str,
1533 messages: Vec<DescriptorProto>,
1534 enums: Vec<EnumDescriptorProto>,
1535 ) -> FileDescriptorProto {
1536 use crate::generated::descriptor::Edition;
1537 FileDescriptorProto {
1538 name: Some(name.to_string()),
1539 package: Some(package.to_string()),
1540 syntax: Some("editions".to_string()),
1541 edition: Some(Edition::EDITION_2023),
1542 message_type: messages,
1543 enum_type: enums,
1544 ..Default::default()
1545 }
1546 }
1547
1548 #[test]
1551 fn test_message_with_package() {
1552 let files = [make_file(
1553 "test.proto",
1554 "my.package",
1555 vec![msg("Foo")],
1556 vec![],
1557 )];
1558 let config = CodeGenConfig::default();
1559 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1560 assert_eq!(ctx.rust_type(".my.package.Foo"), Some("my::package::Foo"));
1561 }
1562
1563 #[test]
1564 fn test_message_no_package() {
1565 let files = [make_file("test.proto", "", vec![msg("Bar")], vec![])];
1566 let config = CodeGenConfig::default();
1567 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1568 assert_eq!(ctx.rust_type(".Bar"), Some("Bar"));
1569 }
1570
1571 #[test]
1572 fn test_nested_message_uses_module_path() {
1573 let outer = msg_with_nested("Outer", vec![msg("Inner")]);
1574 let files = [make_file("test.proto", "pkg", vec![outer], vec![])];
1575 let config = CodeGenConfig::default();
1576 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1577 assert_eq!(ctx.rust_type(".pkg.Outer"), Some("pkg::Outer"));
1578 assert_eq!(ctx.rust_type(".pkg.Outer.Inner"), Some("pkg::outer::Inner"));
1580 }
1581
1582 #[test]
1583 fn test_nested_message_no_package() {
1584 let outer = msg_with_nested("Outer", vec![msg("Inner")]);
1585 let files = [make_file("test.proto", "", vec![outer], vec![])];
1586 let config = CodeGenConfig::default();
1587 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1588 assert_eq!(ctx.rust_type(".Outer"), Some("Outer"));
1589 assert_eq!(ctx.rust_type(".Outer.Inner"), Some("outer::Inner"));
1590 }
1591
1592 #[test]
1593 fn test_deeply_nested_message() {
1594 let deep = msg_with_nested("A", vec![msg_with_nested("B", vec![msg("C")])]);
1595 let files = [make_file("test.proto", "pkg", vec![deep], vec![])];
1596 let config = CodeGenConfig::default();
1597 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1598 assert_eq!(ctx.rust_type(".pkg.A"), Some("pkg::A"));
1599 assert_eq!(ctx.rust_type(".pkg.A.B"), Some("pkg::a::B"));
1600 assert_eq!(ctx.rust_type(".pkg.A.B.C"), Some("pkg::a::b::C"));
1601 }
1602
1603 #[test]
1604 fn test_nested_enum_uses_module_path() {
1605 let outer = msg_with_nested_and_enums("Outer", vec![], vec![enum_desc("Status")]);
1606 let files = [make_file("test.proto", "pkg", vec![outer], vec![])];
1607 let config = CodeGenConfig::default();
1608 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1609 assert_eq!(
1610 ctx.rust_type(".pkg.Outer.Status"),
1611 Some("pkg::outer::Status")
1612 );
1613 }
1614
1615 #[test]
1616 fn test_top_level_enum() {
1617 let files = [make_file(
1618 "test.proto",
1619 "pkg",
1620 vec![],
1621 vec![enum_desc("Status")],
1622 )];
1623 let config = CodeGenConfig::default();
1624 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1625 assert_eq!(ctx.rust_type(".pkg.Status"), Some("pkg::Status"));
1626 }
1627
1628 #[test]
1629 fn test_same_named_nested_types_in_different_parents_are_distinct() {
1630 let outer1 = msg_with_nested("Outer1", vec![msg("Inner")]);
1631 let outer2 = msg_with_nested("Outer2", vec![msg("Inner")]);
1632 let files = [make_file("a.proto", "pkg", vec![outer1, outer2], vec![])];
1633 let config = CodeGenConfig::default();
1634 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1635 assert_eq!(
1637 ctx.rust_type(".pkg.Outer1.Inner"),
1638 Some("pkg::outer1::Inner")
1639 );
1640 assert_eq!(
1641 ctx.rust_type(".pkg.Outer2.Inner"),
1642 Some("pkg::outer2::Inner")
1643 );
1644 assert_ne!(
1645 ctx.rust_type(".pkg.Outer1.Inner"),
1646 ctx.rust_type(".pkg.Outer2.Inner")
1647 );
1648 }
1649
1650 #[test]
1651 fn test_multiple_files() {
1652 let files = [
1653 make_file("a.proto", "ns.a", vec![msg("MsgA")], vec![]),
1654 make_file("b.proto", "ns.b", vec![msg("MsgB")], vec![]),
1655 ];
1656 let config = CodeGenConfig::default();
1657 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1658 assert_eq!(ctx.rust_type(".ns.a.MsgA"), Some("ns::a::MsgA"));
1659 assert_eq!(ctx.rust_type(".ns.b.MsgB"), Some("ns::b::MsgB"));
1660 }
1661
1662 #[test]
1672 fn test_extern_path_exact_per_type_match() {
1673 let files = [make_file(
1674 "test.proto",
1675 "test.pkg",
1676 vec![msg("Msg")],
1677 vec![],
1678 )];
1679 let config = CodeGenConfig {
1680 extern_paths: vec![(".test.pkg.Msg".into(), "::ext_crate::Msg".into())],
1681 ..Default::default()
1682 };
1683 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1684 assert_eq!(ctx.rust_type(".test.pkg.Msg"), Some("::ext_crate::Msg"));
1686 }
1687
1688 #[test]
1689 fn test_extern_path_per_type_overrides_package_prefix() {
1690 let files = [make_file(
1694 "test.proto",
1695 "test.pkg",
1696 vec![msg("Msg"), msg("Other")],
1697 vec![],
1698 )];
1699 let config = CodeGenConfig {
1700 extern_paths: vec![
1701 (".test.pkg".into(), "::pkg_crate".into()),
1702 (".test.pkg.Msg".into(), "::ext_crate::Msg".into()),
1703 ],
1704 ..Default::default()
1705 };
1706 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1707 assert_eq!(ctx.rust_type(".test.pkg.Msg"), Some("::ext_crate::Msg"));
1708 assert_eq!(ctx.rust_type(".test.pkg.Other"), Some("::pkg_crate::Other"));
1709 }
1710
1711 #[test]
1712 fn test_extern_path_nested_type_inherits_per_type_override() {
1713 let outer = msg_with_nested("Outer", vec![msg("Inner")]);
1716 let files = [make_file("test.proto", "test.pkg", vec![outer], vec![])];
1717 let config = CodeGenConfig {
1718 extern_paths: vec![(".test.pkg.Outer".into(), "::ext::Outer".into())],
1719 ..Default::default()
1720 };
1721 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1722 assert_eq!(ctx.rust_type(".test.pkg.Outer"), Some("::ext::Outer"));
1723 assert_eq!(
1724 ctx.rust_type(".test.pkg.Outer.Inner"),
1725 Some("::ext::outer::Inner")
1726 );
1727 }
1728
1729 #[test]
1730 fn test_extern_path_exact_per_type_enum() {
1731 let files = [make_file(
1732 "test.proto",
1733 "test.pkg",
1734 vec![],
1735 vec![enum_desc("Status")],
1736 )];
1737 let config = CodeGenConfig {
1738 extern_paths: vec![(".test.pkg.Status".into(), "::ext_crate::Status".into())],
1739 ..Default::default()
1740 };
1741 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1742 assert_eq!(
1743 ctx.rust_type(".test.pkg.Status"),
1744 Some("::ext_crate::Status")
1745 );
1746 }
1747
1748 #[test]
1749 fn test_extern_path_package_prefix_still_resolves() {
1750 let files = [make_file(
1753 "test.proto",
1754 "test.pkg",
1755 vec![msg("Msg")],
1756 vec![],
1757 )];
1758 let config = CodeGenConfig {
1759 extern_paths: vec![(".test.pkg".into(), "::pkg_crate".into())],
1760 ..Default::default()
1761 };
1762 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1763 assert_eq!(ctx.rust_type(".test.pkg.Msg"), Some("::pkg_crate::Msg"));
1764 }
1765
1766 #[test]
1767 fn test_extern_path_per_type_does_not_affect_unmapped_type() {
1768 let files = [make_file(
1770 "test.proto",
1771 "test.pkg",
1772 vec![msg("Msg"), msg("Other")],
1773 vec![],
1774 )];
1775 let config = CodeGenConfig {
1776 extern_paths: vec![(".test.pkg.Msg".into(), "::ext_crate::Msg".into())],
1777 ..Default::default()
1778 };
1779 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1780 assert_eq!(ctx.rust_type(".test.pkg.Msg"), Some("::ext_crate::Msg"));
1781 assert_eq!(ctx.rust_type(".test.pkg.Other"), Some("test::pkg::Other"));
1783 }
1784
1785 #[test]
1786 fn test_keyword_package_segment_in_type_map() {
1787 let files = [make_file(
1790 "latlng.proto",
1791 "google.type",
1792 vec![msg("LatLng")],
1793 vec![],
1794 )];
1795 let config = CodeGenConfig::default();
1796 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1797 assert_eq!(
1798 ctx.rust_type(".google.type.LatLng"),
1799 Some("google::type::LatLng")
1800 );
1801 }
1802
1803 #[test]
1804 fn test_keyword_package_relative_same_package() {
1805 let files = [make_file(
1806 "latlng.proto",
1807 "google.type",
1808 vec![msg("LatLng"), msg("Expr")],
1809 vec![],
1810 )];
1811 let config = CodeGenConfig::default();
1812 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1813 assert_eq!(
1815 ctx.rust_type_relative(".google.type.LatLng", "google.type", 0),
1816 Some("LatLng".into())
1817 );
1818 }
1819
1820 #[test]
1821 fn test_keyword_package_cross_package() {
1822 let files = [
1823 make_file("latlng.proto", "google.type", vec![msg("LatLng")], vec![]),
1824 make_file("svc.proto", "google.cloud", vec![msg("Service")], vec![]),
1825 ];
1826 let config = CodeGenConfig::default();
1827 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1828 assert_eq!(
1831 ctx.rust_type_relative(".google.type.LatLng", "google.cloud", 0),
1832 Some("super::type::LatLng".into())
1833 );
1834 }
1835
1836 #[test]
1837 fn test_keyword_nested_message_module() {
1838 let outer = msg_with_nested("Type", vec![msg("Inner")]);
1840 let files = [make_file("test.proto", "pkg", vec![outer], vec![])];
1841 let config = CodeGenConfig::default();
1842 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1843 assert_eq!(ctx.rust_type(".pkg.Type"), Some("pkg::Type"));
1844 assert_eq!(ctx.rust_type(".pkg.Type.Inner"), Some("pkg::type::Inner"));
1845 }
1846
1847 #[test]
1848 fn test_unknown_type_returns_none() {
1849 let files = [make_file("test.proto", "pkg", vec![msg("Foo")], vec![])];
1850 let config = CodeGenConfig::default();
1851 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1852 assert_eq!(ctx.rust_type(".pkg.Unknown"), None);
1853 }
1854
1855 #[test]
1858 fn test_relative_same_package_top_level() {
1859 let files = [make_file("a.proto", "pkg", vec![msg("Foo")], vec![])];
1860 let config = CodeGenConfig::default();
1861 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1862 assert_eq!(
1864 ctx.rust_type_relative(".pkg.Foo", "pkg", 0),
1865 Some("Foo".into())
1866 );
1867 }
1868
1869 #[test]
1870 fn test_relative_cross_package() {
1871 let files = [
1872 make_file("a.proto", "pkg_a", vec![msg("Foo")], vec![]),
1873 make_file("b.proto", "pkg_b", vec![msg("Bar")], vec![]),
1874 ];
1875 let config = CodeGenConfig::default();
1876 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1877 assert_eq!(
1879 ctx.rust_type_relative(".pkg_a.Foo", "pkg_b", 0),
1880 Some("super::pkg_a::Foo".into())
1881 );
1882 }
1883
1884 #[test]
1885 fn test_relative_no_package() {
1886 let files = [make_file("a.proto", "", vec![msg("Foo")], vec![])];
1887 let config = CodeGenConfig::default();
1888 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1889 assert_eq!(ctx.rust_type_relative(".Foo", "", 0), Some("Foo".into()));
1890 }
1891
1892 #[test]
1893 fn test_relative_unknown_returns_none() {
1894 let files = [make_file("a.proto", "pkg", vec![msg("Foo")], vec![])];
1895 let config = CodeGenConfig::default();
1896 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1897 assert_eq!(ctx.rust_type_relative(".pkg.Unknown", "pkg", 0), None);
1898 }
1899
1900 #[test]
1901 fn test_relative_dotted_package() {
1902 let files = [make_file("a.proto", "my.pkg", vec![msg("Foo")], vec![])];
1903 let config = CodeGenConfig::default();
1904 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1905 assert_eq!(
1906 ctx.rust_type_relative(".my.pkg.Foo", "my.pkg", 0),
1907 Some("Foo".into())
1908 );
1909 }
1910
1911 #[test]
1912 fn test_relative_cross_dotted_packages() {
1913 let files = [
1914 make_file(
1915 "timestamp.proto",
1916 "google.protobuf",
1917 vec![msg("Timestamp")],
1918 vec![],
1919 ),
1920 make_file(
1921 "test.proto",
1922 "protobuf_test_messages.proto3",
1923 vec![msg("TestAllTypesProto3")],
1924 vec![],
1925 ),
1926 ];
1927 let config = CodeGenConfig::default();
1928 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1929
1930 assert_eq!(
1932 ctx.rust_type_relative(
1933 ".google.protobuf.Timestamp",
1934 "protobuf_test_messages.proto3",
1935 0,
1936 ),
1937 Some("super::super::google::protobuf::Timestamp".into())
1938 );
1939 }
1940
1941 #[test]
1942 fn test_relative_nested_type_from_same_package() {
1943 let outer = msg_with_nested("Outer", vec![msg("Inner")]);
1945 let files = [make_file("test.proto", "pkg", vec![outer], vec![])];
1946 let config = CodeGenConfig::default();
1947 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1948
1949 assert_eq!(
1951 ctx.rust_type_relative(".pkg.Outer.Inner", "pkg", 0),
1952 Some("outer::Inner".into())
1953 );
1954 }
1955
1956 #[test]
1957 fn test_relative_shared_prefix_not_confused() {
1958 let files = [
1959 make_file("ab.proto", "a.b", vec![msg("Msg1")], vec![]),
1960 make_file("abc.proto", "a.bc", vec![msg("Msg2")], vec![]),
1961 ];
1962 let config = CodeGenConfig::default();
1963 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1964
1965 assert_eq!(
1967 ctx.rust_type_relative(".a.b.Msg1", "a.bc", 0),
1968 Some("super::b::Msg1".into())
1969 );
1970 assert_eq!(
1972 ctx.rust_type_relative(".a.bc.Msg2", "a.b", 0),
1973 Some("super::bc::Msg2".into())
1974 );
1975 }
1976
1977 #[test]
1980 fn test_relative_cross_package_nesting_1() {
1981 let outer = msg_with_nested_and_enums("Business", vec![], vec![enum_desc("Status")]);
1985 let files = [
1986 make_file("admin.proto", "a.b.admin.v1", vec![msg("Svc")], vec![]),
1987 make_file("biz.proto", "a.b.v1", vec![outer], vec![]),
1988 ];
1989 let config = CodeGenConfig::default();
1990 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
1991
1992 assert_eq!(
1994 ctx.rust_type_relative(".a.b.v1.Business.Status", "a.b.admin.v1", 0),
1995 Some("super::super::v1::business::Status".into())
1996 );
1997 assert_eq!(
1999 ctx.rust_type_relative(".a.b.v1.Business.Status", "a.b.admin.v1", 1),
2000 Some("super::super::super::v1::business::Status".into())
2001 );
2002 }
2003
2004 #[test]
2005 fn test_relative_same_package_nesting_1() {
2006 let files = [make_file(
2008 "test.proto",
2009 "pkg",
2010 vec![msg("Foo"), msg("Bar")],
2011 vec![],
2012 )];
2013 let config = CodeGenConfig::default();
2014 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
2015
2016 assert_eq!(
2018 ctx.rust_type_relative(".pkg.Foo", "pkg", 0),
2019 Some("Foo".into())
2020 );
2021 assert_eq!(
2023 ctx.rust_type_relative(".pkg.Foo", "pkg", 1),
2024 Some("super::Foo".into())
2025 );
2026 assert_eq!(
2028 ctx.rust_type_relative(".pkg.Foo", "pkg", 2),
2029 Some("super::super::Foo".into())
2030 );
2031 }
2032
2033 #[test]
2036 fn test_resolve_file_extern_exact_match_only() {
2037 let mappings = [(
2038 "google/protobuf/descriptor.proto".to_string(),
2039 "::buffa_descriptor::generated::descriptor".to_string(),
2040 )];
2041 assert_eq!(
2043 resolve_file_extern("google/protobuf/descriptor.proto", &mappings),
2044 Some("::buffa_descriptor::generated::descriptor"),
2045 );
2046 assert_eq!(
2049 resolve_file_extern("google/protobuf/timestamp.proto", &mappings),
2050 None,
2051 );
2052 assert_eq!(
2054 resolve_file_extern("vendor/google/protobuf/descriptor.proto", &mappings),
2055 None,
2056 );
2057 }
2058
2059 #[test]
2060 fn test_resolve_extern_prefix_exact_match() {
2061 let result = resolve_extern_prefix(
2062 "my.common",
2063 &[(".my.common".into(), "::common_protos".into())],
2064 );
2065 assert_eq!(result, Some("::common_protos".into()));
2066 }
2067
2068 #[test]
2069 fn test_resolve_extern_prefix_sub_package() {
2070 let result = resolve_extern_prefix(
2071 "my.common.sub",
2072 &[(".my.common".into(), "::common_protos".into())],
2073 );
2074 assert_eq!(result, Some("::common_protos::sub".into()));
2075 }
2076
2077 #[test]
2078 fn test_resolve_extern_prefix_no_match() {
2079 let result = resolve_extern_prefix(
2080 "other.pkg",
2081 &[(".my.common".into(), "::common_protos".into())],
2082 );
2083 assert_eq!(result, None);
2084 }
2085
2086 #[test]
2087 fn test_resolve_extern_prefix_partial_name_no_match() {
2088 let result = resolve_extern_prefix(
2090 "my.commonext",
2091 &[(".my.common".into(), "::common_protos".into())],
2092 );
2093 assert_eq!(result, None);
2094 }
2095
2096 #[test]
2097 fn test_resolve_extern_prefix_longest_match_wins() {
2098 let result = resolve_extern_prefix(
2100 "my.common.sub",
2101 &[
2102 (".my".into(), "::crate_a".into()),
2103 (".my.common".into(), "::crate_b".into()),
2104 ],
2105 );
2106 assert_eq!(result, Some("::crate_b::sub".into()));
2107 }
2108
2109 #[test]
2110 fn test_resolve_extern_prefix_catchall() {
2111 let result = resolve_extern_prefix("greet.v1", &[(".".into(), "crate::proto".into())]);
2112 assert_eq!(result, Some("crate::proto::greet::v1".into()));
2113 }
2114
2115 #[test]
2116 fn test_resolve_extern_prefix_catchall_empty_pkg() {
2117 let result = resolve_extern_prefix("", &[(".".into(), "crate::proto".into())]);
2120 assert_eq!(result, Some("crate::proto".into()));
2121 }
2122
2123 #[test]
2124 fn test_resolve_extern_prefix_catchall_longest_wins() {
2125 let result = resolve_extern_prefix(
2128 "google.protobuf",
2129 &[
2130 (".".into(), "crate::proto".into()),
2131 (
2132 ".google.protobuf".into(),
2133 "::buffa_types::google::protobuf".into(),
2134 ),
2135 ],
2136 );
2137 assert_eq!(result, Some("::buffa_types::google::protobuf".into()));
2138 }
2139
2140 #[test]
2141 fn test_resolve_extern_prefix_catchall_keyword_package() {
2142 let result = resolve_extern_prefix("google.type", &[(".".into(), "crate::proto".into())]);
2145 assert_eq!(result, Some("crate::proto::google::type".into()));
2146 }
2147
2148 #[test]
2151 fn test_resolve_extern_type_exact_match() {
2152 let result = resolve_extern_type(
2154 ".google.protobuf.Timestamp",
2155 &[(
2156 ".google.protobuf.Timestamp".into(),
2157 "::pbjson_types::Timestamp".into(),
2158 )],
2159 );
2160 assert_eq!(result, Some("::pbjson_types::Timestamp".into()));
2161 }
2162
2163 #[test]
2164 fn test_resolve_extern_type_exact_wins_over_prefix() {
2165 let result = resolve_extern_type(
2167 ".my.pkg.Msg",
2168 &[
2169 (".my.pkg".into(), "::pkg_crate".into()),
2170 (".my.pkg.Msg".into(), "::ext_crate::Msg".into()),
2171 ],
2172 );
2173 assert_eq!(result, Some("::ext_crate::Msg".into()));
2174 }
2175
2176 #[test]
2177 fn test_resolve_extern_type_package_prefix_appends_type() {
2178 let result = resolve_extern_type(
2181 ".my.common.sub.Msg",
2182 &[(".my.common".into(), "::common_protos".into())],
2183 );
2184 assert_eq!(result, Some("::common_protos::sub::Msg".into()));
2185 }
2186
2187 #[test]
2188 fn test_resolve_extern_type_catchall() {
2189 let result = resolve_extern_type(".greet.v1.Hello", &[(".".into(), "crate::proto".into())]);
2190 assert_eq!(result, Some("crate::proto::greet::v1::Hello".into()));
2191 }
2192
2193 #[test]
2194 fn test_resolve_extern_type_no_match() {
2195 let result = resolve_extern_type(
2196 ".other.pkg.Msg",
2197 &[(".my.common".into(), "::common_protos".into())],
2198 );
2199 assert_eq!(result, None);
2200 }
2201
2202 #[test]
2203 fn test_resolve_extern_type_partial_name_no_match() {
2204 let result = resolve_extern_type(
2206 ".my.commonext.Msg",
2207 &[(".my.common".into(), "::common_protos".into())],
2208 );
2209 assert_eq!(result, None);
2210 }
2211
2212 #[test]
2215 fn test_split_extern_top_level() {
2216 let outer = msg_with_nested("Value", vec![msg("Inner")]);
2217 let files = [make_file(
2218 "struct.proto",
2219 "google.protobuf",
2220 vec![outer],
2221 vec![],
2222 )];
2223 let config = CodeGenConfig::default();
2224 let extern_paths = vec![(
2225 ".google.protobuf".into(),
2226 "::buffa_types::google::protobuf".into(),
2227 )];
2228 let ctx = CodeGenContext::new(&files, &config, &extern_paths);
2229
2230 let split = ctx
2231 .rust_type_relative_split(".google.protobuf.Value", "my.pkg", 3)
2232 .expect("type resolves");
2233 assert!(split.is_extern);
2234 assert_eq!(split.to_package, "::buffa_types::google::protobuf");
2236 assert_eq!(split.within_package, "Value");
2237 }
2238
2239 #[test]
2240 fn test_split_extern_nested_type() {
2241 let outer = msg_with_nested("Value", vec![msg("Inner")]);
2246 let files = [make_file(
2247 "struct.proto",
2248 "google.protobuf",
2249 vec![outer],
2250 vec![],
2251 )];
2252 let config = CodeGenConfig::default();
2253 let extern_paths = vec![(
2254 ".google.protobuf".into(),
2255 "::buffa_types::google::protobuf".into(),
2256 )];
2257 let ctx = CodeGenContext::new(&files, &config, &extern_paths);
2258
2259 let split = ctx
2260 .rust_type_relative_split(".google.protobuf.Value.Inner", "my.pkg", 0)
2261 .expect("nested type resolves");
2262 assert!(split.is_extern);
2263 assert_eq!(split.to_package, "::buffa_types::google::protobuf");
2264 assert_eq!(split.within_package, "value::Inner");
2265 }
2266
2267 #[test]
2268 fn test_split_per_type_extern_override() {
2269 let outer = msg_with_nested("Outer", vec![msg("Inner")]);
2275 let files = [make_file("custom.proto", "my.pkg", vec![outer], vec![])];
2276 let config = CodeGenConfig {
2277 extern_paths: vec![(".my.pkg.Outer".into(), "::ext::custom::Outer".into())],
2278 ..Default::default()
2279 };
2280 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
2281
2282 let split = ctx
2283 .rust_type_relative_split(".my.pkg.Outer", "other.pkg", 2)
2284 .expect("overridden type resolves");
2285 assert!(split.is_extern);
2286 assert_eq!(split.to_package, "::ext::custom");
2287 assert_eq!(split.within_package, "Outer");
2288
2289 let nested = ctx
2292 .rust_type_relative_split(".my.pkg.Outer.Inner", "other.pkg", 0)
2293 .expect("nested type resolves");
2294 assert!(nested.is_extern);
2295 assert_eq!(nested.to_package, "::ext::custom");
2296 assert_eq!(nested.within_package, "outer::Inner");
2297 }
2298
2299 #[test]
2300 fn test_extern_path_top_level_message() {
2301 let files = [make_file(
2302 "common.proto",
2303 "my.common",
2304 vec![msg("SharedMsg")],
2305 vec![],
2306 )];
2307 let config = CodeGenConfig {
2308 extern_paths: vec![(".my.common".into(), "::common_protos".into())],
2309 ..Default::default()
2310 };
2311 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
2312 assert_eq!(
2313 ctx.rust_type(".my.common.SharedMsg"),
2314 Some("::common_protos::SharedMsg")
2315 );
2316 }
2317
2318 #[test]
2319 fn test_extern_path_nested_message() {
2320 let files = [make_file(
2321 "common.proto",
2322 "my.common",
2323 vec![msg_with_nested("Outer", vec![msg("Inner")])],
2324 vec![],
2325 )];
2326 let config = CodeGenConfig {
2327 extern_paths: vec![(".my.common".into(), "::common_protos".into())],
2328 ..Default::default()
2329 };
2330 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
2331 assert_eq!(
2332 ctx.rust_type(".my.common.Outer"),
2333 Some("::common_protos::Outer")
2334 );
2335 assert_eq!(
2336 ctx.rust_type(".my.common.Outer.Inner"),
2337 Some("::common_protos::outer::Inner")
2338 );
2339 }
2340
2341 #[test]
2342 fn test_extern_path_nested_types_use_deconflicted_module() {
2343 let money =
2345 msg_with_nested_and_enums("Money", vec![msg("Breakdown")], vec![enum_desc("Currency")]);
2346 let files = [
2347 make_file("lyft_money.proto", "pb.lyft", vec![money], vec![]),
2348 make_file("money.proto", "pb.lyft.money", vec![msg("Money")], vec![]),
2349 make_file(
2350 "users.proto",
2351 "pb.lyft.users",
2352 vec![msg("DailyTotalFares")],
2353 vec![],
2354 ),
2355 ];
2356 let config = CodeGenConfig {
2357 extern_paths: vec![(".pb.lyft".into(), "::idl_pb_lyft::pb::lyft".into())],
2358 ..Default::default()
2359 };
2360 let ctx = CodeGenContext::for_generate(&files, &["users.proto".to_string()], &config);
2361 assert_eq!(
2362 ctx.rust_type(".pb.lyft.Money.Currency"),
2363 Some("::idl_pb_lyft::pb::lyft::money_::Currency")
2364 );
2365 assert_eq!(
2366 ctx.rust_type(".pb.lyft.Money.Breakdown"),
2367 Some("::idl_pb_lyft::pb::lyft::money_::Breakdown")
2368 );
2369 }
2370
2371 #[test]
2372 fn test_extern_path_enum() {
2373 let files = [make_file(
2374 "common.proto",
2375 "my.common",
2376 vec![],
2377 vec![enum_desc("Status")],
2378 )];
2379 let config = CodeGenConfig {
2380 extern_paths: vec![(".my.common".into(), "::common_protos".into())],
2381 ..Default::default()
2382 };
2383 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
2384 assert_eq!(
2385 ctx.rust_type(".my.common.Status"),
2386 Some("::common_protos::Status")
2387 );
2388 }
2389
2390 #[test]
2391 fn test_extern_path_does_not_affect_other_packages() {
2392 let files = [
2393 make_file("common.proto", "my.common", vec![msg("SharedMsg")], vec![]),
2394 make_file(
2395 "service.proto",
2396 "my.service",
2397 vec![msg("MyService")],
2398 vec![],
2399 ),
2400 ];
2401 let config = CodeGenConfig {
2402 extern_paths: vec![(".my.common".into(), "::common_protos".into())],
2403 ..Default::default()
2404 };
2405 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
2406 assert_eq!(
2408 ctx.rust_type(".my.common.SharedMsg"),
2409 Some("::common_protos::SharedMsg")
2410 );
2411 assert_eq!(
2413 ctx.rust_type(".my.service.MyService"),
2414 Some("my::service::MyService")
2415 );
2416 }
2417
2418 #[test]
2419 fn test_extern_path_relative_returns_absolute() {
2420 let files = [
2423 make_file("common.proto", "my.common", vec![msg("SharedMsg")], vec![]),
2424 make_file(
2425 "service.proto",
2426 "my.service",
2427 vec![msg("MyService")],
2428 vec![],
2429 ),
2430 ];
2431 let config = CodeGenConfig {
2432 extern_paths: vec![(".my.common".into(), "::common_protos".into())],
2433 ..Default::default()
2434 };
2435 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
2436 assert_eq!(
2438 ctx.rust_type_relative(".my.common.SharedMsg", "my.service", 0),
2439 Some("::common_protos::SharedMsg".into())
2440 );
2441 }
2442
2443 #[test]
2446 fn test_is_enum_closed_proto3_default_open() {
2447 let files = [make_file("a.proto", "p", vec![], vec![enum_desc("E")])];
2448 let config = CodeGenConfig::default();
2449 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
2450 assert_eq!(ctx.is_enum_closed(".p.E"), Some(true));
2454 }
2455
2456 #[test]
2457 fn test_is_enum_closed_editions_default_open() {
2458 let files = [editions_file("a.proto", "p", vec![], vec![enum_desc("E")])];
2459 let config = CodeGenConfig::default();
2460 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
2461 assert_eq!(ctx.is_enum_closed(".p.E"), Some(false));
2463 }
2464
2465 #[test]
2466 fn test_is_enum_closed_per_enum_override() {
2467 let files = [editions_file(
2470 "a.proto",
2471 "p",
2472 vec![],
2473 vec![enum_desc("Open"), enum_with_closed_feature("Closed")],
2474 )];
2475 let config = CodeGenConfig::default();
2476 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
2477 assert_eq!(ctx.is_enum_closed(".p.Open"), Some(false));
2478 assert_eq!(ctx.is_enum_closed(".p.Closed"), Some(true));
2479 }
2480
2481 #[test]
2482 fn test_is_enum_closed_nested_per_enum_override() {
2483 let files = [editions_file(
2485 "a.proto",
2486 "p",
2487 vec![msg_with_nested_and_enums(
2488 "M",
2489 vec![],
2490 vec![enum_with_closed_feature("Inner")],
2491 )],
2492 vec![],
2493 )];
2494 let config = CodeGenConfig::default();
2495 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
2496 assert_eq!(ctx.is_enum_closed(".p.M.Inner"), Some(true));
2497 }
2498
2499 #[test]
2500 fn test_is_enum_closed_unknown_enum_returns_none() {
2501 let files = [editions_file("a.proto", "p", vec![], vec![])];
2502 let config = CodeGenConfig::default();
2503 let ctx = CodeGenContext::new(&files, &config, &config.extern_paths);
2504 assert_eq!(ctx.is_enum_closed(".other.Unknown"), None);
2506 }
2507
2508 #[test]
2509 fn test_for_generate_auto_injects_wkt_mapping() {
2510 let ts_msg = DescriptorProto {
2513 name: Some("Timestamp".into()),
2514 ..Default::default()
2515 };
2516 let files = [FileDescriptorProto {
2517 name: Some("google/protobuf/timestamp.proto".into()),
2518 package: Some("google.protobuf".into()),
2519 syntax: Some("proto3".into()),
2520 message_type: vec![ts_msg],
2521 ..Default::default()
2522 }];
2523 let config = CodeGenConfig::default();
2524 let ctx = CodeGenContext::for_generate(&files, &["other.proto".into()], &config);
2526 assert_eq!(
2527 ctx.rust_type(".google.protobuf.Timestamp"),
2528 Some("::buffa_types::google::protobuf::Timestamp"),
2529 "WKT auto-mapping must be applied via for_generate"
2530 );
2531 }
2532
2533 #[test]
2534 fn test_for_generate_suppresses_wkt_when_generating_wkt() {
2535 let ts_msg = DescriptorProto {
2538 name: Some("Timestamp".into()),
2539 ..Default::default()
2540 };
2541 let files = [FileDescriptorProto {
2542 name: Some("google/protobuf/timestamp.proto".into()),
2543 package: Some("google.protobuf".into()),
2544 syntax: Some("proto3".into()),
2545 message_type: vec![ts_msg],
2546 ..Default::default()
2547 }];
2548 let config = CodeGenConfig::default();
2549 let ctx = CodeGenContext::for_generate(
2550 &files,
2551 &["google/protobuf/timestamp.proto".into()],
2552 &config,
2553 );
2554 assert_eq!(
2556 ctx.rust_type(".google.protobuf.Timestamp"),
2557 Some("google::protobuf::Timestamp")
2558 );
2559 }
2560
2561 #[test]
2564 fn test_matching_attributes_catchall() {
2565 let attrs = vec![(".".into(), "#[derive(Foo)]".into())];
2567 let result = CodeGenContext::matching_attributes(&attrs, "my.pkg.MyMessage").unwrap();
2568 assert!(result.to_string().contains("derive"));
2569 }
2570
2571 #[test]
2572 fn test_matching_attributes_exact_match() {
2573 let attrs = vec![(".my.pkg.MyMessage".into(), "#[derive(Bar)]".into())];
2574 let result = CodeGenContext::matching_attributes(&attrs, "my.pkg.MyMessage").unwrap();
2575 assert!(result.to_string().contains("derive"));
2576 }
2577
2578 #[test]
2579 fn test_matching_attributes_package_prefix() {
2580 let attrs = vec![(".my.pkg".into(), "#[derive(Baz)]".into())];
2581 let result = CodeGenContext::matching_attributes(&attrs, "my.pkg.MyMessage").unwrap();
2582 assert!(result.to_string().contains("derive"));
2583 }
2584
2585 #[test]
2586 fn test_matching_attributes_no_partial_segment_match() {
2587 let attrs = vec![(".my.pk".into(), "#[derive(Bad)]".into())];
2589 let result = CodeGenContext::matching_attributes(&attrs, "my.pkg.MyMessage").unwrap();
2590 assert!(result.is_empty());
2591 }
2592
2593 #[test]
2594 fn test_matching_attributes_no_match() {
2595 let attrs = vec![(".other.pkg".into(), "#[derive(Nope)]".into())];
2596 let result = CodeGenContext::matching_attributes(&attrs, "my.pkg.MyMessage").unwrap();
2597 assert!(result.is_empty());
2598 }
2599
2600 #[test]
2601 fn test_matching_attributes_multiple_accumulate() {
2602 let attrs = vec![
2604 (".".into(), "#[derive(A)]".into()),
2605 (".my.pkg".into(), "#[derive(B)]".into()),
2606 ];
2607 let result = CodeGenContext::matching_attributes(&attrs, "my.pkg.MyMessage").unwrap();
2608 let s = result.to_string();
2609 assert!(s.contains("A") && s.contains("B"));
2610 }
2611
2612 #[test]
2613 fn test_matching_attributes_invalid_attr_errors() {
2614 let attrs = vec![(".".into(), "not valid {{{{".into())];
2617 let err = CodeGenContext::matching_attributes(&attrs, "my.pkg.Msg").unwrap_err();
2618 assert!(matches!(
2619 err,
2620 crate::CodeGenError::InvalidCustomAttribute { .. }
2621 ));
2622 }
2623
2624 #[test]
2625 fn test_matches_proto_prefix_catchall() {
2626 assert!(matches_proto_prefix(".", ".anything.here"));
2627 assert!(matches_proto_prefix(".", "."));
2628 }
2629
2630 #[test]
2631 fn test_matches_proto_prefix_segment_boundary() {
2632 assert!(!matches_proto_prefix(".my.pk", ".my.pkg.Msg"));
2634 assert!(matches_proto_prefix(".my.pkg", ".my.pkg.Msg"));
2636 }
2637}