This repository has been archived by the owner on Jun 20, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 98
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add failing test for desired functionality (using comments in the .proto file for each JSONSchema's description field) * Move .proto => FileDescriptorSet loader into its own helper function so multiple tests can use it. * Add sourceCodeInfo "indexer" (requires "--include_source_info" protoc flag to work) You give it a list of FileDescriptor, it indexes all of the SourceCodeInfo locations by what they reference and gives back a dictionary/registry that can be used to look up source info for a message/field/enum/etc definition. Example: fileDescriptors := ... fieldDescriptor := ... srcInfo := newSourceCodeInfo(fileDescriptors) fieldSrc := srcInfo.GetField(fieldDescriptor) println(fieldSrc.LeadingComments) * Populate schema description from .proto file comments (requires "--include_source_info" protoc flag to work)
- Loading branch information
Showing
8 changed files
with
278 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package converter | ||
|
||
import ( | ||
"github.com/golang/protobuf/proto" | ||
"github.com/golang/protobuf/protoc-gen-go/descriptor" | ||
) | ||
|
||
// Protobuf tag values for relevant message fields. Full list here: | ||
// https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/descriptor.proto | ||
const ( | ||
tag_FileDescriptor_messageType int32 = 4 | ||
tag_FileDescriptor_enumType int32 = 5 | ||
tag_Descriptor_field int32 = 2 | ||
tag_Descriptor_nestedType int32 = 3 | ||
tag_Descriptor_enumType int32 = 4 | ||
tag_Descriptor_oneofDecl int32 = 8 | ||
tag_EnumDescriptor_value int32 = 2 | ||
) | ||
|
||
type sourceCodeInfo struct { | ||
lookup map[proto.Message]*descriptor.SourceCodeInfo_Location | ||
} | ||
|
||
func (s sourceCodeInfo) GetMessage(m *descriptor.DescriptorProto) *descriptor.SourceCodeInfo_Location { | ||
return s.lookup[m] | ||
} | ||
|
||
func (s sourceCodeInfo) GetField(f *descriptor.FieldDescriptorProto) *descriptor.SourceCodeInfo_Location { | ||
return s.lookup[f] | ||
} | ||
|
||
func (s sourceCodeInfo) GetEnum(e *descriptor.EnumDescriptorProto) *descriptor.SourceCodeInfo_Location { | ||
return s.lookup[e] | ||
} | ||
|
||
func (s sourceCodeInfo) GetEnumValue(e *descriptor.EnumValueDescriptorProto) *descriptor.SourceCodeInfo_Location { | ||
return s.lookup[e] | ||
} | ||
|
||
func newSourceCodeInfo(fs []*descriptor.FileDescriptorProto) *sourceCodeInfo { | ||
// For each source location in the provided files | ||
// - resolve the (annoyingly) encoded path to its message/field/service/enum/etc definition | ||
// - store the source info by its resolved definition | ||
lookup := map[proto.Message]*descriptor.SourceCodeInfo_Location{} | ||
for _, f := range fs { | ||
for _, loc := range f.GetSourceCodeInfo().GetLocation() { | ||
declaration := getDefinitionAtPath(f, loc.Path) | ||
if declaration != nil { | ||
lookup[declaration] = loc | ||
} | ||
} | ||
} | ||
return &sourceCodeInfo{lookup} | ||
} | ||
|
||
// Resolve a protobuf "file-source path" to its associated definition (eg message/field/enum/etc). | ||
// Note that some paths don't point to definitions (some reference subcomponents like name, type, | ||
// field #, etc) and will therefore return nil. | ||
func getDefinitionAtPath(file *descriptor.FileDescriptorProto, path []int32) proto.Message { | ||
// The way protobuf encodes "file-source path" is a little opaque/tricky; | ||
// this doc describes how it works: | ||
// https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/descriptor.proto#L730 | ||
|
||
// Starting at the root of the file descriptor, traverse its object graph by following the | ||
// specified path (and updating our position/state at each step) until either: | ||
// - we reach the definition referenced by the path (and return it) | ||
// - we hit a dead end because the path references a grammar element more granular than a | ||
// definition (so we return nil) | ||
var pos proto.Message = file | ||
for step := 0; step < len(path); step++ { | ||
switch p := pos.(type) { | ||
case *descriptor.FileDescriptorProto: | ||
switch path[step] { | ||
case tag_FileDescriptor_messageType: | ||
step++ | ||
pos = p.MessageType[path[step]] | ||
case tag_FileDescriptor_enumType: | ||
step++ | ||
pos = p.EnumType[path[step]] | ||
default: | ||
return nil // ignore all other types | ||
} | ||
|
||
case *descriptor.DescriptorProto: | ||
switch path[step] { | ||
case tag_Descriptor_field: | ||
step++ | ||
pos = p.Field[path[step]] | ||
case tag_Descriptor_nestedType: | ||
step++ | ||
pos = p.NestedType[path[step]] | ||
case tag_Descriptor_enumType: | ||
step++ | ||
pos = p.EnumType[path[step]] | ||
case tag_Descriptor_oneofDecl: | ||
step++ | ||
pos = p.OneofDecl[path[step]] | ||
default: | ||
return nil // ignore all other types | ||
} | ||
|
||
case *descriptor.EnumDescriptorProto: | ||
switch path[step] { | ||
case tag_EnumDescriptor_value: | ||
step++ | ||
pos = p.Value[path[step]] | ||
default: | ||
return nil // ignore all other types | ||
} | ||
|
||
default: | ||
return nil // ignore all other types | ||
} | ||
} | ||
return pos | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package converter | ||
|
||
import ( | ||
"github.com/golang/protobuf/protoc-gen-go/descriptor" | ||
"testing" | ||
) | ||
|
||
func TestSourceInfoLookup(t *testing.T) { | ||
// Read in the test file & get references to the things we've declared. | ||
// Note that the hardcoded indexes must reflect the declaration order in | ||
// the .proto file. | ||
fds := mustReadProtoFiles(t, sampleProtoDirectory, "MessageWithComments.proto") | ||
protoFile := fds.File[0] | ||
msgWithComments := protoFile.MessageType[0] | ||
msgWithComments_name1 := msgWithComments.Field[0] | ||
|
||
// Create an instance of our thing and test that it returns the expected | ||
// source data for each of our above declarations. | ||
src := newSourceCodeInfo(fds.File) | ||
assertCommentsMatch(t, src.GetMessage(msgWithComments), &descriptor.SourceCodeInfo_Location{ | ||
LeadingComments: strPtr(" This is a message level comment and talks about what this message is and why you should care about it!\n"), | ||
}) | ||
assertCommentsMatch(t, src.GetField(msgWithComments_name1), &descriptor.SourceCodeInfo_Location{ | ||
LeadingComments: strPtr(" This field is supposed to represent blahblahblah\n"), | ||
}) | ||
} | ||
|
||
func assertCommentsMatch(t *testing.T, actual, expected *descriptor.SourceCodeInfo_Location) { | ||
if len(actual.LeadingDetachedComments) != len(expected.LeadingDetachedComments) { | ||
t.Fatalf("Wrong value for LeadingDetachedComments.\n got: %v\n want: %v", | ||
actual.LeadingDetachedComments, expected.LeadingDetachedComments) | ||
} | ||
for i := 0; i < len(actual.LeadingDetachedComments); i++ { | ||
if actual.LeadingDetachedComments[i] != expected.LeadingDetachedComments[i] { | ||
t.Fatalf("Wrong value for LeadingDetachedComments.\n got: %v\n want: %v", | ||
actual.LeadingDetachedComments, expected.LeadingDetachedComments) | ||
} | ||
} | ||
if actual.GetTrailingComments() != expected.GetTrailingComments() { | ||
t.Fatalf("Wrong value for TrailingComments.\n got: %q\n want: %q", | ||
actual.GetTrailingComments(), expected.GetTrailingComments()) | ||
} | ||
if actual.GetLeadingComments() != expected.GetLeadingComments() { | ||
t.Fatalf("Wrong value for LeadingComments.\n got: %q\n want: %q", | ||
actual.GetLeadingComments(), expected.GetLeadingComments()) | ||
} | ||
} | ||
|
||
// Go doesn't have syntax for addressing a string literal, so this is the next best thing. | ||
func strPtr(s string) *string { | ||
return &s | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package testdata | ||
|
||
const MessageWithComments = `{ | ||
"$schema": "http://json-schema.org/draft-04/schema#", | ||
"properties": { | ||
"name1": { | ||
"type": "string", | ||
"description": "This field is supposed to represent blahblahblah" | ||
} | ||
}, | ||
"additionalProperties": true, | ||
"type": "object", | ||
"description": "This is a message level comment and talks about what this message is and why you should care about it!" | ||
}` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
syntax = "proto3"; | ||
package samples; | ||
|
||
// This is a message level comment and talks about what this message is and why you should care about it! | ||
message MessageWithComments { | ||
|
||
// This field is supposed to represent blahblahblah | ||
string name1 = 1; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"$schema": "http://json-schema.org/draft-04/schema#", | ||
"properties": { | ||
"name1": { | ||
"type": "string", | ||
"description": "This field is supposed to represent blahblahblah" | ||
} | ||
}, | ||
"additionalProperties": true, | ||
"type": "object", | ||
"description": "This is a message level comment and talks about what this message is and why you should care about it!" | ||
} |