...

Source file src/github.com/grpc-ecosystem/grpc-gateway/v2/internal/descriptor/services.go

Documentation: github.com/grpc-ecosystem/grpc-gateway/v2/internal/descriptor

     1  package descriptor
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/grpc-ecosystem/grpc-gateway/v2/internal/httprule"
     9  	options "google.golang.org/genproto/googleapis/api/annotations"
    10  	"google.golang.org/grpc/grpclog"
    11  	"google.golang.org/protobuf/proto"
    12  	"google.golang.org/protobuf/types/descriptorpb"
    13  )
    14  
    15  // loadServices registers services and their methods from "targetFile" to "r".
    16  // It must be called after loadFile is called for all files so that loadServices
    17  // can resolve names of message types and their fields.
    18  func (r *Registry) loadServices(file *File) error {
    19  	if grpclog.V(1) {
    20  		grpclog.Infof("Loading services from %s", file.GetName())
    21  	}
    22  	var svcs []*Service
    23  	for _, sd := range file.GetService() {
    24  		if grpclog.V(2) {
    25  			grpclog.Infof("Registering %s", sd.GetName())
    26  		}
    27  		svc := &Service{
    28  			File:                   file,
    29  			ServiceDescriptorProto: sd,
    30  			ForcePrefixedName:      r.standalone,
    31  		}
    32  		for _, md := range sd.GetMethod() {
    33  			if grpclog.V(2) {
    34  				grpclog.Infof("Processing %s.%s", sd.GetName(), md.GetName())
    35  			}
    36  			opts, err := extractAPIOptions(md)
    37  			if err != nil {
    38  				grpclog.Errorf("Failed to extract HttpRule from %s.%s: %v", svc.GetName(), md.GetName(), err)
    39  				return err
    40  			}
    41  			optsList := r.LookupExternalHTTPRules((&Method{Service: svc, MethodDescriptorProto: md}).FQMN())
    42  			if opts != nil {
    43  				optsList = append(optsList, opts)
    44  			}
    45  			if len(optsList) == 0 {
    46  				if r.generateUnboundMethods {
    47  					defaultOpts, err := defaultAPIOptions(svc, md)
    48  					if err != nil {
    49  						grpclog.Errorf("Failed to generate default HttpRule from %s.%s: %v", svc.GetName(), md.GetName(), err)
    50  						return err
    51  					}
    52  					optsList = append(optsList, defaultOpts)
    53  				} else {
    54  					if grpclog.V(1) {
    55  						logFn := grpclog.Infof
    56  						if r.warnOnUnboundMethods {
    57  							logFn = grpclog.Warningf
    58  						}
    59  						logFn("No HttpRule found for method: %s.%s", svc.GetName(), md.GetName())
    60  					}
    61  				}
    62  			}
    63  			meth, err := r.newMethod(svc, md, optsList)
    64  			if err != nil {
    65  				return err
    66  			}
    67  			svc.Methods = append(svc.Methods, meth)
    68  			r.meths[meth.FQMN()] = meth
    69  		}
    70  		if len(svc.Methods) == 0 {
    71  			continue
    72  		}
    73  		if grpclog.V(2) {
    74  			grpclog.Infof("Registered %s with %d method(s)", svc.GetName(), len(svc.Methods))
    75  		}
    76  		svcs = append(svcs, svc)
    77  	}
    78  	file.Services = svcs
    79  	return nil
    80  }
    81  
    82  func (r *Registry) newMethod(svc *Service, md *descriptorpb.MethodDescriptorProto, optsList []*options.HttpRule) (*Method, error) {
    83  	requestType, err := r.LookupMsg(svc.File.GetPackage(), md.GetInputType())
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	responseType, err := r.LookupMsg(svc.File.GetPackage(), md.GetOutputType())
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  	meth := &Method{
    92  		Service:               svc,
    93  		MethodDescriptorProto: md,
    94  		RequestType:           requestType,
    95  		ResponseType:          responseType,
    96  	}
    97  
    98  	newBinding := func(opts *options.HttpRule, idx int) (*Binding, error) {
    99  		var (
   100  			httpMethod   string
   101  			pathTemplate string
   102  		)
   103  		switch {
   104  		case opts.GetGet() != "":
   105  			httpMethod = "GET"
   106  			pathTemplate = opts.GetGet()
   107  			if opts.Body != "" {
   108  				return nil, fmt.Errorf("must not set request body when http method is GET: %s", md.GetName())
   109  			}
   110  
   111  		case opts.GetPut() != "":
   112  			httpMethod = "PUT"
   113  			pathTemplate = opts.GetPut()
   114  
   115  		case opts.GetPost() != "":
   116  			httpMethod = "POST"
   117  			pathTemplate = opts.GetPost()
   118  
   119  		case opts.GetDelete() != "":
   120  			httpMethod = "DELETE"
   121  			pathTemplate = opts.GetDelete()
   122  			if opts.Body != "" && !r.allowDeleteBody {
   123  				return nil, fmt.Errorf("must not set request body when http method is DELETE except allow_delete_body option is true: %s", md.GetName())
   124  			}
   125  
   126  		case opts.GetPatch() != "":
   127  			httpMethod = "PATCH"
   128  			pathTemplate = opts.GetPatch()
   129  
   130  		case opts.GetCustom() != nil:
   131  			custom := opts.GetCustom()
   132  			httpMethod = custom.Kind
   133  			pathTemplate = custom.Path
   134  
   135  		default:
   136  			if grpclog.V(1) {
   137  				grpclog.Infof("No pattern specified in google.api.HttpRule: %s", md.GetName())
   138  			}
   139  			return nil, nil
   140  		}
   141  
   142  		parsed, err := httprule.Parse(pathTemplate)
   143  		if err != nil {
   144  			return nil, err
   145  		}
   146  		tmpl := parsed.Compile()
   147  
   148  		if md.GetClientStreaming() && len(tmpl.Fields) > 0 {
   149  			return nil, errors.New("cannot use path parameter in client streaming")
   150  		}
   151  
   152  		b := &Binding{
   153  			Method:     meth,
   154  			Index:      idx,
   155  			PathTmpl:   tmpl,
   156  			HTTPMethod: httpMethod,
   157  		}
   158  
   159  		for _, f := range tmpl.Fields {
   160  			param, err := r.newParam(meth, f)
   161  			if err != nil {
   162  				return nil, err
   163  			}
   164  			b.PathParams = append(b.PathParams, param)
   165  		}
   166  
   167  		// TODO(yugui) Handle query params
   168  
   169  		b.Body, err = r.newBody(meth, opts.Body)
   170  		if err != nil {
   171  			return nil, err
   172  		}
   173  
   174  		b.ResponseBody, err = r.newResponse(meth, opts.ResponseBody)
   175  		if err != nil {
   176  			return nil, err
   177  		}
   178  
   179  		return b, nil
   180  	}
   181  
   182  	applyOpts := func(opts *options.HttpRule) error {
   183  		b, err := newBinding(opts, len(meth.Bindings))
   184  		if err != nil {
   185  			return err
   186  		}
   187  
   188  		if b != nil {
   189  			meth.Bindings = append(meth.Bindings, b)
   190  		}
   191  		for _, additional := range opts.GetAdditionalBindings() {
   192  			if len(additional.AdditionalBindings) > 0 {
   193  				return fmt.Errorf("additional_binding in additional_binding not allowed: %s.%s", svc.GetName(), meth.GetName())
   194  			}
   195  			b, err := newBinding(additional, len(meth.Bindings))
   196  			if err != nil {
   197  				return err
   198  			}
   199  			meth.Bindings = append(meth.Bindings, b)
   200  		}
   201  
   202  		return nil
   203  	}
   204  
   205  	for _, opts := range optsList {
   206  		if err := applyOpts(opts); err != nil {
   207  			return nil, err
   208  		}
   209  	}
   210  
   211  	return meth, nil
   212  }
   213  
   214  func extractAPIOptions(meth *descriptorpb.MethodDescriptorProto) (*options.HttpRule, error) {
   215  	if meth.Options == nil {
   216  		return nil, nil
   217  	}
   218  	if !proto.HasExtension(meth.Options, options.E_Http) {
   219  		return nil, nil
   220  	}
   221  	ext := proto.GetExtension(meth.Options, options.E_Http)
   222  	opts, ok := ext.(*options.HttpRule)
   223  	if !ok {
   224  		return nil, fmt.Errorf("extension is %T; want an HttpRule", ext)
   225  	}
   226  	return opts, nil
   227  }
   228  
   229  func defaultAPIOptions(svc *Service, md *descriptorpb.MethodDescriptorProto) (*options.HttpRule, error) {
   230  	// FQSN prefixes the service's full name with a '.', e.g.: '.example.ExampleService'
   231  	fqsn := strings.TrimPrefix(svc.FQSN(), ".")
   232  
   233  	// This generates an HttpRule that matches the gRPC mapping to HTTP/2 described in
   234  	// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests
   235  	// i.e.:
   236  	//   * method is POST
   237  	//   * path is "/<service name>/<method name>"
   238  	//   * body should contain the serialized request message
   239  	rule := &options.HttpRule{
   240  		Pattern: &options.HttpRule_Post{
   241  			Post: fmt.Sprintf("/%s/%s", fqsn, md.GetName()),
   242  		},
   243  		Body: "*",
   244  	}
   245  	return rule, nil
   246  }
   247  
   248  func (r *Registry) newParam(meth *Method, path string) (Parameter, error) {
   249  	msg := meth.RequestType
   250  	fields, err := r.resolveFieldPath(msg, path, true)
   251  	if err != nil {
   252  		return Parameter{}, err
   253  	}
   254  	l := len(fields)
   255  	if l == 0 {
   256  		return Parameter{}, fmt.Errorf("invalid field access list for %s", path)
   257  	}
   258  	target := fields[l-1].Target
   259  	switch target.GetType() {
   260  	case descriptorpb.FieldDescriptorProto_TYPE_MESSAGE, descriptorpb.FieldDescriptorProto_TYPE_GROUP:
   261  		if grpclog.V(2) {
   262  			grpclog.Infoln("found aggregate type:", target, target.TypeName)
   263  		}
   264  		if IsWellKnownType(*target.TypeName) {
   265  			if grpclog.V(2) {
   266  				grpclog.Infoln("found well known aggregate type:", target)
   267  			}
   268  		} else {
   269  			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)
   270  		}
   271  	}
   272  	return Parameter{
   273  		FieldPath: FieldPath(fields),
   274  		Method:    meth,
   275  		Target:    fields[l-1].Target,
   276  	}, nil
   277  }
   278  
   279  func (r *Registry) newBody(meth *Method, path string) (*Body, error) {
   280  	switch path {
   281  	case "":
   282  		return nil, nil
   283  	case "*":
   284  		return &Body{FieldPath: nil}, nil
   285  	}
   286  	msg := meth.RequestType
   287  	fields, err := r.resolveFieldPath(msg, path, false)
   288  	if err != nil {
   289  		return nil, err
   290  	}
   291  	return &Body{FieldPath: FieldPath(fields)}, nil
   292  }
   293  
   294  func (r *Registry) newResponse(meth *Method, path string) (*Body, error) {
   295  	msg := meth.ResponseType
   296  	switch path {
   297  	case "", "*":
   298  		return nil, nil
   299  	}
   300  	fields, err := r.resolveFieldPath(msg, path, false)
   301  	if err != nil {
   302  		return nil, err
   303  	}
   304  	return &Body{FieldPath: FieldPath(fields)}, nil
   305  }
   306  
   307  // lookupField looks up a field named "name" within "msg".
   308  // It returns nil if no such field found.
   309  func lookupField(msg *Message, name string) *Field {
   310  	for _, f := range msg.Fields {
   311  		if f.GetName() == name {
   312  			return f
   313  		}
   314  	}
   315  	return nil
   316  }
   317  
   318  // resolveFieldPath resolves "path" into a list of fieldDescriptor, starting from "msg".
   319  func (r *Registry) resolveFieldPath(msg *Message, path string, isPathParam bool) ([]FieldPathComponent, error) {
   320  	if path == "" {
   321  		return nil, nil
   322  	}
   323  
   324  	root := msg
   325  	var result []FieldPathComponent
   326  	for i, c := range strings.Split(path, ".") {
   327  		if i > 0 {
   328  			f := result[i-1].Target
   329  			switch f.GetType() {
   330  			case descriptorpb.FieldDescriptorProto_TYPE_MESSAGE, descriptorpb.FieldDescriptorProto_TYPE_GROUP:
   331  				var err error
   332  				msg, err = r.LookupMsg(msg.FQMN(), f.GetTypeName())
   333  				if err != nil {
   334  					return nil, err
   335  				}
   336  			default:
   337  				return nil, fmt.Errorf("not an aggregate type: %s in %s", f.GetName(), path)
   338  			}
   339  		}
   340  
   341  		if grpclog.V(2) {
   342  			grpclog.Infof("Lookup %s in %s", c, msg.FQMN())
   343  		}
   344  		f := lookupField(msg, c)
   345  		if f == nil {
   346  			return nil, fmt.Errorf("no field %q found in %s", path, root.GetName())
   347  		}
   348  		if isPathParam && f.GetProto3Optional() {
   349  			return nil, fmt.Errorf("optional field not allowed in field path: %s in %s", f.GetName(), path)
   350  		}
   351  		result = append(result, FieldPathComponent{Name: c, Target: f})
   352  	}
   353  	return result, nil
   354  }
   355  

View as plain text