523 lines
17 KiB
Go
523 lines
17 KiB
Go
// Copyright 2018 Twitch Interactive, Inc. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License"). You may not
|
|
// use this file except in compliance with the License. A copy of the License is
|
|
// located at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// or in the "license" file accompanying this file. This file is distributed on
|
|
// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
|
// express or implied. See the License for the specific language governing
|
|
// permissions and limitations under the License.
|
|
//
|
|
//
|
|
// This file contains some code from https://github.com/golang/protobuf:
|
|
// Copyright 2010 The Go Authors. All rights reserved.
|
|
// https://github.com/golang/protobuf
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are
|
|
// met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above
|
|
// copyright notice, this list of conditions and the following disclaimer
|
|
// in the documentation and/or other materials provided with the
|
|
// distribution.
|
|
// * Neither the name of Google Inc. nor the names of its
|
|
// contributors may be used to endorse or promote products derived from
|
|
// this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
package gen
|
|
|
|
import (
|
|
"fmt"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"go-common/app/tool/liverpc/protoc-gen-liverpc/gen/stringutils"
|
|
|
|
"github.com/golang/protobuf/protoc-gen-go/descriptor"
|
|
plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
|
|
)
|
|
|
|
// Each type we import as a protocol buffer (other than FileDescriptorProto) needs
|
|
// a pointer to the FileDescriptorProto that represents it. These types achieve that
|
|
// wrapping by placing each Proto inside a struct with the pointer to its File. The
|
|
// structs have the same names as their contents, with "Proto" removed.
|
|
// FileDescriptor is used to store the things that it points to.
|
|
|
|
// WrapTypes walks the incoming data, wrapping DescriptorProtos, EnumDescriptorProtos
|
|
// and FileDescriptorProtos into file-referenced objects within the Generator.
|
|
// It also creates the list of files to generate and so should be called before GenerateAllFiles.
|
|
func WrapTypes(req *plugin.CodeGeneratorRequest) (genFiles, allFiles []*FileDescriptor, allFilesByName map[string]*FileDescriptor) {
|
|
allFiles = make([]*FileDescriptor, 0, len(req.ProtoFile))
|
|
allFilesByName = make(map[string]*FileDescriptor, len(allFiles))
|
|
|
|
for _, f := range req.ProtoFile {
|
|
// We must wrap the descriptors before we wrap the enums
|
|
descs := wrapDescriptors(f)
|
|
buildNestedDescriptors(descs)
|
|
enums := wrapEnumDescriptors(f, descs)
|
|
buildNestedEnums(descs, enums)
|
|
exts := wrapExtensions(f)
|
|
svcs := wrapServices(f)
|
|
fd := &FileDescriptor{
|
|
FileDescriptorProto: f,
|
|
Services: svcs,
|
|
Descriptors: descs,
|
|
Enums: enums,
|
|
Extensions: exts,
|
|
proto3: fileIsProto3(f),
|
|
}
|
|
extractComments(fd)
|
|
|
|
allFiles = append(allFiles, fd)
|
|
allFilesByName[f.GetName()] = fd
|
|
}
|
|
|
|
for _, fd := range allFiles {
|
|
fd.Imported = wrapImported(fd.FileDescriptorProto, allFilesByName)
|
|
}
|
|
|
|
genFiles = make([]*FileDescriptor, 0, len(req.FileToGenerate))
|
|
for _, fileName := range req.FileToGenerate {
|
|
fd := allFilesByName[fileName]
|
|
if fd == nil {
|
|
Fail("could not find file named", fileName)
|
|
}
|
|
fd.Index = len(genFiles)
|
|
genFiles = append(genFiles, fd)
|
|
}
|
|
|
|
return genFiles, allFiles, allFilesByName
|
|
}
|
|
|
|
// The file and package name method are common to messages and enums.
|
|
type common struct {
|
|
file *descriptor.FileDescriptorProto // File this object comes from.
|
|
}
|
|
|
|
func (c *common) File() *descriptor.FileDescriptorProto { return c.file }
|
|
|
|
func fileIsProto3(file *descriptor.FileDescriptorProto) bool {
|
|
return file.GetSyntax() == "proto3"
|
|
}
|
|
|
|
// Descriptor represents a protocol buffer message.
|
|
type Descriptor struct {
|
|
common
|
|
*descriptor.DescriptorProto
|
|
Parent *Descriptor // The containing message, if any.
|
|
nested []*Descriptor // Inner messages, if any.
|
|
enums []*EnumDescriptor // Inner enums, if any.
|
|
ext []*ExtensionDescriptor // Extensions, if any.
|
|
typename []string // Cached typename vector.
|
|
index int // The index into the container, whether the file or another message.
|
|
path string // The SourceCodeInfo path as comma-separated integers.
|
|
group bool
|
|
}
|
|
|
|
func newDescriptor(desc *descriptor.DescriptorProto, parent *Descriptor, file *descriptor.FileDescriptorProto, index int) *Descriptor {
|
|
d := &Descriptor{
|
|
common: common{file},
|
|
DescriptorProto: desc,
|
|
Parent: parent,
|
|
index: index,
|
|
}
|
|
if parent == nil {
|
|
d.path = fmt.Sprintf("%d,%d", messagePath, index)
|
|
} else {
|
|
d.path = fmt.Sprintf("%s,%d,%d", parent.path, messageMessagePath, index)
|
|
}
|
|
|
|
// The only way to distinguish a group from a message is whether
|
|
// the containing message has a TYPE_GROUP field that matches.
|
|
if parent != nil {
|
|
parts := d.TypeName()
|
|
if file.Package != nil {
|
|
parts = append([]string{*file.Package}, parts...)
|
|
}
|
|
exp := "." + strings.Join(parts, ".")
|
|
for _, field := range parent.Field {
|
|
if field.GetType() == descriptor.FieldDescriptorProto_TYPE_GROUP && field.GetTypeName() == exp {
|
|
d.group = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, field := range desc.Extension {
|
|
d.ext = append(d.ext, &ExtensionDescriptor{common{file}, field, d})
|
|
}
|
|
|
|
return d
|
|
}
|
|
|
|
// Return a slice of all the Descriptors defined within this file
|
|
func wrapDescriptors(file *descriptor.FileDescriptorProto) []*Descriptor {
|
|
sl := make([]*Descriptor, 0, len(file.MessageType)+10)
|
|
for i, desc := range file.MessageType {
|
|
sl = wrapThisDescriptor(sl, desc, nil, file, i)
|
|
}
|
|
return sl
|
|
}
|
|
|
|
// Wrap this Descriptor, recursively
|
|
func wrapThisDescriptor(sl []*Descriptor, desc *descriptor.DescriptorProto, parent *Descriptor, file *descriptor.FileDescriptorProto, index int) []*Descriptor {
|
|
sl = append(sl, newDescriptor(desc, parent, file, index))
|
|
me := sl[len(sl)-1]
|
|
for i, nested := range desc.NestedType {
|
|
sl = wrapThisDescriptor(sl, nested, me, file, i)
|
|
}
|
|
return sl
|
|
}
|
|
|
|
func buildNestedDescriptors(descs []*Descriptor) {
|
|
for _, desc := range descs {
|
|
if len(desc.NestedType) != 0 {
|
|
for _, nest := range descs {
|
|
if nest.Parent == desc {
|
|
desc.nested = append(desc.nested, nest)
|
|
}
|
|
}
|
|
if len(desc.nested) != len(desc.NestedType) {
|
|
Fail("internal error: nesting failure for", desc.GetName())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TypeName returns the elements of the dotted type name.
|
|
// The package name is not part of this name.
|
|
func (d *Descriptor) TypeName() []string {
|
|
if d.typename != nil {
|
|
return d.typename
|
|
}
|
|
n := 0
|
|
for parent := d; parent != nil; parent = parent.Parent {
|
|
n++
|
|
}
|
|
s := make([]string, n)
|
|
for parent := d; parent != nil; parent = parent.Parent {
|
|
n--
|
|
s[n] = parent.GetName()
|
|
}
|
|
d.typename = s
|
|
return s
|
|
}
|
|
|
|
// EnumDescriptor describes an enum. If it's at top level, its parent will be nil.
|
|
// Otherwise it will be the descriptor of the message in which it is defined.
|
|
type EnumDescriptor struct {
|
|
common
|
|
*descriptor.EnumDescriptorProto
|
|
parent *Descriptor // The containing message, if any.
|
|
typename []string // Cached typename vector.
|
|
index int // The index into the container, whether the file or a message.
|
|
path string // The SourceCodeInfo path as comma-separated integers.
|
|
}
|
|
|
|
// Construct a new EnumDescriptor
|
|
func newEnumDescriptor(desc *descriptor.EnumDescriptorProto, parent *Descriptor, file *descriptor.FileDescriptorProto, index int) *EnumDescriptor {
|
|
ed := &EnumDescriptor{
|
|
common: common{file},
|
|
EnumDescriptorProto: desc,
|
|
parent: parent,
|
|
index: index,
|
|
}
|
|
if parent == nil {
|
|
ed.path = fmt.Sprintf("%d,%d", enumPath, index)
|
|
} else {
|
|
ed.path = fmt.Sprintf("%s,%d,%d", parent.path, messageEnumPath, index)
|
|
}
|
|
return ed
|
|
}
|
|
|
|
// Return a slice of all the EnumDescriptors defined within this file
|
|
func wrapEnumDescriptors(file *descriptor.FileDescriptorProto, descs []*Descriptor) []*EnumDescriptor {
|
|
sl := make([]*EnumDescriptor, 0, len(file.EnumType)+10)
|
|
// Top-level enums.
|
|
for i, enum := range file.EnumType {
|
|
sl = append(sl, newEnumDescriptor(enum, nil, file, i))
|
|
}
|
|
// Enums within messages. Enums within embedded messages appear in the outer-most message.
|
|
for _, nested := range descs {
|
|
for i, enum := range nested.EnumType {
|
|
sl = append(sl, newEnumDescriptor(enum, nested, file, i))
|
|
}
|
|
}
|
|
return sl
|
|
}
|
|
|
|
func buildNestedEnums(descs []*Descriptor, enums []*EnumDescriptor) {
|
|
for _, desc := range descs {
|
|
if len(desc.EnumType) != 0 {
|
|
for _, enum := range enums {
|
|
if enum.parent == desc {
|
|
desc.enums = append(desc.enums, enum)
|
|
}
|
|
}
|
|
if len(desc.enums) != len(desc.EnumType) {
|
|
Fail("internal error: enum nesting failure for", desc.GetName())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TypeName returns the elements of the dotted type name.
|
|
// The package name is not part of this name.
|
|
func (e *EnumDescriptor) TypeName() (s []string) {
|
|
if e.typename != nil {
|
|
return e.typename
|
|
}
|
|
name := e.GetName()
|
|
if e.parent == nil {
|
|
s = make([]string, 1)
|
|
} else {
|
|
pname := e.parent.TypeName()
|
|
s = make([]string, len(pname)+1)
|
|
copy(s, pname)
|
|
}
|
|
s[len(s)-1] = name
|
|
e.typename = s
|
|
return s
|
|
}
|
|
|
|
// ExtensionDescriptor describes an extension. If it's at top level, its parent will be nil.
|
|
// Otherwise it will be the descriptor of the message in which it is defined.
|
|
type ExtensionDescriptor struct {
|
|
common
|
|
*descriptor.FieldDescriptorProto
|
|
parent *Descriptor // The containing message, if any.
|
|
}
|
|
|
|
// Return a slice of all the top-level ExtensionDescriptors defined within this file.
|
|
func wrapExtensions(file *descriptor.FileDescriptorProto) []*ExtensionDescriptor {
|
|
var sl []*ExtensionDescriptor
|
|
for _, field := range file.Extension {
|
|
sl = append(sl, &ExtensionDescriptor{common{file}, field, nil})
|
|
}
|
|
return sl
|
|
}
|
|
|
|
// TypeName returns the elements of the dotted type name.
|
|
// The package name is not part of this name.
|
|
func (e *ExtensionDescriptor) TypeName() (s []string) {
|
|
name := e.GetName()
|
|
if e.parent == nil {
|
|
// top-level extension
|
|
s = make([]string, 1)
|
|
} else {
|
|
pname := e.parent.TypeName()
|
|
s = make([]string, len(pname)+1)
|
|
copy(s, pname)
|
|
}
|
|
s[len(s)-1] = name
|
|
return s
|
|
}
|
|
|
|
// DescName returns the variable name used for the generated descriptor.
|
|
func (e *ExtensionDescriptor) DescName() string {
|
|
// The full type name.
|
|
typeName := e.TypeName()
|
|
// Each scope of the extension is individually CamelCased, and all are joined with "_" with an "E_" prefix.
|
|
for i, s := range typeName {
|
|
typeName[i] = stringutils.CamelCase(s)
|
|
}
|
|
return "E_" + strings.Join(typeName, "_")
|
|
}
|
|
|
|
// ImportedDescriptor describes a type that has been publicly imported from another file.
|
|
type ImportedDescriptor struct {
|
|
common
|
|
Object Object
|
|
}
|
|
|
|
// Return a slice of all the types that are publicly imported into this file.
|
|
func wrapImported(file *descriptor.FileDescriptorProto, fileMap map[string]*FileDescriptor) (sl []*ImportedDescriptor) {
|
|
for _, index := range file.PublicDependency {
|
|
df := fileMap[file.Dependency[index]]
|
|
for _, d := range df.Descriptors {
|
|
if d.GetOptions().GetMapEntry() {
|
|
continue
|
|
}
|
|
sl = append(sl, &ImportedDescriptor{common{file}, d})
|
|
}
|
|
for _, e := range df.Enums {
|
|
sl = append(sl, &ImportedDescriptor{common{file}, e})
|
|
}
|
|
for _, ext := range df.Extensions {
|
|
sl = append(sl, &ImportedDescriptor{common{file}, ext})
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// TypeName ...
|
|
func (id *ImportedDescriptor) TypeName() []string { return id.Object.TypeName() }
|
|
|
|
// ServiceDescriptor represents a protocol buffer service.
|
|
type ServiceDescriptor struct {
|
|
common
|
|
*descriptor.ServiceDescriptorProto
|
|
Methods []*MethodDescriptor
|
|
Index int // index of the ServiceDescriptorProto in its parent FileDescriptorProto
|
|
|
|
Path string // The SourceCodeInfo path as comma-separated integers.
|
|
}
|
|
|
|
// TypeName ...
|
|
func (sd *ServiceDescriptor) TypeName() []string {
|
|
return []string{sd.GetName()}
|
|
}
|
|
|
|
func wrapServices(file *descriptor.FileDescriptorProto) (sl []*ServiceDescriptor) {
|
|
for i, svc := range file.Service {
|
|
sd := &ServiceDescriptor{
|
|
common: common{file},
|
|
ServiceDescriptorProto: svc,
|
|
Index: i,
|
|
Path: fmt.Sprintf("%d,%d", servicePath, i),
|
|
}
|
|
for j, method := range svc.Method {
|
|
md := &MethodDescriptor{
|
|
common: common{file},
|
|
MethodDescriptorProto: method,
|
|
service: sd,
|
|
Path: fmt.Sprintf("%d,%d,%d,%d", servicePath, i, serviceMethodPath, j),
|
|
}
|
|
sd.Methods = append(sd.Methods, md)
|
|
}
|
|
sl = append(sl, sd)
|
|
}
|
|
return sl
|
|
}
|
|
|
|
// MethodDescriptor represents an RPC method on a protocol buffer
|
|
// service.
|
|
type MethodDescriptor struct {
|
|
common
|
|
*descriptor.MethodDescriptorProto
|
|
service *ServiceDescriptor
|
|
|
|
Path string // The SourceCodeInfo path as comma-separated integers.
|
|
}
|
|
|
|
// TypeName ...
|
|
func (md *MethodDescriptor) TypeName() []string {
|
|
return []string{md.service.GetName(), md.GetName()}
|
|
}
|
|
|
|
// FileDescriptor describes an protocol buffer descriptor file (.proto).
|
|
// It includes slices of all the messages and enums defined within it.
|
|
// Those slices are constructed by WrapTypes.
|
|
type FileDescriptor struct {
|
|
*descriptor.FileDescriptorProto
|
|
Descriptors []*Descriptor // All the messages defined in this file.
|
|
Enums []*EnumDescriptor // All the enums defined in this file.
|
|
Extensions []*ExtensionDescriptor // All the top-level extensions defined in this file.
|
|
Imported []*ImportedDescriptor // All types defined in files publicly imported by this file.
|
|
Services []*ServiceDescriptor // All the services defined in this file.
|
|
|
|
// Comments, stored as a map of path (comma-separated integers) to the comment.
|
|
Comments map[string]*descriptor.SourceCodeInfo_Location
|
|
|
|
Index int // The index of this file in the list of files to generate code for
|
|
|
|
proto3 bool // whether to generate proto3 code for this file
|
|
}
|
|
|
|
// VarName is the variable name used in generated code to refer to the
|
|
// compressed bytes of this descriptor. It is not exported, so it is only valid
|
|
// inside the generated package.
|
|
//
|
|
// protoc-gen-go writes its own version of this file, but so does
|
|
// protoc-gen-gogo - with a different name! Twirp aims to be compatible with
|
|
// both; the simplest way forward is to write the file descriptor again as
|
|
// another variable that we control.
|
|
func (d *FileDescriptor) VarName() string { return fmt.Sprintf("twirpFileDescriptor%d", d.Index) }
|
|
|
|
// PackageComments get pkg comments
|
|
func (d *FileDescriptor) PackageComments() string {
|
|
if loc, ok := d.Comments[strconv.Itoa(packagePath)]; ok {
|
|
text := strings.TrimSuffix(loc.GetLeadingComments(), "\n")
|
|
return text
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// BaseFileName name strip extension
|
|
func (d *FileDescriptor) BaseFileName() string {
|
|
name := *d.Name
|
|
if ext := path.Ext(name); ext == ".proto" || ext == ".protodevel" {
|
|
name = name[:len(name)-len(ext)]
|
|
}
|
|
|
|
return name
|
|
}
|
|
|
|
func extractComments(file *FileDescriptor) {
|
|
file.Comments = make(map[string]*descriptor.SourceCodeInfo_Location)
|
|
for _, loc := range file.GetSourceCodeInfo().GetLocation() {
|
|
if loc.LeadingComments == nil {
|
|
continue
|
|
}
|
|
var p []string
|
|
for _, n := range loc.Path {
|
|
p = append(p, strconv.Itoa(int(n)))
|
|
}
|
|
file.Comments[strings.Join(p, ",")] = loc
|
|
}
|
|
}
|
|
|
|
// Object is an interface abstracting the abilities shared by enums, messages, extensions and imported objects.
|
|
type Object interface {
|
|
TypeName() []string
|
|
File() *descriptor.FileDescriptorProto
|
|
}
|
|
|
|
// The SourceCodeInfo message describes the location of elements of a parsed
|
|
// .proto file by way of a "path", which is a sequence of integers that
|
|
// describe the route from a FileDescriptorProto to the relevant submessage.
|
|
// The path alternates between a field number of a repeated field, and an index
|
|
// into that repeated field. The constants below define the field numbers that
|
|
// are used.
|
|
//
|
|
// See descriptor.proto for more information about this.
|
|
const (
|
|
// tag numbers in FileDescriptorProto
|
|
packagePath = 2 // package
|
|
messagePath = 4 // message_type
|
|
enumPath = 5 // enum_type
|
|
servicePath = 6 // service
|
|
// tag numbers in DescriptorProto
|
|
//messageFieldPath = 2 // field
|
|
messageMessagePath = 3 // nested_type
|
|
messageEnumPath = 4 // enum_type
|
|
//messageOneofPath = 8 // oneof_decl
|
|
// tag numbers in ServiceDescriptorProto
|
|
//serviceNamePath = 1 // name
|
|
serviceMethodPath = 2 // method
|
|
//serviceOptionsPath = 3 // options
|
|
// tag numbers in MethodDescriptorProto
|
|
//methodNamePath = 1 // name
|
|
//methodInputPath = 2 // input_type
|
|
//methodOutputPath = 3 // output_type
|
|
)
|