Skip to content

Commit

Permalink
add example usage of request validation with gorilla/mux router (#359)
Browse files Browse the repository at this point in the history
Signed-off-by: Pierre Fenoll <pierrefenoll@gmail.com>
  • Loading branch information
fenollp authored May 6, 2021
1 parent 070c628 commit 7be9302
Show file tree
Hide file tree
Showing 3 changed files with 276 additions and 3 deletions.
100 changes: 100 additions & 0 deletions openapi3filter/testdata/petstore.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
openapi: "3.0.0"
info:
description: "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters."
version: "1.0.0"
title: "Swagger Petstore"
termsOfService: "http://swagger.io/terms/"
contact:
email: "apiteam@swagger.io"
license:
name: "Apache 2.0"
url: "http://www.apache.org/licenses/LICENSE-2.0.html"
tags:
- name: "pet"
description: "Everything about your Pets"
externalDocs:
description: "Find out more"
url: "http://swagger.io"
- name: "store"
description: "Access to Petstore orders"
- name: "user"
description: "Operations about user"
externalDocs:
description: "Find out more about our store"
url: "http://swagger.io"
paths:
/pet:
post:
tags:
- "pet"
summary: "Add a new pet to the store"
description: ""
operationId: "addPet"
requestBody:
required: true
content:
'application/json':
schema:
$ref: '#/components/schemas/Pet'
responses:
"405":
description: "Invalid input"
components:
schemas:
Category:
type: "object"
properties:
id:
type: "integer"
format: "int64"
name:
type: "string"
xml:
name: "Category"
Tag:
type: "object"
properties:
id:
type: "integer"
format: "int64"
name:
type: "string"
xml:
name: "Tag"
Pet:
type: "object"
required:
- "name"
- "photoUrls"
properties:
id:
type: "integer"
format: "int64"
category:
$ref: "#/components/schemas/Category"
name:
type: "string"
example: "doggie"
photoUrls:
type: "array"
xml:
name: "photoUrl"
wrapped: true
items:
type: "string"
tags:
type: "array"
xml:
name: "tag"
wrapped: true
items:
$ref: "#/components/schemas/Tag"
status:
type: "string"
description: "pet status in the store"
enum:
- "available"
- "pending"
- "sold"
xml:
name: "Pet"
173 changes: 173 additions & 0 deletions openapi3filter/unpack_errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package openapi3filter_test

import (
"fmt"
"net/http"
"net/http/httptest"
"sort"
"strings"

"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/getkin/kin-openapi/routers/gorillamux"
)

func Example() {
doc, err := openapi3.NewLoader().LoadFromFile("./testdata/petstore.yaml")
if err != nil {
panic(err)
}

router, err := gorillamux.NewRouter(doc)
if err != nil {
panic(err)
}

ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
route, pathParams, err := router.FindRoute(r)
if err != nil {
fmt.Println(err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}

err = openapi3filter.ValidateRequest(r.Context(), &openapi3filter.RequestValidationInput{
Request: r,
PathParams: pathParams,
Route: route,
Options: &openapi3filter.Options{
MultiError: true,
},
})
switch err := err.(type) {
case nil:
case openapi3.MultiError:
issues := convertError(err)
names := make([]string, 0, len(issues))
for k := range issues {
names = append(names, k)
}
sort.Strings(names)
for _, k := range names {
msgs := issues[k]
fmt.Println("===== Start New Error =====")
fmt.Println(k + ":")
for _, msg := range msgs {
fmt.Printf("\t%s\n", msg)
}
}
w.WriteHeader(http.StatusBadRequest)
default:
fmt.Println(err.Error())
w.WriteHeader(http.StatusBadRequest)
}
}))
defer ts.Close()

// (note invalid type for name and invalid status)
body := strings.NewReader(`{"name": 100, "photoUrls": [], "status": "invalidStatus"}`)
req, err := http.NewRequest("POST", ts.URL+"/pet", body)
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Printf("response: %d %s\n", resp.StatusCode, resp.Body)

// Output:
// ===== Start New Error =====
// @body.name:
// Error at "/name": Field must be set to string or not be present
// Schema:
// {
// "example": "doggie",
// "type": "string"
// }
//
// Value:
// "number, integer"
//
// ===== Start New Error =====
// @body.status:
// Error at "/status": value is not one of the allowed values
// Schema:
// {
// "description": "pet status in the store",
// "enum": [
// "available",
// "pending",
// "sold"
// ],
// "type": "string"
// }
//
// Value:
// "invalidStatus"
//
// response: 400 {}
}

const (
prefixBody = "@body"
unknown = "@unknown"
)

func convertError(me openapi3.MultiError) map[string][]string {
issues := make(map[string][]string)
for _, err := range me {
switch err := err.(type) {
case *openapi3.SchemaError:
// Can inspect schema validation errors here, e.g. err.Value
field := prefixBody
if path := err.JSONPointer(); len(path) > 0 {
field = fmt.Sprintf("%s.%s", field, strings.Join(path, "."))
}
if _, ok := issues[field]; !ok {
issues[field] = make([]string, 0, 3)
}
issues[field] = append(issues[field], err.Error())
case *openapi3filter.RequestError: // possible there were multiple issues that failed validation
if err, ok := err.Err.(openapi3.MultiError); ok {
for k, v := range convertError(err) {
if _, ok := issues[k]; !ok {
issues[k] = make([]string, 0, 3)
}
issues[k] = append(issues[k], v...)
}
continue
}

// check if invalid HTTP parameter
if err.Parameter != nil {
prefix := err.Parameter.In
name := fmt.Sprintf("%s.%s", prefix, err.Parameter.Name)
if _, ok := issues[name]; !ok {
issues[name] = make([]string, 0, 3)
}
issues[name] = append(issues[name], err.Error())
continue
}

// check if requestBody
if err.RequestBody != nil {
if _, ok := issues[prefixBody]; !ok {
issues[prefixBody] = make([]string, 0, 3)
}
issues[prefixBody] = append(issues[prefixBody], err.Error())
continue
}
default:
reasons, ok := issues[unknown]
if !ok {
reasons = make([]string, 0, 3)
}
reasons = append(reasons, err.Error())
issues[unknown] = reasons
}
}
return issues
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package openapi3filter_test
package legacy_test

import (
"bytes"
Expand All @@ -8,7 +8,7 @@ import (

"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
"github.com/getkin/kin-openapi/routers/legacy"
)

const spec = `
Expand Down Expand Up @@ -73,7 +73,7 @@ func Example() {
panic(err)
}

router, err := legacyrouter.NewRouter(doc)
router, err := legacy.NewRouter(doc)
if err != nil {
panic(err)
}
Expand Down

0 comments on commit 7be9302

Please sign in to comment.