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
16
17
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
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
231 fqsn := strings.TrimPrefix(svc.FQSN(), ".")
232
233
234
235
236
237
238
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
308
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
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