stepancheg / rust-protobuf

Rust implementation of Google protocol buffers
MIT License
2.81k stars 381 forks source link

Calling `set_singular_field` on dynamic message with generated descriptor #741

Open rellfy opened 1 month ago

rellfy commented 1 month ago

The scenario I am working with is that clients may upload a dynamic proto file, which may follow a specific schema of reserved fields that is generated on the server.

Therefore, if the dynamic client-uploaded schema contains a reserved field name, the server creates it using the generated field from the static server files (which defines the reserved fields). For example, let's say that the message Reserved and field name reserved are reserved as a special field. Assume the dynamic client message is ClientMessage:

// This is defined by the client, and may or may not
// include the Reserved message. If it does, the server
// will include it when creating the dynamic message.
message ClientMessage {
    Reserved reserved = 1;
    double foo = 2;
}

// This is also defined and generated in the server.
message Reserved {
    double bar = 1;
}

on the server something like this happens:

// Generate the dynamic message
let mut input_message = input_message_descriptor.new_instance();
let args_hashmap = self.args_hashmap();
for (field, value) in &args_hashmap {
    // ... set some fields on the user-defined dynamic message.
    field.set_singular_field(&mut *input_message, parsed_input);
}
// Now, set the values for reserved fields (as defined by the generated server schema)
// if they have been declared in this client message.
let reserved_field_opt = input_message_descriptor.field_by_name("reserved");  
if let Some(reserved_field) = reserved_field_opt {
    // This is an instance from the server-generated type.
    // The schema matches the client .proto dynamic message.
    // (if not, a runtime error would be fine).
    let mut reserved = Reserved::new();
    // ... populate the reserved struct.
    // Set the field on the dynamic message.
    // This panics, because the Reserved type is
    // generated, while the `reserved` field is dynamic,
    // even though the schemas match.
    reserved_field.set_singular_field(  
        &mut *input_message,  
        ReflectValueBox::Message(Box::new(reserved)),  
    );
}

This panics on protobuf/src/reflect/dynamic/optional.rs line 29:

assert_eq!(value.get_type(), self.elem);

I believe it panics because one message is generated while the other is dynamic, but since the schema matches it should not panic IMO.

So my question would be, is there a way to be able to use a generated type to apply to set_singular_field on a dynamic field? Maybe serializing the generated type and de-serializing as the dynamic would work, but this seems an inefficient way to do it.

If I simply comment out the assert_eq above and link the package locally, it works fine.

My first thought to address this would be to replace the assert_eq, or the equality implementation for the types, with something that checks the underlying schema only.

rellfy commented 1 month ago

To clarify, I am currently setting all these values dynamically, without using the generated Reserved struct. But it would be much nicer to be able to use the generated struct as it is significantly less code to set all the fields.