Skip to content

Commit

Permalink
prost-build: internal refactor with PathMap
Browse files Browse the repository at this point in the history
This commit adds a new internal data structure in prost-build: the
`PathMap`. `PathMap` better supports the frequent configuration pattern
of associating fully-qualified Protobuf paths with a value. The
fully-qualified paths are matched according to the prefix/suffix rules
already used by configuration options such as `Config::btree()`.
`PathMap` will be a good basis to build further support for custom
string, bytes, and maps types in the future, in addition to other code
generation options.

This commit should not have any publicly visible changes, except for a
small tweak to allow for more intuitive field matching when the pattern
is a suffix-matched message.
  • Loading branch information
danburkert committed Dec 31, 2020
1 parent 5eddc35 commit 79f0dfd
Show file tree
Hide file tree
Showing 6 changed files with 326 additions and 164 deletions.
5 changes: 3 additions & 2 deletions conformance/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ fn main() -> io::Result<()> {
Err(error) => conformance_response::Result::ParseError(format!("{:?}", error)),
};

let mut response = ConformanceResponse::default();
response.result = Some(result);
let response = ConformanceResponse {
result: Some(result),
};

let len = response.encoded_len();
bytes.clear();
Expand Down
151 changes: 83 additions & 68 deletions prost-build/src/code_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,16 @@ use prost_types::{

use crate::ast::{Comments, Method, Service};
use crate::extern_paths::ExternPaths;
use crate::ident::{match_ident, to_snake, to_upper_camel};
use crate::ident::{to_snake, to_upper_camel};
use crate::message_graph::MessageGraph;
use crate::Config;
use crate::{BytesType, Config, MapType};

#[derive(PartialEq)]
enum Syntax {
Proto2,
Proto3,
}

#[derive(PartialEq)]
enum BytesTy {
Vec,
Bytes,
}

impl BytesTy {
fn as_str(&self) -> &'static str {
match self {
BytesTy::Vec => "\"vec\"",
BytesTy::Bytes => "\"bytes\"",
}
}
}

