-
-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathoapi_validate.go
More file actions
159 lines (134 loc) · 6.13 KB
/
oapi_validate.go
File metadata and controls
159 lines (134 loc) · 6.13 KB
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
// Provide HTTP middleware functionality to validate that incoming requests conform to a given OpenAPI 3.x specification.
//
// This provides middleware for any `net/http` conforming HTTP Server.
//
// This package is a lightweight wrapper over https://pkg.go.dev/github.com/getkin/kin-openapi/openapi3filter from https://pkg.go.dev/github.com/getkin/kin-openapi.
//
// This is _intended_ to be used with code that's generated through https://pkg.go.dev/github.com/oapi-codegen/oapi-codegen, but should work otherwise.
package nethttpmiddleware
import (
"errors"
"fmt"
"log"
"net/http"
"strings"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/getkin/kin-openapi/routers"
"github.com/getkin/kin-openapi/routers/gorillamux"
)
// ErrorHandler is called when there is an error in validation
type ErrorHandler func(w http.ResponseWriter, message string, statusCode int)
// MultiErrorHandler is called when the OpenAPI filter returns an openapi3.MultiError (https://pkg.go.dev/github.com/getkin/kin-openapi/openapi3#MultiError)
type MultiErrorHandler func(openapi3.MultiError) (int, error)
// Options allows configuring the OapiRequestValidator.
type Options struct {
// Options contains any configuration for the underlying `openapi3filter`
Options openapi3filter.Options
// ErrorHandler is called when a validation error occurs.
//
// If not provided, `http.Error` will be called
ErrorHandler ErrorHandler
// MultiErrorHandler is called when there is an openapi3.MultiError (https://pkg.go.dev/github.com/getkin/kin-openapi/openapi3#MultiError) returned by the `openapi3filter`.
//
// If not provided `defaultMultiErrorHandler` will be used.
MultiErrorHandler MultiErrorHandler
// SilenceServersWarning allows silencing a warning for https://github.com/deepmap/oapi-codegen/issues/882 that reports when an OpenAPI spec has `spec.Servers != nil`
SilenceServersWarning bool
// DoNotValidateServers ensures that there is no Host validation performed (see `SilenceServersWarning` and https://github.com/deepmap/oapi-codegen/issues/882 for more details)
DoNotValidateServers bool
}
// OapiRequestValidator Creates the middleware to validate that incoming requests match the given OpenAPI 3.x spec, with a default set of configuration.
func OapiRequestValidator(spec *openapi3.T) func(next http.Handler) http.Handler {
return OapiRequestValidatorWithOptions(spec, nil)
}
// OapiRequestValidatorWithOptions Creates the middleware to validate that incoming requests match the given OpenAPI 3.x spec, allowing explicit configuration.
//
// NOTE that this may panic if the OpenAPI spec isn't valid, or if it cannot be used to create the middleware
func OapiRequestValidatorWithOptions(spec *openapi3.T, options *Options) func(next http.Handler) http.Handler {
if options != nil && options.DoNotValidateServers {
spec.Servers = nil
}
if spec.Servers != nil && (options == nil || !options.SilenceServersWarning) {
log.Println("WARN: OapiRequestValidatorWithOptions called with an OpenAPI spec that has `Servers` set. This may lead to an HTTP 400 with `no matching operation was found` when sending a valid request, as the validator performs `Host` header validation. If you're expecting `Host` header validation, you can silence this warning by setting `Options.SilenceServersWarning = true`. See https://github.com/deepmap/oapi-codegen/issues/882 for more information.")
}
router, err := gorillamux.NewRouter(spec)
if err != nil {
panic(err)
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// validate request
if statusCode, err := validateRequest(r, router, options); err != nil {
if options != nil && options.ErrorHandler != nil {
options.ErrorHandler(w, err.Error(), statusCode)
} else {
http.Error(w, err.Error(), statusCode)
}
return
}
// serve
next.ServeHTTP(w, r)
})
}
}
// validateRequest is called from the middleware above and actually does the work
// of validating a request.
func validateRequest(r *http.Request, router routers.Router, options *Options) (int, error) {
// Find route
route, pathParams, err := router.FindRoute(r)
if err != nil {
if errors.Is(err, routers.ErrMethodNotAllowed) {
return http.StatusMethodNotAllowed, err
}
return http.StatusNotFound, err // We failed to find a matching route for the request.
}
// Validate request
requestValidationInput := &openapi3filter.RequestValidationInput{
Request: r,
PathParams: pathParams,
Route: route,
}
if options != nil {
requestValidationInput.Options = &options.Options
}
if err := openapi3filter.ValidateRequest(r.Context(), requestValidationInput); err != nil {
me := openapi3.MultiError{}
if errors.As(err, &me) {
errFunc := getMultiErrorHandlerFromOptions(options)
return errFunc(me)
}
switch e := err.(type) {
case *openapi3filter.RequestError:
// We've got a bad request
// Split up the verbose error by lines and return the first one
// openapi errors seem to be multi-line with a decent message on the first
errorLines := strings.Split(e.Error(), "\n")
return http.StatusBadRequest, fmt.Errorf(errorLines[0])
case *openapi3filter.SecurityRequirementsError:
return http.StatusUnauthorized, err
default:
// This should never happen today, but if our upstream code changes,
// we don't want to crash the server, so handle the unexpected error.
return http.StatusInternalServerError, fmt.Errorf("error validating route: %s", err.Error())
}
}
return http.StatusOK, nil
}
// attempt to get the MultiErrorHandler from the options. If it is not set,
// return a default handler
func getMultiErrorHandlerFromOptions(options *Options) MultiErrorHandler {
if options == nil {
return defaultMultiErrorHandler
}
if options.MultiErrorHandler == nil {
return defaultMultiErrorHandler
}
return options.MultiErrorHandler
}
// defaultMultiErrorHandler returns a StatusBadRequest (400) and a list
// of all the errors. This method is called if there are no other
// methods defined on the options.
func defaultMultiErrorHandler(me openapi3.MultiError) (int, error) {
return http.StatusBadRequest, me
}