Skip to content

Commit 2491c45

Browse files
feat: add gin server support (#475)
* fix for ci * update readme * feat: add gin server support * save work * save work * save work * save work * save work * save work * tidy mod * update ci * add gin mw * bump go version to 1.16 Co-authored-by: Marcin Romaszewicz <47459980+deepmap-marcinr@users.noreply.github.com>
0 parents  commit 2491c45

File tree

2 files changed

+424
-0
lines changed

2 files changed

+424
-0
lines changed

oapi_validate.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// Copyright 2021 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+
"errors"
20+
"fmt"
21+
"io/ioutil"
22+
"net/http"
23+
"strings"
24+
25+
"github.com/getkin/kin-openapi/openapi3"
26+
"github.com/getkin/kin-openapi/openapi3filter"
27+
"github.com/getkin/kin-openapi/routers"
28+
"github.com/getkin/kin-openapi/routers/gorillamux"
29+
"github.com/gin-gonic/gin"
30+
)
31+
32+
const GinContextKey = "oapi-codegen/gin-context"
33+
const UserDataKey = "oapi-codegen/user-data"
34+
35+
// Create validator middleware from a YAML file path
36+
func OapiValidatorFromYamlFile(path string) (gin.HandlerFunc, error) {
37+
data, err := ioutil.ReadFile(path)
38+
if err != nil {
39+
return nil, fmt.Errorf("error reading %s: %s", path, err)
40+
}
41+
42+
swagger, err := openapi3.NewLoader().LoadFromData(data)
43+
if err != nil {
44+
return nil, fmt.Errorf("error parsing %s as Swagger YAML: %s",
45+
path, err)
46+
}
47+
return OapiRequestValidator(swagger), nil
48+
}
49+
50+
// This is an gin middleware function which validates incoming HTTP requests
51+
// to make sure that they conform to the given OAPI 3.0 specification. When
52+
// OAPI validation fails on the request, we return an HTTP/400 with error message
53+
func OapiRequestValidator(swagger *openapi3.T) gin.HandlerFunc {
54+
return OapiRequestValidatorWithOptions(swagger, nil)
55+
}
56+
57+
// Options to customize request validation. These are passed through to
58+
// openapi3filter.
59+
type Options struct {
60+
Options openapi3filter.Options
61+
ParamDecoder openapi3filter.ContentParameterDecoder
62+
UserData interface{}
63+
}
64+
65+
// Create a validator from a swagger object, with validation options
66+
func OapiRequestValidatorWithOptions(swagger *openapi3.T, options *Options) gin.HandlerFunc {
67+
router, err := gorillamux.NewRouter(swagger)
68+
if err != nil {
69+
panic(err)
70+
}
71+
return func(c *gin.Context) {
72+
err := ValidateRequestFromContext(c, router, options)
73+
if err != nil {
74+
// note: i am not sure if this is the best way to handle this
75+
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
76+
}
77+
c.Next()
78+
}
79+
}
80+
81+
// ValidateRequestFromContext is called from the middleware above and actually does the work
82+
// of validating a request.
83+
func ValidateRequestFromContext(c *gin.Context, router routers.Router, options *Options) error {
84+
req := c.Request
85+
route, pathParams, err := router.FindRoute(req)
86+
87+
// We failed to find a matching route for the request.
88+
if err != nil {
89+
switch e := err.(type) {
90+
case *routers.RouteError:
91+
// We've got a bad request, the path requested doesn't match
92+
// either server, or path, or something.
93+
return errors.New(e.Reason)
94+
default:
95+
// This should never happen today, but if our upstream code changes,
96+
// we don't want to crash the server, so handle the unexpected error.
97+
return fmt.Errorf("error validating route: %s", err.Error())
98+
}
99+
}
100+
101+
validationInput := &openapi3filter.RequestValidationInput{
102+
Request: req,
103+
PathParams: pathParams,
104+
Route: route,
105+
}
106+
107+
// Pass the gin context into the request validator, so that any callbacks
108+
// which it invokes make it available.
109+
requestContext := context.WithValue(context.Background(), GinContextKey, c)
110+
111+
if options != nil {
112+
validationInput.Options = &options.Options
113+
validationInput.ParamDecoder = options.ParamDecoder
114+
requestContext = context.WithValue(requestContext, UserDataKey, options.UserData)
115+
}
116+
117+
err = openapi3filter.ValidateRequest(requestContext, validationInput)
118+
if err != nil {
119+
switch e := err.(type) {
120+
case *openapi3filter.RequestError:
121+
// We've got a bad request
122+
// Split up the verbose error by lines and return the first one
123+
// openapi errors seem to be multi-line with a decent message on the first
124+
errorLines := strings.Split(e.Error(), "\n")
125+
return fmt.Errorf("error in openapi3filter.RequestError: %s", errorLines[0])
126+
case *openapi3filter.SecurityRequirementsError:
127+
return fmt.Errorf("error in openapi3filter.SecurityRequirementsError: %s", e.Error())
128+
default:
129+
// This should never happen today, but if our upstream code changes,
130+
// we don't want to crash the server, so handle the unexpected error.
131+
return fmt.Errorf("error validating request: %s", err)
132+
}
133+
}
134+
return nil
135+
}
136+
137+
// Helper function to get the echo context from within requests. It returns
138+
// nil if not found or wrong type.
139+
func GetGinContext(c context.Context) *gin.Context {
140+
iface := c.Value(GinContextKey)
141+
if iface == nil {
142+
return nil
143+
}
144+
ginCtx, ok := iface.(*gin.Context)
145+
if !ok {
146+
return nil
147+
}
148+
return ginCtx
149+
}
150+
151+
func GetUserData(c context.Context) interface{} {
152+
return c.Value(UserDataKey)
153+
}

0 commit comments

Comments
 (0)