Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix multiple attributes support #522

Merged
merged 2 commits into from
Sep 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 18 additions & 17 deletions prost-build/src/code_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ pub struct CodeGenerator<'a> {
buf: &'a mut String,
}

fn push_indent(buf: &mut String, depth: u8) {
for _ in 0..depth {
buf.push_str(" ");
}
}

impl<'a> CodeGenerator<'a> {
pub fn generate(
config: &mut Config,
Expand Down Expand Up @@ -256,25 +262,22 @@ 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.
if let Some(attributes) = self.config.type_attributes.get(fq_message_name).cloned() {
self.push_indent();
self.buf.push_str(&attributes);
for attribute in self.config.type_attributes.get(fq_message_name) {
push_indent(&mut self.buf, self.depth);
self.buf.push_str(&attribute);
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.
if let Some(attributes) = self
for attribute in self
.config
.field_attributes
.get_field(fq_message_name, field_name)
.cloned()
{
self.push_indent();
self.buf.push_str(&attributes);
push_indent(&mut self.buf, self.depth);
self.buf.push_str(&attribute);
self.buf.push('\n');
}
}
Expand Down Expand Up @@ -315,7 +318,7 @@ impl<'a> CodeGenerator<'a> {
let bytes_type = self
.config
.bytes_type
.get_field(fq_message_name, field.name())
.get_first_field(fq_message_name, field.name())
.copied()
.unwrap_or_default();
self.buf
Expand Down Expand Up @@ -427,7 +430,7 @@ impl<'a> CodeGenerator<'a> {
let map_type = self
.config
.map_type
.get_field(fq_message_name, field.name())
.get_first_field(fq_message_name, field.name())
.copied()
.unwrap_or_default();
let key_tag = self.field_type_tag(key);
Expand Down Expand Up @@ -570,10 +573,10 @@ impl<'a> CodeGenerator<'a> {
let append_doc = if let Some(field_name) = field_name {
self.config
.disable_comments
.get_field(fq_name, field_name)
.get_first_field(fq_name, field_name)
.is_none()
} else {
self.config.disable_comments.get(fq_name).is_none()
self.config.disable_comments.get(fq_name).next().is_none()
};
if append_doc {
Comments::from_location(self.location()).append_with_indent(self.depth, &mut self.buf)
Expand Down Expand Up @@ -712,9 +715,7 @@ impl<'a> CodeGenerator<'a> {
}

fn push_indent(&mut self) {
for _ in 0..self.depth {
self.buf.push_str(" ");
}
push_indent(&mut self.buf, self.depth);
}

fn push_mod(&mut self, module: &str) {
Expand Down Expand Up @@ -757,7 +758,7 @@ impl<'a> CodeGenerator<'a> {
Type::Bytes => self
.config
.bytes_type
.get_field(fq_message_name, field.name())
.get_first_field(fq_message_name, field.name())
.copied()
.unwrap_or_default()
.rust_type()
Expand Down
241 changes: 160 additions & 81 deletions prost-build/src/path.rs
Original file line number Diff line number Diff line change
@@ -1,61 +1,115 @@
//! Utilities for working with Protobuf paths.

use std::collections::HashMap;
use std::iter;

/// Maps a fully-qualified Protobuf path to a value using path matchers.
#[derive(Debug, Default)]
pub(crate) struct PathMap<T> {
matchers: HashMap<String, T>,
// insertion order might actually matter (to avoid warning about legacy-derive-helpers)
Guiguiprim marked this conversation as resolved.
Show resolved Hide resolved
// see: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#legacy-derive-helpers
pub(crate) matchers: Vec<(String, T)>,
}

impl<T> PathMap<T> {
/// Inserts a new matcher and associated value to the path map.
pub(crate) fn insert(&mut self, matcher: String, value: T) {
self.matchers.insert(matcher, value);
self.matchers.push((matcher, value));
}

/// Returns the value which matches the provided fully-qualified Protobuf path.
pub(crate) fn get(&self, fq_path: &'_ str) -> Option<&T> {
// First, try matching the full path.
iter::once(fq_path)
// Then, try matching path suffixes.
.chain(suffixes(fq_path))
// Then, try matching path prefixes.
.chain(prefixes(fq_path))
// Then, match the global path. This matcher must never fail, since the constructor
// initializes it.
.chain(iter::once("."))
.flat_map(|path| self.matchers.get(path))
.next()
/// Returns a iterator over all the value matching the given fd_path and associated suffix/prefix path
pub(crate) fn get(&self, fq_path: &str) -> Iter<'_, T> {
Iter::new(self, fq_path.to_string())
}

/// Returns the value which matches the provided fully-qualified Protobuf path and field name.
pub(crate) fn get_field(&self, fq_path: &'_ str, field: &'_ str) -> Option<&T> {
let full_path = format!("{}.{}", fq_path, field);
let full_path = full_path.as_str();

// First, try matching the path.
let value = iter::once(full_path)
// Then, try matching path suffixes.
.chain(suffixes(full_path))
// Then, try matching path suffixes without the field name.
.chain(suffixes(fq_path))
// Then, try matching path prefixes.
.chain(prefixes(full_path))
// Then, match the global path. This matcher must never fail, since the constructor
// initializes it.
.chain(iter::once("."))
.flat_map(|path| self.matchers.get(path))
.next();

value
/// Returns a iterator over all the value matching the path `fq_path.field` and associated suffix/prefix path
pub(crate) fn get_field(&self, fq_path: &str, field: &str) -> Iter<'_, T> {
Iter::new(self, format!("{}.{}", fq_path, field))
}

/// Returns the first value found matching the given path
/// If nothing matches the path, suffix paths will be tried, then prefix paths, then the global path
#[allow(unused)]
pub(crate) fn get_first<'a>(&'a self, fq_path: &'_ str) -> Option<&'a T> {
self.find_best_matching(fq_path)
}

/// Returns the first value found matching the path `fq_path.field`
/// If nothing matches the path, suffix paths will be tried, then prefix paths, then the global path
pub(crate) fn get_first_field<'a>(&'a self, fq_path: &'_ str, field: &'_ str) -> Option<&'a T> {
self.find_best_matching(&format!("{}.{}", fq_path, field))
}

/// Removes all matchers from the path map.
pub(crate) fn clear(&mut self) {
self.matchers.clear();
}

/// Returns the first value found best matching the path
/// See [sub_path_iter()] for paths test order
fn find_best_matching(&self, full_path: &str) -> Option<&T> {
sub_path_iter(full_path).find_map(|path| {
self.matchers
.iter()
.find(|(p, _)| p == path)
.map(|(_, v)| v)
})
}
}

/// Iterator inside a PathMap that only returns values that matches a given path
pub(crate) struct Iter<'a, T> {
iter: std::slice::Iter<'a, (String, T)>,
path: String,
}

impl<'a, T> Iter<'a, T> {
fn new(map: &'a PathMap<T>, path: String) -> Self {
Self {
iter: map.matchers.iter(),
path,
}
}

fn is_match(&self, path: &str) -> bool {
sub_path_iter(self.path.as_str()).any(|p| p == path)
}
}

impl<'a, T> std::iter::Iterator for Iter<'a, T> {
type Item = &'a T;

fn next(&mut self) -> Option<Self::Item> {
loop {
match self.iter.next() {
Some((p, v)) => {
if self.is_match(p) {
return Some(v);
}
}
None => return None,
}
}
}
}

impl<'a, T> std::iter::FusedIterator for Iter<'a, T> {}

/// Given a fully-qualified path, returns a sequence of paths:
/// - the path itself
/// - the sequence of suffix paths
/// - the sequence of prefix paths
/// - the global path
///
/// Example: sub_path_iter(".a.b.c") -> [".a.b.c", "a.b.c", "b.c", "c", ".a.b", ".a", "."]
fn sub_path_iter(full_path: &str) -> impl Iterator<Item = &str> {
// First, try matching the path.
iter::once(full_path)
// Then, try matching path suffixes.
.chain(suffixes(full_path))
// Then, try matching path prefixes.
.chain(prefixes(full_path))
// Then, match the global path.
.chain(iter::once("."))
}

/// Given a fully-qualified path, returns a sequence of fully-qualified paths which match a prefix
Expand Down Expand Up @@ -106,60 +160,85 @@ mod tests {
}

#[test]
fn test_path_map_get() {
fn test_get_matches_sub_path() {
let mut path_map = PathMap::default();
path_map.insert(".a.b.c.d".to_owned(), 1);
path_map.insert(".a.b".to_owned(), 2);
path_map.insert("M1".to_owned(), 3);
path_map.insert("M1.M2".to_owned(), 4);
path_map.insert("M1.M2.f1".to_owned(), 5);
path_map.insert("M1.M2.f2".to_owned(), 6);

assert_eq!(None, path_map.get(".a.other"));
assert_eq!(None, path_map.get(".a.bother"));
assert_eq!(None, path_map.get(".other"));
assert_eq!(None, path_map.get(".M1.other"));
assert_eq!(None, path_map.get(".M1.M2.other"));

assert_eq!(Some(&1), path_map.get(".a.b.c.d"));
assert_eq!(Some(&1), path_map.get(".a.b.c.d.other"));

assert_eq!(Some(&2), path_map.get(".a.b"));
assert_eq!(Some(&2), path_map.get(".a.b.c"));
assert_eq!(Some(&2), path_map.get(".a.b.other"));
assert_eq!(Some(&2), path_map.get(".a.b.other.Other"));
assert_eq!(Some(&2), path_map.get(".a.b.c.dother"));

assert_eq!(Some(&3), path_map.get(".M1"));
assert_eq!(Some(&3), path_map.get(".a.b.c.d.M1"));
assert_eq!(Some(&3), path_map.get(".a.b.M1"));
// full path
path_map.insert(".a.b.c.d".to_owned(), 1);
assert_eq!(Some(&1), path_map.get(".a.b.c.d").next());
assert_eq!(Some(&1), path_map.get_field(".a.b.c", "d").next());

// suffix
path_map.clear();
path_map.insert("c.d".to_owned(), 1);
assert_eq!(Some(&1), path_map.get(".a.b.c.d").next());
assert_eq!(Some(&1), path_map.get("b.c.d").next());
assert_eq!(Some(&1), path_map.get_field(".a.b.c", "d").next());

// prefix
path_map.clear();
path_map.insert(".a.b".to_owned(), 1);
assert_eq!(Some(&1), path_map.get(".a.b.c.d").next());
assert_eq!(Some(&1), path_map.get_field(".a.b.c", "d").next());

// global
path_map.clear();
path_map.insert(".".to_owned(), 1);
assert_eq!(Some(&1), path_map.get(".a.b.c.d").next());
assert_eq!(Some(&1), path_map.get("b.c.d").next());
assert_eq!(Some(&1), path_map.get_field(".a.b.c", "d").next());
}

assert_eq!(Some(&4), path_map.get(".M1.M2"));
assert_eq!(Some(&4), path_map.get(".a.b.c.d.M1.M2"));
assert_eq!(Some(&4), path_map.get(".a.b.M1.M2"));
#[test]
fn test_get_best() {
let mut path_map = PathMap::default();

assert_eq!(Some(&5), path_map.get(".M1.M2.f1"));
assert_eq!(Some(&5), path_map.get(".a.M1.M2.f1"));
assert_eq!(Some(&5), path_map.get(".a.b.M1.M2.f1"));
// worst is global
path_map.insert(".".to_owned(), 1);
assert_eq!(Some(&1), path_map.get_first(".a.b.c.d"));
assert_eq!(Some(&1), path_map.get_first("b.c.d"));
assert_eq!(Some(&1), path_map.get_first_field(".a.b.c", "d"));

assert_eq!(Some(&6), path_map.get(".M1.M2.f2"));
assert_eq!(Some(&6), path_map.get(".a.M1.M2.f2"));
assert_eq!(Some(&6), path_map.get(".a.b.M1.M2.f2"));
// then prefix
path_map.insert(".a.b".to_owned(), 2);
assert_eq!(Some(&2), path_map.get_first(".a.b.c.d"));
assert_eq!(Some(&2), path_map.get_first_field(".a.b.c", "d"));

// then suffix
path_map.insert("c.d".to_owned(), 3);
assert_eq!(Some(&3), path_map.get_first(".a.b.c.d"));
assert_eq!(Some(&3), path_map.get_first("b.c.d"));
assert_eq!(Some(&3), path_map.get_first_field(".a.b.c", "d"));

// best is full path
path_map.insert(".a.b.c.d".to_owned(), 4);
assert_eq!(Some(&4), path_map.get_first(".a.b.c.d"));
assert_eq!(Some(&4), path_map.get_first_field(".a.b.c", "d"));
}

// get_field
#[test]
fn test_get_keep_order() {
let mut path_map = PathMap::default();
path_map.insert(".".to_owned(), 1);
path_map.insert(".a.b".to_owned(), 2);
path_map.insert(".a.b.c.d".to_owned(), 3);

assert_eq!(Some(&2), path_map.get_field(".a.b.Other", "other"));
let mut iter = path_map.get(".a.b.c.d");
assert_eq!(Some(&1), iter.next());
assert_eq!(Some(&2), iter.next());
assert_eq!(Some(&3), iter.next());
assert_eq!(None, iter.next());

assert_eq!(Some(&4), path_map.get_field(".M1.M2", "other"));
assert_eq!(Some(&4), path_map.get_field(".a.M1.M2", "other"));
assert_eq!(Some(&4), path_map.get_field(".a.b.M1.M2", "other"));
path_map.clear();

assert_eq!(Some(&5), path_map.get_field(".M1.M2", "f1"));
assert_eq!(Some(&5), path_map.get_field(".a.M1.M2", "f1"));
assert_eq!(Some(&5), path_map.get_field(".a.b.M1.M2", "f1"));
path_map.insert(".a.b.c.d".to_owned(), 1);
path_map.insert(".a.b".to_owned(), 2);
path_map.insert(".".to_owned(), 3);

assert_eq!(Some(&6), path_map.get_field(".M1.M2", "f2"));
assert_eq!(Some(&6), path_map.get_field(".a.M1.M2", "f2"));
assert_eq!(Some(&6), path_map.get_field(".a.b.M1.M2", "f2"));
let mut iter = path_map.get(".a.b.c.d");
assert_eq!(Some(&1), iter.next());
assert_eq!(Some(&2), iter.next());
assert_eq!(Some(&3), iter.next());
assert_eq!(None, iter.next());
}
}