An easy code generating tool for transport and model files out from a .proto file.
- Create a folder, e.g. pbgen
- Put there a .proto file, e.g. service.proto
- Create a file (e.g. pbgen.go) with the following content:
package main
import (
"github.com/balduser/easypbgen"
"fmt"
"os"
)
func main() {
pbgen, err := easypbgen.ParseFile(os.Args[1], nil)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
easypbgen.PrintAll(pbgen) // необязательно
easypbgen.GenerateTransport(pbgen)
easypbgen.GenerateModel(pbgen)
}
- Move to this folder in CLI.
- Create a module pbgen:
go mod init pbgen
- Install easypbgen from github:
go mod tidy
- Launch code generation with a command
go run pbgen.go -service.proto
Files will appear in the same folder.
- You should have at least one Service in your file
- Service should be defined before it's messages
It's impossible to create an ultimate tool for any task, so the code generated by easypbgen may not be optimal. Some parts of generated code may not satisfy project's requirements. So to have the proper code generation and the opportunity to launch code generation few times without editing same parts every time it is possible to determine correct parts of code manually and place it in variables of main script or in the config file. To place them in the launching script boby you have to make map[string]string, and the pointer to in must be given as a second argument to the ParseFile() function. Following keys are valid:
-
<service_name>TransportFile
- uri of the transport code file to be generated -
<service_name>ModelFile
- uri of the model code file to be generated
-
model<service_name>Message<message_name>
- code of a distinct message -
transport<service_name><method_name>
- code of a distinct method in transport layer (adapter) -
transport<service_name>Decode<message_name>
- code of a distinct decoder -
transport<service_name>Encode<message_name>
- code of a distinct encoder -
model<service_name>Ending
- text or a code for inserting in the end of model file
transportHeading
- generated transport file headingtemplateG
- transport layer adapter templatetemplateDec
- transport layer decoder templatetemplateEnc
- transport layer encoder templateencFieldTypeTemplate
- encoder's "type" field templatemodelHeading
- generated model file headingmodelTemplate
- generated model file stucture template
Examples
- some message code
"modelBlogpostAPIServiceMessageCreatePostRequest":
`type CreatePostRequest struct {
userID uint32
postText string
hellobug float64
}
`
- some transport layer method (adapter) code
func (g grpcTransport) CreatePost(ctx context.Context, request *pb.CreatePostRequest) (*pb.CreatePostResponse, error) {
_, response, err := g.createPost.ServeGRPC(ctx, request)
if err != nil {
g.log.Error().Err(err).Msg("WE LOVE BUGS!")
return nil, err
}
resp := response.(*pb.CreatePostResponse)
return resp, nil
}
- some decoder code
func decodeCreatePost(ctx context.Context, grpcRequest interface{}) (interface{}, error) {
req := grpcRequest.(*pb.CreatePostRequest)
result := &model.CreatePostRequest{
UserID: req.UserID,
PostText: req.PostText,
hellobug: float64,
}
return result, nil
}
- some encoder code
func encodeCreatePost(ctx context.Context, grpcResponse interface{}) (interface{}, error) {
resp := grpcResponse.(*model.CreatePostResponse)
response := &pb.CreatePostResponse{
CreatePostSuccess: resp.CreatePostSuccess,
ErrorText: resp.ErrorText,
hellobug: 0,
}
return response, nil
}
Default templates may be seen in templates.go
Example of a script with the configuration parameters in the body
package main
import (
"github.com/balduser/easypbgen"
"fmt"
"os"
)
func main() {
config := map[string]string {
"transportHeading": `package transport
// Hello there!
`,
"modelBlogpostAPIServiceEnding":
`type CreatePostRequest struct {
myPrettyFieldName bug64
}
`,
}
pbgen, err := easypbgen.ParseFile(os.Args[1], &config)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
easypbgen.GenerateTransport(pbgen)
easypbgen.GenerateModel(pbgen)
}
For encreasing the readability of a launching script some parts of a code may be moved to the configuration file, which is a .go file placed in the script's folder. It must have package main
and it should contain map[string]string instead of a launching script. The pointer to this map must anyway be given to the ParseFile() as a second argument
In case if configuration file is used, the project must be built with go build
before the code generation.
Sometimes your model should implement more structures than the transport code. In this case you may place special comments for the data model in a .proto file. This can be done with symbols /*#
and #*/
in separate (necessary) lines e.g.:
...
// Protocol buffers code
/*#
message LikePostRequest {
uint32 postID = 1;
}
message LikeCommentRequest {
uint32 commentID = 1;
}
message SuccessResponse {
bool Success = 1; // true for success, false for failure
string errorText = 2;
}
#*/
- Tests!
- "reserved" in messages
- "default" in messages
- enum in messages (check, maybe refactor)
- stream in rpcs ( https://golang-blog.blogspot.com/2021/03/grpc-golang-basic-tutorial.html & https://github.com/grpc/grpc-go/blob/master/examples/route_guide/routeguide/route_guide.proto )