Skip to content

Commit efe7ede

Browse files
committed
Initial release of oapi-codegen
0 parents  commit efe7ede

2 files changed

Lines changed: 275 additions & 0 deletions

File tree

oapi_validate.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright 2019 DeepMap, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package middleware
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"github.com/getkin/kin-openapi/openapi3"
21+
"github.com/getkin/kin-openapi/openapi3filter"
22+
"github.com/labstack/echo/v4"
23+
"io/ioutil"
24+
"net/http"
25+
)
26+
27+
// This is an Echo middleware function which validates incoming HTTP requests
28+
// to make sure that they conform to the given OAPI 3.0 specification. When
29+
// OAPI validation failes on the request, we return an HTTP/400.
30+
31+
// Create validator middleware from a YAML file path
32+
func OapiValidatorFromYamlFile(path string) (echo.MiddlewareFunc, error) {
33+
data, err := ioutil.ReadFile(path)
34+
if err != nil {
35+
return nil, fmt.Errorf("error reading %s: %s", path, err)
36+
}
37+
38+
swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromYAMLData(data)
39+
if err != nil {
40+
return nil, fmt.Errorf("error parsing %s as Swagger YAML: %s",
41+
path, err)
42+
}
43+
return OapiRequestValidator(swagger), nil
44+
}
45+
46+
// Create a validator from a swagger object.
47+
func OapiRequestValidator(swagger *openapi3.Swagger) echo.MiddlewareFunc {
48+
router := openapi3filter.NewRouter().WithSwagger(swagger)
49+
return func(next echo.HandlerFunc) echo.HandlerFunc {
50+
return func(c echo.Context) error {
51+
err := ValidateRequestFromContext(c, router)
52+
if err != nil {
53+
return err
54+
}
55+
return next(c)
56+
}
57+
}
58+
}
59+
60+
// This function is called from the middleware above and actually does the work
61+
// of validating a request.
62+
// TODO(marcin): kin-openapi, which we use for swagger validation, currently only
63+
// validates string parameters, and assumes that the rest are valid. I need to
64+
// handle param validation myself, or fix their code.
65+
func ValidateRequestFromContext(ctx echo.Context, router *openapi3filter.Router) error {
66+
req := ctx.Request()
67+
route, pathParams, err := router.FindRoute(req.Method, req.URL)
68+
69+
// We failed to find a matching route for the request.
70+
if err != nil {
71+
switch e := err.(type) {
72+
case *openapi3filter.RouteError:
73+
// We've got a bad request, the path requested doesn't match
74+
// either server, or path, or something.
75+
ctx.Logger().Errorf("error finding request route for %s: %s",
76+
req.URL.String(), e.Reason)
77+
return echo.NewHTTPError(http.StatusBadRequest, e.Reason)
78+
default:
79+
// This should never happen today, but if our upstream code changes,
80+
// we don't want to crash the server, so handle the unexpected error.
81+
ctx.Logger().Errorf("error finding route %s: %s", req.URL.String(), err)
82+
return echo.NewHTTPError(http.StatusInternalServerError,
83+
"error validating request")
84+
}
85+
}
86+
87+
err = openapi3filter.ValidateRequest(context.Background(),
88+
&openapi3filter.RequestValidationInput{
89+
Request: req,
90+
PathParams: pathParams,
91+
Route: route,
92+
})
93+
if err != nil {
94+
switch e := err.(type) {
95+
case *openapi3filter.RequestError:
96+
// We've got a bad request
97+
ctx.Logger().Errorf("request validation failed for %s: %s",
98+
req.URL.String(), e.Reason)
99+
return echo.NewHTTPError(http.StatusBadRequest, e.Reason)
100+
default:
101+
// This should never happen today, but if our upstream code changes,
102+
// we don't want to crash the server, so handle the unexpected error.
103+
ctx.Logger().Errorf("error validating request for %s: %s", req.URL.String(), err)
104+
return echo.NewHTTPError(http.StatusInternalServerError,
105+
"error validating request")
106+
}
107+
}
108+
return nil
109+
}

