Skip to content

Commit

Permalink
feat: Generate implementation for Name trait (#931)
Browse files Browse the repository at this point in the history
* Add type names generator

* Fix test

* Update CI test toolchain version
  • Loading branch information
tinrab authored Nov 13, 2023
1 parent 97cd4e2 commit 3ce212f
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
matrix:
toolchain:
- stable
- "1.64"
- "1.65"
os:
- ubuntu-latest
- macos-latest
Expand Down
39 changes: 39 additions & 0 deletions prost-build/src/code_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ impl<'a> CodeGenerator<'a> {
code_gen.package
);

if code_gen.config.enable_type_names {
code_gen.buf.push_str(&format!(
"const PACKAGE: &str = \"{}\";\n",
code_gen.package,
));
}

code_gen.path.push(4);
for (idx, message) in file.message_type.into_iter().enumerate() {
code_gen.path.push(idx as i32);
Expand Down Expand Up @@ -261,6 +268,38 @@ impl<'a> CodeGenerator<'a> {

self.pop_mod();
}

if self.config.enable_type_names {
self.append_type_name(&message_name, &fq_message_name);
}
}

fn append_type_name(&mut self, message_name: &str, fq_message_name: &str) {
self.buf.push_str(&format!(
"impl {}::Name for {} {{\n",
self.config.prost_path.as_deref().unwrap_or("::prost"),
to_upper_camel(&message_name)
));
self.depth += 1;

self.buf
.push_str("const PACKAGE: &'static str = PACKAGE;\n");
self.buf.push_str(&format!(
"const NAME: &'static str = \"{}\";\n",
message_name
));

if let Some(domain_name) = self.config.type_name_domains.get_first(fq_message_name) {
self.buf.push_str(&format!(
r#"fn type_url() -> String {{
format!("{}/{{}}", Self::full_name())
}}"#,
domain_name
));
}

self.depth -= 1;
self.buf.push_str("}\n");
}

fn append_type_attributes(&mut self, fq_message_name: &str) {
Expand Down
46 changes: 46 additions & 0 deletions prost-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ pub struct Config {
out_dir: Option<PathBuf>,
extern_paths: Vec<(String, String)>,
default_package_filename: String,
enable_type_names: bool,
type_name_domains: PathMap<String>,
protoc_args: Vec<OsString>,
disable_comments: PathMap<()>,
skip_debug: PathMap<()>,
Expand Down Expand Up @@ -839,6 +841,46 @@ impl Config {
self
}

/// Configures the code generator to include type names.
///
/// Message types will implement `Name` trait, which provides type and package name.
/// This is needed for encoding messages as `Any` type.
pub fn enable_type_names(&mut self) -> &mut Self {
self.enable_type_names = true;
self
}

/// Specify domain names to use with message type URLs.
///
/// # Domains
///
/// **`paths`** - a path matching any number of types. It works the same way as in
/// [`btree_map`](#method.btree_map), just with the field name omitted.
///
/// **`domain`** - an arbitrary string to be used as a prefix for type URLs.
///
/// # Examples
///
/// ```rust
/// # let mut config = prost_build::Config::new();
/// // Full type URL of the message `google.profile.Person`,
/// // will be `type.googleapis.com/google.profile.Person`.
/// config.type_name_domain(&["."], "type.googleapis.com");
/// ```
pub fn type_name_domain<I, S, D>(&mut self, paths: I, domain: D) -> &mut Self
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
D: AsRef<str>,
{
self.type_name_domains.clear();
for matcher in paths {
self.type_name_domains
.insert(matcher.as_ref().to_string(), domain.as_ref().to_string());
}
self
}

/// Configures the path that's used for deriving `Message` for generated messages.
/// This is mainly useful for generating crates that wish to re-export prost.
/// Defaults to `::prost::Message` if not specified.
Expand Down Expand Up @@ -1257,6 +1299,8 @@ impl default::Default for Config {
out_dir: None,
extern_paths: Vec::new(),
default_package_filename: "_".to_string(),
enable_type_names: false,
type_name_domains: PathMap::default(),
protoc_args: Vec::new(),
disable_comments: PathMap::default(),
skip_debug: PathMap::default(),
Expand All @@ -1282,6 +1326,8 @@ impl fmt::Debug for Config {
.field("out_dir", &self.out_dir)
.field("extern_paths", &self.extern_paths)
.field("default_package_filename", &self.default_package_filename)
.field("enable_type_names", &self.enable_type_names)
.field("type_name_domains", &self.type_name_domains)
.field("protoc_args", &self.protoc_args)
.field("disable_comments", &self.disable_comments)
.field("skip_debug", &self.skip_debug)
Expand Down
6 changes: 6 additions & 0 deletions tests/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ fn main() {
)
.unwrap();

prost_build::Config::new()
.enable_type_names()
.type_name_domain(&[".type_names.Foo"], "tests")
.compile_protos(&[src.join("type_names.proto")], includes)
.unwrap();

// Check that attempting to compile a .proto without a package declaration does not result in an error.
config
.compile_protos(&[src.join("no_package.proto")], includes)
Expand Down
2 changes: 2 additions & 0 deletions tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ mod no_unused_results;
#[cfg(test)]
#[cfg(feature = "std")]
mod skip_debug;
#[cfg(test)]
mod type_names;

mod test_enum_named_option_value {
include!(concat!(env!("OUT_DIR"), "/myenum.optionn.rs"));
Expand Down
9 changes: 9 additions & 0 deletions tests/src/type_names.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
syntax = "proto3";

package type_names;

message Foo {
}

message Bar {
}
15 changes: 15 additions & 0 deletions tests/src/type_names.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use prost::alloc::{format, string::String};
use prost::Name;

include!(concat!(env!("OUT_DIR"), "/type_names.rs"));

#[test]
fn valid_type_names() {
assert_eq!("Foo", Foo::NAME);
assert_eq!("type_names", Foo::PACKAGE);
assert_eq!("tests/type_names.Foo", Foo::type_url());

assert_eq!("Bar", Bar::NAME);
assert_eq!("type_names", Bar::PACKAGE);
assert_eq!("/type_names.Bar", Bar::type_url());
}

0 comments on commit 3ce212f

Please sign in to comment.