Skip to content

Commit

Permalink
echo middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
enavasaitis committed Sep 5, 2019
1 parent 5af509b commit 59fc188
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 4 deletions.
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ require (
github.com/gin-gonic/gin v1.3.0
github.com/golang/protobuf v1.3.1 // indirect
github.com/json-iterator/go v1.1.6 // indirect
github.com/mattn/go-isatty v0.0.7 // indirect
github.com/labstack/echo v3.3.10+incompatible
github.com/labstack/gommon v0.3.0 // indirect
github.com/miketonks/swag v0.0.0-20190624155305-d1f7aa61ab61
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/stretchr/testify v1.3.0
github.com/stretchr/testify v1.4.0
github.com/ugorji/go v1.1.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.1.0
golang.org/x/net v0.0.0-20190607181551-461777fb6f67 // indirect
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
gopkg.in/go-playground/validator.v8 v8.18.2 // indirect
gopkg.in/yaml.v2 v2.2.2 // indirect
)
147 changes: 146 additions & 1 deletion swag-validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strings"

"github.com/gin-gonic/gin"
"github.com/labstack/echo"
"github.com/miketonks/swag/swagger"
"github.com/xeipuuv/gojsonschema"
)
Expand Down Expand Up @@ -140,7 +141,7 @@ func loadValueForKey(properties map[string]interface{}, key string, values []str
return result
}

// SwaggerValidator middleware
// SwaggerValidator Gin middleware
func SwaggerValidator(api *swagger.API) gin.HandlerFunc {

apiMap := map[string]gojsonschema.JSONLoader{}
Expand Down Expand Up @@ -283,6 +284,150 @@ func SwaggerValidator(api *swagger.API) gin.HandlerFunc {
}
}

// SwaggerValidatorEcho middleware
func SwaggerValidatorEcho(api *swagger.API) echo.MiddlewareFunc {

apiMap := map[string]gojsonschema.JSONLoader{}
for _, p := range api.Paths {
for _, e := range []*swagger.Endpoint{
p.Delete,
p.Get,
p.Post,
p.Put,
p.Patch,
p.Head,
p.Options,
p.Trace,
p.Connect} {
if e != nil && e.Handler != nil {
schema := buildRequestSchema(e)
schema.Definitions = buildSchemaDefinitions(api)
schemaLoader := gojsonschema.NewGoLoader(schema)

key := e.Method + e.Path
apiMap[key] = schemaLoader
}
}
}

// This part runs at runtime, with context for individual request
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
schemaLoader, found := apiMap[c.Request().Method+c.Path()]
if !found {
return next(c)
}
ref, _ := schemaLoader.LoadJSON()
properties, _ := ref.(map[string]interface{})["properties"].(map[string]interface{})

document := map[string]interface{}{}

for _, key := range c.ParamNames() {
document[key] = loadValueForKey(properties, key, []string{c.Param(key)})
}
for k, v := range c.Request().URL.Query() {
document[k] = loadValueForKey(properties, k, v)
}

// For muiltipart form, handle params and file uploads
if c.Request().Header.Get(echo.HeaderContentType) == "multipart/form-data" {
r := c.Request()
r.ParseMultipartForm(MaxMemory)

for k, v := range c.Request().PostForm {
document[k] = coerce(v[0], "", "")
}
if r.MultipartForm != nil && r.MultipartForm.File != nil {
for k := range r.MultipartForm.File {
document[k] = "x"
}
}
} else if c.Request().Header.Get(echo.HeaderContentType) == "application/x-www-form-urlencoded" {
r := c.Request()
r.ParseForm()

body := map[string]interface{}{}
for k, v := range c.Request().PostForm {
body[k] = coerce(v[0], "", "")
}
document["body"] = body
} else if c.Request().ContentLength > 0 {
// For all other types parse body as json, if possible

// read the response body to a variable
var body interface{}
b, err := ioutil.ReadAll(c.Request().Body)
if err != nil {
return c.JSON(
http.StatusBadRequest,
echo.Map{
"message": "Validation error",
"details": map[string]string{
"body": "Failed to read request body",
},
},
)
}
err = json.Unmarshal(b, &body)
// TODO Consider different error cases: Empty Body, Invalid JSON, Form Data
if err != nil {
return c.JSON(
http.StatusBadRequest,
echo.Map{
"message": "Validation error",
"details": map[string]string{
"body": "Invalid JSON format",
},
},
)
}
document["body"] = body

//reset the response body to the original unread state
c.Request().Body = ioutil.NopCloser(bytes.NewBuffer(b))
}

gojsonschema.Locale = CustomLocale{}

documentLoader := gojsonschema.NewGoLoader(document)
result, err := gojsonschema.Validate(schemaLoader, documentLoader)

if err != nil {
// fmt.Printf("ERROR: %s\n", err)
return c.JSON(http.StatusInternalServerError, echo.Map{
"message": "swagger document " + err.Error(),
})

} else if result.Valid() {
// fmt.Printf("The document is valid\n")
next(c)
} else {
// fmt.Printf("The document is not valid. see errors :\n")
errors := map[string]string{}
for _, err := range result.Errors() {
description := err.Description()
details := err.Details()

field := details["field"].(string)
if val, ok := details["property"]; ok {
field += "." + val.(string)
}
field = strings.TrimPrefix(field, "body.")
field = strings.TrimPrefix(field, "(root).")
errors[field] = description
}
// fmt.Printf("The document is not valid. see errors : %+v\n", errors)
return c.JSON(http.StatusBadRequest, echo.Map{
"message": "Validation error",
"details": errors,
})
}

return nil
}
}
}

// Data types are defined here: https://swagger.io/specification/#dataTypes
func coerce(value string, valueType string, valueFormat string) interface{} {
switch valueType {
Expand Down

0 comments on commit 59fc188

Please sign in to comment.