Skip to content

Commit 8519112

Browse files
author
Jamie Tanna
committed
sq
1 parent c1c5bb1 commit 8519112

File tree

2 files changed

+216
-5
lines changed

2 files changed

+216
-5
lines changed

oapi_validate.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,15 @@ func performRequestValidationForErrorHandlerWithOpts(next http.Handler, w http.R
171171
return
172172
}
173173

174+
// // TODO
175+
// me := openapi3.MultiError{}
176+
// if errors.As(err, &me) {
177+
// fmt.Printf("me: %v\n", me)
178+
// // errFunc := getMultiErrorHandlerFromOptions(options)
179+
// // return errFunc(me)
180+
// }
181+
// // TODO
182+
174183
switch e := err.(type) {
175184
case *openapi3filter.RequestError:
176185
// We've got a bad request

oapi_validate_example_test.go

Lines changed: 207 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -479,9 +479,6 @@ components:
479479
out += "required\n"
480480
}
481481

482-
fmt.Printf("e: %#v\n", e)
483-
fmt.Printf("e.Err: %#v\n", e.Err)
484-
485482
if childErr := e.Unwrap(); childErr != nil {
486483
out += "There was a child error, which was "
487484
switch e := childErr.(type) {
@@ -531,8 +528,7 @@ components:
531528
fmt.Println("# A request that is malformed is rejected with HTTP 400 Bad Request (with an invalid request body), and is then logged by the ErrorHandlerWithOpts")
532529

533530
body := map[string]string{
534-
"id": "not-long-enough",
535-
"name": "Jamie",
531+
"id": "not-long-enough",
536532
}
537533

538534
data, err := json.Marshal(body)
@@ -617,3 +613,209 @@ components:
617613
// Received an HTTP 401 response. Expected HTTP 401
618614
// Response body: You're not allowed!
619615
}
616+
617+
func ExampleOapiRequestValidatorWithOptions_withErrorHandlerWithOptsAndMultiError() {
618+
rawSpec := `
619+
openapi: "3.0.0"
620+
info:
621+
version: 1.0.0
622+
title: TestServer
623+
servers:
624+
- url: http://example.com/
625+
paths:
626+
/resource:
627+
post:
628+
operationId: createResource
629+
responses:
630+
'204':
631+
description: No content
632+
requestBody:
633+
required: true
634+
content:
635+
application/json:
636+
schema:
637+
properties:
638+
id:
639+
type: string
640+
minLength: 100
641+
name:
642+
type: string
643+
enum:
644+
- Marcin
645+
additionalProperties: false
646+
`
647+
648+
must := func(err error) {
649+
if err != nil {
650+
panic(err)
651+
}
652+
}
653+
654+
use := func(r *http.ServeMux, middlewares ...func(next http.Handler) http.Handler) http.Handler {
655+
var s http.Handler
656+
s = r
657+
658+
for _, mw := range middlewares {
659+
s = mw(s)
660+
}
661+
662+
return s
663+
}
664+
665+
logResponseBody := func(rr *httptest.ResponseRecorder) {
666+
if rr.Result().Body != nil {
667+
data, _ := io.ReadAll(rr.Result().Body)
668+
if len(data) > 0 {
669+
fmt.Printf("Response body: %s", data)
670+
}
671+
}
672+
}
673+
674+
spec, err := openapi3.NewLoader().LoadFromData([]byte(rawSpec))
675+
must(err)
676+
677+
// NOTE that we need to make sure that the `Servers` aren't set, otherwise the OpenAPI validation middleware will validate that the `Host` header (of incoming requests) are targeting known `Servers` in the OpenAPI spec
678+
// See also: Options#SilenceServersWarning
679+
spec.Servers = nil
680+
681+
router := http.NewServeMux()
682+
683+
router.HandleFunc("/resource", func(w http.ResponseWriter, r *http.Request) {
684+
fmt.Printf("%s /resource was called\n", r.Method)
685+
686+
if r.Method == http.MethodPost {
687+
w.WriteHeader(http.StatusNoContent)
688+
return
689+
}
690+
691+
w.WriteHeader(http.StatusMethodNotAllowed)
692+
})
693+
694+
errorHandlerFunc := func(ctx context.Context, w http.ResponseWriter, r *http.Request, opts middleware.ErrorHandlerOpts) {
695+
err := opts.Error
696+
697+
if opts.MatchedRoute == nil {
698+
fmt.Printf("ErrorHandlerWithOpts: An HTTP %d was returned by the middleware with error message: %s\n", opts.StatusCode, err.Error())
699+
700+
// NOTE that you may want to override the default (an HTTP 400 Bad Request) to an HTTP 404 Not Found (or maybe an HTTP 405 Method Not Allowed, depending on what the requested resource was)
701+
http.Error(w, fmt.Sprintf("No route was found (according to ErrorHandlerWithOpts), and we changed the HTTP status code to %d", http.StatusNotFound), http.StatusNotFound)
702+
return
703+
}
704+
705+
switch e := err.(type) {
706+
case *openapi3filter.SecurityRequirementsError:
707+
out := fmt.Sprintf("A SecurityRequirementsError was returned when attempting to authenticate the request to %s %s against %d Security Schemes: %s\n", opts.MatchedRoute.Route.Method, opts.MatchedRoute.Route.Path, len(e.SecurityRequirements), e.Error())
708+
for _, sr := range e.SecurityRequirements {
709+
for k, v := range sr {
710+
out += fmt.Sprintf("- %s: %v\n", k, v)
711+
}
712+
}
713+
714+
fmt.Printf("ErrorHandlerWithOpts: %s\n", out)
715+
716+
http.Error(w, "You're not allowed!", opts.StatusCode)
717+
return
718+
case *openapi3filter.RequestError:
719+
out := fmt.Sprintf("A RequestError was returned when attempting to validate the request to %s %s: %s\n", opts.MatchedRoute.Route.Method, opts.MatchedRoute.Route.Path, e.Error())
720+
721+
if e.RequestBody != nil {
722+
out += "This operation has a request body, which was "
723+
if !e.RequestBody.Required {
724+
out += "not "
725+
}
726+
out += "required\n"
727+
}
728+
729+
if childErr := e.Unwrap(); childErr != nil {
730+
out += "There was a child error, which was "
731+
switch e := childErr.(type) {
732+
case *openapi3.SchemaError:
733+
out += "a SchemaError, which failed to validate on the " + e.SchemaField + " field"
734+
default:
735+
out += "an unknown type (" + reflect.TypeOf(e).String() + ")"
736+
}
737+
}
738+
739+
fmt.Printf("ErrorHandlerWithOpts: %s\n", out)
740+
741+
http.Error(w, "A bad request was made - but I'm not going to tell you where or how", opts.StatusCode)
742+
return
743+
}
744+
745+
http.Error(w, err.Error(), opts.StatusCode)
746+
}
747+
748+
// create middleware
749+
mw := middleware.OapiRequestValidatorWithOptions(spec, &middleware.Options{
750+
Options: openapi3filter.Options{
751+
// make sure that multiple errors in a given request are returned
752+
MultiError: true,
753+
},
754+
ErrorHandlerWithOpts: errorHandlerFunc,
755+
})
756+
757+
// then wire it in
758+
server := use(router, mw)
759+
760+
// ================================================================================
761+
fmt.Println("# A request that is malformed is rejected with HTTP 400 Bad Request (with no request body), and is then logged by the ErrorHandlerWithOpts")
762+
763+
req, err := http.NewRequest(http.MethodPost, "/resource", nil)
764+
must(err)
765+
req.Header.Set("Content-Type", "application/json")
766+
767+
rr := httptest.NewRecorder()
768+
769+
server.ServeHTTP(rr, req)
770+
771+
fmt.Printf("Received an HTTP %d response. Expected HTTP 400\n", rr.Code)
772+
logResponseBody(rr)
773+
fmt.Println()
774+
775+
// ================================================================================
776+
fmt.Println("# A request that is malformed is rejected with HTTP 400 Bad Request (with an invalid request body, with multiple issues), and is then logged by the ErrorHandlerWithOpts")
777+
778+
body := map[string]string{
779+
"id": "not-long-enough",
780+
"name": "Jamie",
781+
}
782+
783+
data, err := json.Marshal(body)
784+
must(err)
785+
786+
req, err = http.NewRequest(http.MethodPost, "/resource", bytes.NewReader(data))
787+
must(err)
788+
req.Header.Set("Content-Type", "application/json")
789+
790+
rr = httptest.NewRecorder()
791+
792+
server.ServeHTTP(rr, req)
793+
794+
fmt.Printf("Received an HTTP %d response. Expected HTTP 400\n", rr.Code)
795+
logResponseBody(rr)
796+
fmt.Println()
797+
798+
// Output:
799+
// # A request that is malformed is rejected with HTTP 400 Bad Request (with no request body), and is then logged by the ErrorHandlerWithOpts
800+
// ErrorHandlerWithOpts: A RequestError was returned when attempting to validate the request to POST /resource: request body has an error: value is required but missing
801+
// This operation has a request body, which was required
802+
// There was a child error, which was an unknown type (*errors.errorString)
803+
// Received an HTTP 400 response. Expected HTTP 400
804+
// Response body: A bad request was made - but I'm not going to tell you where or how
805+
//
806+
// # A request that is malformed is rejected with HTTP 400 Bad Request (with an invalid request body), and is then logged by the ErrorHandlerWithOpts
807+
// ErrorHandlerWithOpts: A RequestError was returned when attempting to validate the request to POST /resource: request body has an error: doesn't match schema: Error at "/id": minimum string length is 100
808+
// Schema:
809+
// {
810+
// "minLength": 100,
811+
// "type": "string"
812+
// }
813+
//
814+
// Value:
815+
// "not-long-enough"
816+
//
817+
// This operation has a request body, which was required
818+
// There was a child error, which was a SchemaError, which failed to validate on the minLength field
819+
// Received an HTTP 400 response. Expected HTTP 400
820+
// Response body: A bad request was made - but I'm not going to tell you where or how
821+
}

0 commit comments

Comments
 (0)