Skip to content

Commit

Permalink
🦴 bones: Emit well-formed bones for well-known types (#26)
Browse files Browse the repository at this point in the history
Render protobuf well-known types properly in JSON. Some well-known protobuf
types have a JSON form different to how their proto representation would
normally be rendered. For instance, timestamp and duration are rendered in
JSON as a single string, but are in the proto file as a message. Boxed
types are rendered as their value only and not nested in a message.

Extend the test cases for all the well-known types by adding a new rpc and
response message for the well-known cases. Slightly modify the test so the
output is better when the golden file does not match.

Add regenerated proto output files for `exemplar.proto`.

This merges the following commits:
* build: Fix proto find regex to exclude underscores
* bones: Emit well-formed bones for well-known types

     Makefile                                      |   2 +-
     bones/exemplar.go                             |  53 ++
     bones/generate_test.go                        |   2 +-
     .../exemplar.Exemplar.WellKnown.jsonnet       | 212 ++++++
     pb/exemplar/exemplar.pb                       | Bin 1735 -> 9061 bytes
     pb/exemplar/exemplar.pb.go                    | 712 ++++++++++++++----
     pb/exemplar/exemplar_grpc.pb.go               |  36 +
     proto/exemplar/exemplar.proto                 |  42 ++
     8 files changed, 927 insertions(+), 132 deletions(-)

Pull-Request: #26
Fixes: #23
Link: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf
  • Loading branch information
camh- committed Jan 21, 2022
2 parents eb2f9c7 + 56da68b commit 8ea14f9
Show file tree
Hide file tree
Showing 8 changed files with 927 additions and 132 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ lint: ## Lint go source code
.PHONY: lint

# --- Protos ---------------------------------------------------------------------
PROTOFILES = $(shell find proto -name google -prune -o -regex '.*/[^_].*\.proto' -print | LANG=C sort)
PROTOFILES = $(shell find proto -name google -prune -o -regex '.*/[^_][^/]*\.proto' -print | LANG=C sort)

lint-proto: ## Lint *.proto files
buf lint proto
Expand Down
53 changes: 53 additions & 0 deletions bones/exemplar.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ func MethodExemplar(md protoreflect.MethodDescriptor) exemplar {
func MessageExemplar(md protoreflect.MessageDescriptor) exemplar {
var e exemplar

if strings.HasPrefix(string(md.FullName()), "google.protobuf.") {
if e = WellKnownExemplar(md); len(e.lines) > 0 {
return e
}
}

for _, fd := range fields(md) {
fe := FieldExemplar(fd)
if fd.ContainingOneof() != nil {
Expand All @@ -75,6 +81,46 @@ func MessageExemplar(md protoreflect.MessageDescriptor) exemplar {
return e
}

// WellKnownExemplar returns an exemplar for a well-known type (those in the
// google.protobuf package). These are typically messages that are rendered in
// JSON as a single field, rather than as an object.
//
// https://developers.google.com/protocol-buffers/docs/reference/google.protobuf
func WellKnownExemplar(md protoreflect.MessageDescriptor) exemplar {
var e exemplar
switch string(md.Name()) {
case "Api", "Enum", "EnumValue", "Field", "Method", "Mixin", "Option", "SourceContext", "Type":
return e // empty exemplar. will be formatted as a message
case "Any":
// Emit an Any that can be read back in without modification
// Duration chosen at random, almost. Also for its simplicity.
e.line(`"@type": "type.googleapis.com/google.protobuf.Duration",`)
e.line(`value: "0s",`)
e.nest("{", "}")
case "BoolValue", "BytesValue", "DoubleValue", "FloatValue",
"Int32Value", "Int64Value", "StringValue", "UInt32Value", "UInt64Value":
return FieldValueExemplar(md.Fields().ByName("value"))
case "Duration":
e.line(`"0s"`)
case "Empty":
e.line("{}")
case "FieldMask":
e.line(`"field1.field2,field3"`)
case "ListValue":
return FieldValueExemplar(md.Fields().ByName("values"))
case "Struct":
e = FieldValueExemplar(md.Fields().Get(0).MapValue())
e.prepend("structField: ")
e.append(",")
e.nest("{", "}")
case "Timestamp":
e.line(`"2006-01-02T15:04:05.999999999Z"`)
case "Value":
e.line(`"https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#value"`)
}
return e
}

// FieldExemplar returns an exemplar for a message field. It has the JSON name
// for the field prefixed and a comment appended to the first line describing
// the type of the field.
Expand Down Expand Up @@ -133,6 +179,13 @@ func EnumExemplar(fd protoreflect.FieldDescriptor) exemplar {
if fd.Kind() != protoreflect.EnumKind {
return e
}

// The well-known google.protobuf.NullValue enum renders as "null"
if fd.Enum().FullName() == "google.protobuf.NullValue" {
e.line("null")
return e
}

ev := fd.Enum().Values()
name := ev.Get(0).Name()
if ev.Len() > 1 {
Expand Down
2 changes: 1 addition & 1 deletion bones/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,6 @@ func requireSameContent(t *testing.T, wantDir, gotDir string) {
gotFileName := filepath.Join(gotDir, gotFile.Name())
gotBytes, err := os.ReadFile(gotFileName)
require.NoError(t, err)
require.Equalf(t, wantBytes, gotBytes, "file contents are not the same want: %s, got: %s", wantFile.Name(), gotFile.Name())
require.Equalf(t, string(wantBytes), string(gotBytes), "file contents are not the same for %s", wantFile.Name())
}
}
212 changes: 212 additions & 0 deletions bones/testdata/golden/exemplar/exemplar.Exemplar.WellKnown.jsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// exemplar.Exemplar.WellKnown (Unary)

// Input:
// {
// request: {
// name: "", // string
// },
// }

function(input) {
response: {
any: { // Any
"@type": "type.googleapis.com/google.protobuf.Duration",
value: "0s",
},
api: { // Api
name: "", // string
methods: [ // repeated Method
{
name: "", // string
requestTypeUrl: "", // string
requestStreaming: false, // bool
responseTypeUrl: "", // string
responseStreaming: false, // bool
options: [ // repeated Option
{
name: "", // string
value: { // Any
"@type": "type.googleapis.com/google.protobuf.Duration",
value: "0s",
},
}
],
syntax: "SYNTAX_PROTO3", // Syntax
}
],
options: [ // repeated Option
{
name: "", // string
value: { // Any
"@type": "type.googleapis.com/google.protobuf.Duration",
value: "0s",
},
}
],
version: "", // string
sourceContext: { // SourceContext
fileName: "", // string
},
mixins: [ // repeated Mixin
{
name: "", // string
root: "", // string
}
],
syntax: "SYNTAX_PROTO3", // Syntax
},
boolValue: false, // BoolValue
bytesValue: "", // BytesValue
doubleValue: 0.0, // DoubleValue
duration: "0s", // Duration
empty: {}, // Empty
anEnum: { // Enum
name: "", // string
enumvalue: [ // repeated EnumValue
{
name: "", // string
number: 0, // int32
options: [ // repeated Option
{
name: "", // string
value: { // Any
"@type": "type.googleapis.com/google.protobuf.Duration",
value: "0s",
},
}
],
}
],
options: [ // repeated Option
{
name: "", // string
value: { // Any
"@type": "type.googleapis.com/google.protobuf.Duration",
value: "0s",
},
}
],
sourceContext: { // SourceContext
fileName: "", // string
},
syntax: "SYNTAX_PROTO3", // Syntax
},
enumValue: { // EnumValue
name: "", // string
number: 0, // int32
options: [ // repeated Option
{
name: "", // string
value: { // Any
"@type": "type.googleapis.com/google.protobuf.Duration",
value: "0s",
},
}
],
},
field: { // Field
kind: "TYPE_DOUBLE", // Kind
cardinality: "CARDINALITY_OPTIONAL", // Cardinality
number: 0, // int32
name: "", // string
typeUrl: "", // string
oneofIndex: 0, // int32
packed: false, // bool
options: [ // repeated Option
{
name: "", // string
value: { // Any
"@type": "type.googleapis.com/google.protobuf.Duration",
value: "0s",
},
}
],
jsonName: "", // string
defaultValue: "", // string
},
fieldMask: "field1.field2,field3", // FieldMask
floatValue: 0.0, // FloatValue
int32Value: 0, // Int32Value
int64Value: 0, // Int64Value
listValue: ["https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#value"], // ListValue
method: { // Method
name: "", // string
requestTypeUrl: "", // string
requestStreaming: false, // bool
responseTypeUrl: "", // string
responseStreaming: false, // bool
options: [ // repeated Option
{
name: "", // string
value: { // Any
"@type": "type.googleapis.com/google.protobuf.Duration",
value: "0s",
},
}
],
syntax: "SYNTAX_PROTO3", // Syntax
},
mixin: { // Mixin
name: "", // string
root: "", // string
},
nullValue: null, // NullValue
anOption: { // Option
name: "", // string
value: { // Any
"@type": "type.googleapis.com/google.protobuf.Duration",
value: "0s",
},
},
sourceContext: { // SourceContext
fileName: "", // string
},
stringValue: "", // StringValue
struct: { // Struct
structField: "https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#value",
},
timestamp: "2006-01-02T15:04:05.999999999Z", // Timestamp
type: { // Type
name: "", // string
fields: [ // repeated Field
{
kind: "TYPE_DOUBLE", // Kind
cardinality: "CARDINALITY_OPTIONAL", // Cardinality
number: 0, // int32
name: "", // string
typeUrl: "", // string
oneofIndex: 0, // int32
packed: false, // bool
options: [ // repeated Option
{
name: "", // string
value: { // Any
"@type": "type.googleapis.com/google.protobuf.Duration",
value: "0s",
},
}
],
jsonName: "", // string
defaultValue: "", // string
}
],
oneofs: [""], // repeated string
options: [ // repeated Option
{
name: "", // string
value: { // Any
"@type": "type.googleapis.com/google.protobuf.Duration",
value: "0s",
},
}
],
sourceContext: { // SourceContext
fileName: "", // string
},
syntax: "SYNTAX_PROTO3", // Syntax
},
uint32Value: 0, // UInt32Value
uint64Value: 0, // UInt64Value
value: "https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#value", // Value
},
}
Binary file modified pb/exemplar/exemplar.pb
Binary file not shown.
Loading

0 comments on commit 8ea14f9

Please sign in to comment.