1package descriptor 2 3import ( 4 "fmt" 5 "path" 6 "path/filepath" 7 "strings" 8 9 "github.com/golang/glog" 10 descriptor "github.com/golang/protobuf/protoc-gen-go/descriptor" 11 plugin "github.com/golang/protobuf/protoc-gen-go/plugin" 12) 13 14// Registry is a registry of information extracted from plugin.CodeGeneratorRequest. 15type Registry struct { 16 // msgs is a mapping from fully-qualified message name to descriptor 17 msgs map[string]*Message 18 19 // enums is a mapping from fully-qualified enum name to descriptor 20 enums map[string]*Enum 21 22 // files is a mapping from file path to descriptor 23 files map[string]*File 24 25 // prefix is a prefix to be inserted to golang package paths generated from proto package names. 26 prefix string 27 28 // pkgMap is a user-specified mapping from file path to proto package. 29 pkgMap map[string]string 30 31 // pkgAliases is a mapping from package aliases to package paths in go which are already taken. 32 pkgAliases map[string]string 33 34 // allowDeleteBody permits http delete methods to have a body 35 allowDeleteBody bool 36} 37 38// NewRegistry returns a new Registry. 39func NewRegistry() *Registry { 40 return &Registry{ 41 msgs: make(map[string]*Message), 42 enums: make(map[string]*Enum), 43 files: make(map[string]*File), 44 pkgMap: make(map[string]string), 45 pkgAliases: make(map[string]string), 46 } 47} 48 49// Load loads definitions of services, methods, messages, enumerations and fields from "req". 50func (r *Registry) Load(req *plugin.CodeGeneratorRequest) error { 51 for _, file := range req.GetProtoFile() { 52 r.loadFile(file) 53 } 54 55 var targetPkg string 56 for _, name := range req.FileToGenerate { 57 target := r.files[name] 58 if target == nil { 59 return fmt.Errorf("no such file: %s", name) 60 } 61 name := packageIdentityName(target.FileDescriptorProto) 62 if targetPkg == "" { 63 targetPkg = name 64 } else { 65 if targetPkg != name { 66 return fmt.Errorf("inconsistent package names: %s %s", targetPkg, name) 67 } 68 } 69 70 if err := r.loadServices(target); err != nil { 71 return err 72 } 73 } 74 return nil 75} 76 77// loadFile loads messages, enumerations and fields from "file". 78// It does not loads services and methods in "file". You need to call 79// loadServices after loadFiles is called for all files to load services and methods. 80func (r *Registry) loadFile(file *descriptor.FileDescriptorProto) { 81 pkg := GoPackage{ 82 Path: r.goPackagePath(file), 83 Name: defaultGoPackageName(file), 84 } 85 if err := r.ReserveGoPackageAlias(pkg.Name, pkg.Path); err != nil { 86 for i := 0; ; i++ { 87 alias := fmt.Sprintf("%s_%d", pkg.Name, i) 88 if err := r.ReserveGoPackageAlias(alias, pkg.Path); err == nil { 89 pkg.Alias = alias 90 break 91 } 92 } 93 } 94 f := &File{ 95 FileDescriptorProto: file, 96 GoPkg: pkg, 97 } 98 99 r.files[file.GetName()] = f 100 r.registerMsg(f, nil, file.GetMessageType()) 101 r.registerEnum(f, nil, file.GetEnumType()) 102} 103 104func (r *Registry) registerMsg(file *File, outerPath []string, msgs []*descriptor.DescriptorProto) { 105 for i, md := range msgs { 106 m := &Message{ 107 File: file, 108 Outers: outerPath, 109 DescriptorProto: md, 110 Index: i, 111 } 112 for _, fd := range md.GetField() { 113 m.Fields = append(m.Fields, &Field{ 114 Message: m, 115 FieldDescriptorProto: fd, 116 }) 117 } 118 file.Messages = append(file.Messages, m) 119 r.msgs[m.FQMN()] = m 120 glog.V(1).Infof("register name: %s", m.FQMN()) 121 122 var outers []string 123 outers = append(outers, outerPath...) 124 outers = append(outers, m.GetName()) 125 r.registerMsg(file, outers, m.GetNestedType()) 126 r.registerEnum(file, outers, m.GetEnumType()) 127 } 128} 129 130func (r *Registry) registerEnum(file *File, outerPath []string, enums []*descriptor.EnumDescriptorProto) { 131 for i, ed := range enums { 132 e := &Enum{ 133 File: file, 134 Outers: outerPath, 135 EnumDescriptorProto: ed, 136 Index: i, 137 } 138 file.Enums = append(file.Enums, e) 139 r.enums[e.FQEN()] = e 140 glog.V(1).Infof("register enum name: %s", e.FQEN()) 141 } 142} 143 144// LookupMsg looks up a message type by "name". 145// It tries to resolve "name" from "location" if "name" is a relative message name. 146func (r *Registry) LookupMsg(location, name string) (*Message, error) { 147 glog.V(1).Infof("lookup %s from %s", name, location) 148 if strings.HasPrefix(name, ".") { 149 m, ok := r.msgs[name] 150 if !ok { 151 return nil, fmt.Errorf("no message found: %s", name) 152 } 153 return m, nil 154 } 155 156 if !strings.HasPrefix(location, ".") { 157 location = fmt.Sprintf(".%s", location) 158 } 159 components := strings.Split(location, ".") 160 for len(components) > 0 { 161 fqmn := strings.Join(append(components, name), ".") 162 if m, ok := r.msgs[fqmn]; ok { 163 return m, nil 164 } 165 components = components[:len(components)-1] 166 } 167 return nil, fmt.Errorf("no message found: %s", name) 168} 169 170// LookupEnum looks up a enum type by "name". 171// It tries to resolve "name" from "location" if "name" is a relative enum name. 172func (r *Registry) LookupEnum(location, name string) (*Enum, error) { 173 glog.V(1).Infof("lookup enum %s from %s", name, location) 174 if strings.HasPrefix(name, ".") { 175 e, ok := r.enums[name] 176 if !ok { 177 return nil, fmt.Errorf("no enum found: %s", name) 178 } 179 return e, nil 180 } 181 182 if !strings.HasPrefix(location, ".") { 183 location = fmt.Sprintf(".%s", location) 184 } 185 components := strings.Split(location, ".") 186 for len(components) > 0 { 187 fqen := strings.Join(append(components, name), ".") 188 if e, ok := r.enums[fqen]; ok { 189 return e, nil 190 } 191 components = components[:len(components)-1] 192 } 193 return nil, fmt.Errorf("no enum found: %s", name) 194} 195 196// LookupFile looks up a file by name. 197func (r *Registry) LookupFile(name string) (*File, error) { 198 f, ok := r.files[name] 199 if !ok { 200 return nil, fmt.Errorf("no such file given: %s", name) 201 } 202 return f, nil 203} 204 205// AddPkgMap adds a mapping from a .proto file to proto package name. 206func (r *Registry) AddPkgMap(file, protoPkg string) { 207 r.pkgMap[file] = protoPkg 208} 209 210// SetPrefix registeres the perfix to be added to go package paths generated from proto package names. 211func (r *Registry) SetPrefix(prefix string) { 212 r.prefix = prefix 213} 214 215// ReserveGoPackageAlias reserves the unique alias of go package. 216// If succeeded, the alias will be never used for other packages in generated go files. 217// If failed, the alias is already taken by another package, so you need to use another 218// alias for the package in your go files. 219func (r *Registry) ReserveGoPackageAlias(alias, pkgpath string) error { 220 if taken, ok := r.pkgAliases[alias]; ok { 221 if taken == pkgpath { 222 return nil 223 } 224 return fmt.Errorf("package name %s is already taken. Use another alias", alias) 225 } 226 r.pkgAliases[alias] = pkgpath 227 return nil 228} 229 230// goPackagePath returns the go package path which go files generated from "f" should have. 231// It respects the mapping registered by AddPkgMap if exists. Or use go_package as import path 232// if it includes a slash, Otherwide, it generates a path from the file name of "f". 233func (r *Registry) goPackagePath(f *descriptor.FileDescriptorProto) string { 234 name := f.GetName() 235 if pkg, ok := r.pkgMap[name]; ok { 236 return path.Join(r.prefix, pkg) 237 } 238 239 gopkg := f.Options.GetGoPackage() 240 idx := strings.LastIndex(gopkg, "/") 241 if idx >= 0 { 242 return gopkg 243 } 244 245 return path.Join(r.prefix, path.Dir(name)) 246} 247 248// GetAllFQMNs returns a list of all FQMNs 249func (r *Registry) GetAllFQMNs() []string { 250 var keys []string 251 for k := range r.msgs { 252 keys = append(keys, k) 253 } 254 return keys 255} 256 257// GetAllFQENs returns a list of all FQENs 258func (r *Registry) GetAllFQENs() []string { 259 var keys []string 260 for k := range r.enums { 261 keys = append(keys, k) 262 } 263 return keys 264} 265 266// SetAllowDeleteBody controls whether http delete methods may have a 267// body or fail loading if encountered. 268func (r *Registry) SetAllowDeleteBody(allow bool) { 269 r.allowDeleteBody = allow 270} 271 272// defaultGoPackageName returns the default go package name to be used for go files generated from "f". 273// You might need to use an unique alias for the package when you import it. Use ReserveGoPackageAlias to get a unique alias. 274func defaultGoPackageName(f *descriptor.FileDescriptorProto) string { 275 name := packageIdentityName(f) 276 return strings.Replace(name, ".", "_", -1) 277} 278 279// packageIdentityName returns the identity of packages. 280// protoc-gen-grpc-gateway rejects CodeGenerationRequests which contains more than one packages 281// as protoc-gen-go does. 282func packageIdentityName(f *descriptor.FileDescriptorProto) string { 283 if f.Options != nil && f.Options.GoPackage != nil { 284 gopkg := f.Options.GetGoPackage() 285 idx := strings.LastIndex(gopkg, "/") 286 if idx < 0 { 287 return gopkg 288 } 289 290 return gopkg[idx+1:] 291 } 292 293 if f.Package == nil { 294 base := filepath.Base(f.GetName()) 295 ext := filepath.Ext(base) 296 return strings.TrimSuffix(base, ext) 297 } 298 return f.GetPackage() 299} 300