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

Issue:
Map fields backed by bytes::Bytes trigger compile error
**Problem:**

The following proto

```
syntax = "proto3";

package test;

message Test {
  bytes bytes = 1;
  map<string, bytes> bytes_dict = 2;
}
```

Gets compiled to this

```
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Test {
    #[prost(bytes="bytes", tag="1")]
    pub bytes: ::prost::bytes::Bytes,
    #[prost(map = "string, bytes", tag = "2")]
    pub bytes_dict:
        ::std::collections::HashMap<::prost::alloc::string::String, ::prost::bytes::Bytes>,
}
```

This results in a compile error

```
  |
3 | #[derive(Clone, PartialEq, ::prost::Message)]
  |                            ^^^^^^^^^^^^^^^^ expected struct `Vec`, found struct `prost::bytes::Bytes`
  |
  = note: expected reference `&HashMap<std::string::String, Vec<u8>>`
             found reference `&HashMap<std::string::String, prost::bytes::Bytes>`
  = note: this error originates in the derive macro `::prost::Message` (in Nightly builds, run with -Z macro-backtrace for more info)
```

This originates because the proc-macro deduces the wrong type for the HashMap within the generated Debug implementation.

This appears to be a limitation of the map attribute, which unlike the bytes attribute, does not provide information on the type of bytes in use.

**Proposal:**

The following syntax is already supported

```
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Test {
    #[prost(bytes="bytes", tag="1")]
    pub bytes: ::prost::bytes::Bytes,
    #[prost(map(string, bytes), tag = "2")]
    pub bytes_dict:
        ::std::collections::HashMap<::prost::alloc::string::String, ::prost::bytes::Bytes>,
}
```

I'd like to propose extending the proc macro to support


```
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Test {
    #[prost(bytes="bytes", tag="1")]
    pub bytes: ::prost::bytes::Bytes,
    #[prost(map(bytes="bytes|vec", bytes="bytes|vec"), tag = "2")]
    pub bytes_dict:
        ::std::collections::HashMap<::prost::alloc::string::String, ::prost::bytes::Bytes>,
}
```

And updating prost-build to generate this


Discussion / hints:
@tustvold do you by chance of a reproduction of this I can check out? I can't seem to reproduce it locally.
@LucioFranco Aah I missed a step in the reproducer

In your `build.rs` you need to enable bytes, e.g.

```
prost_build::Config::new()
    .bytes(&["."])
    .compile_protos(...)?;
```

Fix patch:
diff --git a/prost-derive/src/field/map.rs b/prost-derive/src/field/map.rs
index 1228a6fae..608b9b0b1 100644
--- a/prost-derive/src/field/map.rs
+++ b/prost-derive/src/field/map.rs
@@ -319,6 +319,17 @@ impl Field {
         };
         match &self.value_ty {
             ValueTy::Scalar(ty) => {
+                if let &scalar::Ty::Bytes(_) = ty {
+                    return quote! {
+                        struct #wrapper_name<'a>(&'a dyn ::core::fmt::Debug);
+                        impl<'a> ::core::fmt::Debug for #wrapper_name<'a> {
+                            fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+                                self.0.fmt(f)
+                            }
+                        }
+                    };
+                }
+
                 let value = ty.rust_type();
                 quote! {
                     struct #wrapper_name<'a>(&'a ::#libname::collections::#type_name<#key, #value>);


Test patch:
diff --git a/tests/src/build.rs b/tests/src/build.rs
index 522768b6a..334bdc8f4 100644
--- a/tests/src/build.rs
+++ b/tests/src/build.rs
@@ -105,6 +105,7 @@ fn main() {
     }
 
     config
+        .bytes(&["."])
         .compile_protos(&[src.join("well_known_types.proto")], includes)
         .unwrap();
 
diff --git a/tests/src/debug.rs b/tests/src/debug.rs
index ae5729f8e..23031e434 100644
--- a/tests/src/debug.rs
+++ b/tests/src/debug.rs
@@ -27,13 +27,17 @@ fn basic() {
          string_map: {}, \
          enumeration_btree_map: {}, \
          string_btree_map: {}, \
-         oneof: None \
+         oneof: None, \
+         bytes_map: {} \
          }"
     );
     basic
         .enumeration_map
         .insert(0, BasicEnumeration::TWO as i32);
     basic.enumeration = 42;
+    basic
+        .bytes_map
+        .insert("hello".to_string(), "world".as_bytes().into());
     assert_eq!(
         format!("{:?}", basic),
         "Basic { \
@@ -46,7 +50,8 @@ fn basic() {
          string_map: {}, \
          enumeration_btree_map: {}, \
          string_btree_map: {}, \
-         oneof: None \
+         oneof: None, \
+         bytes_map: {\"hello\": [119, 111, 114, 108, 100]} \
          }"
     );
 }
diff --git a/tests/src/message_encoding.rs b/tests/src/message_encoding.rs
index 367c0f837..3726e6762 100644
--- a/tests/src/message_encoding.rs
+++ b/tests/src/message_encoding.rs
@@ -359,6 +359,10 @@ pub struct Basic {
 
     #[prost(oneof = "BasicOneof", tags = "8, 9")]
     pub oneof: Option<BasicOneof>,
+
+    #[prost(map = "string, bytes", tag = "12")]
+    #[cfg(feature = "std")]
+    pub bytes_map: ::std::collections::HashMap<String, Vec<u8>>,
 }
 
 #[derive(Clone, PartialEq, Message)]
diff --git a/tests/src/well_known_types.proto b/tests/src/well_known_types.proto
index ef4047947..be0d8b7c2 100644
--- a/tests/src/well_known_types.proto
+++ b/tests/src/well_known_types.proto
@@ -30,3 +30,9 @@ message Foo {
 
     google.protobuf.BytesValue bytes = 11;
 }
+
+// https://github.com/tokio-rs/prost/issues/531
+message Test {
+  bytes bytes = 1;
+  map<string, bytes> bytes_dict = 2;
+}
\ No newline at end of file