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