Skip to content

Commit 800fd65

Browse files
Jamie Tannajamietanna
authored andcommitted
docs: add example test
As a means to provide a full example of usage, we can use a testable example.
1 parent 4dbd1fe commit 800fd65

File tree

1 file changed

+332
-0
lines changed

1 file changed

+332
-0
lines changed

oapi_validate_example_test.go

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
package nethttpmiddleware_test
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"fmt"
8+
"io"
9+
"net/http"
10+
"net/http/httptest"
11+
12+
"github.com/getkin/kin-openapi/openapi3"
13+
"github.com/getkin/kin-openapi/openapi3filter"
14+
middleware "github.com/oapi-codegen/nethttp-middleware"
15+
)
16+
17+
func ExampleOapiRequestValidatorWithOptions() {
18+
rawSpec := `
19+
openapi: "3.0.0"
20+
info:
21+
version: 1.0.0
22+
title: TestServer
23+
servers:
24+
- url: http://example.com/
25+
paths:
26+
/resource:
27+
post:
28+
operationId: createResource
29+
responses:
30+
'204':
31+
description: No content
32+
requestBody:
33+
required: true
34+
content:
35+
application/json:
36+
schema:
37+
properties:
38+
name:
39+
type: string
40+
additionalProperties: false
41+
/protected_resource:
42+
get:
43+
operationId: getProtectedResource
44+
security:
45+
- BearerAuth:
46+
- someScope
47+
responses:
48+
'204':
49+
description: no content
50+
components:
51+
securitySchemes:
52+
BearerAuth:
53+
type: http
54+
scheme: bearer
55+
bearerFormat: JWT
56+
`
57+
58+
must := func(err error) {
59+
if err != nil {
60+
panic(err)
61+
}
62+
}
63+
64+
use := func(r *http.ServeMux, middlewares ...func(next http.Handler) http.Handler) http.Handler {
65+
var s http.Handler
66+
s = r
67+
68+
for _, mw := range middlewares {
69+
s = mw(s)
70+
}
71+
72+
return s
73+
}
74+
75+
logResponseBody := func(rr *httptest.ResponseRecorder) {
76+
if rr.Result().Body != nil {
77+
data, _ := io.ReadAll(rr.Result().Body)
78+
if len(data) > 0 {
79+
fmt.Printf("Response body: %s", data)
80+
}
81+
}
82+
}
83+
84+
spec, err := openapi3.NewLoader().LoadFromData([]byte(rawSpec))
85+
must(err)
86+
87+
// 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
88+
// See also: Options#SilenceServersWarning
89+
spec.Servers = nil
90+
91+
router := http.NewServeMux()
92+
93+
router.HandleFunc("/resource", func(w http.ResponseWriter, r *http.Request) {
94+
fmt.Printf("%s /resource was called\n", r.Method)
95+
96+
if r.Method == http.MethodPost {
97+
w.WriteHeader(http.StatusNoContent)
98+
return
99+
}
100+
101+
w.WriteHeader(http.StatusMethodNotAllowed)
102+
})
103+
104+
router.HandleFunc("/protected_resource", func(w http.ResponseWriter, r *http.Request) {
105+
// NOTE that we're setting up our `authenticationFunc` (below) to /never/ allow any requests in - so if we get a response from this endpoint, our `authenticationFunc` hasn't correctly worked
106+
107+
if r.Method == http.MethodGet {
108+
w.WriteHeader(http.StatusNoContent)
109+
return
110+
}
111+
112+
w.WriteHeader(http.StatusMethodNotAllowed)
113+
})
114+
115+
authenticationFunc := func(ctx context.Context, ai *openapi3filter.AuthenticationInput) error {
116+
fmt.Printf("`AuthenticationFunc` was called for securitySchemeName=%s\n", ai.SecuritySchemeName)
117+
return fmt.Errorf("this check always fails - don't let anyone in!")
118+
}
119+
120+
// create middleware
121+
mw := middleware.OapiRequestValidatorWithOptions(spec, &middleware.Options{
122+
Options: openapi3filter.Options{
123+
AuthenticationFunc: authenticationFunc,
124+
},
125+
})
126+
127+
// then wire it in
128+
server := use(router, mw)
129+
130+
// ================================================================================
131+
fmt.Println("# A request that is malformed is rejected with HTTP 400 Bad Request (with no request body)")
132+
133+
req, err := http.NewRequest(http.MethodPost, "/resource", bytes.NewReader(nil))
134+
must(err)
135+
req.Header.Set("Content-Type", "application/json")
136+
137+
rr := httptest.NewRecorder()
138+
139+
server.ServeHTTP(rr, req)
140+
141+
fmt.Printf("Received an HTTP %d response. Expected HTTP 400\n", rr.Code)
142+
logResponseBody(rr)
143+
fmt.Println()
144+
145+
// ================================================================================
146+
fmt.Println("# A request that is malformed is rejected with HTTP 400 Bad Request (because an invalid property is sent, and we have `additionalProperties: false`)")
147+
body := map[string]string{
148+
"invalid": "not expected",
149+
}
150+
151+
data, err := json.Marshal(body)
152+
must(err)
153+
154+
req, err = http.NewRequest(http.MethodPost, "/resource", bytes.NewReader(data))
155+
must(err)
156+
req.Header.Set("Content-Type", "application/json")
157+
158+
rr = httptest.NewRecorder()
159+
160+
server.ServeHTTP(rr, req)
161+
162+
fmt.Printf("Received an HTTP %d response. Expected HTTP 400\n", rr.Code)
163+
logResponseBody(rr)
164+
fmt.Println()
165+
166+
// ================================================================================
167+
fmt.Println("# A request that is well-formed is passed through to the Handler")
168+
body = map[string]string{
169+
"name": "Jamie",
170+
}
171+
172+
data, err = json.Marshal(body)
173+
must(err)
174+
175+
req, err = http.NewRequest(http.MethodPost, "/resource", bytes.NewReader(data))
176+
must(err)
177+
req.Header.Set("Content-Type", "application/json")
178+
179+
rr = httptest.NewRecorder()
180+
181+
server.ServeHTTP(rr, req)
182+
183+
fmt.Printf("Received an HTTP %d response. Expected HTTP 204\n", rr.Code)
184+
logResponseBody(rr)
185+
fmt.Println()
186+
187+
// ================================================================================
188+
fmt.Println("# A request to an authenticated endpoint must go through an `AuthenticationFunc`, and if it fails, an HTTP 401 is returned")
189+
190+
req, err = http.NewRequest(http.MethodGet, "/protected_resource", nil)
191+
must(err)
192+
193+
rr = httptest.NewRecorder()
194+
195+
server.ServeHTTP(rr, req)
196+
197+
fmt.Printf("Received an HTTP %d response. Expected HTTP 401\n", rr.Code)
198+
logResponseBody(rr)
199+
fmt.Println()
200+
201+
// Output:
202+
// # A request that is malformed is rejected with HTTP 400 Bad Request (with no request body)
203+
// Received an HTTP 400 response. Expected HTTP 400
204+
// Response body: request body has an error: value is required but missing
205+
//
206+
// # A request that is malformed is rejected with HTTP 400 Bad Request (because an invalid property is sent, and we have `additionalProperties: false`)
207+
// Received an HTTP 400 response. Expected HTTP 400
208+
// Response body: request body has an error: doesn't match schema: property "invalid" is unsupported
209+
//
210+
// # A request that is well-formed is passed through to the Handler
211+
// POST /resource was called
212+
// Received an HTTP 204 response. Expected HTTP 204
213+
//
214+
// # A request to an authenticated endpoint must go through an `AuthenticationFunc`, and if it fails, an HTTP 401 is returned
215+
// `AuthenticationFunc` was called for securitySchemeName=BearerAuth
216+
// Received an HTTP 401 response. Expected HTTP 401
217+
// Response body: security requirements failed: this check always fails - don't let anyone in!
218+
}
219+
220+
func ExampleOapiRequestValidatorWithOptions_withErrorHandler() {
221+
rawSpec := `
222+
openapi: "3.0.0"
223+
info:
224+
version: 1.0.0
225+
title: TestServer
226+
servers:
227+
- url: http://example.com/
228+
paths:
229+
/resource:
230+
post:
231+
operationId: createResource
232+
responses:
233+
'204':
234+
description: No content
235+
requestBody:
236+
required: true
237+
content:
238+
application/json:
239+
schema:
240+
properties:
241+
name:
242+
type: string
243+
additionalProperties: false
244+
`
245+
246+
must := func(err error) {
247+
if err != nil {
248+
panic(err)
249+
}
250+
}
251+
252+
use := func(r *http.ServeMux, middlewares ...func(next http.Handler) http.Handler) http.Handler {
253+
var s http.Handler
254+
s = r
255+
256+
for _, mw := range middlewares {
257+
s = mw(s)
258+
}
259+
260+
return s
261+
}
262+
263+
logResponseBody := func(rr *httptest.ResponseRecorder) {
264+
if rr.Result().Body != nil {
265+
data, _ := io.ReadAll(rr.Result().Body)
266+
if len(data) > 0 {
267+
fmt.Printf("Response body: %s", data)
268+
}
269+
}
270+
}
271+
272+
spec, err := openapi3.NewLoader().LoadFromData([]byte(rawSpec))
273+
must(err)
274+
275+
// 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
276+
// See also: Options#SilenceServersWarning
277+
spec.Servers = nil
278+
279+
router := http.NewServeMux()
280+
281+
router.HandleFunc("/resource", func(w http.ResponseWriter, r *http.Request) {
282+
fmt.Printf("%s /resource was called\n", r.Method)
283+
284+
if r.Method == http.MethodPost {
285+
w.WriteHeader(http.StatusNoContent)
286+
return
287+
}
288+
289+
w.WriteHeader(http.StatusMethodNotAllowed)
290+
})
291+
292+
authenticationFunc := func(ctx context.Context, ai *openapi3filter.AuthenticationInput) error {
293+
fmt.Printf("`AuthenticationFunc` was called for securitySchemeName=%s\n", ai.SecuritySchemeName)
294+
return fmt.Errorf("this check always fails - don't let anyone in!")
295+
}
296+
297+
errorHandlerFunc := func(w http.ResponseWriter, message string, statusCode int) {
298+
fmt.Printf("ErrorHandler: An HTTP %d was returned by the middleware with error message: request body has an error: value is required but missing\n", statusCode)
299+
http.Error(w, "This was rewritten by the ErrorHandler", statusCode)
300+
}
301+
302+
// create middleware
303+
mw := middleware.OapiRequestValidatorWithOptions(spec, &middleware.Options{
304+
Options: openapi3filter.Options{
305+
AuthenticationFunc: authenticationFunc,
306+
},
307+
ErrorHandler: errorHandlerFunc,
308+
})
309+
310+
// then wire it in
311+
server := use(router, mw)
312+
313+
// ================================================================================
314+
fmt.Println("# A request that is malformed is rejected with HTTP 400 Bad Request (with no request body), and is then logged by the ErrorHandler")
315+
316+
req, err := http.NewRequest(http.MethodPost, "/resource", bytes.NewReader(nil))
317+
must(err)
318+
req.Header.Set("Content-Type", "application/json")
319+
320+
rr := httptest.NewRecorder()
321+
322+
server.ServeHTTP(rr, req)
323+
324+
fmt.Printf("Received an HTTP %d response. Expected HTTP 400\n", rr.Code)
325+
logResponseBody(rr)
326+
327+
// Output:
328+
// # A request that is malformed is rejected with HTTP 400 Bad Request (with no request body), and is then logged by the ErrorHandler
329+
// ErrorHandler: An HTTP 400 was returned by the middleware with error message: request body has an error: value is required but missing
330+
// Received an HTTP 400 response. Expected HTTP 400
331+
// Response body: This was rewritten by the ErrorHandler
332+
}

0 commit comments

Comments
 (0)