Repository: tokio-rs/prost (language: rust)

Issue:
Follow up, implement `Name` trait implementation via `prost-build`
Follow up work on https://github.com/tokio-rs/prost/pull/896 to generate the prost-build portion of this implementation. We should generate the impl based on the filedescriptorset values.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b157b5dce..eed5c142a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -33,7 +33,7 @@ jobs:
       matrix:
         toolchain:
           - stable
-          - "1.64"
+          - "1.65"
         os:
           - ubuntu-latest
           - macos-latest
diff --git a/prost-build/src/code_generator.rs b/prost-build/src/code_generator.rs
index 37771a98b..ccf7e2415 100644
--- a/prost-build/src/code_generator.rs
+++ b/prost-build/src/code_generator.rs
@@ -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);
@@ -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) {
diff --git a/prost-build/src/lib.rs b/prost-build/src/lib.rs
index 67cb13cfc..3fdec73b8 100644
--- a/prost-build/src/lib.rs
+++ b/prost-build/src/lib.rs
@@ -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<()>,
@@ -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.
@@ -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(),
@@ -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)


Test patch:
diff --git a/tests/src/build.rs b/tests/src/build.rs
index bfb086a68..aec3f6458 100644
--- a/tests/src/build.rs
+++ b/tests/src/build.rs
@@ -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)
diff --git a/tests/src/lib.rs b/tests/src/lib.rs
index c03c94c10..954c6b367 100644
--- a/tests/src/lib.rs
+++ b/tests/src/lib.rs
@@ -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"));
diff --git a/tests/src/type_names.proto b/tests/src/type_names.proto
new file mode 100644
index 000000000..68c130a9b
--- /dev/null
+++ b/tests/src/type_names.proto
@@ -0,0 +1,9 @@
+syntax = "proto3";
+
+package type_names;
+
+message Foo {
+}
+
+message Bar {
+}
diff --git a/tests/src/type_names.rs b/tests/src/type_names.rs
new file mode 100644
index 000000000..2843d115a
--- /dev/null
+++ b/tests/src/type_names.rs
@@ -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());
+}