Skip to content

Commit 7be9302

Browse files
authored
add example usage of request validation with gorilla/mux router (#359)
Signed-off-by: Pierre Fenoll <pierrefenoll@gmail.com>
1 parent 070c628 commit 7be9302

File tree

3 files changed

+276
-3
lines changed

3 files changed

+276
-3
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
openapi: "3.0.0"
2+
info:
3+
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."
4+
version: "1.0.0"
5+
title: "Swagger Petstore"
6+
termsOfService: "http://swagger.io/terms/"
7+
contact:
8+
email: "apiteam@swagger.io"
9+
license:
10+
name: "Apache 2.0"
11+
url: "http://www.apache.org/licenses/LICENSE-2.0.html"
12+
tags:
13+
- name: "pet"
14+
description: "Everything about your Pets"
15+
externalDocs:
16+
description: "Find out more"
17+
url: "http://swagger.io"
18+
- name: "store"
19+
description: "Access to Petstore orders"
20+
- name: "user"
21+
description: "Operations about user"
22+
externalDocs:
23+
description: "Find out more about our store"
24+
url: "http://swagger.io"
25+
paths:
26+
/pet:
27+
post:
28+
tags:
29+
- "pet"
30+
summary: "Add a new pet to the store"
31+
description: ""
32+
operationId: "addPet"
33+
requestBody:
34+
required: true
35+
content:
36+
'application/json':
37+
schema:
38+
$ref: '#/components/schemas/Pet'
39+
responses:
40+
"405":
41+
description: "Invalid input"
42+
components:
43+
schemas:
44+
Category:
45+
type: "object"
46+
properties:
47+
id:
48+
type: "integer"
49+
format: "int64"
50+
name:
51+
type: "string"
52+
xml:
53+
name: "Category"
54+
Tag:
55+
type: "object"
56+
properties:
57+
id:
58+
type: "integer"
59+
format: "int64"
60+
name:
61+
type: "string"
62+
xml:
63+
name: "Tag"
64+
Pet:
65+
type: "object"
66+
required:
67+
- "name"
68+
- "photoUrls"
69+
properties:
70+
id:
71+
type: "integer"
72+
format: "int64"
73+
category:
74+
$ref: "#/components/schemas/Category"
75+
name:
76+
type: "string"
77+
example: "doggie"
78+
photoUrls:
79+
type: "array"
80+
xml:
81+
name: "photoUrl"
82+
wrapped: true
83+
items:
84+
type: "string"
85+
tags:
86+
type: "array"
87+
xml:
88+
name: "tag"
89+
wrapped: true
90+
items:
91+
$ref: "#/components/schemas/Tag"
92+
status:
93+
type: "string"
94+
description: "pet status in the store"
95+
enum:
96+
- "available"
97+
- "pending"
98+
- "sold"
99+
xml:
100+
name: "Pet"
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package openapi3filter_test
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"net/http/httptest"
7+
"sort"
8+
"strings"
9+
10+
"github.com/getkin/kin-openapi/openapi3"
11+
"github.com/getkin/kin-openapi/openapi3filter"
12+
"github.com/getkin/kin-openapi/routers/gorillamux"
13+
)
14+
15+
func Example() {
16+
doc, err := openapi3.NewLoader().LoadFromFile("./testdata/petstore.yaml")
17+
if err != nil {
18+
panic(err)
19+
}
20+
21+
router, err := gorillamux.NewRouter(doc)
22+
if err != nil {
23+
panic(err)
24+
}
25+
26+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
27+
route, pathParams, err := router.FindRoute(r)
28+
if err != nil {
29+
fmt.Println(err.Error())
30+
w.WriteHeader(http.StatusInternalServerError)
31+
return
32+
}
33+
34+
err = openapi3filter.ValidateRequest(r.Context(), &openapi3filter.RequestValidationInput{
35+
Request: r,
36+
PathParams: pathParams,
37+
Route: route,
38+
Options: &openapi3filter.Options{
39+
MultiError: true,
40+
},
41+
})
42+
switch err := err.(type) {
43+
case nil:
44+
case openapi3.MultiError:
45+
issues := convertError(err)
46+
names := make([]string, 0, len(issues))
47+
for k := range issues {
48+
names = append(names, k)
49+
}
50+
sort.Strings(names)
51+
for _, k := range names {
52+
msgs := issues[k]
53+
fmt.Println("===== Start New Error =====")
54+
fmt.Println(k + ":")
55+
for _, msg := range msgs {
56+
fmt.Printf("\t%s\n", msg)
57+
}
58+
}
59+
w.WriteHeader(http.StatusBadRequest)
60+
default:
61+
fmt.Println(err.Error())
62+
w.WriteHeader(http.StatusBadRequest)
63+
}
64+
}))
65+
defer ts.Close()
66+
67+
// (note invalid type for name and invalid status)
68+
body := strings.NewReader(`{"name": 100, "photoUrls": [], "status": "invalidStatus"}`)
69+
req, err := http.NewRequest("POST", ts.URL+"/pet", body)
70+
if err != nil {
71+
panic(err)
72+
}
73+
req.Header.Set("Content-Type", "application/json")
74+
resp, err := http.DefaultClient.Do(req)
75+
if err != nil {
76+
panic(err)
77+
}
78+
defer resp.Body.Close()
79+
fmt.Printf("response: %d %s\n", resp.StatusCode, resp.Body)
80+
81+
// Output:
82+
// ===== Start New Error =====
83+
// @body.name:
84+
// Error at "/name": Field must be set to string or not be present
85+
// Schema:
86+
// {
87+
// "example": "doggie",
88+
// "type": "string"
89+
// }
90+
//
91+
// Value:
92+
// "number, integer"
93+
//
94+
// ===== Start New Error =====
95+
// @body.status:
96+
// Error at "/status": value is not one of the allowed values
97+
// Schema:
98+
// {
99+
// "description": "pet status in the store",
100+
// "enum": [
101+
// "available",
102+
// "pending",
103+
// "sold"
104+
// ],
105+
// "type": "string"
106+
// }
107+
//
108+
// Value:
109+
// "invalidStatus"
110+
//
111+
// response: 400 {}
112+
}
113+
114+
const (
115+
prefixBody = "@body"
116+
unknown = "@unknown"
117+
)
118+
119+
func convertError(me openapi3.MultiError) map[string][]string {
120+
issues := make(map[string][]string)
121+
for _, err := range me {
122+
switch err := err.(type) {
123+
case *openapi3.SchemaError:
124+
// Can inspect schema validation errors here, e.g. err.Value
125+
field := prefixBody
126+
if path := err.JSONPointer(); len(path) > 0 {
127+
field = fmt.Sprintf("%s.%s", field, strings.Join(path, "."))
128+
}
129+
if _, ok := issues[field]; !ok {
130+
issues[field] = make([]string, 0, 3)
131+
}
132+
issues[field] = append(issues[field], err.Error())
133+
case *openapi3filter.RequestError: // possible there were multiple issues that failed validation
134+
if err, ok := err.Err.(openapi3.MultiError); ok {
135+
for k, v := range convertError(err) {
136+
if _, ok := issues[k]; !ok {
137+
issues[k] = make([]string, 0, 3)
138+
}
139+
issues[k] = append(issues[k], v...)
140+
}
141+
continue
142+
}
143+
144+
// check if invalid HTTP parameter
145+
if err.Parameter != nil {
146+
prefix := err.Parameter.In
147+
name := fmt.Sprintf("%s.%s", prefix, err.Parameter.Name)
148+
if _, ok := issues[name]; !ok {
149+
issues[name] = make([]string, 0, 3)
150+
}
151+
issues[name] = append(issues[name], err.Error())
152+
continue
153+
}
154+
155+
// check if requestBody
156+
if err.RequestBody != nil {
157+
if _, ok := issues[prefixBody]; !ok {
158+
issues[prefixBody] = make([]string, 0, 3)
159+
}
160+
issues[prefixBody] = append(issues[prefixBody], err.Error())
161+
continue
162+
}
163+
default:
164+
reasons, ok := issues[unknown]
165+
if !ok {
166+
reasons = make([]string, 0, 3)
167+
}
168+
reasons = append(reasons, err.Error())
169+
issues[unknown] = reasons
170+
}
171+
}
172+
return issues
173+
}

openapi3filter/validate_request_test.go renamed to routers/legacy/validate_request_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package openapi3filter_test
1+
package legacy_test
22

33
import (
44
"bytes"
@@ -8,7 +8,7 @@ import (
88

99
"github.com/getkin/kin-openapi/openapi3"
1010
"github.com/getkin/kin-openapi/openapi3filter"
11-
legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
11+
"github.com/getkin/kin-openapi/routers/legacy"
1212
)
1313

1414
const spec = `
@@ -73,7 +73,7 @@ func Example() {
7373
panic(err)
7474
}
7575

76-
router, err := legacyrouter.NewRouter(doc)
76+
router, err := legacy.NewRouter(doc)
7777
if err != nil {
7878
panic(err)
7979
}

0 commit comments

Comments
 (0)