Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add example usage of request validation with gorilla/mux router #359

Merged
merged 1 commit into from
May 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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