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

Auto-generate convenient constructors #813

Open
Victor-N-Suadicani opened this issue Feb 8, 2023 · 1 comment
Open

Auto-generate convenient constructors #813

Victor-N-Suadicani opened this issue Feb 8, 2023 · 1 comment

Comments

@Victor-N-Suadicani
Copy link
Contributor

I have a lot of protobuf messages like this:

message MyServiceResponse {
    message Success {
        // This is just one example, the Success message could in principle hold anything
        oneof status {
            Done done = 1;
            Skipped skipped = 2;
        }
    }

    oneof result {
        Success success = 1;
        InvalidRequest invalid_request = 2;
        InternalError internal_error = 3;
        NotFound not_found = 4;
    }
}

message InvalidRequest {
    string error = 1;
}

message InternalError {
    string error = 2;
}

message NotFound {}

message Done {}

message Skipped {}

prost_build takes this and generates something like this:

#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct MyServiceResponse {
    #[prost(oneof = "my_service_response::Result", tags = "1, 2, 3, 4")]
    pub result: ::core::option::Option<my_service_response::Result>,
}
pub mod my_service_response {
    #[allow(clippy::derive_partial_eq_without_eq)]
    #[derive(Clone, PartialEq, ::prost::Message)]
    pub struct Success {
        #[prost(oneof = "success::Status", tags = "1, 2")]
        pub status: ::core::option::Option<success::Status>,
    }
    pub mod success {
        #[allow(clippy::derive_partial_eq_without_eq)]
        #[derive(Clone, PartialEq, ::prost::Oneof)]
        pub enum Status {
            #[prost(message, tag = "1")]
            Done(Done),
            #[prost(message, tag = "2")]
            Skipped(Skipped),
        }
    }
    #[allow(clippy::derive_partial_eq_without_eq)]
    #[derive(Clone, PartialEq, ::prost::Oneof)]
    pub enum Result {
        #[prost(message, tag = "1")]
        Success(Success),
        #[prost(message, tag = "2")]
        InvalidRequest(InvalidRequest),
        #[prost(message, tag = "3")]
        InternalError(InternalError),
        #[prost(message, tag = "4")]
        DocumentNotFound(NotFound),
    }
}

Constructing an instance of MyServiceResponse is unfortunately quite tedious:

let response = MyServiceResponse {
    result: Some(my_service_response::Result::Success(
        my_service_response::Success {
            status: Some(my_service_response::success::Status::Done(
                Done {},
            )),
        },
    )),
};

That's quite a deep level of nesting where the only actual field comes at the end.

Therefore I've taken to writing methods like this:

fn success(status: my_service_response::success::Status) -> Self {
    Self {
        result: Some(my_service_response::Result::Success(
            my_service_response::Success {
                status: Some(status),
            },
        )),
    };
}

Which lets me construct responses much more concisely. However, writing this function is still quite tedious, especially if there's a lot of messages like this.

At first, I thought I could fix this myself, but I don't see any way to generate these functions based only on the resulting Rust code (i.e. by writing a macro) as that would require knowledge of multiple types at once. At least it seems very difficult.

I'm wondering if prost-build could somehow automatically generate constructors like this? I could imagine it would make construction of messages a lot easier, not just for this specific message but for messages in general. Basically the idea would be to generate this kind of function for messages that only have one field, and if that one also only has one field, then take the input as that field (and recursively etc.). Hope that makes sense.

@mzabaluev
Copy link
Contributor

I believe builders (#399) can deliver some convenience, by eliminating the layer of Option for the oneof value. Constituent message fields can in turn be initialized with a builder. I've got #901 open to optionally generate builder APIs for messages.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants