File size: 4,300 Bytes
1f10f31 | 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 | package oasmiddleware
import (
"bytes"
"io"
"net/http"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/getkin/kin-openapi/routers"
)
type (
RequestNotFoundHookFunc = func(error, http.ResponseWriter, *http.Request) bool
RequestValidationErrorFunc = func(error, http.ResponseWriter, *http.Request) bool
ResponseValidationFunc = func(error, *http.Request)
)
// ValidateRequestOption provides the hook functions and the openapi3filter
// option to be passed in to the underlying library
type ValidateRequestOption struct {
// RouteNotFoundHook is called when the route is not found at the spec level
// if the hook returns `true` the request flow is stopped
RouteNotFoundHook RequestNotFoundHookFunc
// RouteValidationErrorHook is called when the route parameters or body are
// not validated. if the hook returns `true` the request flow is stopped
RouteValidationErrorHook RequestValidationErrorFunc
// FilterOptions are the openapi3filter option to pass to the underlying lib
FilterOptions *openapi3filter.Options
}
// ValidateRequest is the middleware to be used to validate the request to the spec
// passed in for the validation router
func ValidateRequest(validationRouter routers.Router, opts ValidateRequestOption) func(h http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
skipServe := false
route, pathParams, err := validationRouter.FindRoute(r.WithContext(ctx))
if err != nil {
if opts.RouteNotFoundHook != nil {
skipServe = opts.RouteNotFoundHook(err, w, r)
}
} else {
requestValidationInput := &openapi3filter.RequestValidationInput{
Request: r,
PathParams: pathParams,
Route: route,
Options: opts.FilterOptions,
}
if err := openapi3filter.ValidateRequest(ctx, requestValidationInput); err != nil {
if opts.RouteValidationErrorHook != nil {
skipServe = opts.RouteValidationErrorHook(err, w, r)
}
}
}
if !skipServe {
h.ServeHTTP(w, r)
}
}
return http.HandlerFunc(fn)
}
}
// ValidateResponseOption provides the hook function and the openapi3filter
// option to be passed in to the underlying library
type ValidateResponseOption struct {
// ResponseValidationErrorHook is called when the route response body is not validated
ResponseValidationErrorHook ResponseValidationFunc
// RouteFilterHook is called after the route is found; return false to skip validation for that route.
// If nil, all matched routes are validated.
RouteFilterHook func(*routers.Route) bool
// FilterOptions are the openapi3filter option to pass to the underlying lib
FilterOptions *openapi3filter.Options
}
// ValidateResponse is the middleware to be used to validate the response to the spec
// passed in for the validation router
func ValidateResponse(validationRouter routers.Router, opts ValidateResponseOption) func(h http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
route, pathParams, err := validationRouter.FindRoute(r)
if err != nil {
h.ServeHTTP(w, r)
if opts.ResponseValidationErrorHook != nil {
opts.ResponseValidationErrorHook(err, r)
}
} else {
if opts.RouteFilterHook != nil && !opts.RouteFilterHook(route) {
h.ServeHTTP(w, r)
return
}
// need to wrap std lib response to access the body
rww := NewResponseWriterWrapper(w)
h.ServeHTTP(rww, r)
b := new(bytes.Buffer)
_, err := b.ReadFrom(rww.Body())
if err != nil {
return
}
bodyReader := bytes.NewReader(b.Bytes())
responseValidationInput := &openapi3filter.ResponseValidationInput{
RequestValidationInput: &openapi3filter.RequestValidationInput{
Request: r,
PathParams: pathParams,
Route: route,
Options: opts.FilterOptions,
},
Header: rww.Header(),
Body: io.NopCloser(bodyReader),
Status: *rww.StatusCode(),
}
if err := openapi3filter.ValidateResponse(r.Context(), responseValidationInput); err != nil {
if opts.ResponseValidationErrorHook != nil {
opts.ResponseValidationErrorHook(err, r)
}
}
}
}
return http.HandlerFunc(fn)
}
}
|