pub struct CodeGenerator<'a> {
config: &'a mut Config,
package: String,
Expand Down Expand Up @@ -267,24 +252,25 @@ impl<'a> CodeGenerator<'a> {
fn append_type_attributes(&mut self, fq_message_name: &str) {
assert_eq!(b'.', fq_message_name.as_bytes()[0]);
// TODO: this clone is dirty, but expedious.
for (matcher, attribute) in self.config.type_attributes.clone() {
if match_ident(&matcher, fq_message_name, None) {
self.push_indent();
self.buf.push_str(&attribute);
self.buf.push('\n');
}
if let Some(attributes) = self.config.type_attributes.get(fq_message_name).cloned() {
self.push_indent();
self.buf.push_str(&attributes);
self.buf.push('\n');
}
}

fn append_field_attributes(&mut self, fq_message_name: &str, field_name: &str) {
assert_eq!(b'.', fq_message_name.as_bytes()[0]);
// TODO: this clone is dirty, but expedious.
for (matcher, attribute) in self.config.field_attributes.clone() {
if match_ident(&matcher, fq_message_name, Some(field_name)) {
self.push_indent();
self.buf.push_str(&attribute);
self.buf.push('\n');
}
if let Some(attributes) = self
.config
.field_attributes
.get_field(fq_message_name, field_name)
.cloned()
{
self.push_indent();
self.buf.push_str(&attributes);
self.buf.push('\n');
}
}

Expand Down Expand Up @@ -321,9 +307,14 @@ impl<'a> CodeGenerator<'a> {
self.buf.push_str(&type_tag);

if type_ == Type::Bytes {
self.buf.push('=');
let bytes_type = self
.config
.bytes_type
.get_field(fq_message_name, field.name())
.copied()
.unwrap_or_default();
self.buf
.push_str(self.bytes_backing_type(&field, fq_message_name).as_str());
.push_str(&format!("={:?}", bytes_type.annotation()));
}

match field.label() {
Expand Down Expand Up @@ -431,34 +422,28 @@ impl<'a> CodeGenerator<'a> {
self.append_doc(fq_message_name, Some(field.name()));
self.push_indent();

let btree_map = self
let map_type = self
.config
.btree_map
.iter()
.any(|matcher| match_ident(matcher, fq_message_name, Some(field.name())));
let (annotation_ty, lib_name, rust_ty) = if btree_map {
("btree_map", "::prost::alloc::collections", "BTreeMap")
} else {
("map", "::std::collections", "HashMap")
};

.map_type
.get_field(fq_message_name, field.name())
.copied()
.unwrap_or_default();
let key_tag = self.field_type_tag(key);
let value_tag = self.map_value_type_tag(value);

self.buf.push_str(&format!(
"#[prost({}=\"{}, {}\", tag=\"{}\")]\n",
annotation_ty,
map_type.annotation(),
key_tag,
value_tag,
field.number()
));
self.append_field_attributes(fq_message_name, field.name());
self.push_indent();
self.buf.push_str(&format!(
"pub {}: {}::{}<{}, {}>,\n",
"pub {}: {}<{}, {}>,\n",
to_snake(field.name()),
lib_name,
rust_ty,
map_type.rust_type(),
key_ty,
value_ty
));
Expand Down Expand Up @@ -580,12 +565,15 @@ impl<'a> CodeGenerator<'a> {
}

fn append_doc(&mut self, fq_name: &str, field_name: Option<&str>) {
if !self
.config
.disable_comments
.iter()
.any(|matcher| match_ident(matcher, fq_name, field_name))
{
let append_doc = if let Some(field_name) = field_name {
self.config
.disable_comments
.get_field(fq_name, field_name)
.is_none()
} else {
self.config.disable_comments.get(fq_name).is_none()
};
if append_doc {
Comments::from_location(self.location()).append_with_indent(self.depth, &mut self.buf)
}
}
Expand Down Expand Up @@ -759,10 +747,14 @@ impl<'a> CodeGenerator<'a> {
Type::Int64 | Type::Sfixed64 | Type::Sint64 => String::from("i64"),
Type::Bool => String::from("bool"),
Type::String => String::from("::prost::alloc::string::String"),
Type::Bytes => match self.bytes_backing_type(field, fq_message_name) {
BytesTy::Bytes => String::from("::prost::bytes::Bytes"),
BytesTy::Vec => String::from("::prost::alloc::vec::Vec<u8>"),
},
Type::Bytes => self
.config
.bytes_type
.get_field(fq_message_name, field.name())
.copied()
.unwrap_or_default()
.rust_type()
.to_owned(),
Type::Group | Type::Message => self.resolve_ident(field.type_name()),
}
}
Expand Down Expand Up @@ -845,19 +837,6 @@ impl<'a> CodeGenerator<'a> {
}
}

fn bytes_backing_type(&self, field: &FieldDescriptorProto, fq_message_name: &str) -> BytesTy {
let bytes = self
.config
.bytes
.iter()
.any(|matcher| match_ident(matcher, fq_message_name, Some(field.name())));
if bytes {
BytesTy::Bytes
} else {
BytesTy::Vec
}
}

/// Returns `true` if the field options includes the `deprecated` option.
fn deprecated(&self, field: &FieldDescriptorProto) -> bool {
field
Expand Down Expand Up @@ -1019,6 +998,42 @@ fn strip_enum_prefix<'a>(prefix: &str, name: &'a str) -> &'a str {
}
}

impl MapType {
/// The `prost-derive` annotation type corresponding to the map type.
fn annotation(&self) -> &'static str {
match self {
MapType::HashMap => "map",
MapType::BTreeMap => "btree_map",
}
}

/// The fully-qualified Rust type corresponding to the map type.
fn rust_type(&self) -> &'static str {
match self {
MapType::HashMap => "::std::collections::HashMap",
MapType::BTreeMap => "::prost::alloc::collections::BTreeMap",
}
}
}

impl BytesType {
/// The `prost-derive` annotation type corresponding to the bytes type.
fn annotation(&self) -> &'static str {
match self {
BytesType::Vec => "vec",
BytesType::Bytes => "bytes",
}
}

/// The fully-qualified Rust type corresponding to the bytes type.
fn rust_type(&self) -> &'static str {
match self {
BytesType::Vec => "::prost::alloc::vec::Vec<u8>",
BytesType::Bytes => "::prost::bytes::Bytes",
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
74 changes: 0 additions & 74 deletions prost-build/src/ident.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,40 +40,6 @@ pub fn to_upper_camel(s: &str) -> String {
ident
}

/// Matches a 'matcher' against a fully qualified identifier.
pub fn match_ident(matcher: &str, fq_name: &str, field_name: Option<&str>) -> bool {
assert_eq!(b'.', fq_name.as_bytes()[0]);

if matcher.is_empty() {
return false;
} else if matcher == "." {
return true;
}

let match_paths = matcher.split('.').collect::<Vec<_>>();
let field_paths = {
let mut paths = fq_name.split('.').collect::<Vec<_>>();
if let Some(field_name) = field_name {
paths.push(field_name);
}
paths
};

if &matcher[..1] == "." {
// Prefix match.
if match_paths.len() > field_paths.len() {
false
} else {
match_paths[..] == field_paths[..match_paths.len()]
}
// Suffix match.
} else if match_paths.len() > field_paths.len() {
false
} else {
match_paths[..] == field_paths[field_paths.len() - match_paths.len()..]
}
}

#[cfg(test)]
mod tests {

Expand Down Expand Up @@ -193,44 +159,4 @@ mod tests {
assert_eq!("FuzzBuster", &to_upper_camel("FuzzBuster"));
assert_eq!("Self_", &to_upper_camel("self"));
}

#[test]
fn test_match_ident() {
// Prefix matches
assert!(match_ident(".", ".foo.bar.Baz", Some("buzz")));
assert!(match_ident(".foo", ".foo.bar.Baz", Some("buzz")));
assert!(match_ident(".foo.bar", ".foo.bar.Baz", Some("buzz")));
assert!(match_ident(".foo.bar.Baz", ".foo.bar.Baz", Some("buzz")));
assert!(match_ident(
".foo.bar.Baz.buzz",
".foo.bar.Baz",
Some("buzz")
));

assert!(!match_ident(".fo", ".foo.bar.Baz", Some("buzz")));
assert!(!match_ident(".foo.", ".foo.bar.Baz", Some("buzz")));
assert!(!match_ident(".buzz", ".foo.bar.Baz", Some("buzz")));
assert!(!match_ident(".Baz.buzz", ".foo.bar.Baz", Some("buzz")));

// Suffix matches
assert!(match_ident("buzz", ".foo.bar.Baz", Some("buzz")));
assert!(match_ident("Baz.buzz", ".foo.bar.Baz", Some("buzz")));
assert!(match_ident("bar.Baz.buzz", ".foo.bar.Baz", Some("buzz")));
assert!(match_ident(
"foo.bar.Baz.buzz",
".foo.bar.Baz",
Some("buzz")
));

assert!(!match_ident("buz", ".foo.bar.Baz", Some("buzz")));
assert!(!match_ident("uz", ".foo.bar.Baz", Some("buzz")));

// Type names
assert!(match_ident("Baz", ".foo.bar.Baz", None));
assert!(match_ident(".", ".foo.bar.Baz", None));
assert!(match_ident(".foo.bar", ".foo.bar.Baz", None));
assert!(match_ident(".foo.bar.Baz", ".foo.bar.Baz", None));
assert!(!match_ident(".fo", ".foo.bar.Baz", None));
assert!(!match_ident(".buzz.Baz", ".foo.bar.Baz", None));
}
}

0 comments on commit 79f0dfd

Please sign in to comment.