#![doc(hidden)] use std::fmt; use std::mem; use std::ops::Deref; use protobuf::descriptor::FileDescriptorProto; use protobuf::reflect::FileDescriptor; use protobuf::reflect::MessageDescriptor; use crate::protobuf_ident::ProtobufIdent; use crate::protobuf_rel_path::ProtobufRelPath; use crate::ProtobufIdentRef; use crate::ProtobufRelPathRef; /// Protobuf absolute name (e. g. `.foo.Bar`). #[derive(Clone, Eq, PartialEq, Debug, Hash)] #[doc(hidden)] pub struct ProtobufAbsPath { pub path: String, } #[doc(hidden)] #[derive(Eq, PartialEq, Debug, Hash)] #[repr(C)] pub struct ProtobufAbsPathRef(str); impl Default for ProtobufAbsPath { fn default() -> ProtobufAbsPath { ProtobufAbsPath::root() } } impl Deref for ProtobufAbsPathRef { type Target = str; fn deref(&self) -> &str { &self.0 } } impl Deref for ProtobufAbsPath { type Target = ProtobufAbsPathRef; fn deref(&self) -> &ProtobufAbsPathRef { ProtobufAbsPathRef::new(&self.path) } } impl ProtobufAbsPathRef { pub fn is_root(&self) -> bool { self.0.is_empty() } pub fn root() -> &'static ProtobufAbsPathRef { Self::new("") } pub fn new(path: &str) -> &ProtobufAbsPathRef { assert!(ProtobufAbsPath::is_abs(path), "{:?} is not absolute", path); // SAFETY: repr(transparent) unsafe { mem::transmute(path) } } pub fn remove_prefix(&self, prefix: &ProtobufAbsPathRef) -> Option<&ProtobufRelPathRef> { if self.0.starts_with(&prefix.0) { let rem = &self.0[prefix.0.len()..]; if rem.is_empty() { return Some(ProtobufRelPathRef::empty()); } if rem.starts_with('.') { return Some(ProtobufRelPathRef::new(&rem[1..])); } } None } pub fn starts_with(&self, that: &ProtobufAbsPathRef) -> bool { self.remove_prefix(that).is_some() } pub fn as_str(&self) -> &str { &self.0 } pub fn to_owned(&self) -> ProtobufAbsPath { ProtobufAbsPath { path: self.0.to_owned(), } } pub fn parent(&self) -> Option<&ProtobufAbsPathRef> { match self.0.rfind('.') { Some(pos) => Some(ProtobufAbsPathRef::new(&self.0[..pos])), None => { if self.0.is_empty() { None } else { Some(ProtobufAbsPathRef::root()) } } } } pub fn self_and_parents(&self) -> Vec<&ProtobufAbsPathRef> { let mut tmp = self; let mut r: Vec<&ProtobufAbsPathRef> = Vec::new(); r.push(&self); while let Some(parent) = tmp.parent() { r.push(parent); tmp = parent; } r } } impl ProtobufAbsPath { pub fn root() -> ProtobufAbsPath { ProtobufAbsPathRef::root().to_owned() } pub fn as_ref(&self) -> &ProtobufAbsPathRef { ProtobufAbsPathRef::new(&self.path) } /// If given name is an fully quialified protobuf name. pub fn is_abs(path: &str) -> bool { path.is_empty() || (path.starts_with(".") && path != ".") } pub fn try_new(path: &str) -> Option { if ProtobufAbsPath::is_abs(path) { Some(ProtobufAbsPath::new(path)) } else { None } } pub fn new>(path: S) -> ProtobufAbsPath { let path = path.into(); assert!( ProtobufAbsPath::is_abs(&path), "path is not absolute: `{}`", path ); assert!(!path.ends_with("."), "{}", path); ProtobufAbsPath { path } } pub fn new_from_rel(path: &str) -> ProtobufAbsPath { assert!( !path.starts_with("."), "rel path must not start with dot: {:?}", path ); ProtobufAbsPath { path: if path.is_empty() { String::new() } else { format!(".{}", path) }, } } pub fn package_from_file_proto(file: &FileDescriptorProto) -> ProtobufAbsPath { Self::new_from_rel(file.package()) } pub fn package_from_file_descriptor(file: &FileDescriptor) -> ProtobufAbsPath { Self::package_from_file_proto(file.proto()) } pub fn from_message(message: &MessageDescriptor) -> ProtobufAbsPath { Self::new_from_rel(&message.full_name()) } pub fn concat(a: &ProtobufAbsPathRef, b: &ProtobufRelPathRef) -> ProtobufAbsPath { let mut a = a.to_owned(); a.push_relative(b); a } pub fn from_path_without_dot(path: &str) -> ProtobufAbsPath { assert!(!path.is_empty()); assert!(!path.starts_with(".")); assert!(!path.ends_with(".")); ProtobufAbsPath::new(format!(".{}", path)) } pub fn from_path_maybe_dot(path: &str) -> ProtobufAbsPath { if path.starts_with(".") { ProtobufAbsPath::new(path.to_owned()) } else { ProtobufAbsPath::from_path_without_dot(path) } } pub fn push_simple(&mut self, simple: &ProtobufIdentRef) { self.path.push('.'); self.path.push_str(&simple); } pub fn push_relative(&mut self, relative: &ProtobufRelPathRef) { if !relative.is_empty() { self.path.push_str(&format!(".{}", relative)); } } pub fn remove_suffix(&self, suffix: &ProtobufRelPathRef) -> Option<&ProtobufAbsPathRef> { if suffix.is_empty() { return Some(ProtobufAbsPathRef::new(&self.path)); } if self.path.ends_with(suffix.as_str()) { let rem = &self.path[..self.path.len() - suffix.as_str().len()]; if rem.is_empty() { return Some(ProtobufAbsPathRef::root()); } if rem.ends_with('.') { return Some(ProtobufAbsPathRef::new(&rem[..rem.len() - 1])); } } None } /// Pop the last name component pub fn pop(&mut self) -> Option { match self.path.rfind('.') { Some(dot) => { let ident = ProtobufIdent::new(&self.path[dot + 1..]); self.path.truncate(dot); Some(ident) } None => None, } } pub fn to_root_rel(&self) -> ProtobufRelPath { if self == &Self::root() { ProtobufRelPath::empty() } else { ProtobufRelPath::new(&self.path[1..]) } } pub fn ends_with(&self, that: &ProtobufRelPath) -> bool { self.remove_suffix(that).is_some() } } impl From<&'_ str> for ProtobufAbsPath { fn from(s: &str) -> Self { ProtobufAbsPath::new(s.to_owned()) } } impl From for ProtobufAbsPath { fn from(s: String) -> Self { ProtobufAbsPath::new(s) } } impl fmt::Display for ProtobufAbsPathRef { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", &self.0) } } impl fmt::Display for ProtobufAbsPath { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", ProtobufAbsPathRef::new(&self.0)) } } #[cfg(test)] mod test { use super::*; #[test] fn absolute_path_push_simple() { let mut foo = ProtobufAbsPath::new(".foo".to_owned()); foo.push_simple(ProtobufIdentRef::new("bar")); assert_eq!(ProtobufAbsPath::new(".foo.bar".to_owned()), foo); let mut foo = ProtobufAbsPath::root(); foo.push_simple(ProtobufIdentRef::new("bar")); assert_eq!(ProtobufAbsPath::new(".bar".to_owned()), foo); } #[test] fn absolute_path_remove_prefix() { assert_eq!( Some(ProtobufRelPathRef::empty()), ProtobufAbsPath::new(".foo".to_owned()) .remove_prefix(&ProtobufAbsPath::new(".foo".to_owned())) ); assert_eq!( Some(ProtobufRelPathRef::new("bar")), ProtobufAbsPath::new(".foo.bar".to_owned()) .remove_prefix(&ProtobufAbsPath::new(".foo".to_owned())) ); assert_eq!( Some(ProtobufRelPathRef::new("baz.qux")), ProtobufAbsPath::new(".foo.bar.baz.qux".to_owned()) .remove_prefix(&ProtobufAbsPath::new(".foo.bar".to_owned())) ); assert_eq!( None, ProtobufAbsPath::new(".foo.barbaz".to_owned()) .remove_prefix(ProtobufAbsPathRef::new(".foo.bar")) ); } #[test] fn self_and_parents() { assert_eq!( vec![ ProtobufAbsPathRef::new(".ab.cde.fghi"), ProtobufAbsPathRef::new(".ab.cde"), ProtobufAbsPathRef::new(".ab"), ProtobufAbsPathRef::root(), ], ProtobufAbsPath::new(".ab.cde.fghi".to_owned()).self_and_parents() ); } #[test] fn ends_with() { assert!(ProtobufAbsPath::new(".foo.bar").ends_with(&ProtobufRelPath::new(""))); assert!(ProtobufAbsPath::new(".foo.bar").ends_with(&ProtobufRelPath::new("bar"))); assert!(ProtobufAbsPath::new(".foo.bar").ends_with(&ProtobufRelPath::new("foo.bar"))); assert!(!ProtobufAbsPath::new(".foo.bar").ends_with(&ProtobufRelPath::new("foo.bar.baz"))); } }