diff --git a/README.md b/README.md index 56b87c2..b9b650e 100644 --- a/README.md +++ b/README.md @@ -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"; @@ -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 diff --git a/cmd/cmd_build_test.go b/cmd/cmd_build_test.go new file mode 100644 index 0000000..27bce1a --- /dev/null +++ b/cmd/cmd_build_test.go @@ -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) +} diff --git a/cmd/cmd_buld.go b/cmd/cmd_buld.go new file mode 100644 index 0000000..b894484 --- /dev/null +++ b/cmd/cmd_buld.go @@ -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 ", + 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 +} diff --git a/cmd/cmd_root.go b/cmd/cmd_root.go index 7bb4b7e..4b1502b 100644 --- a/cmd/cmd_root.go +++ b/cmd/cmd_root.go @@ -85,6 +85,7 @@ func NewRootCmd() *cobra.Command { NewProduceCmd(), NewConsumeCmd(), NewListCmd(), + NewBuildCmd(), ) return cmd diff --git a/go.mod b/go.mod index 2f5b905..ff46216 100644 --- a/go.mod +++ b/go.mod @@ -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 ( @@ -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 diff --git a/internal/proto/testdata/types.proto b/internal/proto/testdata/types.proto new file mode 100644 index 0000000..1fbe3f3 --- /dev/null +++ b/internal/proto/testdata/types.proto @@ -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 map_string_int32_field = 20; + map 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; +}