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

Build json template #15

Merged
merged 5 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ $ protokaf produce -h
### Examples
This proto file will be used in the examples below.

`api/example.ptoto`
`api/example.proto`
```protobuf
syntax = "proto3";

Expand Down Expand Up @@ -127,6 +127,44 @@ $ protokaf produce HelloRequest -t test -d '{"name": "Alice", "age": 11}'
$ protokaf produce --template-functions-print
```

## Build json template by proto file
This can be useful for creating body for produce command
```sh
$ protokaf build HelloRequest --proto internal/proto/testdata/example.proto
```
For proto file
```protobuf
syntax = "proto3";

package example;

message HelloRequest {
enum Status {
PENDING = 0;
COMPLETED = 1;
}

string name = 1;
int32 age = 2;
optional float amount = 4;
Status status = 5;
repeated string keys = 6;
}
```
command will print
```json
{
"name": "",
"age": 0,
"amount": 0,
"status": "PENDING",
"keys": [
""
]
}

```

## Consume
### Help
```sh
Expand Down
77 changes: 77 additions & 0 deletions cmd/cmd_build_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package cmd

import (
"testing"

"github.com/stretchr/testify/require"
)

func Test_NewBuildCmd(t *testing.T) {
cmd := NewBuildCmd()
NewFlags(cmd).Init()
cmd.SetArgs([]string{"ExampleMessage", "--proto", "../internal/proto/testdata/types.proto"})

expected := `{
"int32Field": 0,
"int64Field": "0",
"uint32Field": 0,
"uint64Field": "0",
"sint32Field": 0,
"sint64Field": "0",
"fixed32Field": 0,
"fixed64Field": "0",
"sfixed32Field": 0,
"sfixed64Field": "0",
"floatField": 0,
"doubleField": 0,
"boolField": true,
"stringField": "",
"bytesField": "",
"enumField": "UNKNOWN",
"messageField": {
"nestedInt32": 0,
"nestedString": ""
},
"repeatedInt32Field": [
0
],
"repeatedStringField": [
""
],
"mapStringInt32Field": {
"": 0
},
"mapInt32MessageField": {
"0": {
"nestedInt32": 0,
"nestedString": ""
}
},
"option1": 0,
"anyField": null,
"timestampField": "1970-01-01T00:00:00Z",
"durationField": "0s",
"structField": {
"": null
},
"valueField": null,
"listValueField": [
null
],
"boolValueField": true,
"bytesValueField": null,
"doubleValueField": 0,
"floatValueField": 0,
"int32ValueField": 0,
"int64ValueField": "0",
"stringValueField": "",
"uint32ValueField": 0,
"uint64ValueField": "0"
}`

stdout, stderr, err := getCommandOut(t, cmd)

require.Nil(t, err)
require.Equal(t, "", stderr)
require.Contains(t, stdout, expected)
}
110 changes: 110 additions & 0 deletions cmd/cmd_buld.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package cmd

import (
"github.com/golang/protobuf/jsonpb"
"github.com/jhump/protoreflect/desc"
"github.com/jhump/protoreflect/dynamic"
"github.com/spf13/cobra"
"google.golang.org/protobuf/types/descriptorpb"
"google.golang.org/protobuf/types/known/anypb"
)

func NewBuildCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "build <MessageName>",
Short: "Build json by proto message",
Args: messageNameRequired,
RunE: func(cmd *cobra.Command, args []string) (err error) {
// parse protofiles & create proto object
p, err := parseProtofiles()
if err != nil {
return
}

messageDescriptor, err := findMessage(p, args[0])
if err != nil {
return
}

msg := buildMessage(dynamic.NewMessage(messageDescriptor))

b, err := msg.MarshalJSONPB(&jsonpb.Marshaler{
EmitDefaults: true,
Indent: " ",
})
if err != nil {
return
}

cmd.Println(string(b))

return
},
}

return cmd
}

func buildMessage(message *dynamic.Message) *dynamic.Message {
for _, field := range message.GetMessageDescriptor().GetFields() {
switch {
case field.IsRepeated():
message.SetField(field, []interface{}{buildDefaultValue(field)})
case field.IsMap():
message.SetField(
field,
map[interface{}]interface{}{
buildDefaultValue(field.GetMapKeyType()): buildDefaultValue(field.GetMapValueType()),
},
)
case field.GetOneOf() != nil:
oneOfField := field.GetOneOf().GetChoices()[0]
message.SetField(oneOfField, buildDefaultValue(oneOfField))
default:
message.SetField(field, buildDefaultValue(field))
}
}

return message
}

func buildDefaultValue(field *desc.FieldDescriptor) interface{} {
switch field.GetType() {
case descriptorpb.FieldDescriptorProto_TYPE_FIXED32,
descriptorpb.FieldDescriptorProto_TYPE_UINT32:
return uint32(0)
case descriptorpb.FieldDescriptorProto_TYPE_SFIXED32,
descriptorpb.FieldDescriptorProto_TYPE_INT32,
descriptorpb.FieldDescriptorProto_TYPE_SINT32:
return int32(0)
case descriptorpb.FieldDescriptorProto_TYPE_FIXED64,
descriptorpb.FieldDescriptorProto_TYPE_UINT64:
return uint64(0)
case descriptorpb.FieldDescriptorProto_TYPE_SFIXED64,
descriptorpb.FieldDescriptorProto_TYPE_INT64,
descriptorpb.FieldDescriptorProto_TYPE_SINT64:
return int64(0)
case descriptorpb.FieldDescriptorProto_TYPE_FLOAT:
return float32(0)
case descriptorpb.FieldDescriptorProto_TYPE_DOUBLE:
return float64(0)
case descriptorpb.FieldDescriptorProto_TYPE_BOOL:
return true
case descriptorpb.FieldDescriptorProto_TYPE_BYTES:
return []byte(nil)
case descriptorpb.FieldDescriptorProto_TYPE_STRING:
return ""
case descriptorpb.FieldDescriptorProto_TYPE_ENUM:
return field.GetEnumType().GetValues()[0].GetNumber()
case descriptorpb.FieldDescriptorProto_TYPE_MESSAGE:
if field.GetMessageType().GetFullyQualifiedName() == "google.protobuf.Any" {
val, _ := anypb.New(nil)

return val
}

return buildMessage(dynamic.NewMessage(field.GetMessageType()))
}

return nil
}
1 change: 1 addition & 0 deletions cmd/cmd_root.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ func NewRootCmd() *cobra.Command {
NewProduceCmd(),
NewConsumeCmd(),
NewListCmd(),
NewBuildCmd(),
)

return cmd
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/uber/jaeger-client-go v2.29.1+incompatible
github.com/xdg/scram v1.0.3
go.uber.org/zap v1.18.1
google.golang.org/protobuf v1.33.0
)

require (
Expand Down Expand Up @@ -55,7 +56,6 @@ require (
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
80 changes: 80 additions & 0 deletions internal/proto/testdata/types.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
syntax = "proto3";

package example;

import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/struct.proto";
import "google/protobuf/wrappers.proto";

// Enum definition
enum ExampleEnum {
UNKNOWN = 0;
OPTION_ONE = 1;
OPTION_TWO = 2;
}

// Message definition
message ExampleMessage {
// Scalar types
int32 int32_field = 1;
int64 int64_field = 2;
uint32 uint32_field = 3;
uint64 uint64_field = 4;
sint32 sint32_field = 5;
sint64 sint64_field = 6;
fixed32 fixed32_field = 7;
fixed64 fixed64_field = 8;
sfixed32 sfixed32_field = 9;
sfixed64 sfixed64_field = 10;
float float_field = 11;
double double_field = 12;
bool bool_field = 13;
string string_field = 14;
bytes bytes_field = 15;

// Enum type
ExampleEnum enum_field = 16;

// Nested message
message NestedMessage {
int32 nested_int32 = 1;
string nested_string = 2;
}

// Message type
NestedMessage message_field = 17;

// Repeated fields
repeated int32 repeated_int32_field = 18;
repeated string repeated_string_field = 19;

// Map fields
map<string, int32> map_string_int32_field = 20;
map<int32, NestedMessage> map_int32_message_field = 21;

// Oneof field
oneof my_oneof {
int32 option1 = 22;
string option2 = 23;
NestedMessage option3 = 24;
}

// Well-known types
google.protobuf.Any any_field = 25;
google.protobuf.Timestamp timestamp_field = 26;
google.protobuf.Duration duration_field = 27;
google.protobuf.Struct struct_field = 28;
google.protobuf.Value value_field = 29;
google.protobuf.ListValue list_value_field = 30;
google.protobuf.BoolValue bool_value_field = 31;
google.protobuf.BytesValue bytes_value_field = 32;
google.protobuf.DoubleValue double_value_field = 33;
google.protobuf.FloatValue float_value_field = 34;
google.protobuf.Int32Value int32_value_field = 35;
google.protobuf.Int64Value int64_value_field = 36;
google.protobuf.StringValue string_value_field = 37;
google.protobuf.UInt32Value uint32_value_field = 38;
google.protobuf.UInt64Value uint64_value_field = 39;
}
Loading