| // Should not be a part of public API |
| #![doc(hidden)] |
| |
| use crate::descriptor::DescriptorProto; |
| use crate::descriptor::EnumDescriptorProto; |
| use crate::descriptor::EnumValueDescriptorProto; |
| use crate::descriptor::FieldDescriptorProto; |
| /// utilities to work with descriptor |
| use crate::descriptor::FileDescriptorProto; |
| use crate::descriptor::OneofDescriptorProto; |
| |
| use crate::rust; |
| use crate::strx; |
| |
| // Copy-pasted from libsyntax. |
| fn ident_start(c: char) -> bool { |
| (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' |
| } |
| |
| // Copy-pasted from libsyntax. |
| fn ident_continue(c: char) -> bool { |
| (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' |
| } |
| |
| pub fn proto_path_to_rust_mod(path: &str) -> String { |
| let without_dir = strx::remove_to(path, '/'); |
| let without_suffix = strx::remove_suffix(without_dir, ".proto"); |
| |
| let name = without_suffix |
| .chars() |
| .enumerate() |
| .map(|(i, c)| { |
| let valid = if i == 0 { |
| ident_start(c) |
| } else { |
| ident_continue(c) |
| }; |
| if valid { |
| c |
| } else { |
| '_' |
| } |
| }) |
| .collect::<String>(); |
| |
| let name = if rust::is_rust_keyword(&name) { |
| format!("{}_pb", name) |
| } else { |
| name |
| }; |
| name |
| } |
| |
| pub struct RootScope<'a> { |
| pub file_descriptors: &'a [FileDescriptorProto], |
| } |
| |
| impl<'a> RootScope<'a> { |
| fn packages(&'a self) -> Vec<FileScope<'a>> { |
| self.file_descriptors |
| .iter() |
| .map(|fd| FileScope { |
| file_descriptor: fd, |
| }) |
| .collect() |
| } |
| |
| // find enum by fully qualified name |
| pub fn find_enum(&'a self, fqn: &str) -> EnumWithScope<'a> { |
| match self.find_message_or_enum(fqn) { |
| MessageOrEnumWithScope::Enum(e) => e, |
| _ => panic!("not an enum: {}", fqn), |
| } |
| } |
| |
| // find message by fully qualified name |
| pub fn find_message(&'a self, fqn: &str) -> MessageWithScope<'a> { |
| match self.find_message_or_enum(fqn) { |
| MessageOrEnumWithScope::Message(m) => m, |
| _ => panic!("not a message: {}", fqn), |
| } |
| } |
| |
| // find message or enum by fully qualified name |
| pub fn find_message_or_enum(&'a self, fqn: &str) -> MessageOrEnumWithScope<'a> { |
| assert!(fqn.starts_with("."), "name must start with dot: {}", fqn); |
| let fqn1 = &fqn[1..]; |
| self.packages() |
| .into_iter() |
| .flat_map(|p| { |
| (if p.get_package().is_empty() { |
| p.find_message_or_enum(fqn1) |
| } else if fqn1.starts_with(&(p.get_package().to_string() + ".")) { |
| let remaining = &fqn1[(p.get_package().len() + 1)..]; |
| p.find_message_or_enum(remaining) |
| } else { |
| None |
| }) |
| .into_iter() |
| }) |
| .next() |
| .expect(&format!("enum not found by name: {}", fqn)) |
| } |
| } |
| |
| #[derive(Debug, Copy, Clone, PartialEq, Eq)] |
| pub enum Syntax { |
| PROTO2, |
| PROTO3, |
| } |
| |
| impl Syntax { |
| pub fn parse(s: &str) -> Self { |
| match s { |
| "" | "proto2" => Syntax::PROTO2, |
| "proto3" => Syntax::PROTO3, |
| _ => panic!("unsupported syntax value: {:?}", s), |
| } |
| } |
| } |
| |
| #[derive(Clone)] |
| pub struct FileScope<'a> { |
| pub file_descriptor: &'a FileDescriptorProto, |
| } |
| |
| impl<'a> FileScope<'a> { |
| fn get_package(&self) -> &'a str { |
| self.file_descriptor.get_package() |
| } |
| |
| pub fn syntax(&self) -> Syntax { |
| Syntax::parse(self.file_descriptor.get_syntax()) |
| } |
| |
| pub fn to_scope(&self) -> Scope<'a> { |
| Scope { |
| file_scope: self.clone(), |
| path: Vec::new(), |
| } |
| } |
| |
| fn find_message_or_enum(&self, name: &str) -> Option<MessageOrEnumWithScope<'a>> { |
| assert!(!name.starts_with(".")); |
| self.find_messages_and_enums() |
| .into_iter() |
| .filter(|e| e.name_to_package() == name) |
| .next() |
| } |
| |
| // find all enums in given file descriptor |
| pub fn find_enums(&self) -> Vec<EnumWithScope<'a>> { |
| let mut r = Vec::new(); |
| |
| self.to_scope().walk_scopes(|scope| { |
| r.extend(scope.get_enums()); |
| }); |
| |
| r |
| } |
| |
| // find all messages in given file descriptor |
| pub fn find_messages(&self) -> Vec<MessageWithScope<'a>> { |
| let mut r = Vec::new(); |
| |
| self.to_scope().walk_scopes(|scope| { |
| r.extend(scope.get_messages()); |
| }); |
| |
| r |
| } |
| |
| // find all messages and enums in given file descriptor |
| pub fn find_messages_and_enums(&self) -> Vec<MessageOrEnumWithScope<'a>> { |
| let mut r = Vec::new(); |
| |
| self.to_scope().walk_scopes(|scope| { |
| r.extend(scope.get_messages_and_enums()); |
| }); |
| |
| r |
| } |
| } |
| |
| #[derive(Clone)] |
| pub struct Scope<'a> { |
| pub file_scope: FileScope<'a>, |
| pub path: Vec<&'a DescriptorProto>, |
| } |
| |
| impl<'a> Scope<'a> { |
| pub fn get_file_descriptor(&self) -> &'a FileDescriptorProto { |
| self.file_scope.file_descriptor |
| } |
| |
| // get message descriptors in this scope |
| fn get_message_descriptors(&self) -> &'a [DescriptorProto] { |
| if self.path.is_empty() { |
| self.file_scope.file_descriptor.get_message_type() |
| } else { |
| self.path.last().unwrap().get_nested_type() |
| } |
| } |
| |
| // get enum descriptors in this scope |
| fn get_enum_descriptors(&self) -> &'a [EnumDescriptorProto] { |
| if self.path.is_empty() { |
| self.file_scope.file_descriptor.get_enum_type() |
| } else { |
| self.path.last().unwrap().get_enum_type() |
| } |
| } |
| |
| // get messages with attached scopes in this scope |
| pub fn get_messages(&self) -> Vec<MessageWithScope<'a>> { |
| self.get_message_descriptors() |
| .iter() |
| .map(|m| MessageWithScope { |
| scope: self.clone(), |
| message: m, |
| }) |
| .collect() |
| } |
| |
| // get enums with attached scopes in this scope |
| pub fn get_enums(&self) -> Vec<EnumWithScope<'a>> { |
| self.get_enum_descriptors() |
| .iter() |
| .map(|e| EnumWithScope { |
| scope: self.clone(), |
| en: e, |
| }) |
| .collect() |
| } |
| |
| // get messages and enums with attached scopes in this scope |
| pub fn get_messages_and_enums(&self) -> Vec<MessageOrEnumWithScope<'a>> { |
| self.get_messages() |
| .into_iter() |
| .map(|m| MessageOrEnumWithScope::Message(m)) |
| .chain( |
| self.get_enums() |
| .into_iter() |
| .map(|m| MessageOrEnumWithScope::Enum(m)), |
| ) |
| .collect() |
| } |
| |
| // nested scopes, i. e. scopes of nested messages |
| fn nested_scopes(&self) -> Vec<Scope<'a>> { |
| self.get_message_descriptors() |
| .iter() |
| .map(|m| { |
| let mut nested = self.clone(); |
| nested.path.push(m); |
| nested |
| }) |
| .collect() |
| } |
| |
| fn walk_scopes_impl<F: FnMut(&Scope<'a>)>(&self, callback: &mut F) { |
| (*callback)(self); |
| |
| for nested in self.nested_scopes() { |
| nested.walk_scopes_impl(callback); |
| } |
| } |
| |
| // apply callback for this scope and all nested scopes |
| fn walk_scopes<F>(&self, mut callback: F) |
| where |
| F: FnMut(&Scope<'a>), |
| { |
| self.walk_scopes_impl(&mut callback); |
| } |
| |
| pub fn prefix(&self) -> String { |
| if self.path.is_empty() { |
| "".to_string() |
| } else { |
| let v: Vec<&'a str> = self.path.iter().map(|m| m.get_name()).collect(); |
| let mut r = v.join("."); |
| r.push_str("."); |
| r |
| } |
| } |
| |
| // rust type name prefix for this scope |
| pub fn rust_prefix(&self) -> String { |
| self.prefix().replace(".", "_") |
| } |
| } |
| |
| pub trait WithScope<'a> { |
| fn get_scope(&self) -> &Scope<'a>; |
| |
| fn get_file_descriptor(&self) -> &'a FileDescriptorProto { |
| self.get_scope().get_file_descriptor() |
| } |
| |
| // message or enum name |
| fn get_name(&self) -> &'a str; |
| |
| fn escape_prefix(&self) -> &'static str; |
| |
| fn name_to_package(&self) -> String { |
| let mut r = self.get_scope().prefix(); |
| r.push_str(self.get_name()); |
| r |
| } |
| |
| /// Return absolute name starting with dot |
| fn name_absolute(&self) -> String { |
| let mut r = String::new(); |
| r.push_str("."); |
| let package = self.get_file_descriptor().get_package(); |
| if !package.is_empty() { |
| r.push_str(package); |
| r.push_str("."); |
| } |
| r.push_str(&self.name_to_package()); |
| r |
| } |
| |
| // rust type name of this descriptor |
| fn rust_name(&self) -> String { |
| let mut r = self.get_scope().rust_prefix(); |
| // Only escape if prefix is not empty |
| if r.is_empty() && rust::is_rust_keyword(self.get_name()) { |
| r.push_str(self.escape_prefix()); |
| } |
| r.push_str(self.get_name()); |
| r |
| } |
| |
| // fully-qualified name of this type |
| fn rust_fq_name(&self) -> String { |
| format!( |
| "{}::{}", |
| proto_path_to_rust_mod(self.get_scope().get_file_descriptor().get_name()), |
| self.rust_name() |
| ) |
| } |
| } |
| |
| #[derive(Clone)] |
| pub struct MessageWithScope<'a> { |
| pub scope: Scope<'a>, |
| pub message: &'a DescriptorProto, |
| } |
| |
| impl<'a> WithScope<'a> for MessageWithScope<'a> { |
| fn get_scope(&self) -> &Scope<'a> { |
| &self.scope |
| } |
| |
| fn escape_prefix(&self) -> &'static str { |
| "message_" |
| } |
| |
| fn get_name(&self) -> &'a str { |
| self.message.get_name() |
| } |
| } |
| |
| impl<'a> MessageWithScope<'a> { |
| pub fn into_scope(mut self) -> Scope<'a> { |
| self.scope.path.push(self.message); |
| self.scope |
| } |
| |
| pub fn to_scope(&self) -> Scope<'a> { |
| self.clone().into_scope() |
| } |
| |
| pub fn fields(&self) -> Vec<FieldWithContext<'a>> { |
| self.message |
| .get_field() |
| .iter() |
| .map(|f| FieldWithContext { |
| field: f, |
| message: self.clone(), |
| }) |
| .collect() |
| } |
| |
| pub fn oneofs(&self) -> Vec<OneofWithContext<'a>> { |
| self.message |
| .get_oneof_decl() |
| .iter() |
| .enumerate() |
| .map(|(index, oneof)| OneofWithContext { |
| message: self.clone(), |
| oneof: &oneof, |
| index: index as u32, |
| }) |
| .collect() |
| } |
| |
| pub fn oneof_by_index(&self, index: u32) -> OneofWithContext<'a> { |
| self.oneofs().swap_remove(index as usize) |
| } |
| |
| /// Pair of (key, value) if this message is map entry |
| pub fn map_entry(&'a self) -> Option<(FieldWithContext<'a>, FieldWithContext<'a>)> { |
| if self.message.get_options().get_map_entry() { |
| let key = self |
| .fields() |
| .into_iter() |
| .find(|f| f.field.get_number() == 1) |
| .unwrap(); |
| let value = self |
| .fields() |
| .into_iter() |
| .find(|f| f.field.get_number() == 2) |
| .unwrap(); |
| Some((key, value)) |
| } else { |
| None |
| } |
| } |
| } |
| |
| #[derive(Clone)] |
| pub struct EnumWithScope<'a> { |
| pub scope: Scope<'a>, |
| pub en: &'a EnumDescriptorProto, |
| } |
| |
| impl<'a> EnumWithScope<'a> { |
| // enum values |
| pub fn values(&'a self) -> &'a [EnumValueDescriptorProto] { |
| self.en.get_value() |
| } |
| |
| // find enum value by name |
| pub fn value_by_name(&'a self, name: &str) -> &'a EnumValueDescriptorProto { |
| self.en |
| .get_value() |
| .into_iter() |
| .find(|v| v.get_name() == name) |
| .unwrap() |
| } |
| } |
| |
| pub trait EnumValueDescriptorEx { |
| fn rust_name(&self) -> String; |
| } |
| |
| impl EnumValueDescriptorEx for EnumValueDescriptorProto { |
| fn rust_name(&self) -> String { |
| let mut r = String::new(); |
| if rust::is_rust_keyword(self.get_name()) { |
| r.push_str("value_"); |
| } |
| r.push_str(self.get_name()); |
| r |
| } |
| } |
| |
| impl<'a> WithScope<'a> for EnumWithScope<'a> { |
| fn get_scope(&self) -> &Scope<'a> { |
| &self.scope |
| } |
| |
| fn escape_prefix(&self) -> &'static str { |
| "enum_" |
| } |
| |
| fn get_name(&self) -> &'a str { |
| self.en.get_name() |
| } |
| } |
| |
| pub enum MessageOrEnumWithScope<'a> { |
| Message(MessageWithScope<'a>), |
| Enum(EnumWithScope<'a>), |
| } |
| |
| impl<'a> WithScope<'a> for MessageOrEnumWithScope<'a> { |
| fn get_scope(&self) -> &Scope<'a> { |
| match self { |
| &MessageOrEnumWithScope::Message(ref m) => m.get_scope(), |
| &MessageOrEnumWithScope::Enum(ref e) => e.get_scope(), |
| } |
| } |
| |
| fn escape_prefix(&self) -> &'static str { |
| match self { |
| &MessageOrEnumWithScope::Message(ref m) => m.escape_prefix(), |
| &MessageOrEnumWithScope::Enum(ref e) => e.escape_prefix(), |
| } |
| } |
| |
| fn get_name(&self) -> &'a str { |
| match self { |
| &MessageOrEnumWithScope::Message(ref m) => m.get_name(), |
| &MessageOrEnumWithScope::Enum(ref e) => e.get_name(), |
| } |
| } |
| } |
| |
| pub trait FieldDescriptorProtoExt { |
| fn rust_name(&self) -> String; |
| } |
| |
| impl FieldDescriptorProtoExt for FieldDescriptorProto { |
| fn rust_name(&self) -> String { |
| if rust::is_rust_keyword(self.get_name()) { |
| format!("field_{}", self.get_name()) |
| } else { |
| self.get_name().to_string() |
| } |
| } |
| } |
| |
| #[derive(Clone)] |
| pub struct FieldWithContext<'a> { |
| pub field: &'a FieldDescriptorProto, |
| pub message: MessageWithScope<'a>, |
| } |
| |
| impl<'a> FieldWithContext<'a> { |
| #[doc(hidden)] |
| pub fn is_oneof(&self) -> bool { |
| self.field.has_oneof_index() |
| } |
| |
| pub fn oneof(&self) -> Option<OneofWithContext<'a>> { |
| if self.is_oneof() { |
| Some( |
| self.message |
| .oneof_by_index(self.field.get_oneof_index() as u32), |
| ) |
| } else { |
| None |
| } |
| } |
| |
| pub fn number(&self) -> u32 { |
| self.field.get_number() as u32 |
| } |
| |
| /// Shortcut |
| pub fn name(&self) -> &str { |
| self.field.get_name() |
| } |
| |
| // field name in generated code |
| #[deprecated] |
| pub fn rust_name(&self) -> String { |
| self.field.rust_name() |
| } |
| |
| // From field to file root |
| pub fn containing_messages(&self) -> Vec<&'a DescriptorProto> { |
| let mut r = Vec::new(); |
| r.push(self.message.message); |
| r.extend(self.message.scope.path.iter().rev()); |
| r |
| } |
| } |
| |
| #[derive(Clone)] |
| pub struct OneofVariantWithContext<'a> { |
| pub oneof: &'a OneofWithContext<'a>, |
| pub field: &'a FieldDescriptorProto, |
| } |
| |
| #[derive(Clone)] |
| pub struct OneofWithContext<'a> { |
| pub oneof: &'a OneofDescriptorProto, |
| pub index: u32, |
| pub message: MessageWithScope<'a>, |
| } |
| |
| impl<'a> OneofWithContext<'a> { |
| /// Oneof rust name |
| pub fn name(&'a self) -> &'a str { |
| match self.oneof.get_name() { |
| "type" => "field_type", |
| "box" => "field_box", |
| x => x, |
| } |
| } |
| |
| /// rust type name of enum |
| pub fn rust_name(&self) -> String { |
| format!( |
| "{}_oneof_{}", |
| self.message.rust_name(), |
| self.oneof.get_name() |
| ) |
| } |
| |
| /// Oneof variants |
| pub fn variants(&'a self) -> Vec<OneofVariantWithContext<'a>> { |
| self.message |
| .fields() |
| .iter() |
| .filter(|f| f.field.has_oneof_index() && f.field.get_oneof_index() == self.index as i32) |
| .map(|f| OneofVariantWithContext { |
| oneof: self, |
| field: &f.field, |
| }) |
| .collect() |
| } |
| } |
| |
| /// Find message by rust type name |
| pub fn find_message_by_rust_name<'a>( |
| fd: &'a FileDescriptorProto, |
| rust_name: &str, |
| ) -> MessageWithScope<'a> { |
| FileScope { |
| file_descriptor: fd, |
| } |
| .find_messages() |
| .into_iter() |
| .find(|m| m.rust_name() == rust_name) |
| .unwrap() |
| } |
| |
| /// Find enum by rust type name |
| pub fn find_enum_by_rust_name<'a>( |
| fd: &'a FileDescriptorProto, |
| rust_name: &str, |
| ) -> EnumWithScope<'a> { |
| FileScope { |
| file_descriptor: fd, |
| } |
| .find_enums() |
| .into_iter() |
| .find(|e| e.rust_name() == rust_name) |
| .unwrap() |
| } |
| |
| #[cfg(test)] |
| mod test { |
| |
| use super::proto_path_to_rust_mod; |
| |
| #[test] |
| fn test_mod_path_proto_ext() { |
| assert_eq!("proto", proto_path_to_rust_mod("proto.proto")); |
| } |
| |
| #[test] |
| fn test_mod_path_unknown_ext() { |
| assert_eq!("proto_proto3", proto_path_to_rust_mod("proto.proto3")); |
| } |
| |
| #[test] |
| fn test_mod_path_empty_ext() { |
| assert_eq!("proto", proto_path_to_rust_mod("proto")); |
| } |
| } |