Skip to content
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