update vendor/ dir to latest w/o heroku, moby

had to lock a lot of things in place
This commit is contained in:
Reed Allman
2017-08-03 02:38:15 -07:00
parent 780791da1c
commit 30f3c45dbc
5637 changed files with 191713 additions and 1133103 deletions

View File

@@ -1,6 +1,6 @@
language: go
go:
- 1.7.1
- 1.8.x
install:
- go get -u github.com/axw/gocov/gocov
- go get -u gopkg.in/matm/v1/gocov-html
@@ -8,7 +8,6 @@ install:
- go get -u github.com/stretchr/testify/assert
- go get -u golang.org/x/net/context
- go get -u gopkg.in/yaml.v2
- go get -u github.com/gorilla/context
- go get -u github.com/go-openapi/analysis
- go get -u github.com/go-openapi/errors
- go get -u github.com/go-openapi/loads
@@ -21,4 +20,4 @@ after_success:
- bash <(curl -s https://codecov.io/bash)
notifications:
slack:
secure: EmObnQuM9Mw8J9vpFaKKHqSMN4Wsr/A9+v7ewAD5cEhA0T1P4m7MbJMiJOhxUhj/X+BFh2DamW+P2lT8mybj5wg8wnkQ2BteKA8Tawi6f9PRw2NRheO8tAi8o/npLnlmet0kc93mn+oLuqHw36w4+j5mkOl2FghkfGiUVhwrhkCP7KXQN+3TU87e+/HzQumlJ3nsE+6terVxkH3PmaUTsS5ONaODZfuxFpfb7RsoEl3skHf6d+tr+1nViLxxly7558Nc33C+W1mr0qiEvMLZ+kJ/CpGWBJ6CUJM3jm6hNe2eMuIPwEK2hxZob8c7n22VPap4K6a0bBRoydoDXaba+2sD7Ym6ivDO/DVyL44VeBBLyIiIBylDGQdZH+6SoWm90Qe/i7tnY/T5Ao5igT8f3cfQY1c3EsTfqmlDfrhmACBmwSlgkdVBLTprHL63JMY24LWmh4jhxsmMRZhCL4dze8su1w6pLN/pD1pGHtKYCEVbdTmaM3PblNRFf12XB7qosmQsgUndH4Vq3bTbU0s1pKjeDhRyLvFzvR0TBbo0pDLEoF1A/i5GVFWa7yLZNUDudQERRh7qv/xBl2excIaQ1sV4DSVm7bAE9l6Kp+yeHQJW2uN6Y3X8wu9gB9nv9l5HBze7wh8KE6PyWAOLYYqZg9/sAtsv/2GcQqXcKFF1zcA=
secure: EmObnQuM9Mw8J9vpFaKKHqSMN4Wsr/A9+v7ewAD5cEhA0T1P4m7MbJMiJOhxUhj/X+BFh2DamW+P2lT8mybj5wg8wnkQ2BteKA8Tawi6f9PRw2NRheO8tAi8o/npLnlmet0kc93mn+oLuqHw36w4+j5mkOl2FghkfGiUVhwrhkCP7KXQN+3TU87e+/HzQumlJ3nsE+6terVxkH3PmaUTsS5ONaODZfuxFpfb7RsoEl3skHf6d+tr+1nViLxxly7558Nc33C+W1mr0qiEvMLZ+kJ/CpGWBJ6CUJM3jm6hNe2eMuIPwEK2hxZob8c7n22VPap4K6a0bBRoydoDXaba+2sD7Ym6ivDO/DVyL44VeBBLyIiIBylDGQdZH+6SoWm90Qe/i7tnY/T5Ao5igT8f3cfQY1c3EsTfqmlDfrhmACBmwSlgkdVBLTprHL63JMY24LWmh4jhxsmMRZhCL4dze8su1w6pLN/pD1pGHtKYCEVbdTmaM3PblNRFf12XB7qosmQsgUndH4Vq3bTbU0s1pKjeDhRyLvFzvR0TBbo0pDLEoF1A/i5GVFWa7yLZNUDudQERRh7qv/xBl2excIaQ1sV4DSVm7bAE9l6Kp+yeHQJW2uN6Y3X8wu9gB9nv9l5HBze7wh8KE6PyWAOLYYqZg9/sAtsv/2GcQqXcKFF1zcA=

View File

@@ -38,9 +38,9 @@ func TestBindRequest_BodyValidation(t *testing.T) {
if assert.NoError(t, err) {
req.Header.Set("Content-Type", runtime.JSONMime)
ri, ok := ctx.RouteInfo(req)
ri, rCtx, ok := ctx.RouteInfo(req)
if assert.True(t, ok) {
req = rCtx
err := ctx.BindValidRequest(req, ri, rbn(func(r *http.Request, rr *MatchedRoute) error {
defer r.Body.Close()
var data interface{}
@@ -65,9 +65,9 @@ func TestBindRequest_DeleteNoBody(t *testing.T) {
if assert.NoError(t, err) {
req.Header.Set("Accept", "*/*")
ri, ok := ctx.RouteInfo(req)
ri, rCtx, ok := ctx.RouteInfo(req)
if assert.True(t, ok) {
req = rCtx
err := ctx.BindValidRequest(req, ri, rbn(func(r *http.Request, rr *MatchedRoute) error {
return nil
}))
@@ -83,9 +83,9 @@ func TestBindRequest_DeleteNoBody(t *testing.T) {
req.Header.Set("Content-Type", runtime.JSONMime)
req.ContentLength = 1
ri, ok := ctx.RouteInfo(req)
ri, rCtx, ok := ctx.RouteInfo(req)
if assert.True(t, ok) {
req = rCtx
err := ctx.BindValidRequest(req, ri, rbn(func(r *http.Request, rr *MatchedRoute) error {
defer r.Body.Close()
var data interface{}

View File

@@ -15,6 +15,7 @@
package middleware
import (
stdContext "context"
"log"
"net/http"
"os"
@@ -29,7 +30,6 @@ import (
"github.com/go-openapi/runtime/security"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
"github.com/gorilla/context"
)
// Debug when true turns on verbose logging
@@ -68,7 +68,8 @@ func (fn ResponderFunc) WriteResponse(rw http.ResponseWriter, pr runtime.Produce
}
// Context is a type safe wrapper around an untyped request context
// used throughout to store request context with the gorilla context module
// used throughout to store request context with the standard context attached
// to the http.Request
type Context struct {
spec *loads.Document
analyzer *analysis.Spec
@@ -106,10 +107,15 @@ func newRoutableUntypedAPI(spec *loads.Document, api *untyped.API, context *Cont
var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// lookup route info in the context
route, _ := context.RouteInfo(r)
route, rCtx, _ := context.RouteInfo(r)
if rCtx != nil {
r = rCtx
}
// bind and validate the request using reflection
bound, validation := context.BindAndValidate(r, route)
var bound interface{}
var validation error
bound, r, validation = context.BindAndValidate(r, route)
if validation != nil {
context.Respond(w, r, route.Produces, route, validation)
return
@@ -292,19 +298,23 @@ func (c *Context) BindValidRequest(request *http.Request, route *MatchedRoute, b
}
// ContentType gets the parsed value of a content type
func (c *Context) ContentType(request *http.Request) (string, string, error) {
if v, ok := context.GetOk(request, ctxContentType); ok {
if val, ok := v.(*contentTypeValue); ok {
return val.MediaType, val.Charset, nil
}
// Returns the media type, its charset and a shallow copy of the request
// when its context doesn't contain the content type value, otherwise it returns
// the same request
// Returns the error that runtime.ContentType may retunrs.
func (c *Context) ContentType(request *http.Request) (string, string, *http.Request, error) {
var rCtx = request.Context()
if v, ok := rCtx.Value(ctxContentType).(*contentTypeValue); ok {
return v.MediaType, v.Charset, request, nil
}
mt, cs, err := runtime.ContentType(request.Header)
if err != nil {
return "", "", err
return "", "", nil, err
}
context.Set(request, ctxContentType, &contentTypeValue{mt, cs})
return mt, cs, nil
rCtx = stdContext.WithValue(rCtx, ctxContentType, &contentTypeValue{mt, cs})
return mt, cs, request.WithContext(rCtx), nil
}
// LookupRoute looks a route up and returns true when it is found
@@ -316,34 +326,43 @@ func (c *Context) LookupRoute(request *http.Request) (*MatchedRoute, bool) {
}
// RouteInfo tries to match a route for this request
func (c *Context) RouteInfo(request *http.Request) (*MatchedRoute, bool) {
if v, ok := context.GetOk(request, ctxMatchedRoute); ok {
if val, ok := v.(*MatchedRoute); ok {
return val, ok
}
// Returns the matched route, a shallow copy of the request if its context
// contains the matched router, otherwise the same request, and a bool to
// indicate if it the request matches one of the routes, if it doesn't
// then it returns false and nil for the other two return values
func (c *Context) RouteInfo(request *http.Request) (*MatchedRoute, *http.Request, bool) {
var rCtx = request.Context()
if v, ok := rCtx.Value(ctxMatchedRoute).(*MatchedRoute); ok {
return v, request, ok
}
if route, ok := c.LookupRoute(request); ok {
context.Set(request, ctxMatchedRoute, route)
return route, ok
rCtx = stdContext.WithValue(rCtx, ctxMatchedRoute, route)
return route, request.WithContext(rCtx), ok
}
return nil, false
return nil, nil, false
}
// ResponseFormat negotiates the response content type
func (c *Context) ResponseFormat(r *http.Request, offers []string) string {
if v, ok := context.GetOk(r, ctxResponseFormat); ok {
if val, ok := v.(string); ok {
return val
}
// Returns the response format and a shallow copy of the request if its context
// doesn't contain the response format, otherwise the same request
func (c *Context) ResponseFormat(r *http.Request, offers []string) (string, *http.Request) {
var rCtx = r.Context()
if v, ok := rCtx.Value(ctxResponseFormat).(string); ok {
debugLog("[%s %s] found response format %q in context", r.Method, r.URL.Path, v)
return v, r
}
format := NegotiateContentType(r, offers, "")
if format != "" {
context.Set(r, ctxResponseFormat, format)
debugLog("[%s %s] set response format %q in context", r.Method, r.URL.Path, format)
r = r.WithContext(stdContext.WithValue(rCtx, ctxResponseFormat, format))
}
return format
debugLog("[%s %s] negotiated response format %q", r.Method, r.URL.Path, format)
return format, r
}
// AllowedMethods gets the allowed methods for the path of this request
@@ -352,12 +371,17 @@ func (c *Context) AllowedMethods(request *http.Request) []string {
}
// Authorize authorizes the request
func (c *Context) Authorize(request *http.Request, route *MatchedRoute) (interface{}, error) {
// Returns the principal object and a shallow copy of the request when its
// context doesn't contain the principal, otherwise the same request or an error
// (the last) if one of the authenticators returns one or an Unauthenticated error
func (c *Context) Authorize(request *http.Request, route *MatchedRoute) (interface{}, *http.Request, error) {
if route == nil || len(route.Authenticators) == 0 {
return nil, nil
return nil, nil, nil
}
if v, ok := context.GetOk(request, ctxSecurityPrincipal); ok {
return v, nil
var rCtx = request.Context()
if v := rCtx.Value(ctxSecurityPrincipal); v != nil {
return v, request, nil
}
var lastError error
@@ -372,38 +396,40 @@ func (c *Context) Authorize(request *http.Request, route *MatchedRoute) (interfa
}
continue
}
context.Set(request, ctxSecurityPrincipal, usr)
context.Set(request, ctxSecurityScopes, route.Scopes[scheme])
return usr, nil
rCtx = stdContext.WithValue(rCtx, ctxSecurityPrincipal, usr)
rCtx = stdContext.WithValue(rCtx, ctxSecurityScopes, route.Scopes[scheme])
return usr, request.WithContext(rCtx), nil
}
if lastError != nil {
return nil, lastError
return nil, nil, lastError
}
return nil, errors.Unauthenticated("invalid credentials")
return nil, nil, errors.Unauthenticated("invalid credentials")
}
// BindAndValidate binds and validates the request
func (c *Context) BindAndValidate(request *http.Request, matched *MatchedRoute) (interface{}, error) {
if v, ok := context.GetOk(request, ctxBoundParams); ok {
if val, ok := v.(*validation); ok {
debugLog("got cached validation (valid: %t)", len(val.result) == 0)
if len(val.result) > 0 {
return val.bound, errors.CompositeValidationError(val.result...)
}
return val.bound, nil
// Returns the validation map and a shallow copy of the request when its context
// doesn't contain the validation, otherwise it returns the same request or an
// CompositeValidationError error
func (c *Context) BindAndValidate(request *http.Request, matched *MatchedRoute) (interface{}, *http.Request, error) {
var rCtx = request.Context()
if v, ok := rCtx.Value(ctxBoundParams).(*validation); ok {
debugLog("got cached validation (valid: %t)", len(v.result) == 0)
if len(v.result) > 0 {
return v.bound, request, errors.CompositeValidationError(v.result...)
}
return v.bound, request, nil
}
result := validateRequest(c, request, matched)
if result != nil {
context.Set(request, ctxBoundParams, result)
}
rCtx = stdContext.WithValue(rCtx, ctxBoundParams, result)
request = request.WithContext(rCtx)
if len(result.result) > 0 {
return result.bound, errors.CompositeValidationError(result.result...)
return result.bound, request, errors.CompositeValidationError(result.result...)
}
debugLog("no validation errors found")
return result.bound, nil
return result.bound, request, nil
}
// NotFound the default not found responder for when no route has been matched yet
@@ -413,6 +439,7 @@ func (c *Context) NotFound(rw http.ResponseWriter, r *http.Request) {
// Respond renders the response after doing some content negotiation
func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []string, route *MatchedRoute, data interface{}) {
debugLog("responding to %s %s with produces: %v", r.Method, r.URL.Path, produces)
offers := []string{}
for _, mt := range produces {
if mt != c.api.DefaultProduces() {
@@ -421,15 +448,17 @@ func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []st
}
// the default producer is last so more specific producers take precedence
offers = append(offers, c.api.DefaultProduces())
debugLog("offers: %v", offers)
format := c.ResponseFormat(r, offers)
var format string
format, r = c.ResponseFormat(r, offers)
rw.Header().Set(runtime.HeaderContentType, format)
if resp, ok := data.(Responder); ok {
producers := route.Producers
prod, ok := producers[format]
if !ok {
prods := c.api.ProducersFor([]string{c.api.DefaultProduces()})
prods := c.api.ProducersFor(normalizeOffers([]string{c.api.DefaultProduces()}))
pr, ok := prods[c.api.DefaultProduces()]
if !ok {
panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
@@ -457,7 +486,7 @@ func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []st
if r.Method == "HEAD" {
return
}
producers := c.api.ProducersFor(offers)
producers := c.api.ProducersFor(normalizeOffers(offers))
prod, ok := producers[format]
if !ok {
panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
@@ -478,7 +507,7 @@ func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []st
prod, ok := producers[format]
if !ok {
if !ok {
prods := c.api.ProducersFor([]string{c.api.DefaultProduces()})
prods := c.api.ProducersFor(normalizeOffers([]string{c.api.DefaultProduces()}))
pr, ok := prods[c.api.DefaultProduces()]
if !ok {
panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))

View File

@@ -25,7 +25,6 @@ import (
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/internal/testing/petstore"
"github.com/go-openapi/runtime/middleware/untyped"
"github.com/gorilla/context"
"github.com/stretchr/testify/assert"
)
@@ -94,42 +93,49 @@ func TestContextAuthorize(t *testing.T) {
request, _ := runtime.JSONRequest("GET", "/api/pets", nil)
v, ok := context.GetOk(request, ctxSecurityPrincipal)
assert.False(t, ok)
assert.Nil(t, v)
ri, ok := ctx.RouteInfo(request)
ri, reqWithCtx, ok := ctx.RouteInfo(request)
assert.True(t, ok)
p, err := ctx.Authorize(request, ri)
assert.NotNil(t, reqWithCtx)
request = reqWithCtx
p, reqWithCtx, err := ctx.Authorize(request, ri)
assert.Error(t, err)
assert.Nil(t, p)
assert.Nil(t, reqWithCtx)
v, ok = context.GetOk(request, ctxSecurityPrincipal)
assert.False(t, ok)
v := request.Context().Value(ctxSecurityPrincipal)
assert.Nil(t, v)
request.SetBasicAuth("wrong", "wrong")
p, err = ctx.Authorize(request, ri)
p, reqWithCtx, err = ctx.Authorize(request, ri)
assert.Error(t, err)
assert.Nil(t, p)
assert.Nil(t, reqWithCtx)
v, ok = context.GetOk(request, ctxSecurityPrincipal)
assert.False(t, ok)
v = request.Context().Value(ctxSecurityPrincipal)
assert.Nil(t, v)
request.SetBasicAuth("admin", "admin")
p, err = ctx.Authorize(request, ri)
p, reqWithCtx, err = ctx.Authorize(request, ri)
assert.NoError(t, err)
assert.Equal(t, "admin", p)
assert.NotNil(t, reqWithCtx)
v, ok = context.GetOk(request, ctxSecurityPrincipal)
// Assign the new returned request to follow with the test
request = reqWithCtx
v, ok = request.Context().Value(ctxSecurityPrincipal).(string)
assert.True(t, ok)
assert.Equal(t, "admin", v)
// Once the request context contains the principal the authentication
// isn't rechecked
request.SetBasicAuth("doesn't matter", "doesn't")
pp, rr := ctx.Authorize(request, ri)
pp, reqCtx, rr := ctx.Authorize(request, ri)
assert.Equal(t, p, pp)
assert.Equal(t, err, rr)
assert.Equal(t, request, reqCtx)
}
func TestContextNegotiateContentType(t *testing.T) {
@@ -141,17 +147,13 @@ func TestContextNegotiateContentType(t *testing.T) {
// request.Header.Add("Accept", "*/*")
request.Header.Add("content-type", "text/html")
v, ok := context.GetOk(request, ctxBoundParams)
assert.False(t, ok)
v := request.Context().Value(ctxBoundParams)
assert.Nil(t, v)
ri, _ := ctx.RouteInfo(request)
ri, request, _ := ctx.RouteInfo(request)
res := NegotiateContentType(request, ri.Produces, "")
assert.Equal(t, "", res)
res2 := NegotiateContentType(request, ri.Produces, "text/plain")
assert.Equal(t, "text/plain", res2)
res := NegotiateContentType(request, ri.Produces, "text/plain")
assert.Equal(t, ri.Produces[0], res)
}
func TestContextBindAndValidate(t *testing.T) {
@@ -164,22 +166,22 @@ func TestContextBindAndValidate(t *testing.T) {
request.Header.Add("content-type", "text/html")
request.ContentLength = 1
v, ok := context.GetOk(request, ctxBoundParams)
assert.False(t, ok)
v := request.Context().Value(ctxBoundParams)
assert.Nil(t, v)
ri, _ := ctx.RouteInfo(request)
data, result := ctx.BindAndValidate(request, ri) // this requires a much more thorough test
ri, request, _ := ctx.RouteInfo(request)
data, request, result := ctx.BindAndValidate(request, ri) // this requires a much more thorough test
assert.NotNil(t, data)
assert.NotNil(t, result)
v, ok = context.GetOk(request, ctxBoundParams)
v, ok := request.Context().Value(ctxBoundParams).(*validation)
assert.True(t, ok)
assert.NotNil(t, v)
dd, rr := ctx.BindAndValidate(request, ri)
dd, rCtx, rr := ctx.BindAndValidate(request, ri)
assert.Equal(t, data, dd)
assert.Equal(t, result, rr)
assert.Equal(t, rCtx, request)
}
func TestContextRender(t *testing.T) {
@@ -191,9 +193,9 @@ func TestContextRender(t *testing.T) {
ctx := NewContext(spec, api, nil)
ctx.router = DefaultRouter(spec, ctx.api)
request, _ := http.NewRequest("GET", "pets", nil)
request, _ := http.NewRequest("GET", "/api/pets", nil)
request.Header.Set(runtime.HeaderAccept, ct)
ri, _ := ctx.RouteInfo(request)
ri, request, _ := ctx.RouteInfo(request)
recorder := httptest.NewRecorder()
ctx.Respond(recorder, request, []string{ct}, ri, map[string]interface{}{"name": "hello"})
@@ -207,13 +209,15 @@ func TestContextRender(t *testing.T) {
// recorder = httptest.NewRecorder()
// assert.Panics(t, func() { ctx.Respond(recorder, request, []string{ct}, ri, map[int]interface{}{1: "hello"}) })
// Panic when route is nil and there is not a producer for the requested response format
recorder = httptest.NewRecorder()
request, _ = http.NewRequest("GET", "pets", nil)
assert.Panics(t, func() { ctx.Respond(recorder, request, []string{}, ri, map[string]interface{}{"name": "hello"}) })
request, _ = http.NewRequest("GET", "/api/pets", nil)
request.Header.Set(runtime.HeaderAccept, "text/xml")
assert.Panics(t, func() { ctx.Respond(recorder, request, []string{}, nil, map[string]interface{}{"name": "hello"}) })
request, _ = http.NewRequest("GET", "/pets", nil)
request, _ = http.NewRequest("GET", "/api/pets", nil)
request.Header.Set(runtime.HeaderAccept, ct)
ri, _ = ctx.RouteInfo(request)
ri, request, _ = ctx.RouteInfo(request)
recorder = httptest.NewRecorder()
ctx.Respond(recorder, request, []string{ct}, ri, map[string]interface{}{"name": "hello"})
@@ -233,7 +237,7 @@ func TestContextRender(t *testing.T) {
recorder = httptest.NewRecorder()
request, _ = http.NewRequest("DELETE", "/api/pets/1", nil)
ri, _ = ctx.RouteInfo(request)
ri, request, _ = ctx.RouteInfo(request)
ctx.Respond(recorder, request, ri.Produces, ri, nil)
assert.Equal(t, 204, recorder.Code)
}
@@ -248,21 +252,21 @@ func TestContextValidResponseFormat(t *testing.T) {
request.Header.Set(runtime.HeaderAccept, ct)
// check there's nothing there
cached, ok := context.GetOk(request, ctxResponseFormat)
cached, ok := request.Context().Value(ctxResponseFormat).(string)
assert.False(t, ok)
assert.Empty(t, cached)
// trigger the parse
mt := ctx.ResponseFormat(request, []string{ct})
mt, request := ctx.ResponseFormat(request, []string{ct})
assert.Equal(t, ct, mt)
// check it was cached
cached, ok = context.GetOk(request, ctxResponseFormat)
cached, ok = request.Context().Value(ctxResponseFormat).(string)
assert.True(t, ok)
assert.Equal(t, ct, cached)
// check if the cast works and fetch from cache too
mt = ctx.ResponseFormat(request, []string{ct})
mt, request = ctx.ResponseFormat(request, []string{ct})
assert.Equal(t, ct, mt)
}
@@ -277,22 +281,23 @@ func TestContextInvalidResponseFormat(t *testing.T) {
request.Header.Set(runtime.HeaderAccept, ct)
// check there's nothing there
cached, ok := context.GetOk(request, ctxResponseFormat)
cached, ok := request.Context().Value(ctxResponseFormat).(string)
assert.False(t, ok)
assert.Empty(t, cached)
// trigger the parse
mt := ctx.ResponseFormat(request, []string{other})
mt, request := ctx.ResponseFormat(request, []string{other})
assert.Empty(t, mt)
// check it was cached
cached, ok = context.GetOk(request, ctxResponseFormat)
cached, ok = request.Context().Value(ctxResponseFormat).(string)
assert.False(t, ok)
assert.Empty(t, cached)
// check if the cast works and fetch from cache too
mt = ctx.ResponseFormat(request, []string{other})
mt, rCtx := ctx.ResponseFormat(request, []string{other})
assert.Empty(t, mt)
assert.Equal(t, request, rCtx)
}
func TestContextValidRoute(t *testing.T) {
@@ -303,20 +308,25 @@ func TestContextValidRoute(t *testing.T) {
request, _ := http.NewRequest("GET", "/api/pets", nil)
// check there's nothing there
_, ok := context.GetOk(request, ctxMatchedRoute)
assert.False(t, ok)
cached := request.Context().Value(ctxMatchedRoute)
assert.Nil(t, cached)
matched, ok := ctx.RouteInfo(request)
matched, rCtx, ok := ctx.RouteInfo(request)
assert.True(t, ok)
assert.NotNil(t, matched)
assert.NotNil(t, rCtx)
assert.NotEqual(t, request, rCtx)
request = rCtx
// check it was cached
_, ok = context.GetOk(request, ctxMatchedRoute)
_, ok = request.Context().Value(ctxMatchedRoute).(*MatchedRoute)
assert.True(t, ok)
matched, ok = ctx.RouteInfo(request)
matched, rCtx, ok = ctx.RouteInfo(request)
assert.True(t, ok)
assert.NotNil(t, matched)
assert.Equal(t, request, rCtx)
}
func TestContextInvalidRoute(t *testing.T) {
@@ -327,20 +337,22 @@ func TestContextInvalidRoute(t *testing.T) {
request, _ := http.NewRequest("DELETE", "pets", nil)
// check there's nothing there
_, ok := context.GetOk(request, ctxMatchedRoute)
assert.False(t, ok)
cached := request.Context().Value(ctxMatchedRoute)
assert.Nil(t, cached)
matched, ok := ctx.RouteInfo(request)
matched, rCtx, ok := ctx.RouteInfo(request)
assert.False(t, ok)
assert.Nil(t, matched)
assert.Nil(t, rCtx)
// check it was cached
_, ok = context.GetOk(request, ctxMatchedRoute)
assert.False(t, ok)
// check it was not cached
cached = request.Context().Value(ctxMatchedRoute)
assert.Nil(t, cached)
matched, ok = ctx.RouteInfo(request)
matched, rCtx, ok = ctx.RouteInfo(request)
assert.False(t, ok)
assert.Nil(t, matched)
assert.Nil(t, rCtx)
}
func TestContextValidContentType(t *testing.T) {
@@ -351,22 +363,27 @@ func TestContextValidContentType(t *testing.T) {
request.Header.Set(runtime.HeaderContentType, ct)
// check there's nothing there
_, ok := context.GetOk(request, ctxContentType)
assert.False(t, ok)
cached := request.Context().Value(ctxContentType)
assert.Nil(t, cached)
// trigger the parse
mt, _, err := ctx.ContentType(request)
mt, _, rCtx, err := ctx.ContentType(request)
assert.NoError(t, err)
assert.Equal(t, ct, mt)
assert.NotNil(t, rCtx)
assert.NotEqual(t, request, rCtx)
request = rCtx
// check it was cached
_, ok = context.GetOk(request, ctxContentType)
assert.True(t, ok)
cached = request.Context().Value(ctxContentType)
assert.NotNil(t, cached)
// check if the cast works and fetch from cache too
mt, _, err = ctx.ContentType(request)
mt, _, rCtx, err = ctx.ContentType(request)
assert.NoError(t, err)
assert.Equal(t, ct, mt)
assert.Equal(t, request, rCtx)
}
func TestContextInvalidContentType(t *testing.T) {
@@ -377,19 +394,21 @@ func TestContextInvalidContentType(t *testing.T) {
request.Header.Set(runtime.HeaderContentType, ct)
// check there's nothing there
_, ok := context.GetOk(request, ctxContentType)
assert.False(t, ok)
cached := request.Context().Value(ctxContentType)
assert.Nil(t, cached)
// trigger the parse
mt, _, err := ctx.ContentType(request)
mt, _, rCtx, err := ctx.ContentType(request)
assert.Error(t, err)
assert.Empty(t, mt)
assert.Nil(t, rCtx)
// check it was not cached
_, ok = context.GetOk(request, ctxContentType)
assert.False(t, ok)
cached = request.Context().Value(ctxContentType)
assert.Nil(t, cached)
// check if the failure continues
_, _, err = ctx.ContentType(request)
_, _, rCtx, err = ctx.ContentType(request)
assert.Error(t, err)
assert.Nil(t, rCtx)
}

View File

@@ -20,13 +20,10 @@ Pseudo middleware handler
"net/http"
"github.com/go-openapi/errors"
"github.com/gorilla/context"
)
func newCompleteMiddleware(ctx *Context) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
defer context.Clear(r)
// use context to lookup routes
if matched, ok := ctx.RouteInfo(r); ok {

View File

@@ -134,9 +134,12 @@ func ParseList(header http.Header, key string) []string {
// ParseValueAndParams parses a comma separated list of values with optional
// semicolon separated name-value pairs. Content-Type and Content-Disposition
// headers are in this format.
func ParseValueAndParams(header http.Header, key string) (value string, params map[string]string) {
func ParseValueAndParams(header http.Header, key string) (string, map[string]string) {
return parseValueAndParams(header.Get(key))
}
func parseValueAndParams(s string) (value string, params map[string]string) {
params = make(map[string]string)
s := header.Get(key)
value, s = expectTokenSlash(s)
if value == "" {
return
@@ -169,6 +172,26 @@ type AcceptSpec struct {
Q float64
}
func ParseAccept2(header http.Header, key string) (specs []AcceptSpec) {
for _, en := range ParseList(header, key) {
v, p := parseValueAndParams(en)
var spec AcceptSpec
spec.Value = v
spec.Q = 1.0
if p != nil {
if q, ok := p["q"]; ok {
spec.Q, _ = expectQuality(q)
}
}
if spec.Q < 0.0 {
continue
}
specs = append(specs, spec)
}
return
}
// ParseAccept parses Accept* headers.
func ParseAccept(header http.Header, key string) (specs []AcceptSpec) {
loop:
@@ -183,12 +206,14 @@ loop:
s = skipSpace(s)
if strings.HasPrefix(s, ";") {
s = skipSpace(s[1:])
if !strings.HasPrefix(s, "q=") {
continue loop
for !strings.HasPrefix(s, "q=") && s != "" && !strings.HasPrefix(s, ",") {
s = skipSpace(s[1:])
}
spec.Q, s = expectQuality(s[2:])
if spec.Q < 0.0 {
continue loop
if strings.HasPrefix(s, "q=") {
spec.Q, s = expectQuality(s[2:])
if spec.Q < 0.0 {
continue loop
}
}
}
specs = append(specs, spec)
@@ -282,14 +307,14 @@ func expectTokenOrQuoted(s string) (value string, rest string) {
case escape:
escape = false
p[j] = b
j += 1
j++
case b == '\\':
escape = true
case b == '"':
return string(p[:j]), s[i+1:]
default:
p[j] = b
j += 1
j++
}
}
return "", ""

View File

@@ -48,7 +48,11 @@ func NegotiateContentType(r *http.Request, offers []string, defaultOffer string)
bestQ := -1.0
bestWild := 3
specs := header.ParseAccept(r.Header, "Accept")
for _, offer := range offers {
for _, offer := range normalizeOffers(offers) {
// No Accept header: just return the first offer.
if len(specs) == 0 {
return offer
}
for _, spec := range specs {
switch {
case spec.Q == 0.0:
@@ -80,3 +84,10 @@ func NegotiateContentType(r *http.Request, offers []string, defaultOffer string)
}
return bestOffer
}
func normalizeOffers(orig []string) (norm []string) {
for _, o := range orig {
norm = append(norm, strings.SplitN(o, ";", 2)[0])
}
return
}

View File

@@ -50,6 +50,7 @@ var negotiateContentTypeTests = []struct {
{"text/html;q=0.5, image/png", []string{"text/html"}, "", "text/html"},
{"text/html;q=0.5, image/png", []string{"image/png", "text/html"}, "", "image/png"},
{"text/html;q=0.5, image/png", []string{"text/html", "image/png"}, "", "image/png"},
{"text/html;q=0.5, image/png", []string{"text/html", "image/png"}, "", "image/png"},
{"image/png, image/*;q=0.5", []string{"image/jpg", "image/png"}, "", "image/png"},
{"image/png, image/*;q=0.5", []string{"image/jpg"}, "", "image/jpg"},
{"image/png, image/*;q=0.5", []string{"image/jpg", "image/gif"}, "", "image/jpg"},
@@ -57,6 +58,7 @@ var negotiateContentTypeTests = []struct {
{"image/png, image/*", []string{"image/gif", "image/jpg"}, "", "image/gif"},
{"image/png, image/*", []string{"image/gif", "image/png"}, "", "image/png"},
{"image/png, image/*", []string{"image/png", "image/gif"}, "", "image/png"},
{"application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.7,text/plain;version=0.0.4;q=0.3", []string{"text/plain"}, "", "text/plain"},
}
func TestNegotiateContentType(t *testing.T) {
@@ -68,3 +70,12 @@ func TestNegotiateContentType(t *testing.T) {
}
}
}
func TestNegotiateContentTypeNoAcceptHeader(t *testing.T) {
r := &http.Request{Header: http.Header{}}
offers := []string{"application/json", "text/xml"}
actual := NegotiateContentType(r, offers, "")
if actual != "application/json" {
t.Errorf("NegotiateContentType(empty, %#v, empty)=%q, want %q", offers, actual, "application/json")
}
}

View File

@@ -20,7 +20,11 @@ import "net/http"
func NewOperationExecutor(ctx *Context) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
// use context to lookup routes
route, _ := ctx.RouteInfo(r)
route, rCtx, _ := ctx.RouteInfo(r)
if rCtx != nil {
r = rCtx
}
route.Handler.ServeHTTP(rw, r)
})
}

View File

@@ -28,7 +28,6 @@ import (
"github.com/go-openapi/runtime/middleware/denco"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
"github.com/gorilla/context"
)
// RouteParam is a object to capture route params in a framework agnostic way.
@@ -71,9 +70,8 @@ func NewRouter(ctx *Context, next http.Handler) http.Handler {
}
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
defer context.Clear(r)
if _, ok := ctx.RouteInfo(r); ok {
next.ServeHTTP(rw, r)
if _, rCtx, ok := ctx.RouteInfo(r); ok {
next.ServeHTTP(rw, rCtx)
return
}
@@ -184,7 +182,7 @@ func (d *defaultRouter) Lookup(method, path string) (*MatchedRoute, bool) {
debugLog("found a route for %s %s with %d parameters", method, path, len(entry.Parameters))
var params RouteParams
for _, p := range rp {
v, err := url.QueryUnescape(p.Value)
v, err := url.PathUnescape(p.Value)
if err != nil {
debugLog("failed to escape %q: %v", p.Value, err)
v = p.Value
@@ -226,6 +224,7 @@ func (d *defaultRouteBuilder) AddRoute(method, path string, operation *spec.Oper
bp = bp[:len(bp)-1]
}
debugLog("operation: %#v", *operation)
if handler, ok := d.api.HandlerFor(method, strings.TrimPrefix(path, bp)); ok {
consumes := d.analyzer.ConsumesFor(operation)
produces := d.analyzer.ProducesFor(operation)
@@ -244,8 +243,8 @@ func (d *defaultRouteBuilder) AddRoute(method, path string, operation *spec.Oper
Handler: handler,
Consumes: consumes,
Produces: produces,
Consumers: d.api.ConsumersFor(consumes),
Producers: d.api.ProducersFor(produces),
Consumers: d.api.ConsumersFor(normalizeOffers(consumes)),
Producers: d.api.ProducersFor(normalizeOffers(produces)),
Parameters: parameters,
Formats: d.api.Formats(),
Binder: newUntypedRequestBinder(parameters, d.spec.Spec(), d.api.Formats()),

View File

@@ -167,7 +167,7 @@ func TestRouter_EscapedPath(t *testing.T) {
mw.ServeHTTP(recorder, request)
assert.Equal(t, 200, recorder.Code)
ri, _ := context.RouteInfo(request)
ri, _, _ := context.RouteInfo(request)
if assert.NotNil(t, ri) {
if assert.NotNil(t, ri.Params) {
assert.Equal(t, "abc/def", ri.Params.Get("id"))

View File

@@ -18,15 +18,20 @@ import "net/http"
func newSecureAPI(ctx *Context, next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
route, _ := ctx.RouteInfo(r)
route, rCtx, _ := ctx.RouteInfo(r)
if rCtx != nil {
r = rCtx
}
if route != nil && len(route.Authenticators) == 0 {
next.ServeHTTP(rw, r)
return
}
if _, err := ctx.Authorize(r, route); err != nil {
if _, rCtx, err := ctx.Authorize(r, route); err != nil {
ctx.Respond(rw, r, route.Produces, route, err)
return
} else {
r = rCtx
}
next.ServeHTTP(rw, r)

View File

@@ -28,12 +28,15 @@ import (
func newValidation(ctx *Context, next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
matched, _ := ctx.RouteInfo(r)
matched, rCtx, _ := ctx.RouteInfo(r)
if rCtx != nil {
r = rCtx
}
if matched == nil {
ctx.NotFound(rw, r)
return
}
_, result := ctx.BindAndValidate(r, matched)
_, r, result := ctx.BindAndValidate(r, matched)
if result != nil {
ctx.Respond(rw, r, matched.Produces, matched, result)
@@ -75,6 +78,13 @@ func validateContentType(allowed []string, actual string) error {
if swag.ContainsStringsCI(allowed, mt) {
return nil
}
if swag.ContainsStringsCI(allowed, "*/*") {
return nil
}
parts := strings.Split(actual, "/")
if len(parts) == 2 && swag.ContainsStringsCI(allowed, parts[0]+"/*") {
return nil
}
return errors.InvalidContentType(actual, allowed)
}
@@ -114,10 +124,13 @@ func (v *validation) parameters() {
func (v *validation) contentType() {
if len(v.result) == 0 && runtime.HasBody(v.request) {
debugLog("validating body content type for %s %s", v.request.Method, v.request.URL.EscapedPath())
ct, _, err := v.context.ContentType(v.request)
ct, _, req, err := v.context.ContentType(v.request)
if err != nil {
v.result = append(v.result, err)
} else {
v.request = req
}
if len(v.result) == 0 {
if err := validateContentType(v.route.Consumes, ct); err != nil {
v.result = append(v.result, err)
@@ -135,7 +148,8 @@ func (v *validation) contentType() {
}
func (v *validation) responseFormat() {
if str := v.context.ResponseFormat(v.request, v.route.Produces); str == "" && runtime.HasBody(v.request) {
if str, rCtx := v.context.ResponseFormat(v.request, v.route.Produces); str == "" && runtime.HasBody(v.request) {
v.request = rCtx
v.result = append(v.result, errors.InvalidResponseFormat(v.request.Header.Get(runtime.HeaderAccept), v.route.Produces))
}
}

View File

@@ -114,6 +114,8 @@ func TestValidateContentType(t *testing.T) {
{"text/html; charset=utf-8", []string{"application/json"}, errors.InvalidContentType("text/html; charset=utf-8", []string{"application/json"})},
{"application(", []string{"application/json"}, errors.InvalidContentType("application(", []string{"application/json"})},
{"application/json;char*", []string{"application/json"}, errors.InvalidContentType("application/json;char*", []string{"application/json"})},
{"application/octet-stream", []string{"image/jpeg", "application/*"}, nil},
{"image/png", []string{"*/*", "application/json"}, nil},
}
for _, v := range data {

View File

@@ -662,21 +662,27 @@ func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader) (*
return &target, nil
}
parentRefs = append(parentRefs, target.Ref.String())
target = *t
if t != nil {
target = *t
}
}
t, err := expandItems(target, parentRefs, resolver)
if shouldStopOnError(err, resolver.options) {
return &target, err
}
target = *t
if t != nil {
target = *t
}
for i := range target.AllOf {
t, err := expandSchema(target.AllOf[i], parentRefs, resolver)
if shouldStopOnError(err, resolver.options) {
return &target, err
}
target.AllOf[i] = *t
if t != nil {
target.AllOf[i] = *t
}
}
for i := range target.AnyOf {
t, err := expandSchema(target.AnyOf[i], parentRefs, resolver)
@@ -690,35 +696,45 @@ func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader) (*
if shouldStopOnError(err, resolver.options) {
return &target, err
}
target.OneOf[i] = *t
if t != nil {
target.OneOf[i] = *t
}
}
if target.Not != nil {
t, err := expandSchema(*target.Not, parentRefs, resolver)
if shouldStopOnError(err, resolver.options) {
return &target, err
}
*target.Not = *t
if t != nil {
*target.Not = *t
}
}
for k := range target.Properties {
t, err := expandSchema(target.Properties[k], parentRefs, resolver)
if shouldStopOnError(err, resolver.options) {
return &target, err
}
target.Properties[k] = *t
if t != nil {
target.Properties[k] = *t
}
}
if target.AdditionalProperties != nil && target.AdditionalProperties.Schema != nil {
t, err := expandSchema(*target.AdditionalProperties.Schema, parentRefs, resolver)
if shouldStopOnError(err, resolver.options) {
return &target, err
}
*target.AdditionalProperties.Schema = *t
if t != nil {
*target.AdditionalProperties.Schema = *t
}
}
for k := range target.PatternProperties {
t, err := expandSchema(target.PatternProperties[k], parentRefs, resolver)
if shouldStopOnError(err, resolver.options) {
return &target, err
}
target.PatternProperties[k] = *t
if t != nil {
target.PatternProperties[k] = *t
}
}
for k := range target.Dependencies {
if target.Dependencies[k].Schema != nil {
@@ -726,7 +742,9 @@ func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader) (*
if shouldStopOnError(err, resolver.options) {
return &target, err
}
*target.Dependencies[k].Schema = *t
if t != nil {
*target.Dependencies[k].Schema = *t
}
}
}
if target.AdditionalItems != nil && target.AdditionalItems.Schema != nil {
@@ -734,14 +752,18 @@ func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader) (*
if shouldStopOnError(err, resolver.options) {
return &target, err
}
*target.AdditionalItems.Schema = *t
if t != nil {
*target.AdditionalItems.Schema = *t
}
}
for k := range target.Definitions {
t, err := expandSchema(target.Definitions[k], parentRefs, resolver)
if shouldStopOnError(err, resolver.options) {
return &target, err
}
target.Definitions[k] = *t
if t != nil {
target.Definitions[k] = *t
}
}
return &target, nil
}

View File

@@ -214,6 +214,16 @@ func TestContinueOnErrorExpansion(t *testing.T) {
err = ExpandSpec(testCase.Input, opts)
assert.NoError(t, err)
assert.Equal(t, testCase.Input, testCase.Expected, "Should continue expanding spec when a definition can't be found.")
doc, err := jsonDoc("fixtures/expansion/missingItemRef.json")
spec := new(Swagger)
err = json.Unmarshal(doc, spec)
assert.NoError(t, err)
assert.NotPanics(t, func() {
err = ExpandSpec(spec, opts)
assert.NoError(t, err)
}, "Array of missing refs should not cause a panic, and continue to expand spec.")
}
func TestIssue415(t *testing.T) {

View File

@@ -0,0 +1,31 @@
{
"swagger": "2.0",
"info": {
"version": "2.1.0",
"title": "Missing Item API"
},
"host": "item.com",
"basePath": "/missing/ref",
"schemes": [
"http"
],
"paths": {
"/employees": {
"get": {
"operationId": "LIST-Employees",
"summary": "List Employee Types",
"responses": {
"200": {
"description": "",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/employees-output"
}
}
}
}
}
}
}
}

View File

@@ -125,20 +125,12 @@ func MustCreateRef(refURI string) Ref {
return Ref{Ref: jsonreference.MustCreateRef(refURI)}
}
// // NewResolvedRef creates a resolved ref
// func NewResolvedRef(refURI string, data interface{}) Ref {
// return Ref{
// Ref: jsonreference.MustCreateRef(refURI),
// Resolved: data,
// }
// }
// MarshalJSON marshals this ref into a JSON object
func (r Ref) MarshalJSON() ([]byte, error) {
str := r.String()
if str == "" {
if r.IsRoot() {
return []byte(`{"$ref":"#"}`), nil
return []byte(`{"$ref":""}`), nil
}
return []byte("{}"), nil
}

View File

@@ -0,0 +1,53 @@
package validate
import (
"encoding/json"
"fmt"
"io/ioutil"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
)
var defaulterFixturesPath = filepath.Join("fixtures", "defaulting")
func TestDefaulter(t *testing.T) {
fname := filepath.Join(defaulterFixturesPath, "schema.json")
b, err := ioutil.ReadFile(fname)
assert.NoError(t, err)
var schema spec.Schema
assert.NoError(t, json.Unmarshal(b, &schema))
err = spec.ExpandSchema(&schema, nil, nil /*new(noopResCache)*/)
assert.NoError(t, err, fname+" should expand cleanly")
validator := NewSchemaValidator(&schema, nil, "", strfmt.Default)
x := map[string]interface{}{
"nested": map[string]interface{}{},
"all": map[string]interface{}{},
"any": map[string]interface{}{},
"one": map[string]interface{}{},
}
t.Logf("Before: %v", x)
r := validator.Validate(x)
assert.False(t, r.HasErrors(), fmt.Sprintf("unexpected validation error: %v", r.AsError()))
r.ApplyDefaults()
t.Logf("After: %v", x)
var expected interface{}
err = json.Unmarshal([]byte(`{
"int": 42,
"str": "Hello",
"obj": {"foo": "bar"},
"nested": {"inner": 7},
"all": {"foo": 42, "bar": 42},
"any": {"foo": 42},
"one": {"bar": 42}
}`), &expected)
assert.NoError(t, err)
assert.Equal(t, expected, x)
}

View File

@@ -0,0 +1,101 @@
{
"properties": {
"int": {
"type": "integer",
"default": 42
},
"str": {
"type": "string",
"minLength": 4,
"default": "Hello"
},
"obj": {
"type": "object",
"default": {"foo": "bar"}
},
"nested": {
"type": "object",
"properties": {
"inner": {
"type": "integer",
"default": 7
}
}
},
"all": {
"allOf": [
{
"type": "object",
"properties": {
"foo": {
"type": "integer",
"default": 42
}
}
},
{
"type": "object",
"properties": {
"bar": {
"type": "integer",
"default": 42
}
}
}
]
},
"any": {
"anyOf": [
{
"type": "object",
"properties": {
"foo": {
"type": "integer",
"default": 42
}
}
},
{
"type": "object",
"properties": {
"bar": {
"type": "integer",
"default": 42
}
}
}
]
},
"one": {
"oneOf": [
{
"type": "object",
"properties": {
"foo": {
"type": "integer"
}
},
"required": ["foo"]
},
{
"type": "object",
"properties": {
"bar": {
"type": "integer",
"default": 42
}
}
}
]
},
"not": {
"not": {
"type": "object",
"default": {
"foo": 1
}
}
}
},
"required": ["int", "str", "nested", "all", "any", "one"]
}

View File

@@ -64,14 +64,6 @@ func (o *objectValidator) Validate(data interface{}) *Result {
}
res := new(Result)
if len(o.Required) > 0 {
for _, k := range o.Required {
if _, ok := val[k]; !ok {
res.AddErrors(errors.Required(o.Path+"."+k, o.In))
continue
}
}
}
if o.AdditionalProperties != nil && !o.AdditionalProperties.Allows {
for k := range val {
@@ -102,6 +94,8 @@ func (o *objectValidator) Validate(data interface{}) *Result {
}
}
createdFromDefaults := map[string]bool{}
for pName, pSchema := range o.Properties {
rName := pName
if o.Path != "" {
@@ -109,7 +103,24 @@ func (o *objectValidator) Validate(data interface{}) *Result {
}
if v, ok := val[pName]; ok {
res.Merge(NewSchemaValidator(&pSchema, o.Root, rName, o.KnownFormats).Validate(v))
r := NewSchemaValidator(&pSchema, o.Root, rName, o.KnownFormats).Validate(v)
res.Merge(r)
} else if pSchema.Default != nil {
createdFromDefaults[pName] = true
pName := pName // shaddow
def := pSchema.Default
res.Defaulters = append(res.Defaulters, DefaulterFunc(func() {
val[pName] = def
}))
}
}
if len(o.Required) > 0 {
for _, k := range o.Required {
if _, ok := val[k]; !ok && !createdFromDefaults[k] {
res.AddErrors(errors.Required(o.Path+"."+k, o.In))
continue
}
}
}
@@ -140,9 +151,6 @@ func (o *objectValidator) validatePatternProperty(key string, value interface{},
res := validator.Validate(value)
result.Merge(res)
if res.IsValid() {
succeededOnce = true
}
}
}

View File

@@ -25,10 +25,21 @@ var (
Debug = os.Getenv("SWAGGER_DEBUG") != ""
)
type Defaulter interface {
Apply()
}
type DefaulterFunc func()
func (f DefaulterFunc) Apply() {
f()
}
// Result represents a validation result
type Result struct {
Errors []error
MatchCount int
Defaulters []Defaulter
}
// Merge merges this result with the other one, preserving match counts etc
@@ -38,6 +49,7 @@ func (r *Result) Merge(other *Result) *Result {
}
r.AddErrors(other.Errors...)
r.MatchCount += other.MatchCount
r.Defaulters = append(r.Defaulters, other.Defaulters...)
return r
}
@@ -69,3 +81,9 @@ func (r *Result) AsError() error {
}
return errors.CompositeValidationError(r.Errors...)
}
func (r *Result) ApplyDefaults() {
for _, d := range r.Defaulters {
d.Apply()
}
}

View File

@@ -148,7 +148,6 @@ func (s *SchemaValidator) commonValidator() valueValidator {
return &basicCommonValidator{
Path: s.Path,
In: s.in,
Default: s.Schema.Default,
Enum: s.Schema.Enum,
}
}
@@ -184,7 +183,6 @@ func (s *SchemaValidator) stringValidator() valueValidator {
return &stringValidator{
Path: s.Path,
In: s.in,
Default: s.Schema.Default,
MaxLength: s.Schema.MaxLength,
MinLength: s.Schema.MinLength,
Pattern: s.Schema.Pattern,
@@ -195,7 +193,6 @@ func (s *SchemaValidator) formatValidator() valueValidator {
return &formatValidator{
Path: s.Path,
In: s.in,
//Default: s.Schema.Default,
Format: s.Schema.Format,
KnownFormats: s.KnownFormats,
}

View File

@@ -89,6 +89,7 @@ func (s *schemaPropsValidator) Applies(source interface{}, kind reflect.Kind) bo
func (s *schemaPropsValidator) Validate(data interface{}) *Result {
mainResult := new(Result)
var firstSuccess *Result
if len(s.anyOfValidators) > 0 {
var bestFailures *Result
succeededOnce := false
@@ -97,6 +98,9 @@ func (s *schemaPropsValidator) Validate(data interface{}) *Result {
if result.IsValid() {
bestFailures = nil
succeededOnce = true
if firstSuccess == nil {
firstSuccess = result
}
break
}
if bestFailures == nil || result.MatchCount > bestFailures.MatchCount {
@@ -109,11 +113,14 @@ func (s *schemaPropsValidator) Validate(data interface{}) *Result {
}
if bestFailures != nil {
mainResult.Merge(bestFailures)
} else if firstSuccess != nil {
mainResult.Merge(firstSuccess)
}
}
if len(s.oneOfValidators) > 0 {
var bestFailures *Result
var firstSuccess *Result
validated := 0
for _, oneOfSchema := range s.oneOfValidators {
@@ -121,6 +128,9 @@ func (s *schemaPropsValidator) Validate(data interface{}) *Result {
if result.IsValid() {
validated++
bestFailures = nil
if firstSuccess == nil {
firstSuccess = result
}
continue
}
if validated == 0 && (bestFailures == nil || result.MatchCount > bestFailures.MatchCount) {
@@ -133,6 +143,8 @@ func (s *schemaPropsValidator) Validate(data interface{}) *Result {
if bestFailures != nil {
mainResult.Merge(bestFailures)
}
} else if firstSuccess != nil {
mainResult.Merge(firstSuccess)
}
}