| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347 |
- package descriptor
- import (
- "fmt"
- "strings"
- "git.ikuban.com/server/swagger-api/protoc-gen-openapiv2/internal/httprule"
- "github.com/golang/glog"
- options "google.golang.org/genproto/googleapis/api/annotations"
- "google.golang.org/protobuf/proto"
- "google.golang.org/protobuf/types/descriptorpb"
- )
- // loadServices registers services and their methods from "targetFile" to "r".
- // It must be called after loadFile is called for all files so that loadServices
- // can resolve names of message types and their fields.
- func (r *Registry) loadServices(file *File) error {
- glog.V(1).Infof("Loading services from %s", file.GetName())
- var svcs []*Service
- for _, sd := range file.GetService() {
- glog.V(2).Infof("Registering %s", sd.GetName())
- svc := &Service{
- File: file,
- ServiceDescriptorProto: sd,
- ForcePrefixedName: r.standalone,
- }
- for _, md := range sd.GetMethod() {
- glog.V(2).Infof("Processing %s.%s", sd.GetName(), md.GetName())
- opts, err := extractAPIOptions(md)
- if err != nil {
- glog.Errorf("Failed to extract HttpRule from %s.%s: %v", svc.GetName(), md.GetName(), err)
- return err
- }
- var optsList []*options.HttpRule
- if r.generateRPCMethods {
- defaultOpts, err := defaultAPIOptions(svc, md)
- if err != nil {
- glog.Errorf("Failed to generate default HttpRule from %s.%s: %v", svc.GetName(), md.GetName(), err)
- return err
- }
- optsList = append(optsList, defaultOpts)
- } else {
- optsList = r.LookupExternalHTTPRules((&Method{Service: svc, MethodDescriptorProto: md}).FQMN())
- if opts != nil {
- optsList = append(optsList, opts)
- }
- }
- if len(optsList) == 0 {
- if r.generateUnboundMethods {
- defaultOpts, err := defaultAPIOptions(svc, md)
- if err != nil {
- glog.Errorf("Failed to generate default HttpRule from %s.%s: %v", svc.GetName(), md.GetName(), err)
- return err
- }
- optsList = append(optsList, defaultOpts)
- } else {
- logFn := glog.V(1).Infof
- if r.warnOnUnboundMethods {
- logFn = glog.Warningf
- }
- logFn("No HttpRule found for method: %s.%s", svc.GetName(), md.GetName())
- }
- }
- meth, err := r.newMethod(svc, md, optsList)
- if err != nil {
- return err
- }
- svc.Methods = append(svc.Methods, meth)
- }
- if len(svc.Methods) == 0 {
- continue
- }
- glog.V(2).Infof("Registered %s with %d method(s)", svc.GetName(), len(svc.Methods))
- svcs = append(svcs, svc)
- }
- file.Services = svcs
- return nil
- }
- func (r *Registry) newMethod(svc *Service, md *descriptorpb.MethodDescriptorProto, optsList []*options.HttpRule) (*Method, error) {
- requestType, err := r.LookupMsg(svc.File.GetPackage(), md.GetInputType())
- if err != nil {
- return nil, err
- }
- responseType, err := r.LookupMsg(svc.File.GetPackage(), md.GetOutputType())
- if err != nil {
- return nil, err
- }
- meth := &Method{
- Service: svc,
- MethodDescriptorProto: md,
- RequestType: requestType,
- ResponseType: responseType,
- }
- newBinding := func(opts *options.HttpRule, idx int) (*Binding, error) {
- var (
- httpMethod string
- pathTemplate string
- )
- switch {
- case opts.GetGet() != "":
- httpMethod = "GET"
- pathTemplate = opts.GetGet()
- if opts.Body != "" {
- return nil, fmt.Errorf("must not set request body when http method is GET: %s", md.GetName())
- }
- case opts.GetPut() != "":
- httpMethod = "PUT"
- pathTemplate = opts.GetPut()
- case opts.GetPost() != "":
- httpMethod = "POST"
- pathTemplate = opts.GetPost()
- case opts.GetDelete() != "":
- httpMethod = "DELETE"
- pathTemplate = opts.GetDelete()
- if opts.Body != "" && !r.allowDeleteBody {
- return nil, fmt.Errorf("must not set request body when http method is DELETE except allow_delete_body option is true: %s", md.GetName())
- }
- case opts.GetPatch() != "":
- httpMethod = "PATCH"
- pathTemplate = opts.GetPatch()
- case opts.GetCustom() != nil:
- custom := opts.GetCustom()
- httpMethod = custom.Kind
- pathTemplate = custom.Path
- default:
- glog.V(1).Infof("No pattern specified in google.api.HttpRule: %s", md.GetName())
- return nil, nil
- }
- parsed, err := httprule.Parse(pathTemplate)
- if err != nil {
- return nil, err
- }
- tmpl := parsed.Compile()
- if md.GetClientStreaming() && len(tmpl.Fields) > 0 {
- return nil, fmt.Errorf("cannot use path parameter in client streaming")
- }
- b := &Binding{
- Method: meth,
- Index: idx,
- PathTmpl: tmpl,
- HTTPMethod: httpMethod,
- }
- for _, f := range tmpl.Fields {
- param, err := r.newParam(meth, f)
- if err != nil {
- return nil, err
- }
- b.PathParams = append(b.PathParams, param)
- }
- // TODO(yugui) Handle query params
- b.Body, err = r.newBody(meth, opts.Body)
- if err != nil {
- return nil, err
- }
- b.ResponseBody, err = r.newResponse(meth, opts.ResponseBody)
- if err != nil {
- return nil, err
- }
- return b, nil
- }
- applyOpts := func(opts *options.HttpRule) error {
- b, err := newBinding(opts, len(meth.Bindings))
- if err != nil {
- return err
- }
- if b != nil {
- meth.Bindings = append(meth.Bindings, b)
- }
- for _, additional := range opts.GetAdditionalBindings() {
- if len(additional.AdditionalBindings) > 0 {
- return fmt.Errorf("additional_binding in additional_binding not allowed: %s.%s", svc.GetName(), meth.GetName())
- }
- b, err := newBinding(additional, len(meth.Bindings))
- if err != nil {
- return err
- }
- meth.Bindings = append(meth.Bindings, b)
- }
- return nil
- }
- for _, opts := range optsList {
- if err := applyOpts(opts); err != nil {
- return nil, err
- }
- }
- return meth, nil
- }
- func extractAPIOptions(meth *descriptorpb.MethodDescriptorProto) (*options.HttpRule, error) {
- if meth.Options == nil {
- return nil, nil
- }
- if !proto.HasExtension(meth.Options, options.E_Http) {
- return nil, nil
- }
- ext := proto.GetExtension(meth.Options, options.E_Http)
- opts, ok := ext.(*options.HttpRule)
- if !ok {
- return nil, fmt.Errorf("extension is %T; want an HttpRule", ext)
- }
- return opts, nil
- }
- func defaultAPIOptions(svc *Service, md *descriptorpb.MethodDescriptorProto) (*options.HttpRule, error) {
- // FQSN prefixes the service's full name with a '.', e.g.: '.example.ExampleService'
- fqsn := strings.TrimPrefix(svc.FQSN(), ".")
- // This generates an HttpRule that matches the gRPC mapping to HTTP/2 described in
- // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests
- // i.e.:
- // * method is POST
- // * path is "/<service name>/<method name>"
- // * body should contain the serialized request message
- rule := &options.HttpRule{
- Pattern: &options.HttpRule_Post{
- Post: fmt.Sprintf("/%s/%s", fqsn, md.GetName()),
- },
- Body: "*",
- }
- return rule, nil
- }
- func (r *Registry) newParam(meth *Method, path string) (Parameter, error) {
- msg := meth.RequestType
- fields, err := r.resolveFieldPath(msg, path, true)
- if err != nil {
- return Parameter{}, err
- }
- l := len(fields)
- if l == 0 {
- return Parameter{}, fmt.Errorf("invalid field access list for %s", path)
- }
- target := fields[l-1].Target
- switch target.GetType() {
- case descriptorpb.FieldDescriptorProto_TYPE_MESSAGE, descriptorpb.FieldDescriptorProto_TYPE_GROUP:
- glog.V(2).Infoln("found aggregate type:", target, target.TypeName)
- if IsWellKnownType(*target.TypeName) {
- glog.V(2).Infoln("found well known aggregate type:", target)
- } else {
- return Parameter{}, fmt.Errorf("%s.%s: %s is a protobuf message type. Protobuf message types cannot be used as path parameters, use a scalar value type (such as string) instead", meth.Service.GetName(), meth.GetName(), path)
- }
- }
- return Parameter{
- FieldPath: FieldPath(fields),
- Method: meth,
- Target: fields[l-1].Target,
- }, nil
- }
- func (r *Registry) newBody(meth *Method, path string) (*Body, error) {
- msg := meth.RequestType
- switch path {
- case "":
- return nil, nil
- case "*":
- return &Body{FieldPath: nil}, nil
- }
- fields, err := r.resolveFieldPath(msg, path, false)
- if err != nil {
- return nil, err
- }
- return &Body{FieldPath: FieldPath(fields)}, nil
- }
- func (r *Registry) newResponse(meth *Method, path string) (*Body, error) {
- msg := meth.ResponseType
- switch path {
- case "", "*":
- return nil, nil
- }
- fields, err := r.resolveFieldPath(msg, path, false)
- if err != nil {
- return nil, err
- }
- return &Body{FieldPath: FieldPath(fields)}, nil
- }
- // lookupField looks up a field named "name" within "msg".
- // It returns nil if no such field found.
- func lookupField(msg *Message, name string) *Field {
- for _, f := range msg.Fields {
- if f.GetName() == name {
- return f
- }
- }
- return nil
- }
- // resolveFieldPath resolves "path" into a list of fieldDescriptor, starting from "msg".
- func (r *Registry) resolveFieldPath(msg *Message, path string, isPathParam bool) ([]FieldPathComponent, error) {
- if path == "" {
- return nil, nil
- }
- root := msg
- var result []FieldPathComponent
- for i, c := range strings.Split(path, ".") {
- if i > 0 {
- f := result[i-1].Target
- switch f.GetType() {
- case descriptorpb.FieldDescriptorProto_TYPE_MESSAGE, descriptorpb.FieldDescriptorProto_TYPE_GROUP:
- var err error
- msg, err = r.LookupMsg(msg.FQMN(), f.GetTypeName())
- if err != nil {
- return nil, err
- }
- default:
- return nil, fmt.Errorf("not an aggregate type: %s in %s", f.GetName(), path)
- }
- }
- glog.V(2).Infof("Lookup %s in %s", c, msg.FQMN())
- f := lookupField(msg, c)
- if f == nil {
- return nil, fmt.Errorf("no field %q found in %s", path, root.GetName())
- }
- if !(isPathParam || r.allowRepeatedFieldsInBody) && f.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_REPEATED {
- return nil, fmt.Errorf("repeated field not allowed in field path: %s in %s", f.GetName(), path)
- }
- if isPathParam && f.GetProto3Optional() {
- return nil, fmt.Errorf("optional field not allowed in field path: %s in %s", f.GetName(), path)
- }
- result = append(result, FieldPathComponent{Name: c, Target: f})
- }
- return result, nil
- }
|