Skip to content

Coverter: Protobuf -> Domain #670

@root-kidik

Description

@root-kidik

Angle.hpp

namespace sf
{

class Angle
{
public:
    constexpr Angle() = default;
    constexpr Angle(float radians);
    [[nodiscard]] constexpr float asDegrees() const;
    [[nodiscard]] constexpr float asRadians() const;
    [[nodiscard]] constexpr Angle wrapSigned() const;
    // And other really usefull methods

private:
    float m_radians{};
};

}

Vector2.hpp

namespace sf
{

template <typename T>
class Vector2
{
public:
    constexpr Vector2() = default;
    constexpr Vector2(T x, T y);

    [[nodiscard]] constexpr T dot(Vector2 rhs) const;
    [[nodiscard]] constexpr T cross(Vector2 rhs) const;
    // And other really usefull methods

private:
    T x{};
    T y{};
};

using Vector2f = Vector2<float>;
// ...

}

Angle.proto


syntax = "proto3";

package api.sf;

message Angle {
    float radians = 1;
}

Vector2f.proto


syntax = "proto3";

package api.sf;

message Vector2f {
    float x = 1;
    float y = 2;
}

Problem

I really want to use native classes from C++, for this I need to manually transfer each time from classes generated by Protobuf to native ones

Solution

Python plugin

import sys
from google.protobuf.compiler import plugin_pb2 as plugin
from jinja2 import Template

converter_template = Template(
"""
{% for message in proto_file.message_type %}
inline {{cpp_namespace}}::{{message.name}} convert({{api_namespace}}::{{message.name}}&& dto)
{
    return { {% for field in message.field %}std::move(*dto.mutable_{{field.name}}()){{ ", " if not loop.last else "" }}{% endfor %} };
}

inline {{api_namespace}}::{{message.name}} convert({{cpp_namespace}}::{{message.name}}&& object)
{
    return { {% for field in message.field %}std::move(object.{{field.name}}){{ ", " if not loop.last else "" }}{% endfor %} };
}            
{% endfor %}
"""
)

def generate_converter(proto_file, response):

    output_code = ""
    for proto_file in proto_file:
        api_namespace = proto_file.package.replace(".", "::")
        cpp_namespace = api_namespace[5:] 
            
        output_code += converter_template.render(proto_file=proto_file, api_namespace=api_namespace, cpp_namespace=cpp_namespace) 
        
        output_file = response.file.add()
        output_file.name = proto_file.name.replace(".proto", ".cpp")
        output_file.content = output_code


def main():
    data = sys.stdin.buffer.read()

    request = plugin.CodeGeneratorRequest.FromString(data)

    response = plugin.CodeGeneratorResponse()

    generate_converter(request.proto_file, response)
    
    sys.stdout.buffer.write(response.SerializeToString())

if __name__ == "__main__":
    main()

Run

protoc --plugin=protoc-gen-custom=/home/rtkid/Documents/pg_grpc_service_template/proto/handlers/plugin.sh --custom_out=. Angle.proto
protoc --plugin=protoc-gen-custom=/home/rtkid/Documents/pg_grpc_service_template/proto/handlers/plugin.sh --custom_out=. Vector2f.proto

Or inside 1 file.

Output

inline sf::Angle convert(api::sf::Angle&& dto)
{
    return { std::move(*dto.mutable_radians()) };
}

inline api::sf::Angle convert(sf::Angle&& object)
{
    return { std::move(object.radians) };
}            

inline sf::Vector2f convert(api::sf::Vector2f&& dto)
{
    return { std::move(*dto.mutable_x()), std::move(*dto.mutable_y()) };
}

inline api::sf::Vector2f convert(sf::Vector2f&& object)
{
    return { std::move(object.x), std::move(object.y) };
}            

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions