-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
-
139
-
140
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174
-
175
-
176
-
177
-
178
-
179
-
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
-
210
-
211
-
212
-
213
-
214
-
215
-
216
-
217
-
218
-
219
-
220
-
221
-
222
-
223
-
224
-
225
-
226
-
227
-
228
-
229
-
230
-
231
-
232
-
233
-
234
-
235
-
236
-
237
-
238
-
239
-
240
-
241
-
242
-
243
-
244
-
245
-
246
-
247
-
248
-
249
-
250
-
251
-
252
-
253
-
254
-
255
-
256
-
257
-
258
-
259
-
260
-
261
-
262
-
263
-
264
-
265
-
266
-
267
-
268
-
269
-
270
-
271
-
272
-
273
-
274
-
275
-
276
-
277
-
278
-
279
-
280
-
281
-
282
-
283
-
284
-
285
-
286
-
287
-
288
-
289
-
290
-
291
-
292
-
293
-
294
-
295
-
296
-
297
-
298
-
299
-
300
-
301
-
302
-
303
-
304
-
305
-
306
-
307
-
308
-
309
-
310
-
311
-
312
-
313
-
314
-
315
-
316
-
317
-
318
-
319
-
320
-
321
-
322
-
323
-
324
-
325
-
326
-
327
-
328
-
329
-
330
-
331
-
332
-
333
-
334
-
335
-
336
-
337
-
338
-
339
-
340
-
341
-
342
-
343
-
344
-
345
-
346
-
347
-
348
-
349
-
350
-
351
-
352
-
353
-
354
-
355
-
356
-
357
-
358
-
359
-
360
-
361
-
362
-
363
-
364
-
365
-
366
-
367
-
368
-
369
-
370
-
371
-
372
-
373
-
374
-
375
-
376
-
377
-
378
-
379
-
380
-
381
-
382
-
383
-
384
-
385
-
386
-
387
-
388
-
389
-
390
-
391
-
392
-
393
-
394
-
395
-
396
-
397
-
398
-
399
-
400
-
401
-
402
-
403
-
404
-
405
-
406
-
407
-
408
-
409
-
410
-
411
-
412
-
413
-
414
-
415
-
416
-
417
-
418
-
419
-
420
-
421
-
422
-
423
-
424
-
425
-
426
-
427
-
428
-
429
-
430
-
431
-
432
-
433
-
434
-
435
-
436
-
437
-
438
-
439
-
440
-
441
-
442
-
443
-
444
-
445
-
446
-
447
-
448
-
449
-
450
-
451
-
452
-
453
-
454
-
455
-
456
-
457
-
458
-
459
-
460
-
461
-
462
-
463
-
464
-
465
-
466
-
467
-
468
-
469
-
470
-
471
-
472
-
473
-
474
-
475
-
476
-
477
-
478
-
479
-
480
-
481
-
482
-
483
-
484
-
485
-
486
-
487
-
488
-
489
-
490
-
491
-
492
-
493
-
494
-
495
-
496
-
497
-
498
-
499
-
500
-
501
-
502
-
503
-
504
-
505
-
506
-
507
-
508
-
509
-
510
-
511
-
512
-
513
-
514
-
515
-
516
-
517
-
518
-
519
-
520
-
521
-
522
-
523
-
524
-
525
-
526
-
527
-
528
-
529
-
530
-
531
-
532
-
533
-
534
-
535
-
536
-
537
-
538
-
539
-
540
-
541
-
542
-
543
-
544
-
545
-
546
-
547
-
548
-
549
-
550
-
551
-
552
-
553
-
554
-
555
-
556
-
557
-
558
-
559
-
560
-
561
-
562
-
563
-
564
-
565
-
566
-
567
-
568
-
569
-
570
-
571
-
572
-
573
-
574
-
575
-
576
-
577
-
578
-
579
-
580
-
581
-
582
-
583
-
584
-
585
-
586
-
587
-
588
-
589
-
590
-
591
-
592
-
593
-
594
-
595
-
596
-
597
-
598
-
599
-
600
-
601
-
602
-
603
-
604
-
605
-
606
-
607
-
608
-
609
-
610
-
611
-
612
-
613
-
614
-
615
-
616
-
617
-
618
-
619
-
620
-
621
-
622
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package middleware
import (
stdContext "context"
"fmt"
"net/http"
"strings"
"sync"
"github.com/go-openapi/analysis"
"github.com/go-openapi/errors"
"github.com/go-openapi/loads"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/logger"
"github.com/go-openapi/runtime/middleware/untyped"
"github.com/go-openapi/runtime/security"
)
// Debug when true turns on verbose logging
var Debug = logger.DebugEnabled()
var Logger logger.Logger = logger.StandardLogger{}
func debugLog(format string, args ...interface{}) {
if Debug {
Logger.Printf(format, args...)
}
}
// A Builder can create middlewares
type Builder func(http.Handler) http.Handler
// PassthroughBuilder returns the handler, aka the builder identity function
func PassthroughBuilder(handler http.Handler) http.Handler { return handler }
// RequestBinder is an interface for types to implement
// when they want to be able to bind from a request
type RequestBinder interface {
BindRequest(*http.Request, *MatchedRoute) error
}
// Responder is an interface for types to implement
// when they want to be considered for writing HTTP responses
type Responder interface {
WriteResponse(http.ResponseWriter, runtime.Producer)
}
// ResponderFunc wraps a func as a Responder interface
type ResponderFunc func(http.ResponseWriter, runtime.Producer)
// WriteResponse writes to the response
func (fn ResponderFunc) WriteResponse(rw http.ResponseWriter, pr runtime.Producer) {
fn(rw, pr)
}
// Context is a type safe wrapper around an untyped request context
// used throughout to store request context with the standard context attached
// to the http.Request
type Context struct {
spec *loads.Document
analyzer *analysis.Spec
api RoutableAPI
router Router
}
type routableUntypedAPI struct {
api *untyped.API
hlock *sync.Mutex
handlers map[string]map[string]http.Handler
defaultConsumes string
defaultProduces string
}
func newRoutableUntypedAPI(spec *loads.Document, api *untyped.API, context *Context) *routableUntypedAPI {
var handlers map[string]map[string]http.Handler
if spec == nil || api == nil {
return nil
}
analyzer := analysis.New(spec.Spec())
for method, hls := range analyzer.Operations() {
um := strings.ToUpper(method)
for path, op := range hls {
schemes := analyzer.SecurityRequirementsFor(op)
if oh, ok := api.OperationHandlerFor(method, path); ok {
if handlers == nil {
handlers = make(map[string]map[string]http.Handler)
}
if b, ok := handlers[um]; !ok || b == nil {
handlers[um] = make(map[string]http.Handler)
}
var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// lookup route info in the context
route, rCtx, _ := context.RouteInfo(r)
if rCtx != nil {
r = rCtx
}
// bind and validate the request using reflection
var bound interface{}
var validation error
bound, r, validation = context.BindAndValidate(r, route)
if validation != nil {
context.Respond(w, r, route.Produces, route, validation)
return
}
// actually handle the request
result, err := oh.Handle(bound)
if err != nil {
// respond with failure
context.Respond(w, r, route.Produces, route, err)
return
}
// respond with success
context.Respond(w, r, route.Produces, route, result)
})
if len(schemes) > 0 {
handler = newSecureAPI(context, handler)
}
handlers[um][path] = handler
}
}
}
return &routableUntypedAPI{
api: api,
hlock: new(sync.Mutex),
handlers: handlers,
defaultProduces: api.DefaultProduces,
defaultConsumes: api.DefaultConsumes,
}
}
func (r *routableUntypedAPI) HandlerFor(method, path string) (http.Handler, bool) {
r.hlock.Lock()
paths, ok := r.handlers[strings.ToUpper(method)]
if !ok {
r.hlock.Unlock()
return nil, false
}
handler, ok := paths[path]
r.hlock.Unlock()
return handler, ok
}
func (r *routableUntypedAPI) ServeErrorFor(operationID string) func(http.ResponseWriter, *http.Request, error) {
return r.api.ServeError
}
func (r *routableUntypedAPI) ConsumersFor(mediaTypes []string) map[string]runtime.Consumer {
return r.api.ConsumersFor(mediaTypes)
}
func (r *routableUntypedAPI) ProducersFor(mediaTypes []string) map[string]runtime.Producer {
return r.api.ProducersFor(mediaTypes)
}
func (r *routableUntypedAPI) AuthenticatorsFor(schemes map[string]spec.SecurityScheme) map[string]runtime.Authenticator {
return r.api.AuthenticatorsFor(schemes)
}
func (r *routableUntypedAPI) Authorizer() runtime.Authorizer {
return r.api.Authorizer()
}
func (r *routableUntypedAPI) Formats() strfmt.Registry {
return r.api.Formats()
}
func (r *routableUntypedAPI) DefaultProduces() string {
return r.defaultProduces
}
func (r *routableUntypedAPI) DefaultConsumes() string {
return r.defaultConsumes
}
// NewRoutableContext creates a new context for a routable API
func NewRoutableContext(spec *loads.Document, routableAPI RoutableAPI, routes Router) *Context {
var an *analysis.Spec
if spec != nil {
an = analysis.New(spec.Spec())
}
ctx := &Context{spec: spec, api: routableAPI, analyzer: an, router: routes}
return ctx
}
// NewContext creates a new context wrapper
func NewContext(spec *loads.Document, api *untyped.API, routes Router) *Context {
var an *analysis.Spec
if spec != nil {
an = analysis.New(spec.Spec())
}
ctx := &Context{spec: spec, analyzer: an}
ctx.api = newRoutableUntypedAPI(spec, api, ctx)
ctx.router = routes
return ctx
}
// Serve serves the specified spec with the specified api registrations as a http.Handler
func Serve(spec *loads.Document, api *untyped.API) http.Handler {
return ServeWithBuilder(spec, api, PassthroughBuilder)
}
// ServeWithBuilder serves the specified spec with the specified api registrations as a http.Handler that is decorated
// by the Builder
func ServeWithBuilder(spec *loads.Document, api *untyped.API, builder Builder) http.Handler {
context := NewContext(spec, api, nil)
return context.APIHandler(builder)
}
type contextKey int8
const (
_ contextKey = iota
ctxContentType
ctxResponseFormat
ctxMatchedRoute
ctxBoundParams
ctxSecurityPrincipal
ctxSecurityScopes
)
// MatchedRouteFrom request context value.
func MatchedRouteFrom(req *http.Request) *MatchedRoute {
mr := req.Context().Value(ctxMatchedRoute)
if mr == nil {
return nil
}
if res, ok := mr.(*MatchedRoute); ok {
return res
}
return nil
}
// SecurityPrincipalFrom request context value.
func SecurityPrincipalFrom(req *http.Request) interface{} {
return req.Context().Value(ctxSecurityPrincipal)
}
// SecurityScopesFrom request context value.
func SecurityScopesFrom(req *http.Request) []string {
rs := req.Context().Value(ctxSecurityScopes)
if res, ok := rs.([]string); ok {
return res
}
return nil
}
type contentTypeValue struct {
MediaType string
Charset string
}
// BasePath returns the base path for this API
func (c *Context) BasePath() string {
return c.spec.BasePath()
}
// RequiredProduces returns the accepted content types for responses
func (c *Context) RequiredProduces() []string {
return c.analyzer.RequiredProduces()
}
// BindValidRequest binds a params object to a request but only when the request is valid
// if the request is not valid an error will be returned
func (c *Context) BindValidRequest(request *http.Request, route *MatchedRoute, binder RequestBinder) error {
var res []error
var requestContentType string
// check and validate content type, select consumer
if runtime.HasBody(request) {
ct, _, err := runtime.ContentType(request.Header)
if err != nil {
res = append(res, err)
} else {
if err := validateContentType(route.Consumes, ct); err != nil {
res = append(res, err)
}
if len(res) == 0 {
cons, ok := route.Consumers[ct]
if !ok {
res = append(res, errors.New(500, "no consumer registered for %s", ct))
} else {
route.Consumer = cons
requestContentType = ct
}
}
}
}
// check and validate the response format
if len(res) == 0 {
// if the route does not provide Produces and a default contentType could not be identified
// based on a body, typical for GET and DELETE requests, then default contentType to.
if len(route.Produces) == 0 && requestContentType == "" {
requestContentType = "*/*"
}
if str := NegotiateContentType(request, route.Produces, requestContentType); str == "" {
res = append(res, errors.InvalidResponseFormat(request.Header.Get(runtime.HeaderAccept), route.Produces))
}
}
// now bind the request with the provided binder
// it's assumed the binder will also validate the request and return an error if the
// request is invalid
if binder != nil && len(res) == 0 {
if err := binder.BindRequest(request, route); err != nil {
return err
}
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// ContentType gets the parsed value of a content type
// Returns the media type, its charset and a shallow copy of the request
// when its context doesn't contain the content type value, otherwise it returns
// the same request
// Returns the error that runtime.ContentType may retunrs.
func (c *Context) ContentType(request *http.Request) (string, string, *http.Request, error) {
var rCtx = request.Context()
if v, ok := rCtx.Value(ctxContentType).(*contentTypeValue); ok {
return v.MediaType, v.Charset, request, nil
}
mt, cs, err := runtime.ContentType(request.Header)
if err != nil {
return "", "", nil, err
}
rCtx = stdContext.WithValue(rCtx, ctxContentType, &contentTypeValue{mt, cs})
return mt, cs, request.WithContext(rCtx), nil
}
// LookupRoute looks a route up and returns true when it is found
func (c *Context) LookupRoute(request *http.Request) (*MatchedRoute, bool) {
if route, ok := c.router.Lookup(request.Method, request.URL.EscapedPath()); ok {
return route, ok
}
return nil, false
}
// RouteInfo tries to match a route for this request
// Returns the matched route, a shallow copy of the request if its context
// contains the matched router, otherwise the same request, and a bool to
// indicate if it the request matches one of the routes, if it doesn't
// then it returns false and nil for the other two return values
func (c *Context) RouteInfo(request *http.Request) (*MatchedRoute, *http.Request, bool) {
var rCtx = request.Context()
if v, ok := rCtx.Value(ctxMatchedRoute).(*MatchedRoute); ok {
return v, request, ok
}
if route, ok := c.LookupRoute(request); ok {
rCtx = stdContext.WithValue(rCtx, ctxMatchedRoute, route)
return route, request.WithContext(rCtx), ok
}
return nil, nil, false
}
// ResponseFormat negotiates the response content type
// Returns the response format and a shallow copy of the request if its context
// doesn't contain the response format, otherwise the same request
func (c *Context) ResponseFormat(r *http.Request, offers []string) (string, *http.Request) {
var rCtx = r.Context()
if v, ok := rCtx.Value(ctxResponseFormat).(string); ok {
debugLog("[%s %s] found response format %q in context", r.Method, r.URL.Path, v)
return v, r
}
format := NegotiateContentType(r, offers, "")
if format != "" {
debugLog("[%s %s] set response format %q in context", r.Method, r.URL.Path, format)
r = r.WithContext(stdContext.WithValue(rCtx, ctxResponseFormat, format))
}
debugLog("[%s %s] negotiated response format %q", r.Method, r.URL.Path, format)
return format, r
}
// AllowedMethods gets the allowed methods for the path of this request
func (c *Context) AllowedMethods(request *http.Request) []string {
return c.router.OtherMethods(request.Method, request.URL.EscapedPath())
}
// ResetAuth removes the current principal from the request context
func (c *Context) ResetAuth(request *http.Request) *http.Request {
rctx := request.Context()
rctx = stdContext.WithValue(rctx, ctxSecurityPrincipal, nil)
rctx = stdContext.WithValue(rctx, ctxSecurityScopes, nil)
return request.WithContext(rctx)
}
// Authorize authorizes the request
// Returns the principal object and a shallow copy of the request when its
// context doesn't contain the principal, otherwise the same request or an error
// (the last) if one of the authenticators returns one or an Unauthenticated error
func (c *Context) Authorize(request *http.Request, route *MatchedRoute) (interface{}, *http.Request, error) {
if route == nil || !route.HasAuth() {
return nil, nil, nil
}
var rCtx = request.Context()
if v := rCtx.Value(ctxSecurityPrincipal); v != nil {
return v, request, nil
}
applies, usr, err := route.Authenticators.Authenticate(request, route)
if !applies || err != nil || !route.Authenticators.AllowsAnonymous() && usr == nil {
if err != nil {
return nil, nil, err
}
return nil, nil, errors.Unauthenticated("invalid credentials")
}
if route.Authorizer != nil {
if err := route.Authorizer.Authorize(request, usr); err != nil {
if _, ok := err.(errors.Error); ok {
return nil, nil, err
}
return nil, nil, errors.New(http.StatusForbidden, err.Error())
}
}
rCtx = request.Context()
rCtx = stdContext.WithValue(rCtx, ctxSecurityPrincipal, usr)
rCtx = stdContext.WithValue(rCtx, ctxSecurityScopes, route.Authenticator.AllScopes())
return usr, request.WithContext(rCtx), nil
}
// BindAndValidate binds and validates the request
// Returns the validation map and a shallow copy of the request when its context
// doesn't contain the validation, otherwise it returns the same request or an
// CompositeValidationError error
func (c *Context) BindAndValidate(request *http.Request, matched *MatchedRoute) (interface{}, *http.Request, error) {
var rCtx = request.Context()
if v, ok := rCtx.Value(ctxBoundParams).(*validation); ok {
debugLog("got cached validation (valid: %t)", len(v.result) == 0)
if len(v.result) > 0 {
return v.bound, request, errors.CompositeValidationError(v.result...)
}
return v.bound, request, nil
}
result := validateRequest(c, request, matched)
rCtx = stdContext.WithValue(rCtx, ctxBoundParams, result)
request = request.WithContext(rCtx)
if len(result.result) > 0 {
return result.bound, request, errors.CompositeValidationError(result.result...)
}
debugLog("no validation errors found")
return result.bound, request, nil
}
// NotFound the default not found responder for when no route has been matched yet
func (c *Context) NotFound(rw http.ResponseWriter, r *http.Request) {
c.Respond(rw, r, []string{c.api.DefaultProduces()}, nil, errors.NotFound("not found"))
}
// Respond renders the response after doing some content negotiation
func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []string, route *MatchedRoute, data interface{}) {
debugLog("responding to %s %s with produces: %v", r.Method, r.URL.Path, produces)
offers := []string{}
for _, mt := range produces {
if mt != c.api.DefaultProduces() {
offers = append(offers, mt)
}
}
// the default producer is last so more specific producers take precedence
offers = append(offers, c.api.DefaultProduces())
debugLog("offers: %v", offers)
var format string
format, r = c.ResponseFormat(r, offers)
rw.Header().Set(runtime.HeaderContentType, format)
if resp, ok := data.(Responder); ok {
producers := route.Producers
prod, ok := producers[format]
if !ok {
prods := c.api.ProducersFor(normalizeOffers([]string{c.api.DefaultProduces()}))
pr, ok := prods[c.api.DefaultProduces()]
if !ok {
panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
}
prod = pr
}
resp.WriteResponse(rw, prod)
return
}
if err, ok := data.(error); ok {
if format == "" {
rw.Header().Set(runtime.HeaderContentType, runtime.JSONMime)
}
if realm := security.FailedBasicAuth(r); realm != "" {
rw.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", realm))
}
if route == nil || route.Operation == nil {
c.api.ServeErrorFor("")(rw, r, err)
return
}
c.api.ServeErrorFor(route.Operation.ID)(rw, r, err)
return
}
if route == nil || route.Operation == nil {
rw.WriteHeader(200)
if r.Method == "HEAD" {
return
}
producers := c.api.ProducersFor(normalizeOffers(offers))
prod, ok := producers[format]
if !ok {
panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
}
if err := prod.Produce(rw, data); err != nil {
panic(err) // let the recovery middleware deal with this
}
return
}
if _, code, ok := route.Operation.SuccessResponse(); ok {
rw.WriteHeader(code)
if code == 204 || r.Method == "HEAD" {
return
}
producers := route.Producers
prod, ok := producers[format]
if !ok {
if !ok {
prods := c.api.ProducersFor(normalizeOffers([]string{c.api.DefaultProduces()}))
pr, ok := prods[c.api.DefaultProduces()]
if !ok {
panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
}
prod = pr
}
}
if err := prod.Produce(rw, data); err != nil {
panic(err) // let the recovery middleware deal with this
}
return
}
c.api.ServeErrorFor(route.Operation.ID)(rw, r, errors.New(http.StatusInternalServerError, "can't produce response"))
}
func (c *Context) APIHandlerSwaggerUI(builder Builder) http.Handler {
b := builder
if b == nil {
b = PassthroughBuilder
}
var title string
sp := c.spec.Spec()
if sp != nil && sp.Info != nil && sp.Info.Title != "" {
title = sp.Info.Title
}
swaggerUIOpts := SwaggerUIOpts{
BasePath: c.BasePath(),
Title: title,
}
return Spec("", c.spec.Raw(), SwaggerUI(swaggerUIOpts, c.RoutesHandler(b)))
}
// APIHandler returns a handler to serve the API, this includes a swagger spec, router and the contract defined in the swagger spec
func (c *Context) APIHandler(builder Builder) http.Handler {
b := builder
if b == nil {
b = PassthroughBuilder
}
var title string
sp := c.spec.Spec()
if sp != nil && sp.Info != nil && sp.Info.Title != "" {
title = sp.Info.Title
}
redocOpts := RedocOpts{
BasePath: c.BasePath(),
Title: title,
}
return Spec("", c.spec.Raw(), Redoc(redocOpts, c.RoutesHandler(b)))
}
// RoutesHandler returns a handler to serve the API, just the routes and the contract defined in the swagger spec
func (c *Context) RoutesHandler(builder Builder) http.Handler {
b := builder
if b == nil {
b = PassthroughBuilder
}
return NewRouter(c, b(NewOperationExecutor(c)))
}