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)
	}
}