mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-02 16:52:25 -06:00 
			
		
		
		
	
		
			
				
	
	
		
			722 lines
		
	
	
	
		
			22 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			722 lines
		
	
	
	
		
			22 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// 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"
 | 
						|
	"net/url"
 | 
						|
	"path"
 | 
						|
	"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()
 | 
						|
 | 
						|
// Logger is the standard libray logger used for printing debug messages
 | 
						|
var Logger logger.Logger = logger.StandardLogger{}
 | 
						|
 | 
						|
func debugLogfFunc(lg logger.Logger) func(string, ...any) {
 | 
						|
	if logger.DebugEnabled() {
 | 
						|
		if lg == nil {
 | 
						|
			return Logger.Debugf
 | 
						|
		}
 | 
						|
 | 
						|
		return lg.Debugf
 | 
						|
	}
 | 
						|
 | 
						|
	// muted logger
 | 
						|
	return func(_ string, _ ...any) {}
 | 
						|
}
 | 
						|
 | 
						|
// 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
 | 
						|
	debugLogf func(string, ...any) // a logging function to debug context and all components using it
 | 
						|
}
 | 
						|
 | 
						|
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(_ 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.
 | 
						|
//
 | 
						|
// If a nil Router is provided, the DefaultRouter (denco-based) will be used.
 | 
						|
func NewRoutableContext(spec *loads.Document, routableAPI RoutableAPI, routes Router) *Context {
 | 
						|
	var an *analysis.Spec
 | 
						|
	if spec != nil {
 | 
						|
		an = analysis.New(spec.Spec())
 | 
						|
	}
 | 
						|
 | 
						|
	return NewRoutableContextWithAnalyzedSpec(spec, an, routableAPI, routes)
 | 
						|
}
 | 
						|
 | 
						|
// NewRoutableContextWithAnalyzedSpec is like NewRoutableContext but takes as input an already analysed spec.
 | 
						|
//
 | 
						|
// If a nil Router is provided, the DefaultRouter (denco-based) will be used.
 | 
						|
func NewRoutableContextWithAnalyzedSpec(spec *loads.Document, an *analysis.Spec, routableAPI RoutableAPI, routes Router) *Context {
 | 
						|
	// Either there are no spec doc and analysis, or both of them.
 | 
						|
	if !((spec == nil && an == nil) || (spec != nil && an != nil)) {
 | 
						|
		panic(errors.New(http.StatusInternalServerError, "routable context requires either both spec doc and analysis, or none of them"))
 | 
						|
	}
 | 
						|
 | 
						|
	return &Context{
 | 
						|
		spec:      spec,
 | 
						|
		api:       routableAPI,
 | 
						|
		analyzer:  an,
 | 
						|
		router:    routes,
 | 
						|
		debugLogf: debugLogfFunc(nil),
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// NewContext creates a new context wrapper.
 | 
						|
//
 | 
						|
// If a nil Router is provided, the DefaultRouter (denco-based) will be used.
 | 
						|
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,
 | 
						|
		router:    routes,
 | 
						|
		debugLogf: debugLogfFunc(nil),
 | 
						|
	}
 | 
						|
	ctx.api = newRoutableUntypedAPI(spec, api, ctx)
 | 
						|
 | 
						|
	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()
 | 
						|
}
 | 
						|
 | 
						|
// SetLogger allows for injecting a logger to catch debug entries.
 | 
						|
//
 | 
						|
// The logger is enabled in DEBUG mode only.
 | 
						|
func (c *Context) SetLogger(lg logger.Logger) {
 | 
						|
	c.debugLogf = debugLogfFunc(lg)
 | 
						|
}
 | 
						|
 | 
						|
// 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 {
 | 
						|
			c.debugLogf("validating content type for %q against [%s]", ct, strings.Join(route.Consumes, ", "))
 | 
						|
			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 {
 | 
						|
		c.debugLogf("[%s %s] found response format %q in context", r.Method, r.URL.Path, v)
 | 
						|
		return v, r
 | 
						|
	}
 | 
						|
 | 
						|
	format := NegotiateContentType(r, offers, "")
 | 
						|
	if format != "" {
 | 
						|
		c.debugLogf("[%s %s] set response format %q in context", r.Method, r.URL.Path, format)
 | 
						|
		r = r.WithContext(stdContext.WithValue(rCtx, ctxResponseFormat, format))
 | 
						|
	}
 | 
						|
	c.debugLogf("[%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 {
 | 
						|
		c.debugLogf("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...)
 | 
						|
	}
 | 
						|
	c.debugLogf("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{}) {
 | 
						|
	c.debugLogf("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())
 | 
						|
	c.debugLogf("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
 | 
						|
		// producers contains keys with normalized format, if a format has MIME type parameter such as `text/plain; charset=utf-8`
 | 
						|
		// then you must provide `text/plain` to get the correct producer. HOWEVER, format here is not normalized.
 | 
						|
		prod, ok := producers[normalizeOffer(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, cantFindProducer(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(http.StatusOK)
 | 
						|
		if r.Method == http.MethodHead {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		producers := c.api.ProducersFor(normalizeOffers(offers))
 | 
						|
		prod, ok := producers[format]
 | 
						|
		if !ok {
 | 
						|
			panic(errors.New(http.StatusInternalServerError, cantFindProducer(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 == http.StatusNoContent || r.Method == http.MethodHead {
 | 
						|
			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, cantFindProducer(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"))
 | 
						|
}
 | 
						|
 | 
						|
// APIHandlerSwaggerUI returns a handler to serve the API.
 | 
						|
//
 | 
						|
// This handler includes a swagger spec, router and the contract defined in the swagger spec.
 | 
						|
//
 | 
						|
// A spec UI (SwaggerUI) is served at {API base path}/docs and the spec document at /swagger.json
 | 
						|
// (these can be modified with uiOptions).
 | 
						|
func (c *Context) APIHandlerSwaggerUI(builder Builder, opts ...UIOption) http.Handler {
 | 
						|
	b := builder
 | 
						|
	if b == nil {
 | 
						|
		b = PassthroughBuilder
 | 
						|
	}
 | 
						|
 | 
						|
	specPath, uiOpts, specOpts := c.uiOptionsForHandler(opts)
 | 
						|
	var swaggerUIOpts SwaggerUIOpts
 | 
						|
	fromCommonToAnyOptions(uiOpts, &swaggerUIOpts)
 | 
						|
 | 
						|
	return Spec(specPath, c.spec.Raw(), SwaggerUI(swaggerUIOpts, c.RoutesHandler(b)), specOpts...)
 | 
						|
}
 | 
						|
 | 
						|
// APIHandlerRapiDoc returns a handler to serve the API.
 | 
						|
//
 | 
						|
// This handler includes a swagger spec, router and the contract defined in the swagger spec.
 | 
						|
//
 | 
						|
// A spec UI (RapiDoc) is served at {API base path}/docs and the spec document at /swagger.json
 | 
						|
// (these can be modified with uiOptions).
 | 
						|
func (c *Context) APIHandlerRapiDoc(builder Builder, opts ...UIOption) http.Handler {
 | 
						|
	b := builder
 | 
						|
	if b == nil {
 | 
						|
		b = PassthroughBuilder
 | 
						|
	}
 | 
						|
 | 
						|
	specPath, uiOpts, specOpts := c.uiOptionsForHandler(opts)
 | 
						|
	var rapidocUIOpts RapiDocOpts
 | 
						|
	fromCommonToAnyOptions(uiOpts, &rapidocUIOpts)
 | 
						|
 | 
						|
	return Spec(specPath, c.spec.Raw(), RapiDoc(rapidocUIOpts, c.RoutesHandler(b)), specOpts...)
 | 
						|
}
 | 
						|
 | 
						|
// APIHandler returns a handler to serve the API.
 | 
						|
//
 | 
						|
// This handler includes a swagger spec, router and the contract defined in the swagger spec.
 | 
						|
//
 | 
						|
// A spec UI (Redoc) is served at {API base path}/docs and the spec document at /swagger.json
 | 
						|
// (these can be modified with uiOptions).
 | 
						|
func (c *Context) APIHandler(builder Builder, opts ...UIOption) http.Handler {
 | 
						|
	b := builder
 | 
						|
	if b == nil {
 | 
						|
		b = PassthroughBuilder
 | 
						|
	}
 | 
						|
 | 
						|
	specPath, uiOpts, specOpts := c.uiOptionsForHandler(opts)
 | 
						|
	var redocOpts RedocOpts
 | 
						|
	fromCommonToAnyOptions(uiOpts, &redocOpts)
 | 
						|
 | 
						|
	return Spec(specPath, c.spec.Raw(), Redoc(redocOpts, c.RoutesHandler(b)), specOpts...)
 | 
						|
}
 | 
						|
 | 
						|
func (c Context) uiOptionsForHandler(opts []UIOption) (string, uiOptions, []SpecOption) {
 | 
						|
	var title string
 | 
						|
	sp := c.spec.Spec()
 | 
						|
	if sp != nil && sp.Info != nil && sp.Info.Title != "" {
 | 
						|
		title = sp.Info.Title
 | 
						|
	}
 | 
						|
 | 
						|
	// default options (may be overridden)
 | 
						|
	optsForContext := []UIOption{
 | 
						|
		WithUIBasePath(c.BasePath()),
 | 
						|
		WithUITitle(title),
 | 
						|
	}
 | 
						|
	optsForContext = append(optsForContext, opts...)
 | 
						|
	uiOpts := uiOptionsWithDefaults(optsForContext)
 | 
						|
 | 
						|
	// If spec URL is provided, there is a non-default path to serve the spec.
 | 
						|
	// This makes sure that the UI middleware is aligned with the Spec middleware.
 | 
						|
	u, _ := url.Parse(uiOpts.SpecURL)
 | 
						|
	var specPath string
 | 
						|
	if u != nil {
 | 
						|
		specPath = u.Path
 | 
						|
	}
 | 
						|
 | 
						|
	pth, doc := path.Split(specPath)
 | 
						|
	if pth == "." {
 | 
						|
		pth = ""
 | 
						|
	}
 | 
						|
 | 
						|
	return pth, uiOpts, []SpecOption{WithSpecDocument(doc)}
 | 
						|
}
 | 
						|
 | 
						|
// 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)))
 | 
						|
}
 | 
						|
 | 
						|
func cantFindProducer(format string) string {
 | 
						|
	return "can't find a producer for " + format
 | 
						|
}
 |