Skip to content

Commit f399298

Browse files
committed
implement the initial version of protoc-gen-pubsub-schema
1 parent 98c030d commit f399298

File tree

6 files changed

+218
-0
lines changed

6 files changed

+218
-0
lines changed

example/user_add_comment.proto

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
syntax = "proto3";
2+
3+
package example;
4+
5+
import "google/protobuf/timestamp.proto";
6+
7+
message UserAddComment {
8+
message User {
9+
string first_name = 1;
10+
string last_name = 2;
11+
message Location {
12+
double longitude = 1;
13+
double latitude = 2;
14+
}
15+
Location location = 3;
16+
}
17+
User user = 1;
18+
string comment = 2;
19+
google.protobuf.Timestamp timestamp = 101;
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
syntax = "proto3";
2+
3+
message UserAddComment {
4+
message User {
5+
string first_name = 1;
6+
string last_name = 2;
7+
message Location {
8+
double longitude = 1;
9+
double latitude = 2;
10+
}
11+
Location location = 3;
12+
}
13+
User user = 1;
14+
string comment = 2;
15+
message Timestamp {
16+
int64 seconds = 1;
17+
int32 nanos = 2;
18+
}
19+
Timestamp timestamp = 101;
20+
}

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module github.com/alpancs/protoc-gen-pubsub-schema
2+
3+
go 1.16
4+
5+
require google.golang.org/protobuf v1.27.1

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
2+
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
3+
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
4+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
5+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
6+
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
7+
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
8+
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=

main.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package main
2+
3+
import (
4+
"io"
5+
"os"
6+
7+
"google.golang.org/protobuf/proto"
8+
"google.golang.org/protobuf/types/pluginpb"
9+
)
10+
11+
func main() {
12+
req, err := decodeRequest(os.Stdin)
13+
if err != nil {
14+
err = encodeResponse(buildResponseError(err), os.Stdout)
15+
if err != nil {
16+
panic(err)
17+
}
18+
return
19+
}
20+
21+
err = encodeResponse(responseBuilder{req}.build(), os.Stdout)
22+
if err != nil {
23+
panic(err)
24+
}
25+
}
26+
27+
func decodeRequest(input io.Reader) (*pluginpb.CodeGeneratorRequest, error) {
28+
rawInput, err := io.ReadAll(input)
29+
if err != nil {
30+
return nil, err
31+
}
32+
33+
req := new(pluginpb.CodeGeneratorRequest)
34+
err = proto.Unmarshal(rawInput, req)
35+
return req, err
36+
}
37+
38+
func encodeResponse(resp *pluginpb.CodeGeneratorResponse, output io.Writer) error {
39+
rawOutput, err := proto.Marshal(resp)
40+
if err != nil {
41+
return err
42+
}
43+
44+
_, err = output.Write(rawOutput)
45+
return err
46+
}

response_builder.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"regexp"
7+
"strings"
8+
9+
"google.golang.org/protobuf/types/descriptorpb"
10+
"google.golang.org/protobuf/types/pluginpb"
11+
)
12+
13+
type responseBuilder struct {
14+
request *pluginpb.CodeGeneratorRequest
15+
}
16+
17+
func buildResponseError(err error) *pluginpb.CodeGeneratorResponse {
18+
errorMessage := err.Error()
19+
return &pluginpb.CodeGeneratorResponse{Error: &errorMessage}
20+
}
21+
22+
func (b responseBuilder) build() *pluginpb.CodeGeneratorResponse {
23+
resp := new(pluginpb.CodeGeneratorResponse)
24+
for _, fileName := range b.request.GetFileToGenerate() {
25+
respFile, err := b.buildFile(fileName)
26+
if err != nil {
27+
errorMessage := err.Error()
28+
resp.Error = &errorMessage
29+
break
30+
}
31+
resp.File = append(resp.File, respFile)
32+
}
33+
return resp
34+
}
35+
36+
func (b responseBuilder) buildFile(reqFileName string) (*pluginpb.CodeGeneratorResponse_File, error) {
37+
respFileName := regexp.MustCompile(`.proto$`).ReplaceAllString(reqFileName, ".pubsub.proto")
38+
content, err := b.buildContent(b.findProtoFileByName(reqFileName))
39+
return &pluginpb.CodeGeneratorResponse_File{
40+
Name: &respFileName,
41+
Content: &content,
42+
}, err
43+
}
44+
45+
func (b responseBuilder) findProtoFileByName(desiredName string) *descriptorpb.FileDescriptorProto {
46+
for _, protoFile := range b.request.GetProtoFile() {
47+
if protoFile.GetName() == desiredName {
48+
return protoFile
49+
}
50+
}
51+
return nil
52+
}
53+
54+
func (b responseBuilder) findMessageByName(desiredName string) *descriptorpb.DescriptorProto {
55+
for _, protoFile := range b.request.GetProtoFile() {
56+
packageName := strings.TrimSuffix("."+protoFile.GetPackage(), ".")
57+
nestedResult := b.findNestedMessageByName(desiredName, protoFile.GetMessageType(), packageName)
58+
if nestedResult != nil {
59+
return nestedResult
60+
}
61+
}
62+
return nil
63+
}
64+
65+
func (b responseBuilder) findNestedMessageByName(desiredName string, messages []*descriptorpb.DescriptorProto, prefix string) *descriptorpb.DescriptorProto {
66+
for _, message := range messages {
67+
fullMessageName := prefix + "." + message.GetName()
68+
if fullMessageName == desiredName {
69+
return message
70+
}
71+
72+
nestedResult := b.findNestedMessageByName(desiredName, message.GetNestedType(), fullMessageName)
73+
if nestedResult != nil {
74+
return nestedResult
75+
}
76+
}
77+
return nil
78+
}
79+
80+
func (b responseBuilder) buildContent(protoFile *descriptorpb.FileDescriptorProto) (string, error) {
81+
if len(protoFile.GetMessageType()) != 1 {
82+
return "", fmt.Errorf(
83+
"only one top-level type may be defined in the file %s. use nested type instead (https://developers.google.com/protocol-buffers/docs/proto3#nested)",
84+
protoFile.GetName(),
85+
)
86+
}
87+
88+
contentBuilder := new(strings.Builder)
89+
fmt.Fprint(contentBuilder, "syntax = \"proto3\";\n\n")
90+
b.buildMessage(contentBuilder, protoFile.GetMessageType()[0], 0)
91+
return contentBuilder.String(), nil
92+
}
93+
94+
func (b responseBuilder) buildMessage(output io.Writer, message *descriptorpb.DescriptorProto, level int) {
95+
fmt.Fprintf(output, "%smessage %s {\n", buildIndent(level), message.GetName())
96+
for _, field := range message.GetField() {
97+
b.buildField(output, field, level+1)
98+
}
99+
fmt.Fprintf(output, "%s}\n", buildIndent(level))
100+
}
101+
102+
func (b responseBuilder) buildField(output io.Writer, field *descriptorpb.FieldDescriptorProto, level int) {
103+
fieldType := strings.ToLower(strings.Replace(field.GetType().String(), "TYPE_", "", 1))
104+
if field.GetType() == descriptorpb.FieldDescriptorProto_TYPE_MESSAGE {
105+
fieldTypeName := field.GetTypeName()
106+
b.buildMessage(output, b.findMessageByName(fieldTypeName), level)
107+
fieldType = fieldTypeName[strings.LastIndexByte(fieldTypeName, '.')+1:]
108+
}
109+
110+
fmt.Fprint(output, buildIndent(level))
111+
if field.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_REPEATED {
112+
fmt.Fprint(output, "repeated ")
113+
}
114+
fmt.Fprintf(output, "%s %s = %d;\n", fieldType, field.GetName(), field.GetNumber())
115+
}
116+
117+
func buildIndent(level int) string {
118+
return strings.Repeat(" ", level)
119+
}

0 commit comments

Comments
 (0)