Skip to content

Commit cdbd170

Browse files
committed
docs: add example test
As a means to provide a full example of usage, we can use a testable example.
1 parent 4588e4f commit cdbd170

1 file changed

Lines changed: 292 additions & 0 deletions

File tree

oapi_validate_example_test.go

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

0 commit comments

Comments
 (0)