oapi_validate_test.go

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
// Copyright 2019 DeepMap, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package middleware
16+
17+
import (
18+
"net/http"
19+
"net/http/httptest"
20+
"testing"
21+
22+
"github.com/getkin/kin-openapi/openapi3"
23+
"github.com/labstack/echo/v4"
24+
"github.com/stretchr/testify/assert"
25+
26+
"github.com/deepmap/oapi-codegen/v2/pkg/testutil"
27+
)
28+
29+
var testSchema = `openapi: "3.0.0"
30+
info:
31+
version: 1.0.0
32+
title: TestServer
33+
servers:
34+
- url: http://deepmap.ai
35+
paths:
36+
/resource:
37+
get:
38+
operationId: getResource
39+
parameters:
40+
- name: id
41+
in: query
42+
schema:
43+
type: integer
44+
minimum: 10
45+
maximum: 100
46+
responses:
47+
'200':
48+
content:
49+
application/json:
50+
schema:
51+
properties:
52+
name:
53+
type: string
54+
id:
55+
type: integer
56+
post:
57+
operationId: createResource
58+
responses:
59+
'204':
60+
description: No content
61+
requestBody:
62+
required: true
63+
content:
64+
application/json:
65+
schema:
66+
properties:
67+
name:
68+
type: string
69+
`
70+
71+
func doGet(t *testing.T, e *echo.Echo, url string) *httptest.ResponseRecorder {
72+
response := testutil.NewRequest().Get(url).WithAcceptJson().Go(t, e)
73+
return response.Recorder
74+
}
75+
76+
func doPost(t *testing.T, e *echo.Echo, url string, jsonBody interface{}) *httptest.ResponseRecorder {
77+
response := testutil.NewRequest().Post(url).WithJsonBody(jsonBody).Go(t, e)
78+
return response.Recorder
79+
}
80+
81+
func TestOapiRequestValidator(t *testing.T) {
82+
swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromYAMLData([]byte(testSchema))
83+
assert.NoError(t, err, "Error initializing swagger")
84+
85+
// Create a new echo router
86+
e := echo.New()
87+
88+
// Install our OpenApi based request validator
89+
e.Use(OapiRequestValidator(swagger))
90+
91+
called := false
92+
93+
// Install a request handler for /resource. We want to make sure it doesn't
94+
// get called.
95+
e.GET("/resource", func(c echo.Context) error {
96+
called = true
97+
return nil
98+
})
99+
// Let's send the request to the wrong server, this should fail validation
100+
{
101+
rec := doGet(t, e, "http://not.deepmap.ai/resource")
102+
assert.Equal(t, http.StatusBadRequest, rec.Code)
103+
assert.False(t, called, "Handler should not have been called")
104+
}
105+
106+
// Let's send a good request, it should pass
107+
{
108+
rec := doGet(t, e, "http://deepmap.ai/resource")
109+
assert.Equal(t, http.StatusOK, rec.Code)
110+
assert.True(t, called, "Handler should have been called")
111+
called = false
112+
}
113+
114+
// Send an out-of-spec parameter
115+
// TODO(marcin): limit checking for numbers is not yet working in
116+
// kin-openapi, they only check the string type. I either need to do
117+
// this myself, or patch their code.
118+
//{
119+
// rec := doGet(t, e, "http://deepmap.ai/resource?id=500")
120+
// assert.Equal(t, http.StatusBadRequest, rec.Code)
121+
// assert.False(t, called, "Handler should not have been called")
122+
// called = false
123+
//}
124+
125+
// Send a bad parameter type
126+
// TODO(marcin): type checking is currently not yet working in kin-openapi
127+
//{
128+
// rec := doGet(t, e, "http://deepmap.ai/resource?id=foo")
129+
// assert.Equal(t, http.StatusBadRequest, rec.Code)
130+
// assert.False(t, called, "Handler should not have been called")
131+
// called = false
132+
//}
133+
134+
// Add a handler for the POST message
135+
e.POST("/resource", func(c echo.Context) error {
136+
called = true
137+
return c.NoContent(http.StatusNoContent)
138+
})
139+
140+
called = false
141+
// Send a good request body
142+
{
143+
body := struct {
144+
Name string `json:"name"`
145+
}{
146+
Name: "Marcin",
147+
}
148+
rec := doPost(t, e, "http://deepmap.ai/resource", body)
149+
assert.Equal(t, http.StatusNoContent, rec.Code)
150+
assert.True(t, called, "Handler should have been called")
151+
called = false
152+
}
153+
154+
// Send a malformed body
155+
{
156+
body := struct {
157+
Name int `json:"name"`
158+
}{
159+
Name: 7,
160+
}
161+
rec := doPost(t, e, "http://deepmap.ai/resource", body)
162+
assert.Equal(t, http.StatusBadRequest, rec.Code)
163+
assert.False(t, called, "Handler should not have been called")
164+
called = false
165+
}
166+
}

0 commit comments

Comments
 (0)