mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
add functions/vendor files
This commit is contained in:
101
vendor/github.com/go-openapi/runtime/middleware/body_test.go
generated
vendored
Normal file
101
vendor/github.com/go-openapi/runtime/middleware/body_test.go
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/go-openapi/runtime/internal/testing/petstore"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type eofReader struct {
|
||||
}
|
||||
|
||||
func (r *eofReader) Read(b []byte) (int, error) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
func (r *eofReader) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type rbn func(*http.Request, *MatchedRoute) error
|
||||
|
||||
func (b rbn) BindRequest(r *http.Request, rr *MatchedRoute) error {
|
||||
return b(r, rr)
|
||||
}
|
||||
|
||||
func TestBindRequest_BodyValidation(t *testing.T) {
|
||||
spec, api := petstore.NewAPI(t)
|
||||
ctx := NewContext(spec, api, nil)
|
||||
api.DefaultConsumes = runtime.JSONMime
|
||||
ctx.router = DefaultRouter(spec, ctx.api)
|
||||
|
||||
req, err := http.NewRequest("GET", path.Join(spec.BasePath(), "/pets"), new(eofReader))
|
||||
if assert.NoError(t, err) {
|
||||
req.Header.Set("Content-Type", runtime.JSONMime)
|
||||
|
||||
ri, ok := ctx.RouteInfo(req)
|
||||
if assert.True(t, ok) {
|
||||
|
||||
err := ctx.BindValidRequest(req, ri, rbn(func(r *http.Request, rr *MatchedRoute) error {
|
||||
defer r.Body.Close()
|
||||
var data interface{}
|
||||
err := runtime.JSONConsumer().Consume(r.Body, &data)
|
||||
_ = data
|
||||
return err
|
||||
}))
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, io.EOF, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBindRequest_DeleteNoBody(t *testing.T) {
|
||||
spec, api := petstore.NewAPI(t)
|
||||
ctx := NewContext(spec, api, nil)
|
||||
api.DefaultConsumes = runtime.JSONMime
|
||||
ctx.router = DefaultRouter(spec, ctx.api)
|
||||
|
||||
req, err := http.NewRequest("DELETE", path.Join(spec.BasePath(), "/pets/123"), new(eofReader))
|
||||
if assert.NoError(t, err) {
|
||||
req.Header.Set("Accept", "*/*")
|
||||
|
||||
ri, ok := ctx.RouteInfo(req)
|
||||
if assert.True(t, ok) {
|
||||
|
||||
err := ctx.BindValidRequest(req, ri, rbn(func(r *http.Request, rr *MatchedRoute) error {
|
||||
return nil
|
||||
}))
|
||||
|
||||
assert.NoError(t, err)
|
||||
//assert.Equal(t, io.EOF, err)
|
||||
}
|
||||
}
|
||||
|
||||
req, err = http.NewRequest("DELETE", path.Join(spec.BasePath(), "/pets/123"), new(eofReader))
|
||||
if assert.NoError(t, err) {
|
||||
req.Header.Set("Accept", "*/*")
|
||||
req.Header.Set("Content-Type", runtime.JSONMime)
|
||||
req.ContentLength = 1
|
||||
|
||||
ri, ok := ctx.RouteInfo(req)
|
||||
if assert.True(t, ok) {
|
||||
|
||||
err := ctx.BindValidRequest(req, ri, rbn(func(r *http.Request, rr *MatchedRoute) error {
|
||||
defer r.Body.Close()
|
||||
var data interface{}
|
||||
err := runtime.JSONConsumer().Consume(r.Body, &data)
|
||||
_ = data
|
||||
return err
|
||||
}))
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, io.EOF, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
526
vendor/github.com/go-openapi/runtime/middleware/context.go
generated
vendored
Normal file
526
vendor/github.com/go-openapi/runtime/middleware/context.go
generated
vendored
Normal file
@@ -0,0 +1,526 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/go-openapi/analysis"
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/loads"
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/go-openapi/runtime/middleware/untyped"
|
||||
"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
|
||||
var Debug = os.Getenv("SWAGGER_DEBUG") != "" || os.Getenv("DEBUG") != ""
|
||||
|
||||
func debugLog(format string, args ...interface{}) {
|
||||
if Debug {
|
||||
log.Printf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// A Builder can create middlewares
|
||||
type Builder func(http.Handler) http.Handler
|
||||
|
||||
// PassthroughBuilder returns the handler, aka the builder identity function
|
||||
func PassthroughBuilder(handler http.Handler) http.Handler { return handler }
|
||||
|
||||
// RequestBinder is an interface for types to implement
|
||||
// when they want to be able to bind from a request
|
||||
type RequestBinder interface {
|
||||
BindRequest(*http.Request, *MatchedRoute) error
|
||||
}
|
||||
|
||||
// Responder is an interface for types to implement
|
||||
// when they want to be considered for writing HTTP responses
|
||||
type Responder interface {
|
||||
WriteResponse(http.ResponseWriter, runtime.Producer)
|
||||
}
|
||||
|
||||
// ResponderFunc wraps a func as a Responder interface
|
||||
type ResponderFunc func(http.ResponseWriter, runtime.Producer)
|
||||
|
||||
// WriteResponse writes to the response
|
||||
func (fn ResponderFunc) WriteResponse(rw http.ResponseWriter, pr runtime.Producer) {
|
||||
fn(rw, pr)
|
||||
}
|
||||
|
||||
// Context is a type safe wrapper around an untyped request context
|
||||
// used throughout to store request context with the gorilla context module
|
||||
type Context struct {
|
||||
spec *loads.Document
|
||||
analyzer *analysis.Spec
|
||||
api RoutableAPI
|
||||
router Router
|
||||
formats strfmt.Registry
|
||||
}
|
||||
|
||||
type routableUntypedAPI struct {
|
||||
api *untyped.API
|
||||
hlock *sync.Mutex
|
||||
handlers map[string]map[string]http.Handler
|
||||
defaultConsumes string
|
||||
defaultProduces string
|
||||
}
|
||||
|
||||
func newRoutableUntypedAPI(spec *loads.Document, api *untyped.API, context *Context) *routableUntypedAPI {
|
||||
var handlers map[string]map[string]http.Handler
|
||||
if spec == nil || api == nil {
|
||||
return nil
|
||||
}
|
||||
analyzer := analysis.New(spec.Spec())
|
||||
for method, hls := range analyzer.Operations() {
|
||||
um := strings.ToUpper(method)
|
||||
for path, op := range hls {
|
||||
schemes := analyzer.SecurityDefinitionsFor(op)
|
||||
|
||||
if oh, ok := api.OperationHandlerFor(method, path); ok {
|
||||
if handlers == nil {
|
||||
handlers = make(map[string]map[string]http.Handler)
|
||||
}
|
||||
if b, ok := handlers[um]; !ok || b == nil {
|
||||
handlers[um] = make(map[string]http.Handler)
|
||||
}
|
||||
|
||||
var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// lookup route info in the context
|
||||
route, _ := context.RouteInfo(r)
|
||||
|
||||
// bind and validate the request using reflection
|
||||
bound, validation := context.BindAndValidate(r, route)
|
||||
if validation != nil {
|
||||
context.Respond(w, r, route.Produces, route, validation)
|
||||
return
|
||||
}
|
||||
|
||||
// actually handle the request
|
||||
result, err := oh.Handle(bound)
|
||||
if err != nil {
|
||||
// respond with failure
|
||||
context.Respond(w, r, route.Produces, route, err)
|
||||
return
|
||||
}
|
||||
|
||||
// respond with success
|
||||
context.Respond(w, r, route.Produces, route, result)
|
||||
})
|
||||
|
||||
if len(schemes) > 0 {
|
||||
handler = newSecureAPI(context, handler)
|
||||
}
|
||||
handlers[um][path] = handler
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &routableUntypedAPI{
|
||||
api: api,
|
||||
hlock: new(sync.Mutex),
|
||||
handlers: handlers,
|
||||
defaultProduces: api.DefaultProduces,
|
||||
defaultConsumes: api.DefaultConsumes,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *routableUntypedAPI) HandlerFor(method, path string) (http.Handler, bool) {
|
||||
r.hlock.Lock()
|
||||
paths, ok := r.handlers[strings.ToUpper(method)]
|
||||
if !ok {
|
||||
r.hlock.Unlock()
|
||||
return nil, false
|
||||
}
|
||||
handler, ok := paths[path]
|
||||
r.hlock.Unlock()
|
||||
return handler, ok
|
||||
}
|
||||
func (r *routableUntypedAPI) ServeErrorFor(operationID string) func(http.ResponseWriter, *http.Request, error) {
|
||||
return r.api.ServeError
|
||||
}
|
||||
func (r *routableUntypedAPI) ConsumersFor(mediaTypes []string) map[string]runtime.Consumer {
|
||||
return r.api.ConsumersFor(mediaTypes)
|
||||
}
|
||||
func (r *routableUntypedAPI) ProducersFor(mediaTypes []string) map[string]runtime.Producer {
|
||||
return r.api.ProducersFor(mediaTypes)
|
||||
}
|
||||
func (r *routableUntypedAPI) AuthenticatorsFor(schemes map[string]spec.SecurityScheme) map[string]runtime.Authenticator {
|
||||
return r.api.AuthenticatorsFor(schemes)
|
||||
}
|
||||
func (r *routableUntypedAPI) Formats() strfmt.Registry {
|
||||
return r.api.Formats()
|
||||
}
|
||||
|
||||
func (r *routableUntypedAPI) DefaultProduces() string {
|
||||
return r.defaultProduces
|
||||
}
|
||||
|
||||
func (r *routableUntypedAPI) DefaultConsumes() string {
|
||||
return r.defaultConsumes
|
||||
}
|
||||
|
||||
// NewRoutableContext creates a new context for a routable API
|
||||
func NewRoutableContext(spec *loads.Document, routableAPI RoutableAPI, routes Router) *Context {
|
||||
var an *analysis.Spec
|
||||
if spec != nil {
|
||||
an = analysis.New(spec.Spec())
|
||||
}
|
||||
ctx := &Context{spec: spec, api: routableAPI, analyzer: an}
|
||||
return ctx
|
||||
}
|
||||
|
||||
// NewContext creates a new context wrapper
|
||||
func NewContext(spec *loads.Document, api *untyped.API, routes Router) *Context {
|
||||
var an *analysis.Spec
|
||||
if spec != nil {
|
||||
an = analysis.New(spec.Spec())
|
||||
}
|
||||
ctx := &Context{spec: spec, analyzer: an}
|
||||
ctx.api = newRoutableUntypedAPI(spec, api, ctx)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// Serve serves the specified spec with the specified api registrations as a http.Handler
|
||||
func Serve(spec *loads.Document, api *untyped.API) http.Handler {
|
||||
return ServeWithBuilder(spec, api, PassthroughBuilder)
|
||||
}
|
||||
|
||||
// ServeWithBuilder serves the specified spec with the specified api registrations as a http.Handler that is decorated
|
||||
// by the Builder
|
||||
func ServeWithBuilder(spec *loads.Document, api *untyped.API, builder Builder) http.Handler {
|
||||
context := NewContext(spec, api, nil)
|
||||
return context.APIHandler(builder)
|
||||
}
|
||||
|
||||
type contextKey int8
|
||||
|
||||
const (
|
||||
_ contextKey = iota
|
||||
ctxContentType
|
||||
ctxResponseFormat
|
||||
ctxMatchedRoute
|
||||
ctxAllowedMethods
|
||||
ctxBoundParams
|
||||
ctxSecurityPrincipal
|
||||
ctxSecurityScopes
|
||||
|
||||
ctxConsumer
|
||||
)
|
||||
|
||||
type contentTypeValue struct {
|
||||
MediaType string
|
||||
Charset string
|
||||
}
|
||||
|
||||
// BasePath returns the base path for this API
|
||||
func (c *Context) BasePath() string {
|
||||
return c.spec.BasePath()
|
||||
}
|
||||
|
||||
// RequiredProduces returns the accepted content types for responses
|
||||
func (c *Context) RequiredProduces() []string {
|
||||
return c.analyzer.RequiredProduces()
|
||||
}
|
||||
|
||||
// BindValidRequest binds a params object to a request but only when the request is valid
|
||||
// if the request is not valid an error will be returned
|
||||
func (c *Context) BindValidRequest(request *http.Request, route *MatchedRoute, binder RequestBinder) error {
|
||||
var res []error
|
||||
|
||||
requestContentType := "*/*"
|
||||
// check and validate content type, select consumer
|
||||
if runtime.HasBody(request) {
|
||||
ct, _, err := runtime.ContentType(request.Header)
|
||||
if err != nil {
|
||||
res = append(res, err)
|
||||
} else {
|
||||
if err := validateContentType(route.Consumes, ct); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
if len(res) == 0 {
|
||||
cons, ok := route.Consumers[ct]
|
||||
if !ok {
|
||||
res = append(res, errors.New(500, "no consumer registered for %s", ct))
|
||||
} else {
|
||||
route.Consumer = cons
|
||||
requestContentType = ct
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check and validate the response format
|
||||
if len(res) == 0 && runtime.HasBody(request) {
|
||||
if str := NegotiateContentType(request, route.Produces, requestContentType); str == "" {
|
||||
res = append(res, errors.InvalidResponseFormat(request.Header.Get(runtime.HeaderAccept), route.Produces))
|
||||
}
|
||||
}
|
||||
|
||||
// now bind the request with the provided binder
|
||||
// it's assumed the binder will also validate the request and return an error if the
|
||||
// request is invalid
|
||||
if binder != nil && len(res) == 0 {
|
||||
if err := binder.BindRequest(request, route); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
mt, cs, err := runtime.ContentType(request.Header)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
context.Set(request, ctxContentType, &contentTypeValue{mt, cs})
|
||||
return mt, cs, nil
|
||||
}
|
||||
|
||||
// LookupRoute looks a route up and returns true when it is found
|
||||
func (c *Context) LookupRoute(request *http.Request) (*MatchedRoute, bool) {
|
||||
if route, ok := c.router.Lookup(request.Method, request.URL.EscapedPath()); ok {
|
||||
return route, ok
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
if route, ok := c.LookupRoute(request); ok {
|
||||
context.Set(request, ctxMatchedRoute, route)
|
||||
return route, ok
|
||||
}
|
||||
|
||||
return 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
|
||||
}
|
||||
}
|
||||
|
||||
format := NegotiateContentType(r, offers, "")
|
||||
if format != "" {
|
||||
context.Set(r, ctxResponseFormat, format)
|
||||
}
|
||||
return format
|
||||
}
|
||||
|
||||
// AllowedMethods gets the allowed methods for the path of this request
|
||||
func (c *Context) AllowedMethods(request *http.Request) []string {
|
||||
return c.router.OtherMethods(request.Method, request.URL.EscapedPath())
|
||||
}
|
||||
|
||||
// Authorize authorizes the request
|
||||
func (c *Context) Authorize(request *http.Request, route *MatchedRoute) (interface{}, error) {
|
||||
if route == nil || len(route.Authenticators) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if v, ok := context.GetOk(request, ctxSecurityPrincipal); ok {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
var lastError error
|
||||
for scheme, authenticator := range route.Authenticators {
|
||||
applies, usr, err := authenticator.Authenticate(&security.ScopedAuthRequest{
|
||||
Request: request,
|
||||
RequiredScopes: route.Scopes[scheme],
|
||||
})
|
||||
if !applies || err != nil || usr == nil {
|
||||
if err != nil {
|
||||
lastError = err
|
||||
}
|
||||
continue
|
||||
}
|
||||
context.Set(request, ctxSecurityPrincipal, usr)
|
||||
context.Set(request, ctxSecurityScopes, route.Scopes[scheme])
|
||||
return usr, nil
|
||||
}
|
||||
|
||||
if lastError != nil {
|
||||
return nil, lastError
|
||||
}
|
||||
|
||||
return 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
|
||||
}
|
||||
}
|
||||
result := validateRequest(c, request, matched)
|
||||
if result != nil {
|
||||
context.Set(request, ctxBoundParams, result)
|
||||
}
|
||||
if len(result.result) > 0 {
|
||||
return result.bound, errors.CompositeValidationError(result.result...)
|
||||
}
|
||||
debugLog("no validation errors found")
|
||||
return result.bound, nil
|
||||
}
|
||||
|
||||
// NotFound the default not found responder for when no route has been matched yet
|
||||
func (c *Context) NotFound(rw http.ResponseWriter, r *http.Request) {
|
||||
c.Respond(rw, r, []string{c.api.DefaultProduces()}, nil, errors.NotFound("not found"))
|
||||
}
|
||||
|
||||
// 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{}) {
|
||||
offers := []string{}
|
||||
for _, mt := range produces {
|
||||
if mt != c.api.DefaultProduces() {
|
||||
offers = append(offers, mt)
|
||||
}
|
||||
}
|
||||
// the default producer is last so more specific producers take precedence
|
||||
offers = append(offers, c.api.DefaultProduces())
|
||||
|
||||
format := 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()})
|
||||
pr, ok := prods[c.api.DefaultProduces()]
|
||||
if !ok {
|
||||
panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
|
||||
}
|
||||
prod = pr
|
||||
}
|
||||
resp.WriteResponse(rw, prod)
|
||||
return
|
||||
}
|
||||
|
||||
if err, ok := data.(error); ok {
|
||||
if format == "" {
|
||||
rw.Header().Set(runtime.HeaderContentType, runtime.JSONMime)
|
||||
}
|
||||
if route == nil || route.Operation == nil {
|
||||
c.api.ServeErrorFor("")(rw, r, err)
|
||||
return
|
||||
}
|
||||
c.api.ServeErrorFor(route.Operation.ID)(rw, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if route == nil || route.Operation == nil {
|
||||
rw.WriteHeader(200)
|
||||
if r.Method == "HEAD" {
|
||||
return
|
||||
}
|
||||
producers := c.api.ProducersFor(offers)
|
||||
prod, ok := producers[format]
|
||||
if !ok {
|
||||
panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
|
||||
}
|
||||
if err := prod.Produce(rw, data); err != nil {
|
||||
panic(err) // let the recovery middleware deal with this
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if _, code, ok := route.Operation.SuccessResponse(); ok {
|
||||
rw.WriteHeader(code)
|
||||
if code == 204 || r.Method == "HEAD" {
|
||||
return
|
||||
}
|
||||
|
||||
producers := route.Producers
|
||||
prod, ok := producers[format]
|
||||
if !ok {
|
||||
if !ok {
|
||||
prods := c.api.ProducersFor([]string{c.api.DefaultProduces()})
|
||||
pr, ok := prods[c.api.DefaultProduces()]
|
||||
if !ok {
|
||||
panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
|
||||
}
|
||||
prod = pr
|
||||
}
|
||||
}
|
||||
if err := prod.Produce(rw, data); err != nil {
|
||||
panic(err) // let the recovery middleware deal with this
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.api.ServeErrorFor(route.Operation.ID)(rw, r, errors.New(http.StatusInternalServerError, "can't produce response"))
|
||||
}
|
||||
|
||||
// APIHandler returns a handler to serve the API, this includes a swagger spec, router and the contract defined in the swagger spec
|
||||
func (c *Context) APIHandler(builder Builder) http.Handler {
|
||||
b := builder
|
||||
if b == nil {
|
||||
b = PassthroughBuilder
|
||||
}
|
||||
|
||||
var title string
|
||||
sp := c.spec.Spec()
|
||||
if sp != nil && sp.Info != nil && sp.Info.Title != "" {
|
||||
title = sp.Info.Title
|
||||
}
|
||||
|
||||
redocOpts := RedocOpts{
|
||||
BasePath: c.BasePath(),
|
||||
Title: title,
|
||||
}
|
||||
|
||||
return Spec("", c.spec.Raw(), Redoc(redocOpts, c.RoutesHandler(builder)))
|
||||
}
|
||||
|
||||
// RoutesHandler returns a handler to serve the API, just the routes and the contract defined in the swagger spec
|
||||
func (c *Context) RoutesHandler(builder Builder) http.Handler {
|
||||
b := builder
|
||||
if b == nil {
|
||||
b = PassthroughBuilder
|
||||
}
|
||||
return NewRouter(c, b(NewOperationExecutor(c)))
|
||||
}
|
||||
395
vendor/github.com/go-openapi/runtime/middleware/context_test.go
generated
vendored
Normal file
395
vendor/github.com/go-openapi/runtime/middleware/context_test.go
generated
vendored
Normal file
@@ -0,0 +1,395 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/go-openapi/loads"
|
||||
"github.com/go-openapi/loads/fmts"
|
||||
"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"
|
||||
)
|
||||
|
||||
type stubOperationHandler struct {
|
||||
}
|
||||
|
||||
func (s *stubOperationHandler) ParameterModel() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stubOperationHandler) Handle(params interface{}) (interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type testBinder struct {
|
||||
}
|
||||
|
||||
func (t *testBinder) BindRequest(r *http.Request, m *MatchedRoute) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
loads.AddLoader(fmts.YAMLMatcher, fmts.YAMLDoc)
|
||||
}
|
||||
|
||||
func TestContentType_Issue264(t *testing.T) {
|
||||
swspec, err := loads.Spec("../fixtures/bugs/264/swagger.yml")
|
||||
if assert.NoError(t, err) {
|
||||
api := untyped.NewAPI(swspec)
|
||||
api.RegisterConsumer("application/json", runtime.JSONConsumer())
|
||||
api.RegisterProducer("application/json", runtime.JSONProducer())
|
||||
api.RegisterOperation("delete", "/key/{id}", new(stubOperationHandler))
|
||||
|
||||
handler := Serve(swspec, api)
|
||||
request, _ := http.NewRequest("DELETE", "/key/1", nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
handler.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServe(t *testing.T) {
|
||||
spec, api := petstore.NewAPI(t)
|
||||
handler := Serve(spec, api)
|
||||
|
||||
// serve spec document
|
||||
request, _ := http.NewRequest("GET", "http://localhost:8080/swagger.json", nil)
|
||||
request.Header.Add("Content-Type", runtime.JSONMime)
|
||||
request.Header.Add("Accept", runtime.JSONMime)
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
|
||||
request, _ = http.NewRequest("GET", "http://localhost:8080/swagger-ui", nil)
|
||||
recorder = httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, 404, recorder.Code)
|
||||
}
|
||||
|
||||
func TestContextAuthorize(t *testing.T) {
|
||||
spec, api := petstore.NewAPI(t)
|
||||
ctx := NewContext(spec, api, nil)
|
||||
ctx.router = DefaultRouter(spec, ctx.api)
|
||||
|
||||
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)
|
||||
assert.True(t, ok)
|
||||
p, err := ctx.Authorize(request, ri)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, p)
|
||||
|
||||
v, ok = context.GetOk(request, ctxSecurityPrincipal)
|
||||
assert.False(t, ok)
|
||||
assert.Nil(t, v)
|
||||
|
||||
request.SetBasicAuth("wrong", "wrong")
|
||||
p, err = ctx.Authorize(request, ri)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, p)
|
||||
|
||||
v, ok = context.GetOk(request, ctxSecurityPrincipal)
|
||||
assert.False(t, ok)
|
||||
assert.Nil(t, v)
|
||||
|
||||
request.SetBasicAuth("admin", "admin")
|
||||
p, err = ctx.Authorize(request, ri)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "admin", p)
|
||||
|
||||
v, ok = context.GetOk(request, ctxSecurityPrincipal)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "admin", v)
|
||||
|
||||
request.SetBasicAuth("doesn't matter", "doesn't")
|
||||
pp, rr := ctx.Authorize(request, ri)
|
||||
assert.Equal(t, p, pp)
|
||||
assert.Equal(t, err, rr)
|
||||
}
|
||||
|
||||
func TestContextNegotiateContentType(t *testing.T) {
|
||||
spec, api := petstore.NewAPI(t)
|
||||
ctx := NewContext(spec, api, nil)
|
||||
ctx.router = DefaultRouter(spec, ctx.api)
|
||||
|
||||
request, _ := http.NewRequest("POST", "/api/pets", nil)
|
||||
// request.Header.Add("Accept", "*/*")
|
||||
request.Header.Add("content-type", "text/html")
|
||||
|
||||
v, ok := context.GetOk(request, ctxBoundParams)
|
||||
assert.False(t, ok)
|
||||
assert.Nil(t, v)
|
||||
|
||||
ri, _ := 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)
|
||||
}
|
||||
|
||||
func TestContextBindAndValidate(t *testing.T) {
|
||||
spec, api := petstore.NewAPI(t)
|
||||
ctx := NewContext(spec, api, nil)
|
||||
ctx.router = DefaultRouter(spec, ctx.api)
|
||||
|
||||
request, _ := http.NewRequest("POST", "/api/pets", nil)
|
||||
request.Header.Add("Accept", "*/*")
|
||||
request.Header.Add("content-type", "text/html")
|
||||
request.ContentLength = 1
|
||||
|
||||
v, ok := context.GetOk(request, ctxBoundParams)
|
||||
assert.False(t, ok)
|
||||
assert.Nil(t, v)
|
||||
|
||||
ri, _ := ctx.RouteInfo(request)
|
||||
data, 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)
|
||||
assert.True(t, ok)
|
||||
assert.NotNil(t, v)
|
||||
|
||||
dd, rr := ctx.BindAndValidate(request, ri)
|
||||
assert.Equal(t, data, dd)
|
||||
assert.Equal(t, result, rr)
|
||||
}
|
||||
|
||||
func TestContextRender(t *testing.T) {
|
||||
ct := runtime.JSONMime
|
||||
spec, api := petstore.NewAPI(t)
|
||||
|
||||
assert.NotNil(t, spec)
|
||||
assert.NotNil(t, api)
|
||||
ctx := NewContext(spec, api, nil)
|
||||
ctx.router = DefaultRouter(spec, ctx.api)
|
||||
|
||||
request, _ := http.NewRequest("GET", "pets", nil)
|
||||
request.Header.Set(runtime.HeaderAccept, ct)
|
||||
ri, _ := ctx.RouteInfo(request)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx.Respond(recorder, request, []string{ct}, ri, map[string]interface{}{"name": "hello"})
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Equal(t, "{\"name\":\"hello\"}\n", recorder.Body.String())
|
||||
|
||||
recorder = httptest.NewRecorder()
|
||||
ctx.Respond(recorder, request, []string{ct}, ri, errors.New("this went wrong"))
|
||||
assert.Equal(t, 500, recorder.Code)
|
||||
|
||||
// recorder = httptest.NewRecorder()
|
||||
// assert.Panics(t, func() { ctx.Respond(recorder, request, []string{ct}, ri, map[int]interface{}{1: "hello"}) })
|
||||
|
||||
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", "/pets", nil)
|
||||
request.Header.Set(runtime.HeaderAccept, ct)
|
||||
ri, _ = ctx.RouteInfo(request)
|
||||
|
||||
recorder = httptest.NewRecorder()
|
||||
ctx.Respond(recorder, request, []string{ct}, ri, map[string]interface{}{"name": "hello"})
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Equal(t, "{\"name\":\"hello\"}\n", recorder.Body.String())
|
||||
|
||||
recorder = httptest.NewRecorder()
|
||||
ctx.Respond(recorder, request, []string{ct}, ri, errors.New("this went wrong"))
|
||||
assert.Equal(t, 500, recorder.Code)
|
||||
|
||||
// recorder = httptest.NewRecorder()
|
||||
// assert.Panics(t, func() { ctx.Respond(recorder, request, []string{ct}, ri, map[int]interface{}{1: "hello"}) })
|
||||
|
||||
// recorder = httptest.NewRecorder()
|
||||
// request, _ = http.NewRequest("GET", "/pets", nil)
|
||||
// assert.Panics(t, func() { ctx.Respond(recorder, request, []string{}, ri, map[string]interface{}{"name": "hello"}) })
|
||||
|
||||
recorder = httptest.NewRecorder()
|
||||
request, _ = http.NewRequest("DELETE", "/api/pets/1", nil)
|
||||
ri, _ = ctx.RouteInfo(request)
|
||||
ctx.Respond(recorder, request, ri.Produces, ri, nil)
|
||||
assert.Equal(t, 204, recorder.Code)
|
||||
}
|
||||
|
||||
func TestContextValidResponseFormat(t *testing.T) {
|
||||
ct := "application/json"
|
||||
spec, api := petstore.NewAPI(t)
|
||||
ctx := NewContext(spec, api, nil)
|
||||
ctx.router = DefaultRouter(spec, ctx.api)
|
||||
|
||||
request, _ := http.NewRequest("GET", "http://localhost:8080", nil)
|
||||
request.Header.Set(runtime.HeaderAccept, ct)
|
||||
|
||||
// check there's nothing there
|
||||
cached, ok := context.GetOk(request, ctxResponseFormat)
|
||||
assert.False(t, ok)
|
||||
assert.Empty(t, cached)
|
||||
|
||||
// trigger the parse
|
||||
mt := ctx.ResponseFormat(request, []string{ct})
|
||||
assert.Equal(t, ct, mt)
|
||||
|
||||
// check it was cached
|
||||
cached, ok = context.GetOk(request, ctxResponseFormat)
|
||||
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})
|
||||
assert.Equal(t, ct, mt)
|
||||
}
|
||||
|
||||
func TestContextInvalidResponseFormat(t *testing.T) {
|
||||
ct := "application/x-yaml"
|
||||
other := "application/sgml"
|
||||
spec, api := petstore.NewAPI(t)
|
||||
ctx := NewContext(spec, api, nil)
|
||||
ctx.router = DefaultRouter(spec, ctx.api)
|
||||
|
||||
request, _ := http.NewRequest("GET", "http://localhost:8080", nil)
|
||||
request.Header.Set(runtime.HeaderAccept, ct)
|
||||
|
||||
// check there's nothing there
|
||||
cached, ok := context.GetOk(request, ctxResponseFormat)
|
||||
assert.False(t, ok)
|
||||
assert.Empty(t, cached)
|
||||
|
||||
// trigger the parse
|
||||
mt := ctx.ResponseFormat(request, []string{other})
|
||||
assert.Empty(t, mt)
|
||||
|
||||
// check it was cached
|
||||
cached, ok = context.GetOk(request, ctxResponseFormat)
|
||||
assert.False(t, ok)
|
||||
assert.Empty(t, cached)
|
||||
|
||||
// check if the cast works and fetch from cache too
|
||||
mt = ctx.ResponseFormat(request, []string{other})
|
||||
assert.Empty(t, mt)
|
||||
}
|
||||
|
||||
func TestContextValidRoute(t *testing.T) {
|
||||
spec, api := petstore.NewAPI(t)
|
||||
ctx := NewContext(spec, api, nil)
|
||||
ctx.router = DefaultRouter(spec, ctx.api)
|
||||
|
||||
request, _ := http.NewRequest("GET", "/api/pets", nil)
|
||||
|
||||
// check there's nothing there
|
||||
_, ok := context.GetOk(request, ctxMatchedRoute)
|
||||
assert.False(t, ok)
|
||||
|
||||
matched, ok := ctx.RouteInfo(request)
|
||||
assert.True(t, ok)
|
||||
assert.NotNil(t, matched)
|
||||
|
||||
// check it was cached
|
||||
_, ok = context.GetOk(request, ctxMatchedRoute)
|
||||
assert.True(t, ok)
|
||||
|
||||
matched, ok = ctx.RouteInfo(request)
|
||||
assert.True(t, ok)
|
||||
assert.NotNil(t, matched)
|
||||
}
|
||||
|
||||
func TestContextInvalidRoute(t *testing.T) {
|
||||
spec, api := petstore.NewAPI(t)
|
||||
ctx := NewContext(spec, api, nil)
|
||||
ctx.router = DefaultRouter(spec, ctx.api)
|
||||
|
||||
request, _ := http.NewRequest("DELETE", "pets", nil)
|
||||
|
||||
// check there's nothing there
|
||||
_, ok := context.GetOk(request, ctxMatchedRoute)
|
||||
assert.False(t, ok)
|
||||
|
||||
matched, ok := ctx.RouteInfo(request)
|
||||
assert.False(t, ok)
|
||||
assert.Nil(t, matched)
|
||||
|
||||
// check it was cached
|
||||
_, ok = context.GetOk(request, ctxMatchedRoute)
|
||||
assert.False(t, ok)
|
||||
|
||||
matched, ok = ctx.RouteInfo(request)
|
||||
assert.False(t, ok)
|
||||
assert.Nil(t, matched)
|
||||
}
|
||||
|
||||
func TestContextValidContentType(t *testing.T) {
|
||||
ct := "application/json"
|
||||
ctx := NewContext(nil, nil, nil)
|
||||
|
||||
request, _ := http.NewRequest("GET", "http://localhost:8080", nil)
|
||||
request.Header.Set(runtime.HeaderContentType, ct)
|
||||
|
||||
// check there's nothing there
|
||||
_, ok := context.GetOk(request, ctxContentType)
|
||||
assert.False(t, ok)
|
||||
|
||||
// trigger the parse
|
||||
mt, _, err := ctx.ContentType(request)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ct, mt)
|
||||
|
||||
// check it was cached
|
||||
_, ok = context.GetOk(request, ctxContentType)
|
||||
assert.True(t, ok)
|
||||
|
||||
// check if the cast works and fetch from cache too
|
||||
mt, _, err = ctx.ContentType(request)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ct, mt)
|
||||
}
|
||||
|
||||
func TestContextInvalidContentType(t *testing.T) {
|
||||
ct := "application("
|
||||
ctx := NewContext(nil, nil, nil)
|
||||
|
||||
request, _ := http.NewRequest("GET", "http://localhost:8080", nil)
|
||||
request.Header.Set(runtime.HeaderContentType, ct)
|
||||
|
||||
// check there's nothing there
|
||||
_, ok := context.GetOk(request, ctxContentType)
|
||||
assert.False(t, ok)
|
||||
|
||||
// trigger the parse
|
||||
mt, _, err := ctx.ContentType(request)
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, mt)
|
||||
|
||||
// check it was not cached
|
||||
_, ok = context.GetOk(request, ctxContentType)
|
||||
assert.False(t, ok)
|
||||
|
||||
// check if the failure continues
|
||||
_, _, err = ctx.ContentType(request)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
19
vendor/github.com/go-openapi/runtime/middleware/denco/LICENSE
generated
vendored
Executable file
19
vendor/github.com/go-openapi/runtime/middleware/denco/LICENSE
generated
vendored
Executable file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2014 Naoya Inada <naoina@kuune.org>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
180
vendor/github.com/go-openapi/runtime/middleware/denco/README.md
generated
vendored
Executable file
180
vendor/github.com/go-openapi/runtime/middleware/denco/README.md
generated
vendored
Executable file
@@ -0,0 +1,180 @@
|
||||
# Denco [](https://travis-ci.org/naoina/denco)
|
||||
|
||||
The fast and flexible HTTP request router for [Go](http://golang.org).
|
||||
|
||||
Denco is based on Double-Array implementation of [Kocha-urlrouter](https://github.com/naoina/kocha-urlrouter).
|
||||
However, Denco is optimized and some features added.
|
||||
|
||||
## Features
|
||||
|
||||
* Fast (See [go-http-routing-benchmark](https://github.com/naoina/go-http-routing-benchmark))
|
||||
* [URL patterns](#url-patterns) (`/foo/:bar` and `/foo/*wildcard`)
|
||||
* Small (but enough) URL router API
|
||||
* HTTP request multiplexer like `http.ServeMux`
|
||||
|
||||
## Installation
|
||||
|
||||
go get -u github.com/go-openapi/runtime/middleware/denco
|
||||
|
||||
## Using as HTTP request multiplexer
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware/denco"
|
||||
)
|
||||
|
||||
func Index(w http.ResponseWriter, r *http.Request, params denco.Params) {
|
||||
fmt.Fprintf(w, "Welcome to Denco!\n")
|
||||
}
|
||||
|
||||
func User(w http.ResponseWriter, r *http.Request, params denco.Params) {
|
||||
fmt.Fprintf(w, "Hello %s!\n", params.Get("name"))
|
||||
}
|
||||
|
||||
func main() {
|
||||
mux := denco.NewMux()
|
||||
handler, err := mux.Build([]denco.Handler{
|
||||
mux.GET("/", Index),
|
||||
mux.GET("/user/:name", User),
|
||||
mux.POST("/user/:name", User),
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
log.Fatal(http.ListenAndServe(":8080", handler))
|
||||
}
|
||||
```
|
||||
|
||||
## Using as URL router
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware/denco"
|
||||
)
|
||||
|
||||
type route struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func main() {
|
||||
router := denco.New()
|
||||
router.Build([]denco.Record{
|
||||
{"/", &route{"root"}},
|
||||
{"/user/:id", &route{"user"}},
|
||||
{"/user/:name/:id", &route{"username"}},
|
||||
{"/static/*filepath", &route{"static"}},
|
||||
})
|
||||
|
||||
data, params, found := router.Lookup("/")
|
||||
// print `&main.route{name:"root"}, denco.Params(nil), true`.
|
||||
fmt.Printf("%#v, %#v, %#v\n", data, params, found)
|
||||
|
||||
data, params, found = router.Lookup("/user/hoge")
|
||||
// print `&main.route{name:"user"}, denco.Params{denco.Param{Name:"id", Value:"hoge"}}, true`.
|
||||
fmt.Printf("%#v, %#v, %#v\n", data, params, found)
|
||||
|
||||
data, params, found = router.Lookup("/user/hoge/7")
|
||||
// print `&main.route{name:"username"}, denco.Params{denco.Param{Name:"name", Value:"hoge"}, denco.Param{Name:"id", Value:"7"}}, true`.
|
||||
fmt.Printf("%#v, %#v, %#v\n", data, params, found)
|
||||
|
||||
data, params, found = router.Lookup("/static/path/to/file")
|
||||
// print `&main.route{name:"static"}, denco.Params{denco.Param{Name:"filepath", Value:"path/to/file"}}, true`.
|
||||
fmt.Printf("%#v, %#v, %#v\n", data, params, found)
|
||||
}
|
||||
```
|
||||
|
||||
See [Godoc](http://godoc.org/github.com/go-openapi/runtime/middleware/denco) for more details.
|
||||
|
||||
## Getting the value of path parameter
|
||||
|
||||
You can get the value of path parameter by 2 ways.
|
||||
|
||||
1. Using [`denco.Params.Get`](http://godoc.org/github.com/go-openapi/runtime/middleware/denco#Params.Get) method
|
||||
2. Find by loop
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware/denco"
|
||||
)
|
||||
|
||||
func main() {
|
||||
router := denco.New()
|
||||
if err := router.Build([]denco.Record{
|
||||
{"/user/:name/:id", "route1"},
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 1. Using denco.Params.Get method.
|
||||
_, params, _ := router.Lookup("/user/alice/1")
|
||||
name := params.Get("name")
|
||||
if name != "" {
|
||||
fmt.Printf("Hello %s.\n", name) // prints "Hello alice.".
|
||||
}
|
||||
|
||||
// 2. Find by loop.
|
||||
for _, param := range params {
|
||||
if param.Name == "name" {
|
||||
fmt.Printf("Hello %s.\n", name) // prints "Hello alice.".
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## URL patterns
|
||||
|
||||
Denco's route matching strategy is "most nearly matching".
|
||||
|
||||
When routes `/:name` and `/alice` have been built, URI `/alice` matches the route `/alice`, not `/:name`.
|
||||
Because URI `/alice` is more match with the route `/alice` than `/:name`.
|
||||
|
||||
For more example, when routes below have been built:
|
||||
|
||||
```
|
||||
/user/alice
|
||||
/user/:name
|
||||
/user/:name/:id
|
||||
/user/alice/:id
|
||||
/user/:id/bob
|
||||
```
|
||||
|
||||
Routes matching are:
|
||||
|
||||
```
|
||||
/user/alice => "/user/alice" (no match with "/user/:name")
|
||||
/user/bob => "/user/:name"
|
||||
/user/naoina/1 => "/user/:name/1"
|
||||
/user/alice/1 => "/user/alice/:id" (no match with "/user/:name/:id")
|
||||
/user/1/bob => "/user/:id/bob" (no match with "/user/:name/:id")
|
||||
/user/alice/bob => "/user/alice/:id" (no match with "/user/:name/:id" and "/user/:id/bob")
|
||||
```
|
||||
|
||||
## Limitation
|
||||
|
||||
Denco has some limitations below.
|
||||
|
||||
* Number of param records (such as `/:name`) must be less than 2^22
|
||||
* Number of elements of internal slice must be less than 2^22
|
||||
|
||||
## Benchmarks
|
||||
|
||||
cd $GOPATH/github.com/go-openapi/runtime/middleware/denco
|
||||
go test -bench . -benchmem
|
||||
|
||||
## License
|
||||
|
||||
Denco is licensed under the MIT License.
|
||||
452
vendor/github.com/go-openapi/runtime/middleware/denco/router.go
generated
vendored
Executable file
452
vendor/github.com/go-openapi/runtime/middleware/denco/router.go
generated
vendored
Executable file
@@ -0,0 +1,452 @@
|
||||
// Package denco provides fast URL router.
|
||||
package denco
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// ParamCharacter is a special character for path parameter.
|
||||
ParamCharacter = ':'
|
||||
|
||||
// WildcardCharacter is a special character for wildcard path parameter.
|
||||
WildcardCharacter = '*'
|
||||
|
||||
// TerminationCharacter is a special character for end of path.
|
||||
TerminationCharacter = '#'
|
||||
|
||||
// MaxSize is max size of records and internal slice.
|
||||
MaxSize = (1 << 22) - 1
|
||||
)
|
||||
|
||||
// Router represents a URL router.
|
||||
type Router struct {
|
||||
// SizeHint expects the maximum number of path parameters in records to Build.
|
||||
// SizeHint will be used to determine the capacity of the memory to allocate.
|
||||
// By default, SizeHint will be determined from given records to Build.
|
||||
SizeHint int
|
||||
|
||||
static map[string]interface{}
|
||||
param *doubleArray
|
||||
}
|
||||
|
||||
// New returns a new Router.
|
||||
func New() *Router {
|
||||
return &Router{
|
||||
SizeHint: -1,
|
||||
static: make(map[string]interface{}),
|
||||
param: newDoubleArray(),
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup returns data and path parameters that associated with path.
|
||||
// params is a slice of the Param that arranged in the order in which parameters appeared.
|
||||
// e.g. when built routing path is "/path/to/:id/:name" and given path is "/path/to/1/alice". params order is [{"id": "1"}, {"name": "alice"}], not [{"name": "alice"}, {"id": "1"}].
|
||||
func (rt *Router) Lookup(path string) (data interface{}, params Params, found bool) {
|
||||
if data, found := rt.static[path]; found {
|
||||
return data, nil, true
|
||||
}
|
||||
if len(rt.param.node) == 1 {
|
||||
return nil, nil, false
|
||||
}
|
||||
nd, params, found := rt.param.lookup(path, make([]Param, 0, rt.SizeHint), 1)
|
||||
if !found {
|
||||
return nil, nil, false
|
||||
}
|
||||
for i := 0; i < len(params); i++ {
|
||||
params[i].Name = nd.paramNames[i]
|
||||
}
|
||||
return nd.data, params, true
|
||||
}
|
||||
|
||||
// Build builds URL router from records.
|
||||
func (rt *Router) Build(records []Record) error {
|
||||
statics, params := makeRecords(records)
|
||||
if len(params) > MaxSize {
|
||||
return fmt.Errorf("denco: too many records")
|
||||
}
|
||||
if rt.SizeHint < 0 {
|
||||
rt.SizeHint = 0
|
||||
for _, p := range params {
|
||||
size := 0
|
||||
for _, k := range p.Key {
|
||||
if k == ParamCharacter || k == WildcardCharacter {
|
||||
size++
|
||||
}
|
||||
}
|
||||
if size > rt.SizeHint {
|
||||
rt.SizeHint = size
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, r := range statics {
|
||||
rt.static[r.Key] = r.Value
|
||||
}
|
||||
if err := rt.param.build(params, 1, 0, make(map[int]struct{})); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Param represents name and value of path parameter.
|
||||
type Param struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
// Params represents the name and value of path parameters.
|
||||
type Params []Param
|
||||
|
||||
// Get gets the first value associated with the given name.
|
||||
// If there are no values associated with the key, Get returns "".
|
||||
func (ps Params) Get(name string) string {
|
||||
for _, p := range ps {
|
||||
if p.Name == name {
|
||||
return p.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type doubleArray struct {
|
||||
bc []baseCheck
|
||||
node []*node
|
||||
}
|
||||
|
||||
func newDoubleArray() *doubleArray {
|
||||
return &doubleArray{
|
||||
bc: []baseCheck{0},
|
||||
node: []*node{nil}, // A start index is adjusting to 1 because 0 will be used as a mark of non-existent node.
|
||||
}
|
||||
}
|
||||
|
||||
// baseCheck contains BASE, CHECK and Extra flags.
|
||||
// From the top, 22bits of BASE, 2bits of Extra flags and 8bits of CHECK.
|
||||
//
|
||||
// BASE (22bit) | Extra flags (2bit) | CHECK (8bit)
|
||||
// |----------------------|--|--------|
|
||||
// 32 10 8 0
|
||||
type baseCheck uint32
|
||||
|
||||
func (bc baseCheck) Base() int {
|
||||
return int(bc >> 10)
|
||||
}
|
||||
|
||||
func (bc *baseCheck) SetBase(base int) {
|
||||
*bc |= baseCheck(base) << 10
|
||||
}
|
||||
|
||||
func (bc baseCheck) Check() byte {
|
||||
return byte(bc)
|
||||
}
|
||||
|
||||
func (bc *baseCheck) SetCheck(check byte) {
|
||||
*bc |= baseCheck(check)
|
||||
}
|
||||
|
||||
func (bc baseCheck) IsEmpty() bool {
|
||||
return bc&0xfffffcff == 0
|
||||
}
|
||||
|
||||
func (bc baseCheck) IsSingleParam() bool {
|
||||
return bc¶mTypeSingle == paramTypeSingle
|
||||
}
|
||||
|
||||
func (bc baseCheck) IsWildcardParam() bool {
|
||||
return bc¶mTypeWildcard == paramTypeWildcard
|
||||
}
|
||||
|
||||
func (bc baseCheck) IsAnyParam() bool {
|
||||
return bc¶mTypeAny != 0
|
||||
}
|
||||
|
||||
func (bc *baseCheck) SetSingleParam() {
|
||||
*bc |= (1 << 8)
|
||||
}
|
||||
|
||||
func (bc *baseCheck) SetWildcardParam() {
|
||||
*bc |= (1 << 9)
|
||||
}
|
||||
|
||||
const (
|
||||
paramTypeSingle = 0x0100
|
||||
paramTypeWildcard = 0x0200
|
||||
paramTypeAny = 0x0300
|
||||
)
|
||||
|
||||
func (da *doubleArray) lookup(path string, params []Param, idx int) (*node, []Param, bool) {
|
||||
indices := make([]uint64, 0, 1)
|
||||
for i := 0; i < len(path); i++ {
|
||||
if da.bc[idx].IsAnyParam() {
|
||||
indices = append(indices, (uint64(i)<<32)|(uint64(idx)&0xffffffff))
|
||||
}
|
||||
c := path[i]
|
||||
if idx = nextIndex(da.bc[idx].Base(), c); idx >= len(da.bc) || da.bc[idx].Check() != c {
|
||||
goto BACKTRACKING
|
||||
}
|
||||
}
|
||||
if next := nextIndex(da.bc[idx].Base(), TerminationCharacter); next < len(da.bc) && da.bc[next].Check() == TerminationCharacter {
|
||||
return da.node[da.bc[next].Base()], params, true
|
||||
}
|
||||
BACKTRACKING:
|
||||
for j := len(indices) - 1; j >= 0; j-- {
|
||||
i, idx := int(indices[j]>>32), int(indices[j]&0xffffffff)
|
||||
if da.bc[idx].IsSingleParam() {
|
||||
idx := nextIndex(da.bc[idx].Base(), ParamCharacter)
|
||||
if idx >= len(da.bc) {
|
||||
break
|
||||
}
|
||||
next := NextSeparator(path, i)
|
||||
params := append(params, Param{Value: path[i:next]})
|
||||
if nd, params, found := da.lookup(path[next:], params, idx); found {
|
||||
return nd, params, true
|
||||
}
|
||||
}
|
||||
if da.bc[idx].IsWildcardParam() {
|
||||
idx := nextIndex(da.bc[idx].Base(), WildcardCharacter)
|
||||
params := append(params, Param{Value: path[i:]})
|
||||
return da.node[da.bc[idx].Base()], params, true
|
||||
}
|
||||
}
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
// build builds double-array from records.
|
||||
func (da *doubleArray) build(srcs []*record, idx, depth int, usedBase map[int]struct{}) error {
|
||||
sort.Stable(recordSlice(srcs))
|
||||
base, siblings, leaf, err := da.arrange(srcs, idx, depth, usedBase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if leaf != nil {
|
||||
nd, err := makeNode(leaf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
da.bc[idx].SetBase(len(da.node))
|
||||
da.node = append(da.node, nd)
|
||||
}
|
||||
for _, sib := range siblings {
|
||||
da.setCheck(nextIndex(base, sib.c), sib.c)
|
||||
}
|
||||
for _, sib := range siblings {
|
||||
records := srcs[sib.start:sib.end]
|
||||
switch sib.c {
|
||||
case ParamCharacter:
|
||||
for _, r := range records {
|
||||
next := NextSeparator(r.Key, depth+1)
|
||||
name := r.Key[depth+1 : next]
|
||||
r.paramNames = append(r.paramNames, name)
|
||||
r.Key = r.Key[next:]
|
||||
}
|
||||
da.bc[idx].SetSingleParam()
|
||||
if err := da.build(records, nextIndex(base, sib.c), 0, usedBase); err != nil {
|
||||
return err
|
||||
}
|
||||
case WildcardCharacter:
|
||||
r := records[0]
|
||||
name := r.Key[depth+1 : len(r.Key)-1]
|
||||
r.paramNames = append(r.paramNames, name)
|
||||
r.Key = ""
|
||||
da.bc[idx].SetWildcardParam()
|
||||
if err := da.build(records, nextIndex(base, sib.c), 0, usedBase); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
if err := da.build(records, nextIndex(base, sib.c), depth+1, usedBase); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setBase sets BASE.
|
||||
func (da *doubleArray) setBase(i, base int) {
|
||||
da.bc[i].SetBase(base)
|
||||
}
|
||||
|
||||
// setCheck sets CHECK.
|
||||
func (da *doubleArray) setCheck(i int, check byte) {
|
||||
da.bc[i].SetCheck(check)
|
||||
}
|
||||
|
||||
// findEmptyIndex returns an index of unused BASE/CHECK node.
|
||||
func (da *doubleArray) findEmptyIndex(start int) int {
|
||||
i := start
|
||||
for ; i < len(da.bc); i++ {
|
||||
if da.bc[i].IsEmpty() {
|
||||
break
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// findBase returns good BASE.
|
||||
func (da *doubleArray) findBase(siblings []sibling, start int, usedBase map[int]struct{}) (base int) {
|
||||
for idx, firstChar := start+1, siblings[0].c; ; idx = da.findEmptyIndex(idx + 1) {
|
||||
base = nextIndex(idx, firstChar)
|
||||
if _, used := usedBase[base]; used {
|
||||
continue
|
||||
}
|
||||
i := 0
|
||||
for ; i < len(siblings); i++ {
|
||||
next := nextIndex(base, siblings[i].c)
|
||||
if len(da.bc) <= next {
|
||||
da.bc = append(da.bc, make([]baseCheck, next-len(da.bc)+1)...)
|
||||
}
|
||||
if !da.bc[next].IsEmpty() {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i == len(siblings) {
|
||||
break
|
||||
}
|
||||
}
|
||||
usedBase[base] = struct{}{}
|
||||
return base
|
||||
}
|
||||
|
||||
func (da *doubleArray) arrange(records []*record, idx, depth int, usedBase map[int]struct{}) (base int, siblings []sibling, leaf *record, err error) {
|
||||
siblings, leaf, err = makeSiblings(records, depth)
|
||||
if err != nil {
|
||||
return -1, nil, nil, err
|
||||
}
|
||||
if len(siblings) < 1 {
|
||||
return -1, nil, leaf, nil
|
||||
}
|
||||
base = da.findBase(siblings, idx, usedBase)
|
||||
if base > MaxSize {
|
||||
return -1, nil, nil, fmt.Errorf("denco: too many elements of internal slice")
|
||||
}
|
||||
da.setBase(idx, base)
|
||||
return base, siblings, leaf, err
|
||||
}
|
||||
|
||||
// node represents a node of Double-Array.
|
||||
type node struct {
|
||||
data interface{}
|
||||
|
||||
// Names of path parameters.
|
||||
paramNames []string
|
||||
}
|
||||
|
||||
// makeNode returns a new node from record.
|
||||
func makeNode(r *record) (*node, error) {
|
||||
dups := make(map[string]bool)
|
||||
for _, name := range r.paramNames {
|
||||
if dups[name] {
|
||||
return nil, fmt.Errorf("denco: path parameter `%v' is duplicated in the key `%v'", name, r.Key)
|
||||
}
|
||||
dups[name] = true
|
||||
}
|
||||
return &node{data: r.Value, paramNames: r.paramNames}, nil
|
||||
}
|
||||
|
||||
// sibling represents an intermediate data of build for Double-Array.
|
||||
type sibling struct {
|
||||
// An index of start of duplicated characters.
|
||||
start int
|
||||
|
||||
// An index of end of duplicated characters.
|
||||
end int
|
||||
|
||||
// A character of sibling.
|
||||
c byte
|
||||
}
|
||||
|
||||
// nextIndex returns a next index of array of BASE/CHECK.
|
||||
func nextIndex(base int, c byte) int {
|
||||
return base ^ int(c)
|
||||
}
|
||||
|
||||
// makeSiblings returns slice of sibling.
|
||||
func makeSiblings(records []*record, depth int) (sib []sibling, leaf *record, err error) {
|
||||
var (
|
||||
pc byte
|
||||
n int
|
||||
)
|
||||
for i, r := range records {
|
||||
if len(r.Key) <= depth {
|
||||
leaf = r
|
||||
continue
|
||||
}
|
||||
c := r.Key[depth]
|
||||
switch {
|
||||
case pc < c:
|
||||
sib = append(sib, sibling{start: i, c: c})
|
||||
case pc == c:
|
||||
continue
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("denco: BUG: routing table hasn't been sorted")
|
||||
}
|
||||
if n > 0 {
|
||||
sib[n-1].end = i
|
||||
}
|
||||
pc = c
|
||||
n++
|
||||
}
|
||||
if n == 0 {
|
||||
return nil, leaf, nil
|
||||
}
|
||||
sib[n-1].end = len(records)
|
||||
return sib, leaf, nil
|
||||
}
|
||||
|
||||
// Record represents a record data for router construction.
|
||||
type Record struct {
|
||||
// Key for router construction.
|
||||
Key string
|
||||
|
||||
// Result value for Key.
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// NewRecord returns a new Record.
|
||||
func NewRecord(key string, value interface{}) Record {
|
||||
return Record{
|
||||
Key: key,
|
||||
Value: value,
|
||||
}
|
||||
}
|
||||
|
||||
// record represents a record that use to build the Double-Array.
|
||||
type record struct {
|
||||
Record
|
||||
paramNames []string
|
||||
}
|
||||
|
||||
// makeRecords returns the records that use to build Double-Arrays.
|
||||
func makeRecords(srcs []Record) (statics, params []*record) {
|
||||
spChars := string([]byte{ParamCharacter, WildcardCharacter})
|
||||
termChar := string(TerminationCharacter)
|
||||
for _, r := range srcs {
|
||||
if strings.ContainsAny(r.Key, spChars) {
|
||||
r.Key += termChar
|
||||
params = append(params, &record{Record: r})
|
||||
} else {
|
||||
statics = append(statics, &record{Record: r})
|
||||
}
|
||||
}
|
||||
return statics, params
|
||||
}
|
||||
|
||||
// recordSlice represents a slice of Record for sort and implements the sort.Interface.
|
||||
type recordSlice []*record
|
||||
|
||||
// Len implements the sort.Interface.Len.
|
||||
func (rs recordSlice) Len() int {
|
||||
return len(rs)
|
||||
}
|
||||
|
||||
// Less implements the sort.Interface.Less.
|
||||
func (rs recordSlice) Less(i, j int) bool {
|
||||
return rs[i].Key < rs[j].Key
|
||||
}
|
||||
|
||||
// Swap implements the sort.Interface.Swap.
|
||||
func (rs recordSlice) Swap(i, j int) {
|
||||
rs[i], rs[j] = rs[j], rs[i]
|
||||
}
|
||||
178
vendor/github.com/go-openapi/runtime/middleware/denco/router_bench_test.go
generated
vendored
Executable file
178
vendor/github.com/go-openapi/runtime/middleware/denco/router_bench_test.go
generated
vendored
Executable file
@@ -0,0 +1,178 @@
|
||||
package denco_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware/denco"
|
||||
)
|
||||
|
||||
func BenchmarkRouterLookupStatic100(b *testing.B) {
|
||||
benchmarkRouterLookupStatic(b, 100)
|
||||
}
|
||||
|
||||
func BenchmarkRouterLookupStatic300(b *testing.B) {
|
||||
benchmarkRouterLookupStatic(b, 300)
|
||||
}
|
||||
|
||||
func BenchmarkRouterLookupStatic700(b *testing.B) {
|
||||
benchmarkRouterLookupStatic(b, 700)
|
||||
}
|
||||
|
||||
func BenchmarkRouterLookupSingleParam100(b *testing.B) {
|
||||
records := makeTestSingleParamRecords(100)
|
||||
benchmarkRouterLookupSingleParam(b, records)
|
||||
}
|
||||
|
||||
func BenchmarkRouterLookupSingleParam300(b *testing.B) {
|
||||
records := makeTestSingleParamRecords(300)
|
||||
benchmarkRouterLookupSingleParam(b, records)
|
||||
}
|
||||
|
||||
func BenchmarkRouterLookupSingleParam700(b *testing.B) {
|
||||
records := makeTestSingleParamRecords(700)
|
||||
benchmarkRouterLookupSingleParam(b, records)
|
||||
}
|
||||
|
||||
func BenchmarkRouterLookupSingle2Param100(b *testing.B) {
|
||||
records := makeTestSingle2ParamRecords(100)
|
||||
benchmarkRouterLookupSingleParam(b, records)
|
||||
}
|
||||
|
||||
func BenchmarkRouterLookupSingle2Param300(b *testing.B) {
|
||||
records := makeTestSingle2ParamRecords(300)
|
||||
benchmarkRouterLookupSingleParam(b, records)
|
||||
}
|
||||
|
||||
func BenchmarkRouterLookupSingle2Param700(b *testing.B) {
|
||||
records := makeTestSingle2ParamRecords(700)
|
||||
benchmarkRouterLookupSingleParam(b, records)
|
||||
}
|
||||
|
||||
func BenchmarkRouterBuildStatic100(b *testing.B) {
|
||||
records := makeTestStaticRecords(100)
|
||||
benchmarkRouterBuild(b, records)
|
||||
}
|
||||
|
||||
func BenchmarkRouterBuildStatic300(b *testing.B) {
|
||||
records := makeTestStaticRecords(300)
|
||||
benchmarkRouterBuild(b, records)
|
||||
}
|
||||
|
||||
func BenchmarkRouterBuildStatic700(b *testing.B) {
|
||||
records := makeTestStaticRecords(700)
|
||||
benchmarkRouterBuild(b, records)
|
||||
}
|
||||
|
||||
func BenchmarkRouterBuildSingleParam100(b *testing.B) {
|
||||
records := makeTestSingleParamRecords(100)
|
||||
benchmarkRouterBuild(b, records)
|
||||
}
|
||||
|
||||
func BenchmarkRouterBuildSingleParam300(b *testing.B) {
|
||||
records := makeTestSingleParamRecords(300)
|
||||
benchmarkRouterBuild(b, records)
|
||||
}
|
||||
|
||||
func BenchmarkRouterBuildSingleParam700(b *testing.B) {
|
||||
records := makeTestSingleParamRecords(700)
|
||||
benchmarkRouterBuild(b, records)
|
||||
}
|
||||
|
||||
func BenchmarkRouterBuildSingle2Param100(b *testing.B) {
|
||||
records := makeTestSingle2ParamRecords(100)
|
||||
benchmarkRouterBuild(b, records)
|
||||
}
|
||||
|
||||
func BenchmarkRouterBuildSingle2Param300(b *testing.B) {
|
||||
records := makeTestSingle2ParamRecords(300)
|
||||
benchmarkRouterBuild(b, records)
|
||||
}
|
||||
|
||||
func BenchmarkRouterBuildSingle2Param700(b *testing.B) {
|
||||
records := makeTestSingle2ParamRecords(700)
|
||||
benchmarkRouterBuild(b, records)
|
||||
}
|
||||
|
||||
func benchmarkRouterLookupStatic(b *testing.B, n int) {
|
||||
b.StopTimer()
|
||||
router := denco.New()
|
||||
records := makeTestStaticRecords(n)
|
||||
if err := router.Build(records); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
record := pickTestRecord(records)
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if r, _, _ := router.Lookup(record.Key); r != record.Value {
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkRouterLookupSingleParam(b *testing.B, records []denco.Record) {
|
||||
router := denco.New()
|
||||
if err := router.Build(records); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
record := pickTestRecord(records)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, _, found := router.Lookup(record.Key); !found {
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkRouterBuild(b *testing.B, records []denco.Record) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
router := denco.New()
|
||||
if err := router.Build(records); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeTestStaticRecords(n int) []denco.Record {
|
||||
records := make([]denco.Record, n)
|
||||
for i := 0; i < n; i++ {
|
||||
records[i] = denco.NewRecord("/"+randomString(50), fmt.Sprintf("testroute%d", i))
|
||||
}
|
||||
return records
|
||||
}
|
||||
|
||||
func makeTestSingleParamRecords(n int) []denco.Record {
|
||||
records := make([]denco.Record, n)
|
||||
for i := 0; i < len(records); i++ {
|
||||
records[i] = denco.NewRecord(fmt.Sprintf("/user%d/:name", i), fmt.Sprintf("testroute%d", i))
|
||||
}
|
||||
return records
|
||||
}
|
||||
|
||||
func makeTestSingle2ParamRecords(n int) []denco.Record {
|
||||
records := make([]denco.Record, n)
|
||||
for i := 0; i < len(records); i++ {
|
||||
records[i] = denco.NewRecord(fmt.Sprintf("/user%d/:name/comment/:id", i), fmt.Sprintf("testroute%d", i))
|
||||
}
|
||||
return records
|
||||
}
|
||||
|
||||
func pickTestRecord(records []denco.Record) denco.Record {
|
||||
return records[len(records)/2]
|
||||
}
|
||||
|
||||
func randomString(n int) string {
|
||||
const srcStrings = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/"
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < n; i++ {
|
||||
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(srcStrings)-1)))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
buf.WriteByte(srcStrings[num.Int64()])
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
526
vendor/github.com/go-openapi/runtime/middleware/denco/router_test.go
generated
vendored
Executable file
526
vendor/github.com/go-openapi/runtime/middleware/denco/router_test.go
generated
vendored
Executable file
@@ -0,0 +1,526 @@
|
||||
package denco_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware/denco"
|
||||
)
|
||||
|
||||
func routes() []denco.Record {
|
||||
return []denco.Record{
|
||||
{"/", "testroute0"},
|
||||
{"/path/to/route", "testroute1"},
|
||||
{"/path/to/other", "testroute2"},
|
||||
{"/path/to/route/a", "testroute3"},
|
||||
{"/path/to/:param", "testroute4"},
|
||||
{"/gists/:param1/foo/:param2", "testroute12"},
|
||||
{"/gists/:param1/foo/bar", "testroute11"},
|
||||
{"/:param1/:param2/foo/:param3", "testroute13"},
|
||||
{"/path/to/wildcard/*routepath", "testroute5"},
|
||||
{"/path/to/:param1/:param2", "testroute6"},
|
||||
{"/path/to/:param1/sep/:param2", "testroute7"},
|
||||
{"/:year/:month/:day", "testroute8"},
|
||||
{"/user/:id", "testroute9"},
|
||||
{"/a/to/b/:param/*routepath", "testroute10"},
|
||||
}
|
||||
}
|
||||
|
||||
var realURIs = []denco.Record{
|
||||
{"/authorizations", "/authorizations"},
|
||||
{"/authorizations/:id", "/authorizations/:id"},
|
||||
{"/applications/:client_id/tokens/:access_token", "/applications/:client_id/tokens/:access_token"},
|
||||
{"/events", "/events"},
|
||||
{"/repos/:owner/:repo/events", "/repos/:owner/:repo/events"},
|
||||
{"/networks/:owner/:repo/events", "/networks/:owner/:repo/events"},
|
||||
{"/orgs/:org/events", "/orgs/:org/events"},
|
||||
{"/users/:user/received_events", "/users/:user/received_events"},
|
||||
{"/users/:user/received_events/public", "/users/:user/received_events/public"},
|
||||
{"/users/:user/events", "/users/:user/events"},
|
||||
{"/users/:user/events/public", "/users/:user/events/public"},
|
||||
{"/users/:user/events/orgs/:org", "/users/:user/events/orgs/:org"},
|
||||
{"/feeds", "/feeds"},
|
||||
{"/notifications", "/notifications"},
|
||||
{"/repos/:owner/:repo/notifications", "/repos/:owner/:repo/notifications"},
|
||||
{"/notifications/threads/:id", "/notifications/threads/:id"},
|
||||
{"/notifications/threads/:id/subscription", "/notifications/threads/:id/subscription"},
|
||||
{"/repos/:owner/:repo/stargazers", "/repos/:owner/:repo/stargazers"},
|
||||
{"/users/:user/starred", "/users/:user/starred"},
|
||||
{"/user/starred", "/user/starred"},
|
||||
{"/user/starred/:owner/:repo", "/user/starred/:owner/:repo"},
|
||||
{"/repos/:owner/:repo/subscribers", "/repos/:owner/:repo/subscribers"},
|
||||
{"/users/:user/subscriptions", "/users/:user/subscriptions"},
|
||||
{"/user/subscriptions", "/user/subscriptions"},
|
||||
{"/repos/:owner/:repo/subscription", "/repos/:owner/:repo/subscription"},
|
||||
{"/user/subscriptions/:owner/:repo", "/user/subscriptions/:owner/:repo"},
|
||||
{"/users/:user/gists", "/users/:user/gists"},
|
||||
{"/gists", "/gists"},
|
||||
{"/gists/:id", "/gists/:id"},
|
||||
{"/gists/:id/star", "/gists/:id/star"},
|
||||
{"/repos/:owner/:repo/git/blobs/:sha", "/repos/:owner/:repo/git/blobs/:sha"},
|
||||
{"/repos/:owner/:repo/git/commits/:sha", "/repos/:owner/:repo/git/commits/:sha"},
|
||||
{"/repos/:owner/:repo/git/refs", "/repos/:owner/:repo/git/refs"},
|
||||
{"/repos/:owner/:repo/git/tags/:sha", "/repos/:owner/:repo/git/tags/:sha"},
|
||||
{"/repos/:owner/:repo/git/trees/:sha", "/repos/:owner/:repo/git/trees/:sha"},
|
||||
{"/issues", "/issues"},
|
||||
{"/user/issues", "/user/issues"},
|
||||
{"/orgs/:org/issues", "/orgs/:org/issues"},
|
||||
{"/repos/:owner/:repo/issues", "/repos/:owner/:repo/issues"},
|
||||
{"/repos/:owner/:repo/issues/:number", "/repos/:owner/:repo/issues/:number"},
|
||||
{"/repos/:owner/:repo/assignees", "/repos/:owner/:repo/assignees"},
|
||||
{"/repos/:owner/:repo/assignees/:assignee", "/repos/:owner/:repo/assignees/:assignee"},
|
||||
{"/repos/:owner/:repo/issues/:number/comments", "/repos/:owner/:repo/issues/:number/comments"},
|
||||
{"/repos/:owner/:repo/issues/:number/events", "/repos/:owner/:repo/issues/:number/events"},
|
||||
{"/repos/:owner/:repo/labels", "/repos/:owner/:repo/labels"},
|
||||
{"/repos/:owner/:repo/labels/:name", "/repos/:owner/:repo/labels/:name"},
|
||||
{"/repos/:owner/:repo/issues/:number/labels", "/repos/:owner/:repo/issues/:number/labels"},
|
||||
{"/repos/:owner/:repo/milestones/:number/labels", "/repos/:owner/:repo/milestones/:number/labels"},
|
||||
{"/repos/:owner/:repo/milestones", "/repos/:owner/:repo/milestones"},
|
||||
{"/repos/:owner/:repo/milestones/:number", "/repos/:owner/:repo/milestones/:number"},
|
||||
{"/emojis", "/emojis"},
|
||||
{"/gitignore/templates", "/gitignore/templates"},
|
||||
{"/gitignore/templates/:name", "/gitignore/templates/:name"},
|
||||
{"/meta", "/meta"},
|
||||
{"/rate_limit", "/rate_limit"},
|
||||
{"/users/:user/orgs", "/users/:user/orgs"},
|
||||
{"/user/orgs", "/user/orgs"},
|
||||
{"/orgs/:org", "/orgs/:org"},
|
||||
{"/orgs/:org/members", "/orgs/:org/members"},
|
||||
{"/orgs/:org/members/:user", "/orgs/:org/members/:user"},
|
||||
{"/orgs/:org/public_members", "/orgs/:org/public_members"},
|
||||
{"/orgs/:org/public_members/:user", "/orgs/:org/public_members/:user"},
|
||||
{"/orgs/:org/teams", "/orgs/:org/teams"},
|
||||
{"/teams/:id", "/teams/:id"},
|
||||
{"/teams/:id/members", "/teams/:id/members"},
|
||||
{"/teams/:id/members/:user", "/teams/:id/members/:user"},
|
||||
{"/teams/:id/repos", "/teams/:id/repos"},
|
||||
{"/teams/:id/repos/:owner/:repo", "/teams/:id/repos/:owner/:repo"},
|
||||
{"/user/teams", "/user/teams"},
|
||||
{"/repos/:owner/:repo/pulls", "/repos/:owner/:repo/pulls"},
|
||||
{"/repos/:owner/:repo/pulls/:number", "/repos/:owner/:repo/pulls/:number"},
|
||||
{"/repos/:owner/:repo/pulls/:number/commits", "/repos/:owner/:repo/pulls/:number/commits"},
|
||||
{"/repos/:owner/:repo/pulls/:number/files", "/repos/:owner/:repo/pulls/:number/files"},
|
||||
{"/repos/:owner/:repo/pulls/:number/merge", "/repos/:owner/:repo/pulls/:number/merge"},
|
||||
{"/repos/:owner/:repo/pulls/:number/comments", "/repos/:owner/:repo/pulls/:number/comments"},
|
||||
{"/user/repos", "/user/repos"},
|
||||
{"/users/:user/repos", "/users/:user/repos"},
|
||||
{"/orgs/:org/repos", "/orgs/:org/repos"},
|
||||
{"/repositories", "/repositories"},
|
||||
{"/repos/:owner/:repo", "/repos/:owner/:repo"},
|
||||
{"/repos/:owner/:repo/contributors", "/repos/:owner/:repo/contributors"},
|
||||
{"/repos/:owner/:repo/languages", "/repos/:owner/:repo/languages"},
|
||||
{"/repos/:owner/:repo/teams", "/repos/:owner/:repo/teams"},
|
||||
{"/repos/:owner/:repo/tags", "/repos/:owner/:repo/tags"},
|
||||
{"/repos/:owner/:repo/branches", "/repos/:owner/:repo/branches"},
|
||||
{"/repos/:owner/:repo/branches/:branch", "/repos/:owner/:repo/branches/:branch"},
|
||||
{"/repos/:owner/:repo/collaborators", "/repos/:owner/:repo/collaborators"},
|
||||
{"/repos/:owner/:repo/collaborators/:user", "/repos/:owner/:repo/collaborators/:user"},
|
||||
{"/repos/:owner/:repo/comments", "/repos/:owner/:repo/comments"},
|
||||
{"/repos/:owner/:repo/commits/:sha/comments", "/repos/:owner/:repo/commits/:sha/comments"},
|
||||
{"/repos/:owner/:repo/comments/:id", "/repos/:owner/:repo/comments/:id"},
|
||||
{"/repos/:owner/:repo/commits", "/repos/:owner/:repo/commits"},
|
||||
{"/repos/:owner/:repo/commits/:sha", "/repos/:owner/:repo/commits/:sha"},
|
||||
{"/repos/:owner/:repo/readme", "/repos/:owner/:repo/readme"},
|
||||
{"/repos/:owner/:repo/keys", "/repos/:owner/:repo/keys"},
|
||||
{"/repos/:owner/:repo/keys/:id", "/repos/:owner/:repo/keys/:id"},
|
||||
{"/repos/:owner/:repo/downloads", "/repos/:owner/:repo/downloads"},
|
||||
{"/repos/:owner/:repo/downloads/:id", "/repos/:owner/:repo/downloads/:id"},
|
||||
{"/repos/:owner/:repo/forks", "/repos/:owner/:repo/forks"},
|
||||
{"/repos/:owner/:repo/hooks", "/repos/:owner/:repo/hooks"},
|
||||
{"/repos/:owner/:repo/hooks/:id", "/repos/:owner/:repo/hooks/:id"},
|
||||
{"/repos/:owner/:repo/releases", "/repos/:owner/:repo/releases"},
|
||||
{"/repos/:owner/:repo/releases/:id", "/repos/:owner/:repo/releases/:id"},
|
||||
{"/repos/:owner/:repo/releases/:id/assets", "/repos/:owner/:repo/releases/:id/assets"},
|
||||
{"/repos/:owner/:repo/stats/contributors", "/repos/:owner/:repo/stats/contributors"},
|
||||
{"/repos/:owner/:repo/stats/commit_activity", "/repos/:owner/:repo/stats/commit_activity"},
|
||||
{"/repos/:owner/:repo/stats/code_frequency", "/repos/:owner/:repo/stats/code_frequency"},
|
||||
{"/repos/:owner/:repo/stats/participation", "/repos/:owner/:repo/stats/participation"},
|
||||
{"/repos/:owner/:repo/stats/punch_card", "/repos/:owner/:repo/stats/punch_card"},
|
||||
{"/repos/:owner/:repo/statuses/:ref", "/repos/:owner/:repo/statuses/:ref"},
|
||||
{"/search/repositories", "/search/repositories"},
|
||||
{"/search/code", "/search/code"},
|
||||
{"/search/issues", "/search/issues"},
|
||||
{"/search/users", "/search/users"},
|
||||
{"/legacy/issues/search/:owner/:repository/:state/:keyword", "/legacy/issues/search/:owner/:repository/:state/:keyword"},
|
||||
{"/legacy/repos/search/:keyword", "/legacy/repos/search/:keyword"},
|
||||
{"/legacy/user/search/:keyword", "/legacy/user/search/:keyword"},
|
||||
{"/legacy/user/email/:email", "/legacy/user/email/:email"},
|
||||
{"/users/:user", "/users/:user"},
|
||||
{"/user", "/user"},
|
||||
{"/users", "/users"},
|
||||
{"/user/emails", "/user/emails"},
|
||||
{"/users/:user/followers", "/users/:user/followers"},
|
||||
{"/user/followers", "/user/followers"},
|
||||
{"/users/:user/following", "/users/:user/following"},
|
||||
{"/user/following", "/user/following"},
|
||||
{"/user/following/:user", "/user/following/:user"},
|
||||
{"/users/:user/following/:target_user", "/users/:user/following/:target_user"},
|
||||
{"/users/:user/keys", "/users/:user/keys"},
|
||||
{"/user/keys", "/user/keys"},
|
||||
{"/user/keys/:id", "/user/keys/:id"},
|
||||
{"/people/:userId", "/people/:userId"},
|
||||
{"/people", "/people"},
|
||||
{"/activities/:activityId/people/:collection", "/activities/:activityId/people/:collection"},
|
||||
{"/people/:userId/people/:collection", "/people/:userId/people/:collection"},
|
||||
{"/people/:userId/openIdConnect", "/people/:userId/openIdConnect"},
|
||||
{"/people/:userId/activities/:collection", "/people/:userId/activities/:collection"},
|
||||
{"/activities/:activityId", "/activities/:activityId"},
|
||||
{"/activities", "/activities"},
|
||||
{"/activities/:activityId/comments", "/activities/:activityId/comments"},
|
||||
{"/comments/:commentId", "/comments/:commentId"},
|
||||
{"/people/:userId/moments/:collection", "/people/:userId/moments/:collection"},
|
||||
}
|
||||
|
||||
type testcase struct {
|
||||
path string
|
||||
value interface{}
|
||||
params []denco.Param
|
||||
found bool
|
||||
}
|
||||
|
||||
func runLookupTest(t *testing.T, records []denco.Record, testcases []testcase) {
|
||||
r := denco.New()
|
||||
if err := r.Build(records); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
data, params, found := r.Lookup(testcase.path)
|
||||
if !reflect.DeepEqual(data, testcase.value) || !reflect.DeepEqual(params, denco.Params(testcase.params)) || !reflect.DeepEqual(found, testcase.found) {
|
||||
t.Errorf("Router.Lookup(%q) => (%#v, %#v, %#v), want (%#v, %#v, %#v)", testcase.path, data, params, found, testcase.value, denco.Params(testcase.params), testcase.found)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouter_Lookup(t *testing.T) {
|
||||
testcases := []testcase{
|
||||
{"/", "testroute0", nil, true},
|
||||
{"/gists/1323/foo/bar", "testroute11", []denco.Param{{"param1", "1323"}}, true},
|
||||
{"/gists/1323/foo/133", "testroute12", []denco.Param{{"param1", "1323"}, {"param2", "133"}}, true},
|
||||
{"/234/1323/foo/133", "testroute13", []denco.Param{{"param1", "234"}, {"param2", "1323"}, {"param3", "133"}}, true},
|
||||
{"/path/to/route", "testroute1", nil, true},
|
||||
{"/path/to/other", "testroute2", nil, true},
|
||||
{"/path/to/route/a", "testroute3", nil, true},
|
||||
{"/path/to/hoge", "testroute4", []denco.Param{{"param", "hoge"}}, true},
|
||||
{"/path/to/wildcard/some/params", "testroute5", []denco.Param{{"routepath", "some/params"}}, true},
|
||||
{"/path/to/o1/o2", "testroute6", []denco.Param{{"param1", "o1"}, {"param2", "o2"}}, true},
|
||||
{"/path/to/p1/sep/p2", "testroute7", []denco.Param{{"param1", "p1"}, {"param2", "p2"}}, true},
|
||||
{"/2014/01/06", "testroute8", []denco.Param{{"year", "2014"}, {"month", "01"}, {"day", "06"}}, true},
|
||||
{"/user/777", "testroute9", []denco.Param{{"id", "777"}}, true},
|
||||
{"/a/to/b/p1/some/wildcard/params", "testroute10", []denco.Param{{"param", "p1"}, {"routepath", "some/wildcard/params"}}, true},
|
||||
{"/missing", nil, nil, false},
|
||||
}
|
||||
runLookupTest(t, routes(), testcases)
|
||||
|
||||
records := []denco.Record{
|
||||
{"/", "testroute0"},
|
||||
{"/:b", "testroute1"},
|
||||
{"/*wildcard", "testroute2"},
|
||||
}
|
||||
testcases = []testcase{
|
||||
{"/", "testroute0", nil, true},
|
||||
{"/true", "testroute1", []denco.Param{{"b", "true"}}, true},
|
||||
{"/foo/bar", "testroute2", []denco.Param{{"wildcard", "foo/bar"}}, true},
|
||||
}
|
||||
runLookupTest(t, records, testcases)
|
||||
|
||||
records = []denco.Record{
|
||||
{"/networks/:owner/:repo/events", "testroute0"},
|
||||
{"/orgs/:org/events", "testroute1"},
|
||||
{"/notifications/threads/:id", "testroute2"},
|
||||
{"/mypathisgreat/:thing-id", "testroute3"},
|
||||
}
|
||||
testcases = []testcase{
|
||||
{"/networks/:owner/:repo/events", "testroute0", []denco.Param{{"owner", ":owner"}, {"repo", ":repo"}}, true},
|
||||
{"/orgs/:org/events", "testroute1", []denco.Param{{"org", ":org"}}, true},
|
||||
{"/notifications/threads/:id", "testroute2", []denco.Param{{"id", ":id"}}, true},
|
||||
{"/mypathisgreat/:thing-id", "testroute3", []denco.Param{{"thing-id", ":thing-id"}}, true},
|
||||
}
|
||||
runLookupTest(t, records, testcases)
|
||||
|
||||
runLookupTest(t, []denco.Record{
|
||||
{"/", "route2"},
|
||||
}, []testcase{
|
||||
{"/user/alice", nil, nil, false},
|
||||
})
|
||||
|
||||
runLookupTest(t, []denco.Record{
|
||||
{"/user/:name", "route1"},
|
||||
}, []testcase{
|
||||
{"/", nil, nil, false},
|
||||
})
|
||||
|
||||
runLookupTest(t, []denco.Record{
|
||||
{"/*wildcard", "testroute0"},
|
||||
{"/a/:b", "testroute1"},
|
||||
}, []testcase{
|
||||
{"/a", "testroute0", []denco.Param{{"wildcard", "a"}}, true},
|
||||
})
|
||||
}
|
||||
|
||||
func TestRouter_Lookup_withManyRoutes(t *testing.T) {
|
||||
n := 1000
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
records := make([]denco.Record, n)
|
||||
for i := 0; i < n; i++ {
|
||||
records[i] = denco.Record{Key: "/" + randomString(rand.Intn(50)+10), Value: fmt.Sprintf("route%d", i)}
|
||||
}
|
||||
router := denco.New()
|
||||
if err := router.Build(records); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, r := range records {
|
||||
data, params, found := router.Lookup(r.Key)
|
||||
if !reflect.DeepEqual(data, r.Value) || len(params) != 0 || !reflect.DeepEqual(found, true) {
|
||||
t.Errorf("Router.Lookup(%q) => (%#v, %#v, %#v), want (%#v, %#v, %#v)", r.Key, data, len(params), found, r.Value, 0, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouter_Lookup_realURIs(t *testing.T) {
|
||||
testcases := []testcase{
|
||||
{"/authorizations", "/authorizations", nil, true},
|
||||
{"/authorizations/1", "/authorizations/:id", []denco.Param{{"id", "1"}}, true},
|
||||
{"/applications/1/tokens/zohRoo7e", "/applications/:client_id/tokens/:access_token", []denco.Param{{"client_id", "1"}, {"access_token", "zohRoo7e"}}, true},
|
||||
{"/events", "/events", nil, true},
|
||||
{"/repos/naoina/denco/events", "/repos/:owner/:repo/events", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/networks/naoina/denco/events", "/networks/:owner/:repo/events", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/orgs/something/events", "/orgs/:org/events", []denco.Param{{"org", "something"}}, true},
|
||||
{"/users/naoina/received_events", "/users/:user/received_events", []denco.Param{{"user", "naoina"}}, true},
|
||||
{"/users/naoina/received_events/public", "/users/:user/received_events/public", []denco.Param{{"user", "naoina"}}, true},
|
||||
{"/users/naoina/events", "/users/:user/events", []denco.Param{{"user", "naoina"}}, true},
|
||||
{"/users/naoina/events/public", "/users/:user/events/public", []denco.Param{{"user", "naoina"}}, true},
|
||||
{"/users/naoina/events/orgs/something", "/users/:user/events/orgs/:org", []denco.Param{{"user", "naoina"}, {"org", "something"}}, true},
|
||||
{"/feeds", "/feeds", nil, true},
|
||||
{"/notifications", "/notifications", nil, true},
|
||||
{"/repos/naoina/denco/notifications", "/repos/:owner/:repo/notifications", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/notifications/threads/1", "/notifications/threads/:id", []denco.Param{{"id", "1"}}, true},
|
||||
{"/notifications/threads/2/subscription", "/notifications/threads/:id/subscription", []denco.Param{{"id", "2"}}, true},
|
||||
{"/repos/naoina/denco/stargazers", "/repos/:owner/:repo/stargazers", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/users/naoina/starred", "/users/:user/starred", []denco.Param{{"user", "naoina"}}, true},
|
||||
{"/user/starred", "/user/starred", nil, true},
|
||||
{"/user/starred/naoina/denco", "/user/starred/:owner/:repo", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/subscribers", "/repos/:owner/:repo/subscribers", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/users/naoina/subscriptions", "/users/:user/subscriptions", []denco.Param{{"user", "naoina"}}, true},
|
||||
{"/user/subscriptions", "/user/subscriptions", nil, true},
|
||||
{"/repos/naoina/denco/subscription", "/repos/:owner/:repo/subscription", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/user/subscriptions/naoina/denco", "/user/subscriptions/:owner/:repo", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/users/naoina/gists", "/users/:user/gists", []denco.Param{{"user", "naoina"}}, true},
|
||||
{"/gists", "/gists", nil, true},
|
||||
{"/gists/1", "/gists/:id", []denco.Param{{"id", "1"}}, true},
|
||||
{"/gists/2/star", "/gists/:id/star", []denco.Param{{"id", "2"}}, true},
|
||||
{"/repos/naoina/denco/git/blobs/03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9", "/repos/:owner/:repo/git/blobs/:sha", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"sha", "03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9"}}, true},
|
||||
{"/repos/naoina/denco/git/commits/03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9", "/repos/:owner/:repo/git/commits/:sha", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"sha", "03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9"}}, true},
|
||||
{"/repos/naoina/denco/git/refs", "/repos/:owner/:repo/git/refs", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/git/tags/03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9", "/repos/:owner/:repo/git/tags/:sha", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"sha", "03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9"}}, true},
|
||||
{"/repos/naoina/denco/git/trees/03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9", "/repos/:owner/:repo/git/trees/:sha", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"sha", "03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9"}}, true},
|
||||
{"/issues", "/issues", nil, true},
|
||||
{"/user/issues", "/user/issues", nil, true},
|
||||
{"/orgs/something/issues", "/orgs/:org/issues", []denco.Param{{"org", "something"}}, true},
|
||||
{"/repos/naoina/denco/issues", "/repos/:owner/:repo/issues", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/issues/1", "/repos/:owner/:repo/issues/:number", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
|
||||
{"/repos/naoina/denco/assignees", "/repos/:owner/:repo/assignees", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/assignees/foo", "/repos/:owner/:repo/assignees/:assignee", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"assignee", "foo"}}, true},
|
||||
{"/repos/naoina/denco/issues/1/comments", "/repos/:owner/:repo/issues/:number/comments", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
|
||||
{"/repos/naoina/denco/issues/1/events", "/repos/:owner/:repo/issues/:number/events", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
|
||||
{"/repos/naoina/denco/labels", "/repos/:owner/:repo/labels", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/labels/bug", "/repos/:owner/:repo/labels/:name", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"name", "bug"}}, true},
|
||||
{"/repos/naoina/denco/issues/1/labels", "/repos/:owner/:repo/issues/:number/labels", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
|
||||
{"/repos/naoina/denco/milestones/1/labels", "/repos/:owner/:repo/milestones/:number/labels", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
|
||||
{"/repos/naoina/denco/milestones", "/repos/:owner/:repo/milestones", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/milestones/1", "/repos/:owner/:repo/milestones/:number", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
|
||||
{"/emojis", "/emojis", nil, true},
|
||||
{"/gitignore/templates", "/gitignore/templates", nil, true},
|
||||
{"/gitignore/templates/Go", "/gitignore/templates/:name", []denco.Param{{"name", "Go"}}, true},
|
||||
{"/meta", "/meta", nil, true},
|
||||
{"/rate_limit", "/rate_limit", nil, true},
|
||||
{"/users/naoina/orgs", "/users/:user/orgs", []denco.Param{{"user", "naoina"}}, true},
|
||||
{"/user/orgs", "/user/orgs", nil, true},
|
||||
{"/orgs/something", "/orgs/:org", []denco.Param{{"org", "something"}}, true},
|
||||
{"/orgs/something/members", "/orgs/:org/members", []denco.Param{{"org", "something"}}, true},
|
||||
{"/orgs/something/members/naoina", "/orgs/:org/members/:user", []denco.Param{{"org", "something"}, {"user", "naoina"}}, true},
|
||||
{"/orgs/something/public_members", "/orgs/:org/public_members", []denco.Param{{"org", "something"}}, true},
|
||||
{"/orgs/something/public_members/naoina", "/orgs/:org/public_members/:user", []denco.Param{{"org", "something"}, {"user", "naoina"}}, true},
|
||||
{"/orgs/something/teams", "/orgs/:org/teams", []denco.Param{{"org", "something"}}, true},
|
||||
{"/teams/1", "/teams/:id", []denco.Param{{"id", "1"}}, true},
|
||||
{"/teams/2/members", "/teams/:id/members", []denco.Param{{"id", "2"}}, true},
|
||||
{"/teams/3/members/naoina", "/teams/:id/members/:user", []denco.Param{{"id", "3"}, {"user", "naoina"}}, true},
|
||||
{"/teams/4/repos", "/teams/:id/repos", []denco.Param{{"id", "4"}}, true},
|
||||
{"/teams/5/repos/naoina/denco", "/teams/:id/repos/:owner/:repo", []denco.Param{{"id", "5"}, {"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/user/teams", "/user/teams", nil, true},
|
||||
{"/repos/naoina/denco/pulls", "/repos/:owner/:repo/pulls", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/pulls/1", "/repos/:owner/:repo/pulls/:number", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
|
||||
{"/repos/naoina/denco/pulls/1/commits", "/repos/:owner/:repo/pulls/:number/commits", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
|
||||
{"/repos/naoina/denco/pulls/1/files", "/repos/:owner/:repo/pulls/:number/files", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
|
||||
{"/repos/naoina/denco/pulls/1/merge", "/repos/:owner/:repo/pulls/:number/merge", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
|
||||
{"/repos/naoina/denco/pulls/1/comments", "/repos/:owner/:repo/pulls/:number/comments", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
|
||||
{"/user/repos", "/user/repos", nil, true},
|
||||
{"/users/naoina/repos", "/users/:user/repos", []denco.Param{{"user", "naoina"}}, true},
|
||||
{"/orgs/something/repos", "/orgs/:org/repos", []denco.Param{{"org", "something"}}, true},
|
||||
{"/repositories", "/repositories", nil, true},
|
||||
{"/repos/naoina/denco", "/repos/:owner/:repo", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/contributors", "/repos/:owner/:repo/contributors", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/languages", "/repos/:owner/:repo/languages", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/teams", "/repos/:owner/:repo/teams", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/tags", "/repos/:owner/:repo/tags", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/branches", "/repos/:owner/:repo/branches", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/branches/master", "/repos/:owner/:repo/branches/:branch", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"branch", "master"}}, true},
|
||||
{"/repos/naoina/denco/collaborators", "/repos/:owner/:repo/collaborators", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/collaborators/something", "/repos/:owner/:repo/collaborators/:user", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"user", "something"}}, true},
|
||||
{"/repos/naoina/denco/comments", "/repos/:owner/:repo/comments", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/commits/03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9/comments", "/repos/:owner/:repo/commits/:sha/comments", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"sha", "03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9"}}, true},
|
||||
{"/repos/naoina/denco/comments/1", "/repos/:owner/:repo/comments/:id", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"id", "1"}}, true},
|
||||
{"/repos/naoina/denco/commits", "/repos/:owner/:repo/commits", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/commits/03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9", "/repos/:owner/:repo/commits/:sha", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"sha", "03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9"}}, true},
|
||||
{"/repos/naoina/denco/readme", "/repos/:owner/:repo/readme", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/keys", "/repos/:owner/:repo/keys", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/keys/1", "/repos/:owner/:repo/keys/:id", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"id", "1"}}, true},
|
||||
{"/repos/naoina/denco/downloads", "/repos/:owner/:repo/downloads", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/downloads/2", "/repos/:owner/:repo/downloads/:id", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"id", "2"}}, true},
|
||||
{"/repos/naoina/denco/forks", "/repos/:owner/:repo/forks", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/hooks", "/repos/:owner/:repo/hooks", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/hooks/2", "/repos/:owner/:repo/hooks/:id", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"id", "2"}}, true},
|
||||
{"/repos/naoina/denco/releases", "/repos/:owner/:repo/releases", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/releases/1", "/repos/:owner/:repo/releases/:id", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"id", "1"}}, true},
|
||||
{"/repos/naoina/denco/releases/1/assets", "/repos/:owner/:repo/releases/:id/assets", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"id", "1"}}, true},
|
||||
{"/repos/naoina/denco/stats/contributors", "/repos/:owner/:repo/stats/contributors", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/stats/commit_activity", "/repos/:owner/:repo/stats/commit_activity", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/stats/code_frequency", "/repos/:owner/:repo/stats/code_frequency", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/stats/participation", "/repos/:owner/:repo/stats/participation", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/stats/punch_card", "/repos/:owner/:repo/stats/punch_card", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
|
||||
{"/repos/naoina/denco/statuses/master", "/repos/:owner/:repo/statuses/:ref", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"ref", "master"}}, true},
|
||||
{"/search/repositories", "/search/repositories", nil, true},
|
||||
{"/search/code", "/search/code", nil, true},
|
||||
{"/search/issues", "/search/issues", nil, true},
|
||||
{"/search/users", "/search/users", nil, true},
|
||||
{"/legacy/issues/search/naoina/denco/closed/test", "/legacy/issues/search/:owner/:repository/:state/:keyword", []denco.Param{{"owner", "naoina"}, {"repository", "denco"}, {"state", "closed"}, {"keyword", "test"}}, true},
|
||||
{"/legacy/repos/search/test", "/legacy/repos/search/:keyword", []denco.Param{{"keyword", "test"}}, true},
|
||||
{"/legacy/user/search/test", "/legacy/user/search/:keyword", []denco.Param{{"keyword", "test"}}, true},
|
||||
{"/legacy/user/email/naoina@kuune.org", "/legacy/user/email/:email", []denco.Param{{"email", "naoina@kuune.org"}}, true},
|
||||
{"/users/naoina", "/users/:user", []denco.Param{{"user", "naoina"}}, true},
|
||||
{"/user", "/user", nil, true},
|
||||
{"/users", "/users", nil, true},
|
||||
{"/user/emails", "/user/emails", nil, true},
|
||||
{"/users/naoina/followers", "/users/:user/followers", []denco.Param{{"user", "naoina"}}, true},
|
||||
{"/user/followers", "/user/followers", nil, true},
|
||||
{"/users/naoina/following", "/users/:user/following", []denco.Param{{"user", "naoina"}}, true},
|
||||
{"/user/following", "/user/following", nil, true},
|
||||
{"/user/following/naoina", "/user/following/:user", []denco.Param{{"user", "naoina"}}, true},
|
||||
{"/users/naoina/following/target", "/users/:user/following/:target_user", []denco.Param{{"user", "naoina"}, {"target_user", "target"}}, true},
|
||||
{"/users/naoina/keys", "/users/:user/keys", []denco.Param{{"user", "naoina"}}, true},
|
||||
{"/user/keys", "/user/keys", nil, true},
|
||||
{"/user/keys/1", "/user/keys/:id", []denco.Param{{"id", "1"}}, true},
|
||||
{"/people/me", "/people/:userId", []denco.Param{{"userId", "me"}}, true},
|
||||
{"/people", "/people", nil, true},
|
||||
{"/activities/foo/people/vault", "/activities/:activityId/people/:collection", []denco.Param{{"activityId", "foo"}, {"collection", "vault"}}, true},
|
||||
{"/people/me/people/vault", "/people/:userId/people/:collection", []denco.Param{{"userId", "me"}, {"collection", "vault"}}, true},
|
||||
{"/people/me/openIdConnect", "/people/:userId/openIdConnect", []denco.Param{{"userId", "me"}}, true},
|
||||
{"/people/me/activities/vault", "/people/:userId/activities/:collection", []denco.Param{{"userId", "me"}, {"collection", "vault"}}, true},
|
||||
{"/activities/foo", "/activities/:activityId", []denco.Param{{"activityId", "foo"}}, true},
|
||||
{"/activities", "/activities", nil, true},
|
||||
{"/activities/foo/comments", "/activities/:activityId/comments", []denco.Param{{"activityId", "foo"}}, true},
|
||||
{"/comments/hoge", "/comments/:commentId", []denco.Param{{"commentId", "hoge"}}, true},
|
||||
{"/people/me/moments/vault", "/people/:userId/moments/:collection", []denco.Param{{"userId", "me"}, {"collection", "vault"}}, true},
|
||||
}
|
||||
runLookupTest(t, realURIs, testcases)
|
||||
}
|
||||
|
||||
func TestRouter_Build(t *testing.T) {
|
||||
// test for duplicate name of path parameters.
|
||||
func() {
|
||||
r := denco.New()
|
||||
if err := r.Build([]denco.Record{
|
||||
{"/:user/:id/:id", "testroute0"},
|
||||
{"/:user/:user/:id", "testroute0"},
|
||||
}); err == nil {
|
||||
t.Errorf("no error returned by duplicate name of path parameters")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func TestRouter_Build_withoutSizeHint(t *testing.T) {
|
||||
for _, v := range []struct {
|
||||
keys []string
|
||||
sizeHint int
|
||||
}{
|
||||
{[]string{"/user"}, 0},
|
||||
{[]string{"/user/:id"}, 1},
|
||||
{[]string{"/user/:id/post"}, 1},
|
||||
{[]string{"/user/:id/:group"}, 2},
|
||||
{[]string{"/user/:id/post/:cid"}, 2},
|
||||
{[]string{"/user/:id/post/:cid", "/admin/:id/post/:cid"}, 2},
|
||||
{[]string{"/user/:id", "/admin/:id/post/:cid"}, 2},
|
||||
{[]string{"/user/:id/post/:cid", "/admin/:id/post/:cid/:type"}, 3},
|
||||
} {
|
||||
r := denco.New()
|
||||
actual := r.SizeHint
|
||||
expect := -1
|
||||
if !reflect.DeepEqual(actual, expect) {
|
||||
t.Errorf(`before Build; Router.SizeHint => (%[1]T=%#[1]v); want (%[2]T=%#[2]v)`, actual, expect)
|
||||
}
|
||||
records := make([]denco.Record, len(v.keys))
|
||||
for i, k := range v.keys {
|
||||
records[i] = denco.Record{Key: k, Value: "value"}
|
||||
}
|
||||
if err := r.Build(records); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual = r.SizeHint
|
||||
expect = v.sizeHint
|
||||
if !reflect.DeepEqual(actual, expect) {
|
||||
t.Errorf(`Router.Build(%#v); Router.SizeHint => (%[2]T=%#[2]v); want (%[3]T=%#[3]v)`, records, actual, expect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouter_Build_withSizeHint(t *testing.T) {
|
||||
for _, v := range []struct {
|
||||
key string
|
||||
sizeHint int
|
||||
expect int
|
||||
}{
|
||||
{"/user", 0, 0},
|
||||
{"/user", 1, 1},
|
||||
{"/user", 2, 2},
|
||||
{"/user/:id", 3, 3},
|
||||
{"/user/:id/:group", 0, 0},
|
||||
{"/user/:id/:group", 1, 1},
|
||||
} {
|
||||
r := denco.New()
|
||||
r.SizeHint = v.sizeHint
|
||||
records := []denco.Record{
|
||||
{v.key, "value"},
|
||||
}
|
||||
if err := r.Build(records); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual := r.SizeHint
|
||||
expect := v.expect
|
||||
if !reflect.DeepEqual(actual, expect) {
|
||||
t.Errorf(`Router.Build(%#v); Router.SizeHint => (%[2]T=%#[2]v); want (%[3]T=%#[3]v)`, records, actual, expect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParams_Get(t *testing.T) {
|
||||
params := denco.Params([]denco.Param{
|
||||
{"name1", "value1"},
|
||||
{"name2", "value2"},
|
||||
{"name3", "value3"},
|
||||
{"name1", "value4"},
|
||||
})
|
||||
for _, v := range []struct{ value, expected string }{
|
||||
{"name1", "value1"},
|
||||
{"name2", "value2"},
|
||||
{"name3", "value3"},
|
||||
{"name4", ""},
|
||||
} {
|
||||
actual := params.Get(v.value)
|
||||
expected := v.expected
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Errorf("Params.Get(%q) => %#v, want %#v", v.value, actual, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
106
vendor/github.com/go-openapi/runtime/middleware/denco/server.go
generated
vendored
Executable file
106
vendor/github.com/go-openapi/runtime/middleware/denco/server.go
generated
vendored
Executable file
@@ -0,0 +1,106 @@
|
||||
package denco
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Mux represents a multiplexer for HTTP request.
|
||||
type Mux struct{}
|
||||
|
||||
// NewMux returns a new Mux.
|
||||
func NewMux() *Mux {
|
||||
return &Mux{}
|
||||
}
|
||||
|
||||
// GET is shorthand of Mux.Handler("GET", path, handler).
|
||||
func (m *Mux) GET(path string, handler HandlerFunc) Handler {
|
||||
return m.Handler("GET", path, handler)
|
||||
}
|
||||
|
||||
// POST is shorthand of Mux.Handler("POST", path, handler).
|
||||
func (m *Mux) POST(path string, handler HandlerFunc) Handler {
|
||||
return m.Handler("POST", path, handler)
|
||||
}
|
||||
|
||||
// PUT is shorthand of Mux.Handler("PUT", path, handler).
|
||||
func (m *Mux) PUT(path string, handler HandlerFunc) Handler {
|
||||
return m.Handler("PUT", path, handler)
|
||||
}
|
||||
|
||||
// HEAD is shorthand of Mux.Handler("HEAD", path, handler).
|
||||
func (m *Mux) HEAD(path string, handler HandlerFunc) Handler {
|
||||
return m.Handler("HEAD", path, handler)
|
||||
}
|
||||
|
||||
// Handler returns a handler for HTTP method.
|
||||
func (m *Mux) Handler(method, path string, handler HandlerFunc) Handler {
|
||||
return Handler{
|
||||
Method: method,
|
||||
Path: path,
|
||||
Func: handler,
|
||||
}
|
||||
}
|
||||
|
||||
// Build builds a http.Handler.
|
||||
func (m *Mux) Build(handlers []Handler) (http.Handler, error) {
|
||||
recordMap := make(map[string][]Record)
|
||||
for _, h := range handlers {
|
||||
recordMap[h.Method] = append(recordMap[h.Method], NewRecord(h.Path, h.Func))
|
||||
}
|
||||
mux := newServeMux()
|
||||
for m, records := range recordMap {
|
||||
router := New()
|
||||
if err := router.Build(records); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mux.routers[m] = router
|
||||
}
|
||||
return mux, nil
|
||||
}
|
||||
|
||||
// Handler represents a handler of HTTP request.
|
||||
type Handler struct {
|
||||
// Method is an HTTP method.
|
||||
Method string
|
||||
|
||||
// Path is a routing path for handler.
|
||||
Path string
|
||||
|
||||
// Func is a function of handler of HTTP request.
|
||||
Func HandlerFunc
|
||||
}
|
||||
|
||||
// The HandlerFunc type is aliased to type of handler function.
|
||||
type HandlerFunc func(w http.ResponseWriter, r *http.Request, params Params)
|
||||
|
||||
type serveMux struct {
|
||||
routers map[string]*Router
|
||||
}
|
||||
|
||||
func newServeMux() *serveMux {
|
||||
return &serveMux{
|
||||
routers: make(map[string]*Router),
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler interface.
|
||||
func (mux *serveMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
handler, params := mux.handler(r.Method, r.URL.Path)
|
||||
handler(w, r, params)
|
||||
}
|
||||
|
||||
func (mux *serveMux) handler(method, path string) (HandlerFunc, []Param) {
|
||||
if router, found := mux.routers[method]; found {
|
||||
if handler, params, found := router.Lookup(path); found {
|
||||
return handler.(HandlerFunc), params
|
||||
}
|
||||
}
|
||||
return NotFound, nil
|
||||
}
|
||||
|
||||
// NotFound replies to the request with an HTTP 404 not found error.
|
||||
// NotFound is called when unknown HTTP method or a handler not found.
|
||||
// If you want to use the your own NotFound handler, please overwrite this variable.
|
||||
var NotFound = func(w http.ResponseWriter, r *http.Request, _ Params) {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
106
vendor/github.com/go-openapi/runtime/middleware/denco/server_test.go
generated
vendored
Executable file
106
vendor/github.com/go-openapi/runtime/middleware/denco/server_test.go
generated
vendored
Executable file
@@ -0,0 +1,106 @@
|
||||
package denco_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware/denco"
|
||||
)
|
||||
|
||||
func testHandlerFunc(w http.ResponseWriter, r *http.Request, params denco.Params) {
|
||||
fmt.Fprintf(w, "method: %s, path: %s, params: %v", r.Method, r.URL.Path, params)
|
||||
}
|
||||
|
||||
func TestMux(t *testing.T) {
|
||||
mux := denco.NewMux()
|
||||
handler, err := mux.Build([]denco.Handler{
|
||||
mux.GET("/", testHandlerFunc),
|
||||
mux.GET("/user/:name", testHandlerFunc),
|
||||
mux.POST("/user/:name", testHandlerFunc),
|
||||
mux.HEAD("/user/:name", testHandlerFunc),
|
||||
mux.PUT("/user/:name", testHandlerFunc),
|
||||
mux.Handler("GET", "/user/handler", testHandlerFunc),
|
||||
mux.Handler("POST", "/user/handler", testHandlerFunc),
|
||||
{"PUT", "/user/inference", testHandlerFunc},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
for _, v := range []struct {
|
||||
status int
|
||||
method, path, expected string
|
||||
}{
|
||||
{200, "GET", "/", "method: GET, path: /, params: []"},
|
||||
{200, "GET", "/user/alice", "method: GET, path: /user/alice, params: [{name alice}]"},
|
||||
{200, "POST", "/user/bob", "method: POST, path: /user/bob, params: [{name bob}]"},
|
||||
{200, "HEAD", "/user/alice", ""},
|
||||
{200, "PUT", "/user/bob", "method: PUT, path: /user/bob, params: [{name bob}]"},
|
||||
{404, "POST", "/", "404 page not found\n"},
|
||||
{404, "GET", "/unknown", "404 page not found\n"},
|
||||
{404, "POST", "/user/alice/1", "404 page not found\n"},
|
||||
{200, "GET", "/user/handler", "method: GET, path: /user/handler, params: []"},
|
||||
{200, "POST", "/user/handler", "method: POST, path: /user/handler, params: []"},
|
||||
{200, "PUT", "/user/inference", "method: PUT, path: /user/inference, params: []"},
|
||||
} {
|
||||
req, err := http.NewRequest(v.method, server.URL+v.path, nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
defer res.Body.Close()
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
actual := string(body)
|
||||
expected := v.expected
|
||||
if res.StatusCode != v.status || actual != expected {
|
||||
t.Errorf(`%s "%s" => %#v %#v, want %#v %#v`, v.method, v.path, res.StatusCode, actual, v.status, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotFound(t *testing.T) {
|
||||
mux := denco.NewMux()
|
||||
handler, err := mux.Build([]denco.Handler{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
origNotFound := denco.NotFound
|
||||
defer func() {
|
||||
denco.NotFound = origNotFound
|
||||
}()
|
||||
denco.NotFound = func(w http.ResponseWriter, r *http.Request, params denco.Params) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
fmt.Fprintf(w, "method: %s, path: %s, params: %v", r.Method, r.URL.Path, params)
|
||||
}
|
||||
res, err := http.Get(server.URL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual := string(body)
|
||||
expected := "method: GET, path: /, params: []"
|
||||
if res.StatusCode != http.StatusServiceUnavailable || actual != expected {
|
||||
t.Errorf(`GET "/" => %#v %#v, want %#v %#v`, res.StatusCode, actual, http.StatusServiceUnavailable, expected)
|
||||
}
|
||||
}
|
||||
12
vendor/github.com/go-openapi/runtime/middleware/denco/util.go
generated
vendored
Executable file
12
vendor/github.com/go-openapi/runtime/middleware/denco/util.go
generated
vendored
Executable file
@@ -0,0 +1,12 @@
|
||||
package denco
|
||||
|
||||
// NextSeparator returns an index of next separator in path.
|
||||
func NextSeparator(path string, start int) int {
|
||||
for start < len(path) {
|
||||
if c := path[start]; c == '/' || c == TerminationCharacter {
|
||||
break
|
||||
}
|
||||
start++
|
||||
}
|
||||
return start
|
||||
}
|
||||
31
vendor/github.com/go-openapi/runtime/middleware/denco/util_test.go
generated
vendored
Executable file
31
vendor/github.com/go-openapi/runtime/middleware/denco/util_test.go
generated
vendored
Executable file
@@ -0,0 +1,31 @@
|
||||
package denco_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware/denco"
|
||||
)
|
||||
|
||||
func TestNextSeparator(t *testing.T) {
|
||||
for _, testcase := range []struct {
|
||||
path string
|
||||
start int
|
||||
expected interface{}
|
||||
}{
|
||||
{"/path/to/route", 0, 0},
|
||||
{"/path/to/route", 1, 5},
|
||||
{"/path/to/route", 9, 14},
|
||||
{"/path.html", 1, 10},
|
||||
{"/foo/bar.html", 1, 4},
|
||||
{"/foo/bar.html/baz.png", 5, 13},
|
||||
{"/foo/bar.html/baz.png", 14, 21},
|
||||
{"path#", 0, 4},
|
||||
} {
|
||||
actual := denco.NextSeparator(testcase.path, testcase.start)
|
||||
expected := testcase.expected
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Errorf("path = %q, start = %v expect %v, but %v", testcase.path, testcase.start, expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
65
vendor/github.com/go-openapi/runtime/middleware/doc.go
generated
vendored
Normal file
65
vendor/github.com/go-openapi/runtime/middleware/doc.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/*Package middleware provides the library with helper functions for serving swagger APIs.
|
||||
|
||||
Pseudo middleware handler
|
||||
|
||||
import (
|
||||
"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 {
|
||||
|
||||
if len(matched.Authenticators) > 0 {
|
||||
if _, err := ctx.Authorize(r, matched); err != nil {
|
||||
ctx.Respond(rw, r, matched.Produces, matched, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
bound, validation := ctx.BindAndValidate(r, matched)
|
||||
if validation != nil {
|
||||
ctx.Respond(rw, r, matched.Produces, matched, validation)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := matched.Handler.Handle(bound)
|
||||
if err != nil {
|
||||
ctx.Respond(rw, r, matched.Produces, matched, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Respond(rw, r, matched.Produces, matched, result)
|
||||
return
|
||||
}
|
||||
|
||||
// Not found, check if it exists in the other methods first
|
||||
if others := ctx.AllowedMethods(r); len(others) > 0 {
|
||||
ctx.Respond(rw, r, ctx.spec.RequiredProduces(), nil, errors.MethodNotAllowed(r.Method, others))
|
||||
return
|
||||
}
|
||||
ctx.Respond(rw, r, ctx.spec.RequiredProduces(), nil, errors.NotFound("path %s was not found", r.URL.Path))
|
||||
})
|
||||
}
|
||||
*/
|
||||
package middleware
|
||||
299
vendor/github.com/go-openapi/runtime/middleware/header/header.go
generated
vendored
Normal file
299
vendor/github.com/go-openapi/runtime/middleware/header/header.go
generated
vendored
Normal file
@@ -0,0 +1,299 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd.
|
||||
|
||||
// this file was taken from the github.com/golang/gddo repository
|
||||
|
||||
// Package header provides functions for parsing HTTP headers.
|
||||
package header
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Octet types from RFC 2616.
|
||||
var octetTypes [256]octetType
|
||||
|
||||
type octetType byte
|
||||
|
||||
const (
|
||||
isToken octetType = 1 << iota
|
||||
isSpace
|
||||
)
|
||||
|
||||
func init() {
|
||||
// OCTET = <any 8-bit sequence of data>
|
||||
// CHAR = <any US-ASCII character (octets 0 - 127)>
|
||||
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
|
||||
// CR = <US-ASCII CR, carriage return (13)>
|
||||
// LF = <US-ASCII LF, linefeed (10)>
|
||||
// SP = <US-ASCII SP, space (32)>
|
||||
// HT = <US-ASCII HT, horizontal-tab (9)>
|
||||
// <"> = <US-ASCII double-quote mark (34)>
|
||||
// CRLF = CR LF
|
||||
// LWS = [CRLF] 1*( SP | HT )
|
||||
// TEXT = <any OCTET except CTLs, but including LWS>
|
||||
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
|
||||
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
|
||||
// token = 1*<any CHAR except CTLs or separators>
|
||||
// qdtext = <any TEXT except <">>
|
||||
|
||||
for c := 0; c < 256; c++ {
|
||||
var t octetType
|
||||
isCtl := c <= 31 || c == 127
|
||||
isChar := 0 <= c && c <= 127
|
||||
isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
|
||||
if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
|
||||
t |= isSpace
|
||||
}
|
||||
if isChar && !isCtl && !isSeparator {
|
||||
t |= isToken
|
||||
}
|
||||
octetTypes[c] = t
|
||||
}
|
||||
}
|
||||
|
||||
// Copy returns a shallow copy of the header.
|
||||
func Copy(header http.Header) http.Header {
|
||||
h := make(http.Header)
|
||||
for k, vs := range header {
|
||||
h[k] = vs
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
var timeLayouts = []string{"Mon, 02 Jan 2006 15:04:05 GMT", time.RFC850, time.ANSIC}
|
||||
|
||||
// ParseTime parses the header as time. The zero value is returned if the
|
||||
// header is not present or there is an error parsing the
|
||||
// header.
|
||||
func ParseTime(header http.Header, key string) time.Time {
|
||||
if s := header.Get(key); s != "" {
|
||||
for _, layout := range timeLayouts {
|
||||
if t, err := time.Parse(layout, s); err == nil {
|
||||
return t.UTC()
|
||||
}
|
||||
}
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// ParseList parses a comma separated list of values. Commas are ignored in
|
||||
// quoted strings. Quoted values are not unescaped or unquoted. Whitespace is
|
||||
// trimmed.
|
||||
func ParseList(header http.Header, key string) []string {
|
||||
var result []string
|
||||
for _, s := range header[http.CanonicalHeaderKey(key)] {
|
||||
begin := 0
|
||||
end := 0
|
||||
escape := false
|
||||
quote := false
|
||||
for i := 0; i < len(s); i++ {
|
||||
b := s[i]
|
||||
switch {
|
||||
case escape:
|
||||
escape = false
|
||||
end = i + 1
|
||||
case quote:
|
||||
switch b {
|
||||
case '\\':
|
||||
escape = true
|
||||
case '"':
|
||||
quote = false
|
||||
}
|
||||
end = i + 1
|
||||
case b == '"':
|
||||
quote = true
|
||||
end = i + 1
|
||||
case octetTypes[b]&isSpace != 0:
|
||||
if begin == end {
|
||||
begin = i + 1
|
||||
end = begin
|
||||
}
|
||||
case b == ',':
|
||||
if begin < end {
|
||||
result = append(result, s[begin:end])
|
||||
}
|
||||
begin = i + 1
|
||||
end = begin
|
||||
default:
|
||||
end = i + 1
|
||||
}
|
||||
}
|
||||
if begin < end {
|
||||
result = append(result, s[begin:end])
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// 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) {
|
||||
params = make(map[string]string)
|
||||
s := header.Get(key)
|
||||
value, s = expectTokenSlash(s)
|
||||
if value == "" {
|
||||
return
|
||||
}
|
||||
value = strings.ToLower(value)
|
||||
s = skipSpace(s)
|
||||
for strings.HasPrefix(s, ";") {
|
||||
var pkey string
|
||||
pkey, s = expectToken(skipSpace(s[1:]))
|
||||
if pkey == "" {
|
||||
return
|
||||
}
|
||||
if !strings.HasPrefix(s, "=") {
|
||||
return
|
||||
}
|
||||
var pvalue string
|
||||
pvalue, s = expectTokenOrQuoted(s[1:])
|
||||
if pvalue == "" {
|
||||
return
|
||||
}
|
||||
pkey = strings.ToLower(pkey)
|
||||
params[pkey] = pvalue
|
||||
s = skipSpace(s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type AcceptSpec struct {
|
||||
Value string
|
||||
Q float64
|
||||
}
|
||||
|
||||
// ParseAccept parses Accept* headers.
|
||||
func ParseAccept(header http.Header, key string) (specs []AcceptSpec) {
|
||||
loop:
|
||||
for _, s := range header[key] {
|
||||
for {
|
||||
var spec AcceptSpec
|
||||
spec.Value, s = expectTokenSlash(s)
|
||||
if spec.Value == "" {
|
||||
continue loop
|
||||
}
|
||||
spec.Q = 1.0
|
||||
s = skipSpace(s)
|
||||
if strings.HasPrefix(s, ";") {
|
||||
s = skipSpace(s[1:])
|
||||
if !strings.HasPrefix(s, "q=") {
|
||||
continue loop
|
||||
}
|
||||
spec.Q, s = expectQuality(s[2:])
|
||||
if spec.Q < 0.0 {
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
specs = append(specs, spec)
|
||||
s = skipSpace(s)
|
||||
if !strings.HasPrefix(s, ",") {
|
||||
continue loop
|
||||
}
|
||||
s = skipSpace(s[1:])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func skipSpace(s string) (rest string) {
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
if octetTypes[s[i]]&isSpace == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s[i:]
|
||||
}
|
||||
|
||||
func expectToken(s string) (token, rest string) {
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
if octetTypes[s[i]]&isToken == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s[:i], s[i:]
|
||||
}
|
||||
|
||||
func expectTokenSlash(s string) (token, rest string) {
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
b := s[i]
|
||||
if (octetTypes[b]&isToken == 0) && b != '/' {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s[:i], s[i:]
|
||||
}
|
||||
|
||||
func expectQuality(s string) (q float64, rest string) {
|
||||
switch {
|
||||
case len(s) == 0:
|
||||
return -1, ""
|
||||
case s[0] == '0':
|
||||
q = 0
|
||||
case s[0] == '1':
|
||||
q = 1
|
||||
default:
|
||||
return -1, ""
|
||||
}
|
||||
s = s[1:]
|
||||
if !strings.HasPrefix(s, ".") {
|
||||
return q, s
|
||||
}
|
||||
s = s[1:]
|
||||
i := 0
|
||||
n := 0
|
||||
d := 1
|
||||
for ; i < len(s); i++ {
|
||||
b := s[i]
|
||||
if b < '0' || b > '9' {
|
||||
break
|
||||
}
|
||||
n = n*10 + int(b) - '0'
|
||||
d *= 10
|
||||
}
|
||||
return q + float64(n)/float64(d), s[i:]
|
||||
}
|
||||
|
||||
func expectTokenOrQuoted(s string) (value string, rest string) {
|
||||
if !strings.HasPrefix(s, "\"") {
|
||||
return expectToken(s)
|
||||
}
|
||||
s = s[1:]
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '"':
|
||||
return s[:i], s[i+1:]
|
||||
case '\\':
|
||||
p := make([]byte, len(s)-1)
|
||||
j := copy(p, s[:i])
|
||||
escape := true
|
||||
for i = i + 1; i < len(s); i++ {
|
||||
b := s[i]
|
||||
switch {
|
||||
case escape:
|
||||
escape = false
|
||||
p[j] = b
|
||||
j += 1
|
||||
case b == '\\':
|
||||
escape = true
|
||||
case b == '"':
|
||||
return string(p[:j]), s[i+1:]
|
||||
default:
|
||||
p[j] = b
|
||||
j += 1
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
82
vendor/github.com/go-openapi/runtime/middleware/negotiate.go
generated
vendored
Normal file
82
vendor/github.com/go-openapi/runtime/middleware/negotiate.go
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd.
|
||||
|
||||
// this file was taken from the github.com/golang/gddo repository
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware/header"
|
||||
)
|
||||
|
||||
// NegotiateContentEncoding returns the best offered content encoding for the
|
||||
// request's Accept-Encoding header. If two offers match with equal weight and
|
||||
// then the offer earlier in the list is preferred. If no offers are
|
||||
// acceptable, then "" is returned.
|
||||
func NegotiateContentEncoding(r *http.Request, offers []string) string {
|
||||
bestOffer := "identity"
|
||||
bestQ := -1.0
|
||||
specs := header.ParseAccept(r.Header, "Accept-Encoding")
|
||||
for _, offer := range offers {
|
||||
for _, spec := range specs {
|
||||
if spec.Q > bestQ &&
|
||||
(spec.Value == "*" || spec.Value == offer) {
|
||||
bestQ = spec.Q
|
||||
bestOffer = offer
|
||||
}
|
||||
}
|
||||
}
|
||||
if bestQ == 0 {
|
||||
bestOffer = ""
|
||||
}
|
||||
return bestOffer
|
||||
}
|
||||
|
||||
// NegotiateContentType returns the best offered content type for the request's
|
||||
// Accept header. If two offers match with equal weight, then the more specific
|
||||
// offer is preferred. For example, text/* trumps */*. If two offers match
|
||||
// with equal weight and specificity, then the offer earlier in the list is
|
||||
// preferred. If no offers match, then defaultOffer is returned.
|
||||
func NegotiateContentType(r *http.Request, offers []string, defaultOffer string) string {
|
||||
bestOffer := defaultOffer
|
||||
bestQ := -1.0
|
||||
bestWild := 3
|
||||
specs := header.ParseAccept(r.Header, "Accept")
|
||||
for _, offer := range offers {
|
||||
for _, spec := range specs {
|
||||
switch {
|
||||
case spec.Q == 0.0:
|
||||
// ignore
|
||||
case spec.Q < bestQ:
|
||||
// better match found
|
||||
case spec.Value == "*/*":
|
||||
if spec.Q > bestQ || bestWild > 2 {
|
||||
bestQ = spec.Q
|
||||
bestWild = 2
|
||||
bestOffer = offer
|
||||
}
|
||||
case strings.HasSuffix(spec.Value, "/*"):
|
||||
if strings.HasPrefix(offer, spec.Value[:len(spec.Value)-1]) &&
|
||||
(spec.Q > bestQ || bestWild > 1) {
|
||||
bestQ = spec.Q
|
||||
bestWild = 1
|
||||
bestOffer = offer
|
||||
}
|
||||
default:
|
||||
if spec.Value == offer &&
|
||||
(spec.Q > bestQ || bestWild > 0) {
|
||||
bestQ = spec.Q
|
||||
bestWild = 0
|
||||
bestOffer = offer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return bestOffer
|
||||
}
|
||||
70
vendor/github.com/go-openapi/runtime/middleware/negotiate_test.go
generated
vendored
Normal file
70
vendor/github.com/go-openapi/runtime/middleware/negotiate_test.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var negotiateContentEncodingTests = []struct {
|
||||
s string
|
||||
offers []string
|
||||
expect string
|
||||
}{
|
||||
{"", []string{"identity", "gzip"}, "identity"},
|
||||
{"*;q=0", []string{"identity", "gzip"}, ""},
|
||||
{"gzip", []string{"identity", "gzip"}, "gzip"},
|
||||
}
|
||||
|
||||
func TestNegotiateContentEnoding(t *testing.T) {
|
||||
for _, tt := range negotiateContentEncodingTests {
|
||||
r := &http.Request{Header: http.Header{"Accept-Encoding": {tt.s}}}
|
||||
actual := NegotiateContentEncoding(r, tt.offers)
|
||||
if actual != tt.expect {
|
||||
t.Errorf("NegotiateContentEncoding(%q, %#v)=%q, want %q", tt.s, tt.offers, actual, tt.expect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var negotiateContentTypeTests = []struct {
|
||||
s string
|
||||
offers []string
|
||||
defaultOffer string
|
||||
expect string
|
||||
}{
|
||||
{"text/html, */*;q=0", []string{"x/y"}, "", ""},
|
||||
{"text/html, */*", []string{"x/y"}, "", "x/y"},
|
||||
{"text/html, image/png", []string{"text/html", "image/png"}, "", "text/html"},
|
||||
{"text/html, image/png", []string{"image/png", "text/html"}, "", "image/png"},
|
||||
{"text/html, image/png; q=0.5", []string{"image/png"}, "", "image/png"},
|
||||
{"text/html, image/png; q=0.5", []string{"text/html"}, "", "text/html"},
|
||||
{"text/html, image/png; q=0.5", []string{"foo/bar"}, "", ""},
|
||||
{"text/html, image/png; q=0.5", []string{"image/png", "text/html"}, "", "text/html"},
|
||||
{"text/html, image/png; q=0.5", []string{"text/html", "image/png"}, "", "text/html"},
|
||||
{"text/html;q=0.5, image/png", []string{"image/png"}, "", "image/png"},
|
||||
{"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"},
|
||||
{"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"},
|
||||
{"image/png, image/*", []string{"image/jpg", "image/gif"}, "", "image/jpg"},
|
||||
{"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"},
|
||||
}
|
||||
|
||||
func TestNegotiateContentType(t *testing.T) {
|
||||
for _, tt := range negotiateContentTypeTests {
|
||||
r := &http.Request{Header: http.Header{"Accept": {tt.s}}}
|
||||
actual := NegotiateContentType(r, tt.offers, tt.defaultOffer)
|
||||
if actual != tt.expect {
|
||||
t.Errorf("NegotiateContentType(%q, %#v, %q)=%q, want %q", tt.s, tt.offers, tt.defaultOffer, actual, tt.expect)
|
||||
}
|
||||
}
|
||||
}
|
||||
48
vendor/github.com/go-openapi/runtime/middleware/not_implemented.go
generated
vendored
Normal file
48
vendor/github.com/go-openapi/runtime/middleware/not_implemented.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-openapi/runtime"
|
||||
)
|
||||
|
||||
type errorResp struct {
|
||||
code int
|
||||
response interface{}
|
||||
headers http.Header
|
||||
}
|
||||
|
||||
func (e *errorResp) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
|
||||
for k, v := range e.headers {
|
||||
for _, val := range v {
|
||||
rw.Header().Add(k, val)
|
||||
}
|
||||
}
|
||||
if e.code > 0 {
|
||||
rw.WriteHeader(e.code)
|
||||
} else {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
if err := producer.Produce(rw, e.response); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// NotImplemented the error response when the response is not implemented
|
||||
func NotImplemented(message string) Responder {
|
||||
return &errorResp{http.StatusNotImplemented, message, make(http.Header)}
|
||||
}
|
||||
26
vendor/github.com/go-openapi/runtime/middleware/operation.go
generated
vendored
Normal file
26
vendor/github.com/go-openapi/runtime/middleware/operation.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package middleware
|
||||
|
||||
import "net/http"
|
||||
|
||||
// NewOperationExecutor creates a context aware middleware that handles the operations after routing
|
||||
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.Handler.ServeHTTP(rw, r)
|
||||
})
|
||||
}
|
||||
64
vendor/github.com/go-openapi/runtime/middleware/operation_test.go
generated
vendored
Normal file
64
vendor/github.com/go-openapi/runtime/middleware/operation_test.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/go-openapi/runtime/internal/testing/petstore"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestOperationExecutor(t *testing.T) {
|
||||
spec, api := petstore.NewAPI(t)
|
||||
api.RegisterOperation("get", "/pets", runtime.OperationHandlerFunc(func(params interface{}) (interface{}, error) {
|
||||
return []interface{}{
|
||||
map[string]interface{}{"id": 1, "name": "a dog"},
|
||||
}, nil
|
||||
}))
|
||||
|
||||
context := NewContext(spec, api, nil)
|
||||
context.router = DefaultRouter(spec, context.api)
|
||||
mw := NewOperationExecutor(context)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/api/pets", nil)
|
||||
request.Header.Add("Accept", "application/json")
|
||||
request.SetBasicAuth("admin", "admin")
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Equal(t, `[{"id":1,"name":"a dog"}]`+"\n", recorder.Body.String())
|
||||
|
||||
spec, api = petstore.NewAPI(t)
|
||||
api.RegisterOperation("get", "/pets", runtime.OperationHandlerFunc(func(params interface{}) (interface{}, error) {
|
||||
return nil, errors.New(422, "expected")
|
||||
}))
|
||||
|
||||
context = NewContext(spec, api, nil)
|
||||
context.router = DefaultRouter(spec, context.api)
|
||||
mw = NewOperationExecutor(context)
|
||||
|
||||
recorder = httptest.NewRecorder()
|
||||
request, _ = http.NewRequest("GET", "/api/pets", nil)
|
||||
request.Header.Add("Accept", "application/json")
|
||||
request.SetBasicAuth("admin", "admin")
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, 422, recorder.Code)
|
||||
assert.Equal(t, `{"code":422,"message":"expected"}`, recorder.Body.String())
|
||||
}
|
||||
480
vendor/github.com/go-openapi/runtime/middleware/parameter.go
generated
vendored
Normal file
480
vendor/github.com/go-openapi/runtime/middleware/parameter.go
generated
vendored
Normal file
@@ -0,0 +1,480 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/go-openapi/spec"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
"github.com/go-openapi/validate"
|
||||
)
|
||||
|
||||
const defaultMaxMemory = 32 << 20
|
||||
|
||||
var textUnmarshalType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem()
|
||||
|
||||
func newUntypedParamBinder(param spec.Parameter, spec *spec.Swagger, formats strfmt.Registry) *untypedParamBinder {
|
||||
binder := new(untypedParamBinder)
|
||||
binder.Name = param.Name
|
||||
binder.parameter = ¶m
|
||||
binder.formats = formats
|
||||
if param.In != "body" {
|
||||
binder.validator = validate.NewParamValidator(¶m, formats)
|
||||
} else {
|
||||
binder.validator = validate.NewSchemaValidator(param.Schema, spec, param.Name, formats)
|
||||
}
|
||||
|
||||
return binder
|
||||
}
|
||||
|
||||
type untypedParamBinder struct {
|
||||
parameter *spec.Parameter
|
||||
formats strfmt.Registry
|
||||
Name string
|
||||
validator validate.EntityValidator
|
||||
}
|
||||
|
||||
func (p *untypedParamBinder) Type() reflect.Type {
|
||||
return p.typeForSchema(p.parameter.Type, p.parameter.Format, p.parameter.Items)
|
||||
}
|
||||
|
||||
func (p *untypedParamBinder) typeForSchema(tpe, format string, items *spec.Items) reflect.Type {
|
||||
switch tpe {
|
||||
case "boolean":
|
||||
return reflect.TypeOf(true)
|
||||
|
||||
case "string":
|
||||
if tt, ok := p.formats.GetType(format); ok {
|
||||
return tt
|
||||
}
|
||||
return reflect.TypeOf("")
|
||||
|
||||
case "integer":
|
||||
switch format {
|
||||
case "int8":
|
||||
return reflect.TypeOf(int8(0))
|
||||
case "int16":
|
||||
return reflect.TypeOf(int16(0))
|
||||
case "int32":
|
||||
return reflect.TypeOf(int32(0))
|
||||
case "int64":
|
||||
return reflect.TypeOf(int64(0))
|
||||
default:
|
||||
return reflect.TypeOf(int64(0))
|
||||
}
|
||||
|
||||
case "number":
|
||||
switch format {
|
||||
case "float":
|
||||
return reflect.TypeOf(float32(0))
|
||||
case "double":
|
||||
return reflect.TypeOf(float64(0))
|
||||
}
|
||||
|
||||
case "array":
|
||||
if items == nil {
|
||||
return nil
|
||||
}
|
||||
itemsType := p.typeForSchema(items.Type, items.Format, items.Items)
|
||||
if itemsType == nil {
|
||||
return nil
|
||||
}
|
||||
return reflect.MakeSlice(reflect.SliceOf(itemsType), 0, 0).Type()
|
||||
|
||||
case "file":
|
||||
return reflect.TypeOf(&runtime.File{}).Elem()
|
||||
|
||||
case "object":
|
||||
return reflect.TypeOf(map[string]interface{}{})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *untypedParamBinder) allowsMulti() bool {
|
||||
return p.parameter.In == "query" || p.parameter.In == "formData"
|
||||
}
|
||||
|
||||
func (p *untypedParamBinder) readValue(values runtime.Gettable, target reflect.Value) ([]string, bool, bool, error) {
|
||||
name, in, cf, tpe := p.parameter.Name, p.parameter.In, p.parameter.CollectionFormat, p.parameter.Type
|
||||
if tpe == "array" {
|
||||
if cf == "multi" {
|
||||
if !p.allowsMulti() {
|
||||
return nil, false, false, errors.InvalidCollectionFormat(name, in, cf)
|
||||
}
|
||||
vv, hasKey, _ := values.GetOK(name)
|
||||
return vv, false, hasKey, nil
|
||||
}
|
||||
|
||||
v, hk, hv := values.GetOK(name)
|
||||
if !hv {
|
||||
return nil, false, hk, nil
|
||||
}
|
||||
d, c, e := p.readFormattedSliceFieldValue(v[len(v)-1], target)
|
||||
return d, c, hk, e
|
||||
}
|
||||
|
||||
vv, hk, _ := values.GetOK(name)
|
||||
return vv, false, hk, nil
|
||||
}
|
||||
|
||||
func (p *untypedParamBinder) Bind(request *http.Request, routeParams RouteParams, consumer runtime.Consumer, target reflect.Value) error {
|
||||
// fmt.Println("binding", p.name, "as", p.Type())
|
||||
switch p.parameter.In {
|
||||
case "query":
|
||||
data, custom, hasKey, err := p.readValue(runtime.Values(request.URL.Query()), target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if custom {
|
||||
return nil
|
||||
}
|
||||
|
||||
return p.bindValue(data, hasKey, target)
|
||||
|
||||
case "header":
|
||||
data, custom, hasKey, err := p.readValue(runtime.Values(request.Header), target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if custom {
|
||||
return nil
|
||||
}
|
||||
return p.bindValue(data, hasKey, target)
|
||||
|
||||
case "path":
|
||||
data, custom, hasKey, err := p.readValue(routeParams, target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if custom {
|
||||
return nil
|
||||
}
|
||||
return p.bindValue(data, hasKey, target)
|
||||
|
||||
case "formData":
|
||||
var err error
|
||||
var mt string
|
||||
|
||||
mt, _, e := runtime.ContentType(request.Header)
|
||||
if e != nil {
|
||||
// because of the interface conversion go thinks the error is not nil
|
||||
// so we first check for nil and then set the err var if it's not nil
|
||||
err = e
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return errors.InvalidContentType("", []string{"multipart/form-data", "application/x-www-form-urlencoded"})
|
||||
}
|
||||
|
||||
if mt != "multipart/form-data" && mt != "application/x-www-form-urlencoded" {
|
||||
return errors.InvalidContentType(mt, []string{"multipart/form-data", "application/x-www-form-urlencoded"})
|
||||
}
|
||||
|
||||
if mt == "multipart/form-data" {
|
||||
if err := request.ParseMultipartForm(defaultMaxMemory); err != nil {
|
||||
return errors.NewParseError(p.Name, p.parameter.In, "", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := request.ParseForm(); err != nil {
|
||||
return errors.NewParseError(p.Name, p.parameter.In, "", err)
|
||||
}
|
||||
|
||||
if p.parameter.Type == "file" {
|
||||
file, header, err := request.FormFile(p.parameter.Name)
|
||||
if err != nil {
|
||||
return errors.NewParseError(p.Name, p.parameter.In, "", err)
|
||||
}
|
||||
target.Set(reflect.ValueOf(runtime.File{Data: file, Header: header}))
|
||||
return nil
|
||||
}
|
||||
|
||||
if request.MultipartForm != nil {
|
||||
data, custom, hasKey, err := p.readValue(runtime.Values(request.MultipartForm.Value), target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if custom {
|
||||
return nil
|
||||
}
|
||||
return p.bindValue(data, hasKey, target)
|
||||
}
|
||||
data, custom, hasKey, err := p.readValue(runtime.Values(request.PostForm), target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if custom {
|
||||
return nil
|
||||
}
|
||||
return p.bindValue(data, hasKey, target)
|
||||
|
||||
case "body":
|
||||
newValue := reflect.New(target.Type())
|
||||
if !runtime.HasBody(request) {
|
||||
if p.parameter.Default != nil {
|
||||
target.Set(reflect.ValueOf(p.parameter.Default))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
if err := consumer.Consume(request.Body, newValue.Interface()); err != nil {
|
||||
if err == io.EOF && p.parameter.Default != nil {
|
||||
target.Set(reflect.ValueOf(p.parameter.Default))
|
||||
return nil
|
||||
}
|
||||
tpe := p.parameter.Type
|
||||
if p.parameter.Format != "" {
|
||||
tpe = p.parameter.Format
|
||||
}
|
||||
return errors.InvalidType(p.Name, p.parameter.In, tpe, nil)
|
||||
}
|
||||
target.Set(reflect.Indirect(newValue))
|
||||
return nil
|
||||
default:
|
||||
return errors.New(500, fmt.Sprintf("invalid parameter location %q", p.parameter.In))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *untypedParamBinder) bindValue(data []string, hasKey bool, target reflect.Value) error {
|
||||
if p.parameter.Type == "array" {
|
||||
return p.setSliceFieldValue(target, p.parameter.Default, data, hasKey)
|
||||
}
|
||||
var d string
|
||||
if len(data) > 0 {
|
||||
d = data[len(data)-1]
|
||||
}
|
||||
return p.setFieldValue(target, p.parameter.Default, d, hasKey)
|
||||
}
|
||||
|
||||
func (p *untypedParamBinder) setFieldValue(target reflect.Value, defaultValue interface{}, data string, hasKey bool) error {
|
||||
tpe := p.parameter.Type
|
||||
if p.parameter.Format != "" {
|
||||
tpe = p.parameter.Format
|
||||
}
|
||||
|
||||
if (!hasKey || (!p.parameter.AllowEmptyValue && data == "")) && p.parameter.Required && p.parameter.Default == nil {
|
||||
return errors.Required(p.Name, p.parameter.In)
|
||||
}
|
||||
|
||||
ok, err := p.tryUnmarshaler(target, defaultValue, data)
|
||||
if err != nil {
|
||||
return errors.InvalidType(p.Name, p.parameter.In, tpe, data)
|
||||
}
|
||||
if ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
defVal := reflect.Zero(target.Type())
|
||||
if defaultValue != nil {
|
||||
defVal = reflect.ValueOf(defaultValue)
|
||||
}
|
||||
|
||||
if tpe == "byte" {
|
||||
if data == "" {
|
||||
if target.CanSet() {
|
||||
target.SetBytes(defVal.Bytes())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
b, err := base64.StdEncoding.DecodeString(data)
|
||||
if err != nil {
|
||||
b, err = base64.URLEncoding.DecodeString(data)
|
||||
if err != nil {
|
||||
return errors.InvalidType(p.Name, p.parameter.In, tpe, data)
|
||||
}
|
||||
}
|
||||
if target.CanSet() {
|
||||
target.SetBytes(b)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch target.Kind() {
|
||||
case reflect.Bool:
|
||||
if data == "" {
|
||||
if target.CanSet() {
|
||||
target.SetBool(defVal.Bool())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
b, err := swag.ConvertBool(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if target.CanSet() {
|
||||
target.SetBool(b)
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if data == "" {
|
||||
if target.CanSet() {
|
||||
rd := defVal.Convert(reflect.TypeOf(int64(0)))
|
||||
target.SetInt(rd.Int())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
i, err := strconv.ParseInt(data, 10, 64)
|
||||
if err != nil {
|
||||
return errors.InvalidType(p.Name, p.parameter.In, tpe, data)
|
||||
}
|
||||
if target.OverflowInt(i) {
|
||||
return errors.InvalidType(p.Name, p.parameter.In, tpe, data)
|
||||
}
|
||||
if target.CanSet() {
|
||||
target.SetInt(i)
|
||||
}
|
||||
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
if data == "" {
|
||||
if target.CanSet() {
|
||||
rd := defVal.Convert(reflect.TypeOf(uint64(0)))
|
||||
target.SetUint(rd.Uint())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
u, err := strconv.ParseUint(data, 10, 64)
|
||||
if err != nil {
|
||||
return errors.InvalidType(p.Name, p.parameter.In, tpe, data)
|
||||
}
|
||||
if target.OverflowUint(u) {
|
||||
return errors.InvalidType(p.Name, p.parameter.In, tpe, data)
|
||||
}
|
||||
if target.CanSet() {
|
||||
target.SetUint(u)
|
||||
}
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if data == "" {
|
||||
if target.CanSet() {
|
||||
rd := defVal.Convert(reflect.TypeOf(float64(0)))
|
||||
target.SetFloat(rd.Float())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
f, err := strconv.ParseFloat(data, 64)
|
||||
if err != nil {
|
||||
return errors.InvalidType(p.Name, p.parameter.In, tpe, data)
|
||||
}
|
||||
if target.OverflowFloat(f) {
|
||||
return errors.InvalidType(p.Name, p.parameter.In, tpe, data)
|
||||
}
|
||||
if target.CanSet() {
|
||||
target.SetFloat(f)
|
||||
}
|
||||
|
||||
case reflect.String:
|
||||
value := data
|
||||
if value == "" {
|
||||
value = defVal.String()
|
||||
}
|
||||
// validate string
|
||||
if target.CanSet() {
|
||||
target.SetString(value)
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
if data == "" && defVal.Kind() == reflect.Ptr {
|
||||
if target.CanSet() {
|
||||
target.Set(defVal)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
newVal := reflect.New(target.Type().Elem())
|
||||
if err := p.setFieldValue(reflect.Indirect(newVal), defVal, data, hasKey); err != nil {
|
||||
return err
|
||||
}
|
||||
if target.CanSet() {
|
||||
target.Set(newVal)
|
||||
}
|
||||
|
||||
default:
|
||||
return errors.InvalidType(p.Name, p.parameter.In, tpe, data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *untypedParamBinder) tryUnmarshaler(target reflect.Value, defaultValue interface{}, data string) (bool, error) {
|
||||
if !target.CanSet() {
|
||||
return false, nil
|
||||
}
|
||||
// When a type implements encoding.TextUnmarshaler we'll use that instead of reflecting some more
|
||||
if reflect.PtrTo(target.Type()).Implements(textUnmarshalType) {
|
||||
if defaultValue != nil && len(data) == 0 {
|
||||
target.Set(reflect.ValueOf(defaultValue))
|
||||
return true, nil
|
||||
}
|
||||
value := reflect.New(target.Type())
|
||||
if err := value.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(data)); err != nil {
|
||||
return true, err
|
||||
}
|
||||
target.Set(reflect.Indirect(value))
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (p *untypedParamBinder) readFormattedSliceFieldValue(data string, target reflect.Value) ([]string, bool, error) {
|
||||
ok, err := p.tryUnmarshaler(target, p.parameter.Default, data)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
if ok {
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
return swag.SplitByFormat(data, p.parameter.CollectionFormat), false, nil
|
||||
}
|
||||
|
||||
func (p *untypedParamBinder) setSliceFieldValue(target reflect.Value, defaultValue interface{}, data []string, hasKey bool) error {
|
||||
sz := len(data)
|
||||
if (!hasKey || (!p.parameter.AllowEmptyValue && (sz == 0 || (sz == 1 && data[0] == "")))) && p.parameter.Required && defaultValue == nil {
|
||||
return errors.Required(p.Name, p.parameter.In)
|
||||
}
|
||||
|
||||
defVal := reflect.Zero(target.Type())
|
||||
if defaultValue != nil {
|
||||
defVal = reflect.ValueOf(defaultValue)
|
||||
}
|
||||
|
||||
if !target.CanSet() {
|
||||
return nil
|
||||
}
|
||||
if sz == 0 {
|
||||
target.Set(defVal)
|
||||
return nil
|
||||
}
|
||||
|
||||
value := reflect.MakeSlice(reflect.SliceOf(target.Type().Elem()), sz, sz)
|
||||
|
||||
for i := 0; i < sz; i++ {
|
||||
if err := p.setFieldValue(value.Index(i), nil, data[i], hasKey); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
target.Set(value)
|
||||
|
||||
return nil
|
||||
}
|
||||
340
vendor/github.com/go-openapi/runtime/middleware/parameter_test.go
generated
vendored
Normal file
340
vendor/github.com/go-openapi/runtime/middleware/parameter_test.go
generated
vendored
Normal file
@@ -0,0 +1,340 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"math"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/go-openapi/spec"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type email struct {
|
||||
Address string
|
||||
}
|
||||
|
||||
type paramFactory func(string) *spec.Parameter
|
||||
|
||||
var paramFactories = []paramFactory{
|
||||
spec.QueryParam,
|
||||
spec.HeaderParam,
|
||||
spec.PathParam,
|
||||
spec.FormDataParam,
|
||||
}
|
||||
|
||||
func np(param *spec.Parameter) *untypedParamBinder {
|
||||
return newUntypedParamBinder(*param, new(spec.Swagger), strfmt.Default)
|
||||
}
|
||||
|
||||
var stringItems = new(spec.Items)
|
||||
|
||||
func init() {
|
||||
stringItems.Type = "string"
|
||||
}
|
||||
|
||||
func testCollectionFormat(t *testing.T, param *spec.Parameter, valid bool) {
|
||||
binder := &untypedParamBinder{
|
||||
parameter: param,
|
||||
}
|
||||
_, _, _, err := binder.readValue(runtime.Values(nil), reflect.ValueOf(nil))
|
||||
if valid {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, errors.InvalidCollectionFormat(param.Name, param.In, param.CollectionFormat), err)
|
||||
}
|
||||
}
|
||||
|
||||
func requiredError(param *spec.Parameter) *errors.Validation {
|
||||
return errors.Required(param.Name, param.In)
|
||||
}
|
||||
|
||||
func validateRequiredTest(t *testing.T, param *spec.Parameter, value reflect.Value) {
|
||||
|
||||
binder := np(param)
|
||||
err := binder.bindValue([]string{}, true, value)
|
||||
assert.Error(t, err)
|
||||
assert.NotNil(t, param)
|
||||
assert.EqualError(t, requiredError(param), err.Error())
|
||||
err = binder.bindValue([]string{""}, true, value)
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, requiredError(param), err.Error())
|
||||
}
|
||||
|
||||
// should be impossible data, but let's go with it
|
||||
err = binder.bindValue([]string{"a"}, false, value)
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, requiredError(param), err.Error())
|
||||
err = binder.bindValue([]string{""}, false, value)
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, requiredError(param), err.Error())
|
||||
}
|
||||
|
||||
func validateRequiredAllowEmptyTest(t *testing.T, param *spec.Parameter, value reflect.Value) {
|
||||
param.AllowEmptyValue = true
|
||||
binder := np(param)
|
||||
err := binder.bindValue([]string{}, true, value)
|
||||
assert.NoError(t, err)
|
||||
if assert.NotNil(t, param) {
|
||||
err = binder.bindValue([]string{""}, true, value)
|
||||
assert.NoError(t, err)
|
||||
err = binder.bindValue([]string{"1"}, false, value)
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, requiredError(param), err.Error())
|
||||
err = binder.bindValue([]string{""}, false, value)
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, requiredError(param), err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequiredValidation(t *testing.T) {
|
||||
strParam := spec.QueryParam("name").Typed("string", "").AsRequired()
|
||||
validateRequiredTest(t, strParam, reflect.ValueOf(""))
|
||||
validateRequiredAllowEmptyTest(t, strParam, reflect.ValueOf(""))
|
||||
|
||||
intParam := spec.QueryParam("id").Typed("integer", "int32").AsRequired()
|
||||
validateRequiredTest(t, intParam, reflect.ValueOf(int32(0)))
|
||||
validateRequiredAllowEmptyTest(t, intParam, reflect.ValueOf(int32(0)))
|
||||
longParam := spec.QueryParam("id").Typed("integer", "int64").AsRequired()
|
||||
validateRequiredTest(t, longParam, reflect.ValueOf(int64(0)))
|
||||
validateRequiredAllowEmptyTest(t, longParam, reflect.ValueOf(int64(0)))
|
||||
|
||||
floatParam := spec.QueryParam("score").Typed("number", "float").AsRequired()
|
||||
validateRequiredTest(t, floatParam, reflect.ValueOf(float32(0)))
|
||||
validateRequiredAllowEmptyTest(t, floatParam, reflect.ValueOf(float32(0)))
|
||||
doubleParam := spec.QueryParam("score").Typed("number", "double").AsRequired()
|
||||
validateRequiredTest(t, doubleParam, reflect.ValueOf(float64(0)))
|
||||
validateRequiredAllowEmptyTest(t, doubleParam, reflect.ValueOf(float64(0)))
|
||||
|
||||
dateTimeParam := spec.QueryParam("registered").Typed("string", "date-time").AsRequired()
|
||||
validateRequiredTest(t, dateTimeParam, reflect.ValueOf(strfmt.DateTime{}))
|
||||
// validateRequiredAllowEmptyTest(t, dateTimeParam, reflect.ValueOf(strfmt.DateTime{}))
|
||||
|
||||
dateParam := spec.QueryParam("registered").Typed("string", "date").AsRequired()
|
||||
validateRequiredTest(t, dateParam, reflect.ValueOf(strfmt.Date{}))
|
||||
// validateRequiredAllowEmptyTest(t, dateParam, reflect.ValueOf(strfmt.DateTime{}))
|
||||
|
||||
sliceParam := spec.QueryParam("tags").CollectionOf(stringItems, "").AsRequired()
|
||||
validateRequiredTest(t, sliceParam, reflect.MakeSlice(reflect.TypeOf([]string{}), 0, 0))
|
||||
validateRequiredAllowEmptyTest(t, sliceParam, reflect.MakeSlice(reflect.TypeOf([]string{}), 0, 0))
|
||||
}
|
||||
|
||||
func TestInvalidCollectionFormat(t *testing.T) {
|
||||
validCf1 := spec.QueryParam("validFmt").CollectionOf(stringItems, "multi")
|
||||
validCf2 := spec.FormDataParam("validFmt2").CollectionOf(stringItems, "multi")
|
||||
invalidCf1 := spec.HeaderParam("invalidHdr").CollectionOf(stringItems, "multi")
|
||||
invalidCf2 := spec.PathParam("invalidPath").CollectionOf(stringItems, "multi")
|
||||
|
||||
testCollectionFormat(t, validCf1, true)
|
||||
testCollectionFormat(t, validCf2, true)
|
||||
testCollectionFormat(t, invalidCf1, false)
|
||||
testCollectionFormat(t, invalidCf2, false)
|
||||
}
|
||||
|
||||
func invalidTypeError(param *spec.Parameter, data interface{}) *errors.Validation {
|
||||
tpe := param.Type
|
||||
if param.Format != "" {
|
||||
tpe = param.Format
|
||||
}
|
||||
return errors.InvalidType(param.Name, param.In, tpe, data)
|
||||
}
|
||||
|
||||
func TestTypeValidation(t *testing.T) {
|
||||
for _, newParam := range paramFactories {
|
||||
intParam := newParam("badInt").Typed("integer", "int32")
|
||||
value := reflect.ValueOf(int32(0))
|
||||
binder := np(intParam)
|
||||
err := binder.bindValue([]string{"yada"}, true, value)
|
||||
// fails for invalid string
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, invalidTypeError(intParam, "yada"), err)
|
||||
// fails for overflow
|
||||
val := int64(math.MaxInt32)
|
||||
str := strconv.FormatInt(val, 10) + "0"
|
||||
v := int32(0)
|
||||
value = reflect.ValueOf(&v).Elem()
|
||||
binder = np(intParam)
|
||||
err = binder.bindValue([]string{str}, true, value)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, invalidTypeError(intParam, str), err)
|
||||
|
||||
longParam := newParam("badLong").Typed("integer", "int64")
|
||||
value = reflect.ValueOf(int64(0))
|
||||
binder = np(longParam)
|
||||
err = binder.bindValue([]string{"yada"}, true, value)
|
||||
// fails for invalid string
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, invalidTypeError(longParam, "yada"), err)
|
||||
// fails for overflow
|
||||
str2 := strconv.FormatInt(math.MaxInt64, 10) + "0"
|
||||
v2 := int64(0)
|
||||
vv2 := reflect.ValueOf(&v2).Elem()
|
||||
binder = np(longParam)
|
||||
err = binder.bindValue([]string{str2}, true, vv2)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, invalidTypeError(longParam, str2), err)
|
||||
|
||||
floatParam := newParam("badFloat").Typed("number", "float")
|
||||
value = reflect.ValueOf(float64(0))
|
||||
binder = np(floatParam)
|
||||
err = binder.bindValue([]string{"yada"}, true, value)
|
||||
// fails for invalid string
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, invalidTypeError(floatParam, "yada"), err)
|
||||
// fails for overflow
|
||||
str3 := strconv.FormatFloat(math.MaxFloat64, 'f', 5, 64)
|
||||
v3 := reflect.TypeOf(float32(0))
|
||||
value = reflect.New(v3).Elem()
|
||||
binder = np(floatParam)
|
||||
err = binder.bindValue([]string{str3}, true, value)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, invalidTypeError(floatParam, str3), err)
|
||||
|
||||
doubleParam := newParam("badDouble").Typed("number", "double")
|
||||
value = reflect.ValueOf(float64(0))
|
||||
binder = np(doubleParam)
|
||||
err = binder.bindValue([]string{"yada"}, true, value)
|
||||
// fails for invalid string
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, invalidTypeError(doubleParam, "yada"), err)
|
||||
// fails for overflow
|
||||
str4 := "9" + strconv.FormatFloat(math.MaxFloat64, 'f', 5, 64)
|
||||
v4 := reflect.TypeOf(float64(0))
|
||||
value = reflect.New(v4).Elem()
|
||||
binder = np(doubleParam)
|
||||
err = binder.bindValue([]string{str4}, true, value)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, invalidTypeError(doubleParam, str4), err)
|
||||
|
||||
dateParam := newParam("badDate").Typed("string", "date")
|
||||
value = reflect.ValueOf(strfmt.Date{})
|
||||
binder = np(dateParam)
|
||||
err = binder.bindValue([]string{"yada"}, true, value)
|
||||
// fails for invalid string
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, invalidTypeError(dateParam, "yada"), err)
|
||||
|
||||
dateTimeParam := newParam("badDateTime").Typed("string", "date-time")
|
||||
value = reflect.ValueOf(strfmt.DateTime{})
|
||||
binder = np(dateTimeParam)
|
||||
err = binder.bindValue([]string{"yada"}, true, value)
|
||||
// fails for invalid string
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, invalidTypeError(dateTimeParam, "yada"), err)
|
||||
|
||||
byteParam := newParam("badByte").Typed("string", "byte")
|
||||
values := url.Values(map[string][]string{})
|
||||
values.Add("badByte", "yaüda")
|
||||
v5 := []byte{}
|
||||
value = reflect.ValueOf(&v5).Elem()
|
||||
binder = np(byteParam)
|
||||
err = binder.bindValue([]string{"yaüda"}, true, value)
|
||||
// fails for invalid string
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, invalidTypeError(byteParam, "yaüda"), err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeDetectionInvalidItems(t *testing.T) {
|
||||
withoutItems := spec.QueryParam("without").CollectionOf(nil, "")
|
||||
binder := &untypedParamBinder{
|
||||
Name: "without",
|
||||
parameter: withoutItems,
|
||||
}
|
||||
assert.Nil(t, binder.Type())
|
||||
|
||||
items := new(spec.Items)
|
||||
items.Type = "array"
|
||||
withInvalidItems := spec.QueryParam("invalidItems").CollectionOf(items, "")
|
||||
binder = &untypedParamBinder{
|
||||
Name: "invalidItems",
|
||||
parameter: withInvalidItems,
|
||||
}
|
||||
assert.Nil(t, binder.Type())
|
||||
|
||||
noType := spec.QueryParam("invalidType")
|
||||
noType.Type = "invalid"
|
||||
binder = &untypedParamBinder{
|
||||
Name: "invalidType",
|
||||
parameter: noType,
|
||||
}
|
||||
assert.Nil(t, binder.Type())
|
||||
}
|
||||
|
||||
// type emailStrFmt struct {
|
||||
// name string
|
||||
// tpe reflect.Type
|
||||
// validator FormatValidator
|
||||
// }
|
||||
//
|
||||
// func (e *emailStrFmt) Name() string {
|
||||
// return e.name
|
||||
// }
|
||||
//
|
||||
// func (e *emailStrFmt) Type() reflect.Type {
|
||||
// return e.tpe
|
||||
// }
|
||||
//
|
||||
// func (e *emailStrFmt) Matches(str string) bool {
|
||||
// return e.validator(str)
|
||||
// }
|
||||
//
|
||||
// func TestTypeDetectionValid(t *testing.T) {
|
||||
// // emlFmt := &emailStrFmt{
|
||||
// // name: "email",
|
||||
// // tpe: reflect.TypeOf(email{}),
|
||||
// // }
|
||||
// // formats := []StringFormat{emlFmt}
|
||||
//
|
||||
// expected := map[string]reflect.Type{
|
||||
// "name": reflect.TypeOf(""),
|
||||
// "id": reflect.TypeOf(int64(0)),
|
||||
// "age": reflect.TypeOf(int32(0)),
|
||||
// "score": reflect.TypeOf(float32(0)),
|
||||
// "factor": reflect.TypeOf(float64(0)),
|
||||
// "friend": reflect.TypeOf(map[string]interface{}{}),
|
||||
// "X-Request-Id": reflect.TypeOf(int64(0)),
|
||||
// "tags": reflect.TypeOf([]string{}),
|
||||
// "confirmed": reflect.TypeOf(true),
|
||||
// "planned": reflect.TypeOf(swagger.Date{}),
|
||||
// "delivered": reflect.TypeOf(swagger.DateTime{}),
|
||||
// "email": reflect.TypeOf(email{}),
|
||||
// "picture": reflect.TypeOf([]byte{}),
|
||||
// "file": reflect.TypeOf(&swagger.File{}).Elem(),
|
||||
// }
|
||||
//
|
||||
// params := parametersForAllTypes("")
|
||||
// emailParam := spec.QueryParam("email").Typed("string", "email")
|
||||
// params["email"] = *emailParam
|
||||
//
|
||||
// fileParam := spec.FileParam("file")
|
||||
// params["file"] = *fileParam
|
||||
//
|
||||
// for _, v := range params {
|
||||
// binder := ¶mBinder{
|
||||
// formats: formats,
|
||||
// name: v.Name,
|
||||
// parameter: &v,
|
||||
// }
|
||||
// assert.Equal(t, expected[v.Name], binder.Type(), "name: %s", v.Name)
|
||||
// }
|
||||
// }
|
||||
101
vendor/github.com/go-openapi/runtime/middleware/redoc.go
generated
vendored
Normal file
101
vendor/github.com/go-openapi/runtime/middleware/redoc.go
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"path"
|
||||
)
|
||||
|
||||
// RedocOpts configures the Redoc middlewares
|
||||
type RedocOpts struct {
|
||||
// BasePath for the UI path, defaults to: /
|
||||
BasePath string
|
||||
// Path combines with BasePath for the full UI path, defaults to: docs
|
||||
Path string
|
||||
// SpecURL the url to find the spec for
|
||||
SpecURL string
|
||||
// RedocURL for the js that generates the redoc site, defaults to: https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js
|
||||
RedocURL string
|
||||
// Title for the documentation site, default to: API documentation
|
||||
Title string
|
||||
}
|
||||
|
||||
// EnsureDefaults in case some options are missing
|
||||
func (r *RedocOpts) EnsureDefaults() {
|
||||
if r.BasePath == "" {
|
||||
r.BasePath = "/"
|
||||
}
|
||||
if r.Path == "" {
|
||||
r.Path = "docs"
|
||||
}
|
||||
if r.SpecURL == "" {
|
||||
r.SpecURL = "/swagger.json"
|
||||
}
|
||||
if r.RedocURL == "" {
|
||||
r.RedocURL = redocLatest
|
||||
}
|
||||
if r.Title == "" {
|
||||
r.Title = "API documentation"
|
||||
}
|
||||
}
|
||||
|
||||
// Redoc creates a middleware to serve a documentation site for a swagger spec.
|
||||
// This allows for altering the spec before starting the http listener.
|
||||
//
|
||||
func Redoc(opts RedocOpts, next http.Handler) http.Handler {
|
||||
opts.EnsureDefaults()
|
||||
|
||||
pth := path.Join(opts.BasePath, opts.Path)
|
||||
tmpl := template.Must(template.New("redoc").Parse(redocTemplate))
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
tmpl.Execute(buf, opts)
|
||||
b := buf.Bytes()
|
||||
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == pth {
|
||||
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
|
||||
rw.Write(b)
|
||||
return
|
||||
}
|
||||
|
||||
if next == nil {
|
||||
rw.Header().Set("Content-Type", "text/plain")
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
rw.Write([]byte(fmt.Sprintf("%q not found", pth)))
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(rw, r)
|
||||
})
|
||||
}
|
||||
|
||||
const (
|
||||
redocLatest = "https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js"
|
||||
redocTemplate = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ .Title }}</title>
|
||||
<!-- needed for adaptive design -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!--
|
||||
ReDoc doesn't change outer page styles
|
||||
-->
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<redoc spec-url='{{ .SpecURL }}'></redoc>
|
||||
<script src="{{ .RedocURL }}"> </script>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
)
|
||||
22
vendor/github.com/go-openapi/runtime/middleware/redoc_test.go
generated
vendored
Normal file
22
vendor/github.com/go-openapi/runtime/middleware/redoc_test.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRedocMiddleware(t *testing.T) {
|
||||
redoc := Redoc(RedocOpts{}, nil)
|
||||
|
||||
req, _ := http.NewRequest("GET", "/docs", nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
redoc.ServeHTTP(recorder, req)
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
assert.Equal(t, "text/html; charset=utf-8", recorder.Header().Get("Content-Type"))
|
||||
assert.Contains(t, recorder.Body.String(), "<title>API documentation</title>")
|
||||
assert.Contains(t, recorder.Body.String(), "<redoc spec-url='/swagger.json'></redoc>")
|
||||
assert.Contains(t, recorder.Body.String(), redocLatest)
|
||||
}
|
||||
104
vendor/github.com/go-openapi/runtime/middleware/request.go
generated
vendored
Normal file
104
vendor/github.com/go-openapi/runtime/middleware/request.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/go-openapi/spec"
|
||||
"github.com/go-openapi/strfmt"
|
||||
)
|
||||
|
||||
// RequestBinder binds and validates the data from a http request
|
||||
type untypedRequestBinder struct {
|
||||
Spec *spec.Swagger
|
||||
Parameters map[string]spec.Parameter
|
||||
Formats strfmt.Registry
|
||||
paramBinders map[string]*untypedParamBinder
|
||||
}
|
||||
|
||||
// NewRequestBinder creates a new binder for reading a request.
|
||||
func newUntypedRequestBinder(parameters map[string]spec.Parameter, spec *spec.Swagger, formats strfmt.Registry) *untypedRequestBinder {
|
||||
binders := make(map[string]*untypedParamBinder)
|
||||
for fieldName, param := range parameters {
|
||||
binders[fieldName] = newUntypedParamBinder(param, spec, formats)
|
||||
}
|
||||
return &untypedRequestBinder{
|
||||
Parameters: parameters,
|
||||
paramBinders: binders,
|
||||
Spec: spec,
|
||||
Formats: formats,
|
||||
}
|
||||
}
|
||||
|
||||
// Bind perform the databinding and validation
|
||||
func (o *untypedRequestBinder) Bind(request *http.Request, routeParams RouteParams, consumer runtime.Consumer, data interface{}) error {
|
||||
val := reflect.Indirect(reflect.ValueOf(data))
|
||||
isMap := val.Kind() == reflect.Map
|
||||
var result []error
|
||||
debugLog("binding %d parameters for %s %s", len(o.Parameters), request.Method, request.URL.EscapedPath())
|
||||
for fieldName, param := range o.Parameters {
|
||||
binder := o.paramBinders[fieldName]
|
||||
debugLog("binding paramter %s for %s %s", fieldName, request.Method, request.URL.EscapedPath())
|
||||
var target reflect.Value
|
||||
if !isMap {
|
||||
binder.Name = fieldName
|
||||
target = val.FieldByName(fieldName)
|
||||
}
|
||||
|
||||
if isMap {
|
||||
tpe := binder.Type()
|
||||
if tpe == nil {
|
||||
if param.Schema.Type.Contains("array") {
|
||||
tpe = reflect.TypeOf([]interface{}{})
|
||||
} else {
|
||||
tpe = reflect.TypeOf(map[string]interface{}{})
|
||||
}
|
||||
}
|
||||
target = reflect.Indirect(reflect.New(tpe))
|
||||
|
||||
}
|
||||
|
||||
if !target.IsValid() {
|
||||
result = append(result, errors.New(500, "parameter name %q is an unknown field", binder.Name))
|
||||
continue
|
||||
}
|
||||
|
||||
if err := binder.Bind(request, routeParams, consumer, target); err != nil {
|
||||
result = append(result, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if binder.validator != nil {
|
||||
rr := binder.validator.Validate(target.Interface())
|
||||
if rr != nil && rr.HasErrors() {
|
||||
result = append(result, rr.AsError())
|
||||
}
|
||||
}
|
||||
|
||||
if isMap {
|
||||
val.SetMapIndex(reflect.ValueOf(param.Name), target)
|
||||
}
|
||||
}
|
||||
|
||||
if len(result) > 0 {
|
||||
return errors.CompositeValidationError(result...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
481
vendor/github.com/go-openapi/runtime/middleware/request_test.go
generated
vendored
Normal file
481
vendor/github.com/go-openapi/runtime/middleware/request_test.go
generated
vendored
Normal file
@@ -0,0 +1,481 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/go-openapi/spec"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type stubConsumer struct {
|
||||
}
|
||||
|
||||
func (s *stubConsumer) Consume(_ io.Reader, _ interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type friend struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
}
|
||||
|
||||
type jsonRequestParams struct {
|
||||
ID int64 // path
|
||||
Name string // query
|
||||
Friend friend // body
|
||||
RequestID int64 // header
|
||||
Tags []string // csv
|
||||
}
|
||||
|
||||
type jsonRequestPtr struct {
|
||||
ID int64 // path
|
||||
Name string // query
|
||||
RequestID int64 // header
|
||||
Tags []string // csv
|
||||
Friend *friend
|
||||
}
|
||||
|
||||
type jsonRequestSlice struct {
|
||||
ID int64 // path
|
||||
Name string // query
|
||||
RequestID int64 // header
|
||||
Tags []string // csv
|
||||
Friend []friend
|
||||
}
|
||||
|
||||
type jsonRequestAllTypes struct {
|
||||
Confirmed bool
|
||||
Planned strfmt.Date
|
||||
Delivered strfmt.DateTime
|
||||
Age int32
|
||||
ID int64
|
||||
Score float32
|
||||
Factor float64
|
||||
Friend friend
|
||||
Name string
|
||||
Tags []string
|
||||
Picture []byte
|
||||
RequestID int64
|
||||
}
|
||||
|
||||
func parametersForAllTypes(fmt string) map[string]spec.Parameter {
|
||||
if fmt == "" {
|
||||
fmt = "csv"
|
||||
}
|
||||
nameParam := spec.QueryParam("name").Typed("string", "")
|
||||
idParam := spec.PathParam("id").Typed("integer", "int64")
|
||||
ageParam := spec.QueryParam("age").Typed("integer", "int32")
|
||||
scoreParam := spec.QueryParam("score").Typed("number", "float")
|
||||
factorParam := spec.QueryParam("factor").Typed("number", "double")
|
||||
|
||||
friendSchema := new(spec.Schema).Typed("object", "")
|
||||
friendParam := spec.BodyParam("friend", friendSchema)
|
||||
|
||||
requestIDParam := spec.HeaderParam("X-Request-Id").Typed("integer", "int64")
|
||||
requestIDParam.Extensions = spec.Extensions(map[string]interface{}{})
|
||||
requestIDParam.Extensions.Add("go-name", "RequestID")
|
||||
|
||||
items := new(spec.Items)
|
||||
items.Type = "string"
|
||||
tagsParam := spec.QueryParam("tags").CollectionOf(items, fmt)
|
||||
|
||||
confirmedParam := spec.QueryParam("confirmed").Typed("boolean", "")
|
||||
plannedParam := spec.QueryParam("planned").Typed("string", "date")
|
||||
deliveredParam := spec.QueryParam("delivered").Typed("string", "date-time")
|
||||
pictureParam := spec.QueryParam("picture").Typed("string", "byte") // base64 encoded during transport
|
||||
|
||||
return map[string]spec.Parameter{
|
||||
"ID": *idParam,
|
||||
"Name": *nameParam,
|
||||
"RequestID": *requestIDParam,
|
||||
"Friend": *friendParam,
|
||||
"Tags": *tagsParam,
|
||||
"Age": *ageParam,
|
||||
"Score": *scoreParam,
|
||||
"Factor": *factorParam,
|
||||
"Confirmed": *confirmedParam,
|
||||
"Planned": *plannedParam,
|
||||
"Delivered": *deliveredParam,
|
||||
"Picture": *pictureParam,
|
||||
}
|
||||
}
|
||||
|
||||
func parametersForJSONRequestParams(fmt string) map[string]spec.Parameter {
|
||||
if fmt == "" {
|
||||
fmt = "csv"
|
||||
}
|
||||
nameParam := spec.QueryParam("name").Typed("string", "")
|
||||
idParam := spec.PathParam("id").Typed("integer", "int64")
|
||||
|
||||
friendSchema := new(spec.Schema).Typed("object", "")
|
||||
friendParam := spec.BodyParam("friend", friendSchema)
|
||||
|
||||
requestIDParam := spec.HeaderParam("X-Request-Id").Typed("integer", "int64")
|
||||
requestIDParam.Extensions = spec.Extensions(map[string]interface{}{})
|
||||
requestIDParam.Extensions.Add("go-name", "RequestID")
|
||||
|
||||
items := new(spec.Items)
|
||||
items.Type = "string"
|
||||
tagsParam := spec.QueryParam("tags").CollectionOf(items, fmt)
|
||||
|
||||
return map[string]spec.Parameter{
|
||||
"ID": *idParam,
|
||||
"Name": *nameParam,
|
||||
"RequestID": *requestIDParam,
|
||||
"Friend": *friendParam,
|
||||
"Tags": *tagsParam,
|
||||
}
|
||||
}
|
||||
func parametersForJSONRequestSliceParams(fmt string) map[string]spec.Parameter {
|
||||
if fmt == "" {
|
||||
fmt = "csv"
|
||||
}
|
||||
nameParam := spec.QueryParam("name").Typed("string", "")
|
||||
idParam := spec.PathParam("id").Typed("integer", "int64")
|
||||
|
||||
friendSchema := new(spec.Schema).Typed("object", "")
|
||||
friendParam := spec.BodyParam("friend", spec.ArrayProperty(friendSchema))
|
||||
|
||||
requestIDParam := spec.HeaderParam("X-Request-Id").Typed("integer", "int64")
|
||||
requestIDParam.Extensions = spec.Extensions(map[string]interface{}{})
|
||||
requestIDParam.Extensions.Add("go-name", "RequestID")
|
||||
|
||||
items := new(spec.Items)
|
||||
items.Type = "string"
|
||||
tagsParam := spec.QueryParam("tags").CollectionOf(items, fmt)
|
||||
|
||||
return map[string]spec.Parameter{
|
||||
"ID": *idParam,
|
||||
"Name": *nameParam,
|
||||
"RequestID": *requestIDParam,
|
||||
"Friend": *friendParam,
|
||||
"Tags": *tagsParam,
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestBindingDefaultValue(t *testing.T) {
|
||||
confirmed := true
|
||||
name := "thomas"
|
||||
friend := map[string]interface{}{"name": "toby", "age": float64(32)}
|
||||
id, age, score, factor := int64(7575), int32(348), float32(5.309), float64(37.403)
|
||||
requestID := 19394858
|
||||
tags := []string{"one", "two", "three"}
|
||||
dt1 := time.Date(2014, 8, 9, 0, 0, 0, 0, time.UTC)
|
||||
planned := strfmt.Date(dt1)
|
||||
dt2 := time.Date(2014, 10, 12, 8, 5, 5, 0, time.UTC)
|
||||
delivered := strfmt.DateTime(dt2)
|
||||
uri, _ := url.Parse("http://localhost:8002/hello")
|
||||
defaults := map[string]interface{}{
|
||||
"id": id,
|
||||
"age": age,
|
||||
"score": score,
|
||||
"factor": factor,
|
||||
"name": name,
|
||||
"friend": friend,
|
||||
"X-Request-Id": requestID,
|
||||
"tags": tags,
|
||||
"confirmed": confirmed,
|
||||
"planned": planned,
|
||||
"delivered": delivered,
|
||||
"picture": []byte("hello"),
|
||||
}
|
||||
op2 := parametersForAllTypes("")
|
||||
op3 := make(map[string]spec.Parameter)
|
||||
for k, p := range op2 {
|
||||
p.Default = defaults[p.Name]
|
||||
op3[k] = p
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("POST", uri.String(), bytes.NewBuffer(nil))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
binder := newUntypedRequestBinder(op3, new(spec.Swagger), strfmt.Default)
|
||||
|
||||
data := make(map[string]interface{})
|
||||
err := binder.Bind(req, RouteParams(nil), runtime.JSONConsumer(), &data)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, defaults["id"], data["id"])
|
||||
assert.Equal(t, name, data["name"])
|
||||
assert.Equal(t, friend, data["friend"])
|
||||
assert.EqualValues(t, requestID, data["X-Request-Id"])
|
||||
assert.Equal(t, tags, data["tags"])
|
||||
assert.Equal(t, planned, data["planned"])
|
||||
assert.Equal(t, delivered, data["delivered"])
|
||||
assert.Equal(t, confirmed, data["confirmed"])
|
||||
assert.Equal(t, age, data["age"])
|
||||
assert.Equal(t, factor, data["factor"])
|
||||
assert.Equal(t, score, data["score"])
|
||||
assert.Equal(t, "hello", string(data["picture"].(strfmt.Base64)))
|
||||
}
|
||||
|
||||
func TestRequestBindingForInvalid(t *testing.T) {
|
||||
|
||||
invalidParam := spec.QueryParam("some")
|
||||
|
||||
op1 := map[string]spec.Parameter{"Some": *invalidParam}
|
||||
|
||||
binder := newUntypedRequestBinder(op1, new(spec.Swagger), strfmt.Default)
|
||||
req, _ := http.NewRequest("GET", "http://localhost:8002/hello?name=the-name", nil)
|
||||
|
||||
err := binder.Bind(req, nil, new(stubConsumer), new(jsonRequestParams))
|
||||
assert.Error(t, err)
|
||||
|
||||
op2 := parametersForJSONRequestParams("")
|
||||
binder = newUntypedRequestBinder(op2, new(spec.Swagger), strfmt.Default)
|
||||
|
||||
req, _ = http.NewRequest("POST", "http://localhost:8002/hello/1?name=the-name", bytes.NewBuffer([]byte(`{"name":"toby","age":32}`)))
|
||||
req.Header.Set("Content-Type", "application(")
|
||||
data := jsonRequestParams{}
|
||||
err = binder.Bind(req, RouteParams([]RouteParam{{"id", "1"}}), runtime.JSONConsumer(), &data)
|
||||
assert.Error(t, err)
|
||||
|
||||
req, _ = http.NewRequest("POST", "http://localhost:8002/hello/1?name=the-name", bytes.NewBuffer([]byte(`{]`)))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
data = jsonRequestParams{}
|
||||
err = binder.Bind(req, RouteParams([]RouteParam{{"id", "1"}}), runtime.JSONConsumer(), &data)
|
||||
assert.Error(t, err)
|
||||
|
||||
invalidMultiParam := spec.HeaderParam("tags").CollectionOf(new(spec.Items), "multi")
|
||||
op3 := map[string]spec.Parameter{"Tags": *invalidMultiParam}
|
||||
binder = newUntypedRequestBinder(op3, new(spec.Swagger), strfmt.Default)
|
||||
|
||||
req, _ = http.NewRequest("POST", "http://localhost:8002/hello/1?name=the-name", bytes.NewBuffer([]byte(`{}`)))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
data = jsonRequestParams{}
|
||||
err = binder.Bind(req, RouteParams([]RouteParam{{"id", "1"}}), runtime.JSONConsumer(), &data)
|
||||
assert.Error(t, err)
|
||||
|
||||
invalidMultiParam = spec.PathParam("").CollectionOf(new(spec.Items), "multi")
|
||||
|
||||
op4 := map[string]spec.Parameter{"Tags": *invalidMultiParam}
|
||||
binder = newUntypedRequestBinder(op4, new(spec.Swagger), strfmt.Default)
|
||||
|
||||
req, _ = http.NewRequest("POST", "http://localhost:8002/hello/1?name=the-name", bytes.NewBuffer([]byte(`{}`)))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
data = jsonRequestParams{}
|
||||
err = binder.Bind(req, RouteParams([]RouteParam{{"id", "1"}}), runtime.JSONConsumer(), &data)
|
||||
assert.Error(t, err)
|
||||
|
||||
invalidInParam := spec.HeaderParam("tags").Typed("string", "")
|
||||
invalidInParam.In = "invalid"
|
||||
op5 := map[string]spec.Parameter{"Tags": *invalidInParam}
|
||||
binder = newUntypedRequestBinder(op5, new(spec.Swagger), strfmt.Default)
|
||||
|
||||
req, _ = http.NewRequest("POST", "http://localhost:8002/hello/1?name=the-name", bytes.NewBuffer([]byte(`{}`)))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
data = jsonRequestParams{}
|
||||
err = binder.Bind(req, RouteParams([]RouteParam{{"id", "1"}}), runtime.JSONConsumer(), &data)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRequestBindingForValid(t *testing.T) {
|
||||
|
||||
for _, fmt := range []string{"csv", "pipes", "tsv", "ssv", "multi"} {
|
||||
op1 := parametersForJSONRequestParams(fmt)
|
||||
|
||||
binder := newUntypedRequestBinder(op1, new(spec.Swagger), strfmt.Default)
|
||||
|
||||
lval := []string{"one", "two", "three"}
|
||||
queryString := ""
|
||||
switch fmt {
|
||||
case "multi":
|
||||
queryString = strings.Join(lval, "&tags=")
|
||||
case "ssv":
|
||||
queryString = strings.Join(lval, " ")
|
||||
case "pipes":
|
||||
queryString = strings.Join(lval, "|")
|
||||
case "tsv":
|
||||
queryString = strings.Join(lval, "\t")
|
||||
default:
|
||||
queryString = strings.Join(lval, ",")
|
||||
}
|
||||
|
||||
urlStr := "http://localhost:8002/hello/1?name=the-name&tags=" + queryString
|
||||
|
||||
req, _ := http.NewRequest("POST", urlStr, bytes.NewBuffer([]byte(`{"name":"toby","age":32}`)))
|
||||
req.Header.Set("Content-Type", "application/json;charset=utf-8")
|
||||
req.Header.Set("X-Request-Id", "1325959595")
|
||||
|
||||
data := jsonRequestParams{}
|
||||
err := binder.Bind(req, RouteParams([]RouteParam{{"id", "1"}}), runtime.JSONConsumer(), &data)
|
||||
|
||||
expected := jsonRequestParams{
|
||||
ID: 1,
|
||||
Name: "the-name",
|
||||
Friend: friend{"toby", 32},
|
||||
RequestID: 1325959595,
|
||||
Tags: []string{"one", "two", "three"},
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, data)
|
||||
}
|
||||
|
||||
op1 := parametersForJSONRequestParams("")
|
||||
|
||||
binder := newUntypedRequestBinder(op1, new(spec.Swagger), strfmt.Default)
|
||||
urlStr := "http://localhost:8002/hello/1?name=the-name&tags=one,two,three"
|
||||
req, _ := http.NewRequest("POST", urlStr, bytes.NewBuffer([]byte(`{"name":"toby","age":32}`)))
|
||||
req.Header.Set("Content-Type", "application/json;charset=utf-8")
|
||||
req.Header.Set("X-Request-Id", "1325959595")
|
||||
|
||||
data2 := jsonRequestPtr{}
|
||||
err := binder.Bind(req, []RouteParam{{"id", "1"}}, runtime.JSONConsumer(), &data2)
|
||||
|
||||
expected2 := jsonRequestPtr{
|
||||
Friend: &friend{"toby", 32},
|
||||
Tags: []string{"one", "two", "three"},
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
if data2.Friend == nil {
|
||||
t.Fatal("friend is nil")
|
||||
}
|
||||
assert.Equal(t, *expected2.Friend, *data2.Friend)
|
||||
assert.Equal(t, expected2.Tags, data2.Tags)
|
||||
|
||||
req, _ = http.NewRequest("POST", urlStr, bytes.NewBuffer([]byte(`[{"name":"toby","age":32}]`)))
|
||||
req.Header.Set("Content-Type", "application/json;charset=utf-8")
|
||||
req.Header.Set("X-Request-Id", "1325959595")
|
||||
op2 := parametersForJSONRequestSliceParams("")
|
||||
binder = newUntypedRequestBinder(op2, new(spec.Swagger), strfmt.Default)
|
||||
data3 := jsonRequestSlice{}
|
||||
err = binder.Bind(req, []RouteParam{{"id", "1"}}, runtime.JSONConsumer(), &data3)
|
||||
|
||||
expected3 := jsonRequestSlice{
|
||||
Friend: []friend{{"toby", 32}},
|
||||
Tags: []string{"one", "two", "three"},
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected3.Friend, data3.Friend)
|
||||
assert.Equal(t, expected3.Tags, data3.Tags)
|
||||
}
|
||||
|
||||
type formRequest struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
func parametersForFormUpload() map[string]spec.Parameter {
|
||||
nameParam := spec.FormDataParam("name").Typed("string", "")
|
||||
|
||||
ageParam := spec.FormDataParam("age").Typed("integer", "int32")
|
||||
|
||||
return map[string]spec.Parameter{"Name": *nameParam, "Age": *ageParam}
|
||||
}
|
||||
|
||||
func TestFormUpload(t *testing.T) {
|
||||
params := parametersForFormUpload()
|
||||
binder := newUntypedRequestBinder(params, new(spec.Swagger), strfmt.Default)
|
||||
|
||||
urlStr := "http://localhost:8002/hello"
|
||||
req, _ := http.NewRequest("POST", urlStr, bytes.NewBufferString(`name=the-name&age=32`))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
data := formRequest{}
|
||||
res := binder.Bind(req, nil, runtime.JSONConsumer(), &data)
|
||||
assert.NoError(t, res)
|
||||
assert.Equal(t, "the-name", data.Name)
|
||||
assert.Equal(t, 32, data.Age)
|
||||
|
||||
req, _ = http.NewRequest("POST", urlStr, bytes.NewBufferString(`name=%3&age=32`))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
data = formRequest{}
|
||||
assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
|
||||
}
|
||||
|
||||
type fileRequest struct {
|
||||
Name string // body
|
||||
File runtime.File // upload
|
||||
}
|
||||
|
||||
func paramsForFileUpload() *untypedRequestBinder {
|
||||
nameParam := spec.FormDataParam("name").Typed("string", "")
|
||||
|
||||
fileParam := spec.FileParam("file")
|
||||
|
||||
params := map[string]spec.Parameter{"Name": *nameParam, "File": *fileParam}
|
||||
return newUntypedRequestBinder(params, new(spec.Swagger), strfmt.Default)
|
||||
}
|
||||
|
||||
func TestBindingFileUpload(t *testing.T) {
|
||||
binder := paramsForFileUpload()
|
||||
|
||||
body := bytes.NewBuffer(nil)
|
||||
writer := multipart.NewWriter(body)
|
||||
part, err := writer.CreateFormFile("file", "plain-jane.txt")
|
||||
assert.NoError(t, err)
|
||||
|
||||
part.Write([]byte("the file contents"))
|
||||
writer.WriteField("name", "the-name")
|
||||
assert.NoError(t, writer.Close())
|
||||
|
||||
urlStr := "http://localhost:8002/hello"
|
||||
req, _ := http.NewRequest("POST", urlStr, body)
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
data := fileRequest{}
|
||||
assert.NoError(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
|
||||
assert.Equal(t, "the-name", data.Name)
|
||||
assert.NotNil(t, data.File)
|
||||
assert.NotNil(t, data.File.Header)
|
||||
assert.Equal(t, "plain-jane.txt", data.File.Header.Filename)
|
||||
|
||||
bb, err := ioutil.ReadAll(data.File.Data)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte("the file contents"), bb)
|
||||
|
||||
req, _ = http.NewRequest("POST", urlStr, body)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
data = fileRequest{}
|
||||
assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
|
||||
|
||||
req, _ = http.NewRequest("POST", urlStr, body)
|
||||
req.Header.Set("Content-Type", "application(")
|
||||
data = fileRequest{}
|
||||
assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
|
||||
|
||||
body = bytes.NewBuffer(nil)
|
||||
writer = multipart.NewWriter(body)
|
||||
part, err = writer.CreateFormFile("bad-name", "plain-jane.txt")
|
||||
assert.NoError(t, err)
|
||||
|
||||
part.Write([]byte("the file contents"))
|
||||
writer.WriteField("name", "the-name")
|
||||
assert.NoError(t, writer.Close())
|
||||
req, _ = http.NewRequest("POST", urlStr, body)
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
data = fileRequest{}
|
||||
assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
|
||||
|
||||
req, _ = http.NewRequest("POST", urlStr, body)
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
req.MultipartReader()
|
||||
|
||||
data = fileRequest{}
|
||||
assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
|
||||
|
||||
}
|
||||
38
vendor/github.com/go-openapi/runtime/middleware/route_param_test.go
generated
vendored
Normal file
38
vendor/github.com/go-openapi/runtime/middleware/route_param_test.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRouteParams(t *testing.T) {
|
||||
coll1 := RouteParams([]RouteParam{
|
||||
{"blah", "foo"},
|
||||
{"abc", "bar"},
|
||||
{"ccc", "efg"},
|
||||
})
|
||||
|
||||
v := coll1.Get("blah")
|
||||
assert.Equal(t, v, "foo")
|
||||
v2 := coll1.Get("abc")
|
||||
assert.Equal(t, v2, "bar")
|
||||
v3 := coll1.Get("ccc")
|
||||
assert.Equal(t, v3, "efg")
|
||||
v4 := coll1.Get("ydkdk")
|
||||
assert.Empty(t, v4)
|
||||
}
|
||||
270
vendor/github.com/go-openapi/runtime/middleware/router.go
generated
vendored
Normal file
270
vendor/github.com/go-openapi/runtime/middleware/router.go
generated
vendored
Normal file
@@ -0,0 +1,270 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
fpath "path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/analysis"
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/loads"
|
||||
"github.com/go-openapi/runtime"
|
||||
"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.
|
||||
// implementations of the muxer should use these route params to communicate with the
|
||||
// swagger framework
|
||||
type RouteParam struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
// RouteParams the collection of route params
|
||||
type RouteParams []RouteParam
|
||||
|
||||
// Get gets the value for the route param for the specified key
|
||||
func (r RouteParams) Get(name string) string {
|
||||
vv, _, _ := r.GetOK(name)
|
||||
if len(vv) > 0 {
|
||||
return vv[len(vv)-1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetOK gets the value but also returns booleans to indicate if a key or value
|
||||
// is present. This aids in validation and satisfies an interface in use there
|
||||
//
|
||||
// The returned values are: data, has key, has value
|
||||
func (r RouteParams) GetOK(name string) ([]string, bool, bool) {
|
||||
for _, p := range r {
|
||||
if p.Name == name {
|
||||
return []string{p.Value}, true, p.Value != ""
|
||||
}
|
||||
}
|
||||
return nil, false, false
|
||||
}
|
||||
|
||||
// NewRouter creates a new context aware router middleware
|
||||
func NewRouter(ctx *Context, next http.Handler) http.Handler {
|
||||
if ctx.router == nil {
|
||||
ctx.router = DefaultRouter(ctx.spec, ctx.api)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
defer context.Clear(r)
|
||||
if _, ok := ctx.RouteInfo(r); ok {
|
||||
next.ServeHTTP(rw, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Not found, check if it exists in the other methods first
|
||||
if others := ctx.AllowedMethods(r); len(others) > 0 {
|
||||
ctx.Respond(rw, r, ctx.analyzer.RequiredProduces(), nil, errors.MethodNotAllowed(r.Method, others))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Respond(rw, r, ctx.analyzer.RequiredProduces(), nil, errors.NotFound("path %s was not found", r.URL.EscapedPath()))
|
||||
})
|
||||
}
|
||||
|
||||
// RoutableAPI represents an interface for things that can serve
|
||||
// as a provider of implementations for the swagger router
|
||||
type RoutableAPI interface {
|
||||
HandlerFor(string, string) (http.Handler, bool)
|
||||
ServeErrorFor(string) func(http.ResponseWriter, *http.Request, error)
|
||||
ConsumersFor([]string) map[string]runtime.Consumer
|
||||
ProducersFor([]string) map[string]runtime.Producer
|
||||
AuthenticatorsFor(map[string]spec.SecurityScheme) map[string]runtime.Authenticator
|
||||
Formats() strfmt.Registry
|
||||
DefaultProduces() string
|
||||
DefaultConsumes() string
|
||||
}
|
||||
|
||||
// Router represents a swagger aware router
|
||||
type Router interface {
|
||||
Lookup(method, path string) (*MatchedRoute, bool)
|
||||
OtherMethods(method, path string) []string
|
||||
}
|
||||
|
||||
type defaultRouteBuilder struct {
|
||||
spec *loads.Document
|
||||
analyzer *analysis.Spec
|
||||
api RoutableAPI
|
||||
records map[string][]denco.Record
|
||||
}
|
||||
|
||||
type defaultRouter struct {
|
||||
spec *loads.Document
|
||||
api RoutableAPI
|
||||
routers map[string]*denco.Router
|
||||
}
|
||||
|
||||
func newDefaultRouteBuilder(spec *loads.Document, api RoutableAPI) *defaultRouteBuilder {
|
||||
return &defaultRouteBuilder{
|
||||
spec: spec,
|
||||
analyzer: analysis.New(spec.Spec()),
|
||||
api: api,
|
||||
records: make(map[string][]denco.Record),
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultRouter creates a default implemenation of the router
|
||||
func DefaultRouter(spec *loads.Document, api RoutableAPI) Router {
|
||||
builder := newDefaultRouteBuilder(spec, api)
|
||||
if spec != nil {
|
||||
for method, paths := range builder.analyzer.Operations() {
|
||||
for path, operation := range paths {
|
||||
fp := fpath.Join(spec.BasePath(), path)
|
||||
debugLog("adding route %s %s %q", method, fp, operation.ID)
|
||||
builder.AddRoute(method, fp, operation)
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.Build()
|
||||
}
|
||||
|
||||
type routeEntry struct {
|
||||
PathPattern string
|
||||
BasePath string
|
||||
Operation *spec.Operation
|
||||
Consumes []string
|
||||
Consumers map[string]runtime.Consumer
|
||||
Produces []string
|
||||
Producers map[string]runtime.Producer
|
||||
Parameters map[string]spec.Parameter
|
||||
Handler http.Handler
|
||||
Formats strfmt.Registry
|
||||
Binder *untypedRequestBinder
|
||||
Authenticators map[string]runtime.Authenticator
|
||||
Scopes map[string][]string
|
||||
}
|
||||
|
||||
// MatchedRoute represents the route that was matched in this request
|
||||
type MatchedRoute struct {
|
||||
routeEntry
|
||||
Params RouteParams
|
||||
Consumer runtime.Consumer
|
||||
Producer runtime.Producer
|
||||
}
|
||||
|
||||
func (d *defaultRouter) Lookup(method, path string) (*MatchedRoute, bool) {
|
||||
mth := strings.ToUpper(method)
|
||||
debugLog("looking up route for %s %s", method, path)
|
||||
if Debug {
|
||||
if len(d.routers) == 0 {
|
||||
debugLog("there are no known routers")
|
||||
}
|
||||
for meth := range d.routers {
|
||||
debugLog("got a router for %s", meth)
|
||||
}
|
||||
}
|
||||
if router, ok := d.routers[mth]; ok {
|
||||
if m, rp, ok := router.Lookup(fpath.Clean(path)); ok && m != nil {
|
||||
if entry, ok := m.(*routeEntry); ok {
|
||||
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)
|
||||
if err != nil {
|
||||
debugLog("failed to escape %q: %v", p.Value, err)
|
||||
v = p.Value
|
||||
}
|
||||
params = append(params, RouteParam{Name: p.Name, Value: v})
|
||||
}
|
||||
return &MatchedRoute{routeEntry: *entry, Params: params}, true
|
||||
}
|
||||
} else {
|
||||
debugLog("couldn't find a route by path for %s %s", method, path)
|
||||
}
|
||||
} else {
|
||||
debugLog("couldn't find a route by method for %s %s", method, path)
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (d *defaultRouter) OtherMethods(method, path string) []string {
|
||||
mn := strings.ToUpper(method)
|
||||
var methods []string
|
||||
for k, v := range d.routers {
|
||||
if k != mn {
|
||||
if _, _, ok := v.Lookup(fpath.Clean(path)); ok {
|
||||
methods = append(methods, k)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return methods
|
||||
}
|
||||
|
||||
var pathConverter = regexp.MustCompile(`{(.+?)}`)
|
||||
|
||||
func (d *defaultRouteBuilder) AddRoute(method, path string, operation *spec.Operation) {
|
||||
mn := strings.ToUpper(method)
|
||||
|
||||
bp := fpath.Clean(d.spec.BasePath())
|
||||
if len(bp) > 0 && bp[len(bp)-1] == '/' {
|
||||
bp = bp[:len(bp)-1]
|
||||
}
|
||||
|
||||
if handler, ok := d.api.HandlerFor(method, strings.TrimPrefix(path, bp)); ok {
|
||||
consumes := d.analyzer.ConsumesFor(operation)
|
||||
produces := d.analyzer.ProducesFor(operation)
|
||||
parameters := d.analyzer.ParamsFor(method, strings.TrimPrefix(path, bp))
|
||||
definitions := d.analyzer.SecurityDefinitionsFor(operation)
|
||||
requirements := d.analyzer.SecurityRequirementsFor(operation)
|
||||
scopes := make(map[string][]string, len(requirements))
|
||||
for _, v := range requirements {
|
||||
scopes[v.Name] = v.Scopes
|
||||
}
|
||||
|
||||
record := denco.NewRecord(pathConverter.ReplaceAllString(path, ":$1"), &routeEntry{
|
||||
BasePath: bp,
|
||||
PathPattern: path,
|
||||
Operation: operation,
|
||||
Handler: handler,
|
||||
Consumes: consumes,
|
||||
Produces: produces,
|
||||
Consumers: d.api.ConsumersFor(consumes),
|
||||
Producers: d.api.ProducersFor(produces),
|
||||
Parameters: parameters,
|
||||
Formats: d.api.Formats(),
|
||||
Binder: newUntypedRequestBinder(parameters, d.spec.Spec(), d.api.Formats()),
|
||||
Authenticators: d.api.AuthenticatorsFor(definitions),
|
||||
Scopes: scopes,
|
||||
})
|
||||
d.records[mn] = append(d.records[mn], record)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *defaultRouteBuilder) Build() *defaultRouter {
|
||||
routers := make(map[string]*denco.Router)
|
||||
for method, records := range d.records {
|
||||
router := denco.New()
|
||||
router.Build(records)
|
||||
routers[method] = router
|
||||
}
|
||||
return &defaultRouter{
|
||||
spec: d.spec,
|
||||
routers: routers,
|
||||
}
|
||||
}
|
||||
227
vendor/github.com/go-openapi/runtime/middleware/router_test.go
generated
vendored
Normal file
227
vendor/github.com/go-openapi/runtime/middleware/router_test.go
generated
vendored
Normal file
@@ -0,0 +1,227 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-openapi/analysis"
|
||||
"github.com/go-openapi/loads"
|
||||
"github.com/go-openapi/runtime/internal/testing/petstore"
|
||||
"github.com/go-openapi/runtime/middleware/untyped"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func terminator(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func TestRouterMiddleware(t *testing.T) {
|
||||
spec, api := petstore.NewAPI(t)
|
||||
context := NewContext(spec, api, nil)
|
||||
mw := NewRouter(context, http.HandlerFunc(terminator))
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/api/pets", nil)
|
||||
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
|
||||
recorder = httptest.NewRecorder()
|
||||
request, _ = http.NewRequest("DELETE", "/api/pets", nil)
|
||||
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, recorder.Code)
|
||||
|
||||
methods := strings.Split(recorder.Header().Get("Allow"), ",")
|
||||
sort.Sort(sort.StringSlice(methods))
|
||||
assert.Equal(t, "GET,POST", strings.Join(methods, ","))
|
||||
|
||||
recorder = httptest.NewRecorder()
|
||||
request, _ = http.NewRequest("GET", "/nopets", nil)
|
||||
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, http.StatusNotFound, recorder.Code)
|
||||
|
||||
recorder = httptest.NewRecorder()
|
||||
request, _ = http.NewRequest("GET", "/pets", nil)
|
||||
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, http.StatusNotFound, recorder.Code)
|
||||
|
||||
spec, api = petstore.NewRootAPI(t)
|
||||
context = NewContext(spec, api, nil)
|
||||
mw = NewRouter(context, http.HandlerFunc(terminator))
|
||||
|
||||
recorder = httptest.NewRecorder()
|
||||
request, _ = http.NewRequest("GET", "/pets", nil)
|
||||
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
|
||||
recorder = httptest.NewRecorder()
|
||||
request, _ = http.NewRequest("DELETE", "/pets", nil)
|
||||
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, recorder.Code)
|
||||
|
||||
methods = strings.Split(recorder.Header().Get("Allow"), ",")
|
||||
sort.Sort(sort.StringSlice(methods))
|
||||
assert.Equal(t, "GET,POST", strings.Join(methods, ","))
|
||||
|
||||
recorder = httptest.NewRecorder()
|
||||
request, _ = http.NewRequest("GET", "/nopets", nil)
|
||||
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, http.StatusNotFound, recorder.Code)
|
||||
|
||||
}
|
||||
|
||||
func TestRouterBuilder(t *testing.T) {
|
||||
spec, api := petstore.NewAPI(t)
|
||||
analyzed := analysis.New(spec.Spec())
|
||||
|
||||
assert.Len(t, analyzed.RequiredConsumes(), 3)
|
||||
assert.Len(t, analyzed.RequiredProduces(), 5)
|
||||
assert.Len(t, analyzed.OperationIDs(), 4)
|
||||
|
||||
// context := NewContext(spec, api)
|
||||
builder := petAPIRouterBuilder(spec, api, analyzed)
|
||||
getRecords := builder.records["GET"]
|
||||
postRecords := builder.records["POST"]
|
||||
deleteRecords := builder.records["DELETE"]
|
||||
|
||||
assert.Len(t, getRecords, 2)
|
||||
assert.Len(t, postRecords, 1)
|
||||
assert.Len(t, deleteRecords, 1)
|
||||
|
||||
assert.Empty(t, builder.records["PATCH"])
|
||||
assert.Empty(t, builder.records["OPTIONS"])
|
||||
assert.Empty(t, builder.records["HEAD"])
|
||||
assert.Empty(t, builder.records["PUT"])
|
||||
|
||||
rec := postRecords[0]
|
||||
assert.Equal(t, rec.Key, "/pets")
|
||||
val := rec.Value.(*routeEntry)
|
||||
assert.Len(t, val.Consumers, 1)
|
||||
assert.Len(t, val.Producers, 1)
|
||||
assert.Len(t, val.Consumes, 1)
|
||||
assert.Len(t, val.Produces, 1)
|
||||
|
||||
assert.Len(t, val.Parameters, 1)
|
||||
|
||||
recG := getRecords[0]
|
||||
assert.Equal(t, recG.Key, "/pets")
|
||||
valG := recG.Value.(*routeEntry)
|
||||
assert.Len(t, valG.Consumers, 2)
|
||||
assert.Len(t, valG.Producers, 4)
|
||||
assert.Len(t, valG.Consumes, 2)
|
||||
assert.Len(t, valG.Produces, 4)
|
||||
|
||||
assert.Len(t, valG.Parameters, 2)
|
||||
}
|
||||
|
||||
func TestRouterCanonicalBasePath(t *testing.T) {
|
||||
spec, api := petstore.NewAPI(t)
|
||||
spec.Spec().BasePath = "/api///"
|
||||
context := NewContext(spec, api, nil)
|
||||
mw := NewRouter(context, http.HandlerFunc(terminator))
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/api/pets", nil)
|
||||
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
}
|
||||
|
||||
func TestRouter_EscapedPath(t *testing.T) {
|
||||
spec, api := petstore.NewAPI(t)
|
||||
spec.Spec().BasePath = "/api/"
|
||||
context := NewContext(spec, api, nil)
|
||||
mw := NewRouter(context, http.HandlerFunc(terminator))
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/api/pets/123", nil)
|
||||
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
|
||||
recorder = httptest.NewRecorder()
|
||||
request, _ = http.NewRequest("GET", "/api/pets/abc%2Fdef", nil)
|
||||
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
ri, _ := context.RouteInfo(request)
|
||||
if assert.NotNil(t, ri) {
|
||||
if assert.NotNil(t, ri.Params) {
|
||||
assert.Equal(t, "abc/def", ri.Params.Get("id"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouterStruct(t *testing.T) {
|
||||
spec, api := petstore.NewAPI(t)
|
||||
router := DefaultRouter(spec, newRoutableUntypedAPI(spec, api, new(Context)))
|
||||
|
||||
methods := router.OtherMethods("post", "/api/pets/{id}")
|
||||
assert.Len(t, methods, 2)
|
||||
|
||||
entry, ok := router.Lookup("delete", "/api/pets/{id}")
|
||||
assert.True(t, ok)
|
||||
assert.NotNil(t, entry)
|
||||
assert.Len(t, entry.Params, 1)
|
||||
assert.Equal(t, "id", entry.Params[0].Name)
|
||||
|
||||
_, ok = router.Lookup("delete", "/pets")
|
||||
assert.False(t, ok)
|
||||
|
||||
_, ok = router.Lookup("post", "/no-pets")
|
||||
assert.False(t, ok)
|
||||
}
|
||||
|
||||
func petAPIRouterBuilder(spec *loads.Document, api *untyped.API, analyzed *analysis.Spec) *defaultRouteBuilder {
|
||||
builder := newDefaultRouteBuilder(spec, newRoutableUntypedAPI(spec, api, new(Context)))
|
||||
builder.AddRoute("GET", "/pets", analyzed.AllPaths()["/pets"].Get)
|
||||
builder.AddRoute("POST", "/pets", analyzed.AllPaths()["/pets"].Post)
|
||||
builder.AddRoute("DELETE", "/pets/{id}", analyzed.AllPaths()["/pets/{id}"].Delete)
|
||||
builder.AddRoute("GET", "/pets/{id}", analyzed.AllPaths()["/pets/{id}"].Get)
|
||||
|
||||
return builder
|
||||
}
|
||||
|
||||
func TestPathConverter(t *testing.T) {
|
||||
cases := []struct {
|
||||
swagger string
|
||||
denco string
|
||||
}{
|
||||
{"/", "/"},
|
||||
{"/something", "/something"},
|
||||
{"/{id}", "/:id"},
|
||||
{"/{id}/something/{anotherId}", "/:id/something/:anotherId"},
|
||||
{"/{petid}", "/:petid"},
|
||||
{"/{pet_id}", "/:pet_id"},
|
||||
{"/{petId}", "/:petId"},
|
||||
{"/{pet-id}", "/:pet-id"},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
actual := pathConverter.ReplaceAllString(tc.swagger, ":$1")
|
||||
assert.Equal(t, tc.denco, actual, "expected swagger path %s to match %s but got %s", tc.swagger, tc.denco, actual)
|
||||
}
|
||||
}
|
||||
34
vendor/github.com/go-openapi/runtime/middleware/security.go
generated
vendored
Normal file
34
vendor/github.com/go-openapi/runtime/middleware/security.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package middleware
|
||||
|
||||
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)
|
||||
if route != nil && len(route.Authenticators) == 0 {
|
||||
next.ServeHTTP(rw, r)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := ctx.Authorize(r, route); err != nil {
|
||||
ctx.Respond(rw, r, route.Produces, route, err)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(rw, r)
|
||||
})
|
||||
}
|
||||
58
vendor/github.com/go-openapi/runtime/middleware/security_test.go
generated
vendored
Normal file
58
vendor/github.com/go-openapi/runtime/middleware/security_test.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/go-openapi/runtime/internal/testing/petstore"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSecurityMiddleware(t *testing.T) {
|
||||
spec, api := petstore.NewAPI(t)
|
||||
context := NewContext(spec, api, nil)
|
||||
context.router = DefaultRouter(spec, context.api)
|
||||
mw := newSecureAPI(context, http.HandlerFunc(terminator))
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/api/pets", nil)
|
||||
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, 401, recorder.Code)
|
||||
|
||||
recorder = httptest.NewRecorder()
|
||||
request, _ = http.NewRequest("GET", "/api/pets", nil)
|
||||
request.SetBasicAuth("admin", "wrong")
|
||||
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, 401, recorder.Code)
|
||||
|
||||
recorder = httptest.NewRecorder()
|
||||
request, _ = http.NewRequest("GET", "/api/pets", nil)
|
||||
request.SetBasicAuth("admin", "admin")
|
||||
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
|
||||
recorder = httptest.NewRecorder()
|
||||
request, _ = http.NewRequest("GET", "//apipets/1", nil)
|
||||
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
|
||||
}
|
||||
47
vendor/github.com/go-openapi/runtime/middleware/spec.go
generated
vendored
Normal file
47
vendor/github.com/go-openapi/runtime/middleware/spec.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path"
|
||||
)
|
||||
|
||||
// Spec creates a middleware to serve a swagger spec.
|
||||
// This allows for altering the spec before starting the http listener.
|
||||
// This can be useful if you want to serve the swagger spec from another path than /swagger.json
|
||||
//
|
||||
func Spec(basePath string, b []byte, next http.Handler) http.Handler {
|
||||
if basePath == "" {
|
||||
basePath = "/"
|
||||
}
|
||||
pth := path.Join(basePath, "swagger.json")
|
||||
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == pth {
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
rw.Write(b)
|
||||
return
|
||||
}
|
||||
|
||||
if next == nil {
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(rw, r)
|
||||
})
|
||||
}
|
||||
56
vendor/github.com/go-openapi/runtime/middleware/spec_test.go
generated
vendored
Normal file
56
vendor/github.com/go-openapi/runtime/middleware/spec_test.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/go-openapi/runtime/internal/testing/petstore"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestServeSpecMiddleware(t *testing.T) {
|
||||
spec, api := petstore.NewAPI(t)
|
||||
ctx := NewContext(spec, api, nil)
|
||||
|
||||
handler := Spec("", ctx.spec.Raw(), nil)
|
||||
// serves spec
|
||||
request, _ := http.NewRequest("GET", "/swagger.json", nil)
|
||||
request.Header.Add(runtime.HeaderContentType, runtime.JSONMime)
|
||||
recorder := httptest.NewRecorder()
|
||||
handler.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
|
||||
// returns 404 when no next handler
|
||||
request, _ = http.NewRequest("GET", "/api/pets", nil)
|
||||
request.Header.Add(runtime.HeaderContentType, runtime.JSONMime)
|
||||
recorder = httptest.NewRecorder()
|
||||
handler.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, 404, recorder.Code)
|
||||
|
||||
// forwards to next handler for other url
|
||||
handler = Spec("", ctx.spec.Raw(), http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
request, _ = http.NewRequest("GET", "/api/pets", nil)
|
||||
request.Header.Add(runtime.HeaderContentType, runtime.JSONMime)
|
||||
recorder = httptest.NewRecorder()
|
||||
handler.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, 200, recorder.Code)
|
||||
|
||||
}
|
||||
301
vendor/github.com/go-openapi/runtime/middleware/string_conversion_test.go
generated
vendored
Normal file
301
vendor/github.com/go-openapi/runtime/middleware/string_conversion_test.go
generated
vendored
Normal file
@@ -0,0 +1,301 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/spec"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var evaluatesAsTrue = []string{"true", "1", "yes", "ok", "y", "on", "selected", "checked", "t", "enabled"}
|
||||
|
||||
type unmarshallerSlice []string
|
||||
|
||||
func (u *unmarshallerSlice) UnmarshalText(data []byte) error {
|
||||
if len(data) == 0 {
|
||||
return errors.New("an error")
|
||||
}
|
||||
*u = strings.Split(string(data), ",")
|
||||
return nil
|
||||
}
|
||||
|
||||
type SomeOperationParams struct {
|
||||
Name string
|
||||
ID int64
|
||||
Confirmed bool
|
||||
Age int
|
||||
Visits int32
|
||||
Count int16
|
||||
Seq int8
|
||||
UID uint64
|
||||
UAge uint
|
||||
UVisits uint32
|
||||
UCount uint16
|
||||
USeq uint8
|
||||
Score float32
|
||||
Rate float64
|
||||
Timestamp strfmt.DateTime
|
||||
Birthdate strfmt.Date
|
||||
LastFailure *strfmt.DateTime
|
||||
Unsupported struct{}
|
||||
Tags []string
|
||||
Prefs []int32
|
||||
Categories unmarshallerSlice
|
||||
}
|
||||
|
||||
func FloatParamTest(t *testing.T, fName, pName, format string, val reflect.Value, defVal, expectedDef interface{}, actual func() interface{}) {
|
||||
fld := val.FieldByName(pName)
|
||||
binder := &untypedParamBinder{
|
||||
parameter: spec.QueryParam(pName).Typed("number", "double").WithDefault(defVal),
|
||||
Name: pName,
|
||||
}
|
||||
|
||||
err := binder.setFieldValue(fld, defVal, "5", true)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 5, actual())
|
||||
|
||||
err = binder.setFieldValue(fld, defVal, "", true)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, expectedDef, actual())
|
||||
|
||||
err = binder.setFieldValue(fld, defVal, "yada", true)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func IntParamTest(t *testing.T, pName string, val reflect.Value, defVal, expectedDef interface{}, actual func() interface{}) {
|
||||
fld := val.FieldByName(pName)
|
||||
|
||||
binder := &untypedParamBinder{
|
||||
parameter: spec.QueryParam(pName).Typed("integer", "int64").WithDefault(defVal),
|
||||
Name: pName,
|
||||
}
|
||||
err := binder.setFieldValue(fld, defVal, "5", true)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 5, actual())
|
||||
|
||||
err = binder.setFieldValue(fld, defVal, "", true)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, expectedDef, actual())
|
||||
|
||||
err = binder.setFieldValue(fld, defVal, "yada", true)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestParamBinding(t *testing.T) {
|
||||
|
||||
actual := new(SomeOperationParams)
|
||||
val := reflect.ValueOf(actual).Elem()
|
||||
pName := "Name"
|
||||
fld := val.FieldByName(pName)
|
||||
|
||||
binder := &untypedParamBinder{
|
||||
parameter: spec.QueryParam(pName).Typed("string", "").WithDefault("some-name"),
|
||||
Name: pName,
|
||||
}
|
||||
|
||||
err := binder.setFieldValue(fld, "some-name", "the name value", true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "the name value", actual.Name)
|
||||
|
||||
err = binder.setFieldValue(fld, "some-name", "", true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "some-name", actual.Name)
|
||||
|
||||
IntParamTest(t, "ID", val, 1, 1, func() interface{} { return actual.ID })
|
||||
IntParamTest(t, "ID", val, nil, 0, func() interface{} { return actual.ID })
|
||||
IntParamTest(t, "Age", val, 1, 1, func() interface{} { return actual.Age })
|
||||
IntParamTest(t, "Age", val, nil, 0, func() interface{} { return actual.Age })
|
||||
IntParamTest(t, "Visits", val, 1, 1, func() interface{} { return actual.Visits })
|
||||
IntParamTest(t, "Visits", val, nil, 0, func() interface{} { return actual.Visits })
|
||||
IntParamTest(t, "Count", val, 1, 1, func() interface{} { return actual.Count })
|
||||
IntParamTest(t, "Count", val, nil, 0, func() interface{} { return actual.Count })
|
||||
IntParamTest(t, "Seq", val, 1, 1, func() interface{} { return actual.Seq })
|
||||
IntParamTest(t, "Seq", val, nil, 0, func() interface{} { return actual.Seq })
|
||||
IntParamTest(t, "UID", val, uint64(1), 1, func() interface{} { return actual.UID })
|
||||
IntParamTest(t, "UID", val, uint64(0), 0, func() interface{} { return actual.UID })
|
||||
IntParamTest(t, "UAge", val, uint(1), 1, func() interface{} { return actual.UAge })
|
||||
IntParamTest(t, "UAge", val, nil, 0, func() interface{} { return actual.UAge })
|
||||
IntParamTest(t, "UVisits", val, uint32(1), 1, func() interface{} { return actual.UVisits })
|
||||
IntParamTest(t, "UVisits", val, nil, 0, func() interface{} { return actual.UVisits })
|
||||
IntParamTest(t, "UCount", val, uint16(1), 1, func() interface{} { return actual.UCount })
|
||||
IntParamTest(t, "UCount", val, nil, 0, func() interface{} { return actual.UCount })
|
||||
IntParamTest(t, "USeq", val, uint8(1), 1, func() interface{} { return actual.USeq })
|
||||
IntParamTest(t, "USeq", val, nil, 0, func() interface{} { return actual.USeq })
|
||||
|
||||
FloatParamTest(t, "score", "Score", "float", val, 1.0, 1, func() interface{} { return actual.Score })
|
||||
FloatParamTest(t, "score", "Score", "float", val, nil, 0, func() interface{} { return actual.Score })
|
||||
FloatParamTest(t, "rate", "Rate", "double", val, 1.0, 1, func() interface{} { return actual.Rate })
|
||||
FloatParamTest(t, "rate", "Rate", "double", val, nil, 0, func() interface{} { return actual.Rate })
|
||||
|
||||
pName = "Confirmed"
|
||||
confirmedField := val.FieldByName(pName)
|
||||
binder = &untypedParamBinder{
|
||||
parameter: spec.QueryParam(pName).Typed("boolean", "").WithDefault(true),
|
||||
Name: pName,
|
||||
}
|
||||
|
||||
for _, tv := range evaluatesAsTrue {
|
||||
err = binder.setFieldValue(confirmedField, true, tv, true)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, actual.Confirmed)
|
||||
}
|
||||
|
||||
err = binder.setFieldValue(confirmedField, true, "", true)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, actual.Confirmed)
|
||||
|
||||
err = binder.setFieldValue(confirmedField, true, "0", true)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, actual.Confirmed)
|
||||
|
||||
pName = "Timestamp"
|
||||
timeField := val.FieldByName(pName)
|
||||
dt := strfmt.DateTime(time.Date(2014, 3, 19, 2, 9, 0, 0, time.UTC))
|
||||
binder = &untypedParamBinder{
|
||||
parameter: spec.QueryParam(pName).Typed("string", "date-time").WithDefault(dt),
|
||||
Name: pName,
|
||||
}
|
||||
exp := strfmt.DateTime(time.Date(2014, 5, 14, 2, 9, 0, 0, time.UTC))
|
||||
|
||||
err = binder.setFieldValue(timeField, dt, exp.String(), true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, exp, actual.Timestamp)
|
||||
|
||||
err = binder.setFieldValue(timeField, dt, "", true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, dt, actual.Timestamp)
|
||||
|
||||
err = binder.setFieldValue(timeField, dt, "yada", true)
|
||||
assert.Error(t, err)
|
||||
|
||||
ddt := strfmt.Date(time.Date(2014, 3, 19, 0, 0, 0, 0, time.UTC))
|
||||
pName = "Birthdate"
|
||||
dateField := val.FieldByName(pName)
|
||||
binder = &untypedParamBinder{
|
||||
parameter: spec.QueryParam(pName).Typed("string", "date").WithDefault(ddt),
|
||||
Name: pName,
|
||||
}
|
||||
expd := strfmt.Date(time.Date(2014, 5, 14, 0, 0, 0, 0, time.UTC))
|
||||
|
||||
err = binder.setFieldValue(dateField, ddt, expd.String(), true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expd, actual.Birthdate)
|
||||
|
||||
err = binder.setFieldValue(dateField, ddt, "", true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ddt, actual.Birthdate)
|
||||
|
||||
err = binder.setFieldValue(dateField, ddt, "yada", true)
|
||||
assert.Error(t, err)
|
||||
|
||||
dt = strfmt.DateTime(time.Date(2014, 3, 19, 2, 9, 0, 0, time.UTC))
|
||||
fdt := &dt
|
||||
pName = "LastFailure"
|
||||
ftimeField := val.FieldByName(pName)
|
||||
binder = &untypedParamBinder{
|
||||
parameter: spec.QueryParam(pName).Typed("string", "date").WithDefault(fdt),
|
||||
Name: pName,
|
||||
}
|
||||
exp = strfmt.DateTime(time.Date(2014, 5, 14, 2, 9, 0, 0, time.UTC))
|
||||
fexp := &exp
|
||||
|
||||
err = binder.setFieldValue(ftimeField, fdt, fexp.String(), true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, fexp, actual.LastFailure)
|
||||
|
||||
err = binder.setFieldValue(ftimeField, fdt, "", true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, fdt, actual.LastFailure)
|
||||
|
||||
err = binder.setFieldValue(ftimeField, fdt, "", true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, fdt, actual.LastFailure)
|
||||
|
||||
actual.LastFailure = nil
|
||||
err = binder.setFieldValue(ftimeField, fdt, "yada", true)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, actual.LastFailure)
|
||||
|
||||
pName = "Unsupported"
|
||||
unsupportedField := val.FieldByName(pName)
|
||||
binder = &untypedParamBinder{
|
||||
parameter: spec.QueryParam(pName).Typed("string", ""),
|
||||
Name: pName,
|
||||
}
|
||||
err = binder.setFieldValue(unsupportedField, nil, "", true)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSliceConversion(t *testing.T) {
|
||||
|
||||
actual := new(SomeOperationParams)
|
||||
val := reflect.ValueOf(actual).Elem()
|
||||
|
||||
// prefsField := val.FieldByName("Prefs")
|
||||
// cData := "yada,2,3"
|
||||
// _, _, err := readFormattedSliceFieldValue("Prefs", prefsField, cData, "csv", nil)
|
||||
// assert.Error(t, err)
|
||||
|
||||
sliced := []string{"some", "string", "values"}
|
||||
seps := map[string]string{"ssv": " ", "tsv": "\t", "pipes": "|", "csv": ",", "": ","}
|
||||
|
||||
tagsField := val.FieldByName("Tags")
|
||||
for k, sep := range seps {
|
||||
binder := &untypedParamBinder{
|
||||
Name: "Tags",
|
||||
parameter: spec.QueryParam("tags").CollectionOf(stringItems, k),
|
||||
}
|
||||
|
||||
actual.Tags = nil
|
||||
cData := strings.Join(sliced, sep)
|
||||
tags, _, err := binder.readFormattedSliceFieldValue(cData, tagsField)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, sliced, tags)
|
||||
cData = strings.Join(sliced, " "+sep+" ")
|
||||
tags, _, err = binder.readFormattedSliceFieldValue(cData, tagsField)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, sliced, tags)
|
||||
tags, _, err = binder.readFormattedSliceFieldValue("", tagsField)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, tags)
|
||||
}
|
||||
|
||||
assert.Nil(t, swag.SplitByFormat("yada", "multi"))
|
||||
assert.Nil(t, swag.SplitByFormat("", ""))
|
||||
|
||||
categoriesField := val.FieldByName("Categories")
|
||||
binder := &untypedParamBinder{
|
||||
Name: "Categories",
|
||||
parameter: spec.QueryParam("categories").CollectionOf(stringItems, "csv"),
|
||||
}
|
||||
cData := strings.Join(sliced, ",")
|
||||
categories, custom, err := binder.readFormattedSliceFieldValue(cData, categoriesField)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, sliced, actual.Categories)
|
||||
assert.True(t, custom)
|
||||
assert.Empty(t, categories)
|
||||
categories, custom, err = binder.readFormattedSliceFieldValue("", categoriesField)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, custom)
|
||||
assert.Empty(t, categories)
|
||||
}
|
||||
275
vendor/github.com/go-openapi/runtime/middleware/untyped/api.go
generated
vendored
Normal file
275
vendor/github.com/go-openapi/runtime/middleware/untyped/api.go
generated
vendored
Normal file
@@ -0,0 +1,275 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package untyped
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/analysis"
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/loads"
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/go-openapi/spec"
|
||||
"github.com/go-openapi/strfmt"
|
||||
)
|
||||
|
||||
// NewAPI creates the default untyped API
|
||||
func NewAPI(spec *loads.Document) *API {
|
||||
var an *analysis.Spec
|
||||
if spec != nil && spec.Spec() != nil {
|
||||
an = analysis.New(spec.Spec())
|
||||
}
|
||||
api := &API{
|
||||
spec: spec,
|
||||
analyzer: an,
|
||||
consumers: make(map[string]runtime.Consumer, 10),
|
||||
producers: make(map[string]runtime.Producer, 10),
|
||||
authenticators: make(map[string]runtime.Authenticator),
|
||||
operations: make(map[string]map[string]runtime.OperationHandler),
|
||||
ServeError: errors.ServeError,
|
||||
Models: make(map[string]func() interface{}),
|
||||
formats: strfmt.NewFormats(),
|
||||
}
|
||||
return api.WithJSONDefaults()
|
||||
}
|
||||
|
||||
// API represents an untyped mux for a swagger spec
|
||||
type API struct {
|
||||
spec *loads.Document
|
||||
analyzer *analysis.Spec
|
||||
DefaultProduces string
|
||||
DefaultConsumes string
|
||||
consumers map[string]runtime.Consumer
|
||||
producers map[string]runtime.Producer
|
||||
authenticators map[string]runtime.Authenticator
|
||||
operations map[string]map[string]runtime.OperationHandler
|
||||
ServeError func(http.ResponseWriter, *http.Request, error)
|
||||
Models map[string]func() interface{}
|
||||
formats strfmt.Registry
|
||||
}
|
||||
|
||||
// WithJSONDefaults loads the json defaults for this api
|
||||
func (d *API) WithJSONDefaults() *API {
|
||||
d.DefaultConsumes = runtime.JSONMime
|
||||
d.DefaultProduces = runtime.JSONMime
|
||||
d.consumers[runtime.JSONMime] = runtime.JSONConsumer()
|
||||
d.producers[runtime.JSONMime] = runtime.JSONProducer()
|
||||
return d
|
||||
}
|
||||
|
||||
// WithoutJSONDefaults clears the json defaults for this api
|
||||
func (d *API) WithoutJSONDefaults() *API {
|
||||
d.DefaultConsumes = ""
|
||||
d.DefaultProduces = ""
|
||||
delete(d.consumers, runtime.JSONMime)
|
||||
delete(d.producers, runtime.JSONMime)
|
||||
return d
|
||||
}
|
||||
|
||||
// Formats returns the registered string formats
|
||||
func (d *API) Formats() strfmt.Registry {
|
||||
if d.formats == nil {
|
||||
d.formats = strfmt.NewFormats()
|
||||
}
|
||||
return d.formats
|
||||
}
|
||||
|
||||
// RegisterFormat registers a custom format validator
|
||||
func (d *API) RegisterFormat(name string, format strfmt.Format, validator strfmt.Validator) {
|
||||
if d.formats == nil {
|
||||
d.formats = strfmt.NewFormats()
|
||||
}
|
||||
d.formats.Add(name, format, validator)
|
||||
}
|
||||
|
||||
// RegisterAuth registers an auth handler in this api
|
||||
func (d *API) RegisterAuth(scheme string, handler runtime.Authenticator) {
|
||||
if d.authenticators == nil {
|
||||
d.authenticators = make(map[string]runtime.Authenticator)
|
||||
}
|
||||
d.authenticators[scheme] = handler
|
||||
}
|
||||
|
||||
// RegisterConsumer registers a consumer for a media type.
|
||||
func (d *API) RegisterConsumer(mediaType string, handler runtime.Consumer) {
|
||||
if d.consumers == nil {
|
||||
d.consumers = make(map[string]runtime.Consumer, 10)
|
||||
}
|
||||
d.consumers[strings.ToLower(mediaType)] = handler
|
||||
}
|
||||
|
||||
// RegisterProducer registers a producer for a media type
|
||||
func (d *API) RegisterProducer(mediaType string, handler runtime.Producer) {
|
||||
if d.producers == nil {
|
||||
d.producers = make(map[string]runtime.Producer, 10)
|
||||
}
|
||||
d.producers[strings.ToLower(mediaType)] = handler
|
||||
}
|
||||
|
||||
// RegisterOperation registers an operation handler for an operation name
|
||||
func (d *API) RegisterOperation(method, path string, handler runtime.OperationHandler) {
|
||||
if d.operations == nil {
|
||||
d.operations = make(map[string]map[string]runtime.OperationHandler, 30)
|
||||
}
|
||||
um := strings.ToUpper(method)
|
||||
if b, ok := d.operations[um]; !ok || b == nil {
|
||||
d.operations[um] = make(map[string]runtime.OperationHandler)
|
||||
}
|
||||
d.operations[um][path] = handler
|
||||
}
|
||||
|
||||
// OperationHandlerFor returns the operation handler for the specified id if it can be found
|
||||
func (d *API) OperationHandlerFor(method, path string) (runtime.OperationHandler, bool) {
|
||||
if d.operations == nil {
|
||||
return nil, false
|
||||
}
|
||||
if pi, ok := d.operations[strings.ToUpper(method)]; ok {
|
||||
h, ok := pi[path]
|
||||
return h, ok
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// ConsumersFor gets the consumers for the specified media types
|
||||
func (d *API) ConsumersFor(mediaTypes []string) map[string]runtime.Consumer {
|
||||
result := make(map[string]runtime.Consumer)
|
||||
for _, mt := range mediaTypes {
|
||||
if consumer, ok := d.consumers[mt]; ok {
|
||||
result[mt] = consumer
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ProducersFor gets the producers for the specified media types
|
||||
func (d *API) ProducersFor(mediaTypes []string) map[string]runtime.Producer {
|
||||
result := make(map[string]runtime.Producer)
|
||||
for _, mt := range mediaTypes {
|
||||
if producer, ok := d.producers[mt]; ok {
|
||||
result[mt] = producer
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// AuthenticatorsFor gets the authenticators for the specified security schemes
|
||||
func (d *API) AuthenticatorsFor(schemes map[string]spec.SecurityScheme) map[string]runtime.Authenticator {
|
||||
result := make(map[string]runtime.Authenticator)
|
||||
for k := range schemes {
|
||||
if a, ok := d.authenticators[k]; ok {
|
||||
result[k] = a
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Validate validates this API for any missing items
|
||||
func (d *API) Validate() error {
|
||||
return d.validate()
|
||||
}
|
||||
|
||||
// validateWith validates the registrations in this API against the provided spec analyzer
|
||||
func (d *API) validate() error {
|
||||
var consumes []string
|
||||
for k := range d.consumers {
|
||||
consumes = append(consumes, k)
|
||||
}
|
||||
|
||||
var produces []string
|
||||
for k := range d.producers {
|
||||
produces = append(produces, k)
|
||||
}
|
||||
|
||||
var authenticators []string
|
||||
for k := range d.authenticators {
|
||||
authenticators = append(authenticators, k)
|
||||
}
|
||||
|
||||
var operations []string
|
||||
for m, v := range d.operations {
|
||||
for p := range v {
|
||||
operations = append(operations, fmt.Sprintf("%s %s", strings.ToUpper(m), p))
|
||||
}
|
||||
}
|
||||
|
||||
var definedAuths []string
|
||||
for k := range d.spec.Spec().SecurityDefinitions {
|
||||
definedAuths = append(definedAuths, k)
|
||||
}
|
||||
|
||||
if err := d.verify("consumes", consumes, d.analyzer.RequiredConsumes()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.verify("produces", produces, d.analyzer.RequiredProduces()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.verify("operation", operations, d.analyzer.OperationMethodPaths()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
requiredAuths := d.analyzer.RequiredSecuritySchemes()
|
||||
if err := d.verify("auth scheme", authenticators, requiredAuths); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.verify("security definitions", definedAuths, requiredAuths); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *API) verify(name string, registrations []string, expectations []string) error {
|
||||
|
||||
sort.Sort(sort.StringSlice(registrations))
|
||||
sort.Sort(sort.StringSlice(expectations))
|
||||
|
||||
expected := map[string]struct{}{}
|
||||
seen := map[string]struct{}{}
|
||||
|
||||
for _, v := range expectations {
|
||||
expected[v] = struct{}{}
|
||||
}
|
||||
|
||||
var unspecified []string
|
||||
for _, v := range registrations {
|
||||
seen[v] = struct{}{}
|
||||
if _, ok := expected[v]; !ok {
|
||||
unspecified = append(unspecified, v)
|
||||
}
|
||||
}
|
||||
|
||||
for k := range seen {
|
||||
delete(expected, k)
|
||||
}
|
||||
|
||||
var unregistered []string
|
||||
for k := range expected {
|
||||
unregistered = append(unregistered, k)
|
||||
}
|
||||
sort.Sort(sort.StringSlice(unspecified))
|
||||
sort.Sort(sort.StringSlice(unregistered))
|
||||
|
||||
if len(unregistered) > 0 || len(unspecified) > 0 {
|
||||
return &errors.APIVerificationFailed{
|
||||
Section: name,
|
||||
MissingSpecification: unspecified,
|
||||
MissingRegistration: unregistered,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
276
vendor/github.com/go-openapi/runtime/middleware/untyped/api_test.go
generated
vendored
Normal file
276
vendor/github.com/go-openapi/runtime/middleware/untyped/api_test.go
generated
vendored
Normal file
@@ -0,0 +1,276 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package untyped
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/go-openapi/analysis"
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/loads"
|
||||
"github.com/go-openapi/runtime"
|
||||
swaggerspec "github.com/go-openapi/spec"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func stubAutenticator() runtime.Authenticator {
|
||||
return runtime.AuthenticatorFunc(func(_ interface{}) (bool, interface{}, error) { return false, nil, nil })
|
||||
}
|
||||
|
||||
type stubConsumer struct {
|
||||
}
|
||||
|
||||
func (s *stubConsumer) Consume(_ io.Reader, _ interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type stubProducer struct {
|
||||
}
|
||||
|
||||
func (s *stubProducer) Produce(_ io.Writer, _ interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type stubOperationHandler struct {
|
||||
}
|
||||
|
||||
func (s *stubOperationHandler) ParameterModel() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stubOperationHandler) Handle(params interface{}) (interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestUntypedAPIRegistrations(t *testing.T) {
|
||||
api := NewAPI(new(loads.Document)).WithJSONDefaults()
|
||||
|
||||
api.RegisterConsumer("application/yada", new(stubConsumer))
|
||||
api.RegisterProducer("application/yada-2", new(stubProducer))
|
||||
api.RegisterOperation("get", "/{someId}", new(stubOperationHandler))
|
||||
api.RegisterAuth("basic", stubAutenticator())
|
||||
|
||||
assert.NotEmpty(t, api.authenticators)
|
||||
|
||||
_, ok := api.authenticators["basic"]
|
||||
assert.True(t, ok)
|
||||
_, ok = api.consumers["application/yada"]
|
||||
assert.True(t, ok)
|
||||
_, ok = api.producers["application/yada-2"]
|
||||
assert.True(t, ok)
|
||||
_, ok = api.consumers["application/json"]
|
||||
assert.True(t, ok)
|
||||
_, ok = api.producers["application/json"]
|
||||
assert.True(t, ok)
|
||||
_, ok = api.operations["GET"]["/{someId}"]
|
||||
assert.True(t, ok)
|
||||
|
||||
h, ok := api.OperationHandlerFor("get", "/{someId}")
|
||||
assert.True(t, ok)
|
||||
assert.NotNil(t, h)
|
||||
|
||||
_, ok = api.OperationHandlerFor("doesntExist", "/{someId}")
|
||||
assert.False(t, ok)
|
||||
}
|
||||
|
||||
func TestUntypedAppValidation(t *testing.T) {
|
||||
invalidSpecStr := `{
|
||||
"consumes": ["application/json"],
|
||||
"produces": ["application/json"],
|
||||
"security": [
|
||||
{"apiKey":[]}
|
||||
],
|
||||
"parameters": {
|
||||
"format": {
|
||||
"in": "query",
|
||||
"name": "format",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"paths": {
|
||||
"/": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "limit",
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"x-go-name": "Limit"
|
||||
}
|
||||
],
|
||||
"get": {
|
||||
"consumes": ["application/x-yaml"],
|
||||
"produces": ["application/x-yaml"],
|
||||
"security": [
|
||||
{"basic":[]}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "skip",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
specStr := `{
|
||||
"consumes": ["application/json"],
|
||||
"produces": ["application/json"],
|
||||
"security": [
|
||||
{"apiKey":[]}
|
||||
],
|
||||
"securityDefinitions": {
|
||||
"basic": { "type": "basic" },
|
||||
"apiKey": { "type": "apiKey", "in":"header", "name":"X-API-KEY" }
|
||||
},
|
||||
"parameters": {
|
||||
"format": {
|
||||
"in": "query",
|
||||
"name": "format",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"paths": {
|
||||
"/": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "limit",
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"x-go-name": "Limit"
|
||||
}
|
||||
],
|
||||
"get": {
|
||||
"consumes": ["application/x-yaml"],
|
||||
"produces": ["application/x-yaml"],
|
||||
"security": [
|
||||
{"basic":[]}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "skip",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
validSpec, err := loads.Analyzed([]byte(specStr), "")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, validSpec)
|
||||
|
||||
spec, err := loads.Analyzed([]byte(invalidSpecStr), "")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, spec)
|
||||
|
||||
analyzed := analysis.New(spec.Spec())
|
||||
analyzedValid := analysis.New(validSpec.Spec())
|
||||
cons := analyzed.ConsumesFor(analyzed.AllPaths()["/"].Get)
|
||||
assert.Len(t, cons, 1)
|
||||
prods := analyzed.RequiredProduces()
|
||||
assert.Len(t, prods, 2)
|
||||
|
||||
api1 := NewAPI(spec)
|
||||
err = api1.Validate()
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "missing [application/x-yaml] consumes registrations", err.Error())
|
||||
api1.RegisterConsumer("application/x-yaml", new(stubConsumer))
|
||||
err = api1.validate()
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "missing [application/x-yaml] produces registrations", err.Error())
|
||||
api1.RegisterProducer("application/x-yaml", new(stubProducer))
|
||||
err = api1.validate()
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "missing [GET /] operation registrations", err.Error())
|
||||
api1.RegisterOperation("get", "/", new(stubOperationHandler))
|
||||
err = api1.validate()
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "missing [apiKey, basic] auth scheme registrations", err.Error())
|
||||
api1.RegisterAuth("basic", stubAutenticator())
|
||||
api1.RegisterAuth("apiKey", stubAutenticator())
|
||||
err = api1.validate()
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "missing [apiKey, basic] security definitions registrations", err.Error())
|
||||
|
||||
api3 := NewAPI(validSpec)
|
||||
api3.RegisterConsumer("application/x-yaml", new(stubConsumer))
|
||||
api3.RegisterProducer("application/x-yaml", new(stubProducer))
|
||||
api3.RegisterOperation("get", "/", new(stubOperationHandler))
|
||||
api3.RegisterAuth("basic", stubAutenticator())
|
||||
api3.RegisterAuth("apiKey", stubAutenticator())
|
||||
err = api3.validate()
|
||||
assert.NoError(t, err)
|
||||
api3.RegisterConsumer("application/something", new(stubConsumer))
|
||||
err = api3.validate()
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "missing from spec file [application/something] consumes", err.Error())
|
||||
|
||||
api2 := NewAPI(spec)
|
||||
api2.RegisterConsumer("application/something", new(stubConsumer))
|
||||
err = api2.validate()
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "missing [application/x-yaml] consumes registrations\nmissing from spec file [application/something] consumes", err.Error())
|
||||
api2.RegisterConsumer("application/x-yaml", new(stubConsumer))
|
||||
delete(api2.consumers, "application/something")
|
||||
api2.RegisterProducer("application/something", new(stubProducer))
|
||||
err = api2.validate()
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "missing [application/x-yaml] produces registrations\nmissing from spec file [application/something] produces", err.Error())
|
||||
delete(api2.producers, "application/something")
|
||||
api2.RegisterProducer("application/x-yaml", new(stubProducer))
|
||||
|
||||
expected := []string{"application/x-yaml"}
|
||||
sort.Sort(sort.StringSlice(expected))
|
||||
consumes := analyzed.ConsumesFor(analyzed.AllPaths()["/"].Get)
|
||||
sort.Sort(sort.StringSlice(consumes))
|
||||
assert.Equal(t, expected, consumes)
|
||||
consumers := api1.ConsumersFor(consumes)
|
||||
assert.Len(t, consumers, 1)
|
||||
|
||||
produces := analyzed.ProducesFor(analyzed.AllPaths()["/"].Get)
|
||||
sort.Sort(sort.StringSlice(produces))
|
||||
assert.Equal(t, expected, produces)
|
||||
producers := api1.ProducersFor(produces)
|
||||
assert.Len(t, producers, 1)
|
||||
|
||||
definitions := analyzedValid.SecurityDefinitionsFor(analyzedValid.AllPaths()["/"].Get)
|
||||
expectedSchemes := map[string]swaggerspec.SecurityScheme{"basic": *swaggerspec.BasicAuth()}
|
||||
assert.Equal(t, expectedSchemes, definitions)
|
||||
authenticators := api3.AuthenticatorsFor(definitions)
|
||||
assert.Len(t, authenticators, 1)
|
||||
|
||||
opHandler := runtime.OperationHandlerFunc(func(data interface{}) (interface{}, error) {
|
||||
return data, nil
|
||||
})
|
||||
d, err := opHandler.Handle(1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, d)
|
||||
|
||||
authenticator := runtime.AuthenticatorFunc(func(params interface{}) (bool, interface{}, error) {
|
||||
if str, ok := params.(string); ok {
|
||||
return ok, str, nil
|
||||
}
|
||||
return true, nil, errors.Unauthenticated("authenticator")
|
||||
})
|
||||
ok, p, err := authenticator.Authenticate("hello")
|
||||
assert.True(t, ok)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "hello", p)
|
||||
}
|
||||
164
vendor/github.com/go-openapi/runtime/middleware/untyped_request_test.go
generated
vendored
Normal file
164
vendor/github.com/go-openapi/runtime/middleware/untyped_request_test.go
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUntypedFormPost(t *testing.T) {
|
||||
params := parametersForFormUpload()
|
||||
binder := newUntypedRequestBinder(params, nil, strfmt.Default)
|
||||
|
||||
urlStr := "http://localhost:8002/hello"
|
||||
req, _ := http.NewRequest("POST", urlStr, bytes.NewBufferString(`name=the-name&age=32`))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
data := make(map[string]interface{})
|
||||
assert.NoError(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
|
||||
assert.Equal(t, "the-name", data["name"])
|
||||
assert.EqualValues(t, 32, data["age"])
|
||||
|
||||
req, _ = http.NewRequest("POST", urlStr, bytes.NewBufferString(`name=%3&age=32`))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
data = make(map[string]interface{})
|
||||
assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
|
||||
}
|
||||
|
||||
func TestUntypedFileUpload(t *testing.T) {
|
||||
binder := paramsForFileUpload()
|
||||
|
||||
body := bytes.NewBuffer(nil)
|
||||
writer := multipart.NewWriter(body)
|
||||
part, err := writer.CreateFormFile("file", "plain-jane.txt")
|
||||
assert.NoError(t, err)
|
||||
|
||||
part.Write([]byte("the file contents"))
|
||||
writer.WriteField("name", "the-name")
|
||||
assert.NoError(t, writer.Close())
|
||||
|
||||
urlStr := "http://localhost:8002/hello"
|
||||
req, _ := http.NewRequest("POST", urlStr, body)
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
data := make(map[string]interface{})
|
||||
assert.NoError(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
|
||||
assert.Equal(t, "the-name", data["name"])
|
||||
assert.NotNil(t, data["file"])
|
||||
assert.IsType(t, runtime.File{}, data["file"])
|
||||
file := data["file"].(runtime.File)
|
||||
assert.NotNil(t, file.Header)
|
||||
assert.Equal(t, "plain-jane.txt", file.Header.Filename)
|
||||
|
||||
bb, err := ioutil.ReadAll(file.Data)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte("the file contents"), bb)
|
||||
|
||||
req, _ = http.NewRequest("POST", urlStr, body)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
data = make(map[string]interface{})
|
||||
assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
|
||||
|
||||
req, _ = http.NewRequest("POST", urlStr, body)
|
||||
req.Header.Set("Content-Type", "application(")
|
||||
data = make(map[string]interface{})
|
||||
assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
|
||||
|
||||
body = bytes.NewBuffer(nil)
|
||||
writer = multipart.NewWriter(body)
|
||||
part, err = writer.CreateFormFile("bad-name", "plain-jane.txt")
|
||||
assert.NoError(t, err)
|
||||
|
||||
part.Write([]byte("the file contents"))
|
||||
writer.WriteField("name", "the-name")
|
||||
assert.NoError(t, writer.Close())
|
||||
req, _ = http.NewRequest("POST", urlStr, body)
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
data = make(map[string]interface{})
|
||||
assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
|
||||
|
||||
req, _ = http.NewRequest("POST", urlStr, body)
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
req.MultipartReader()
|
||||
|
||||
data = make(map[string]interface{})
|
||||
assert.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
|
||||
}
|
||||
|
||||
func TestUntypedBindingTypesForValid(t *testing.T) {
|
||||
|
||||
op2 := parametersForAllTypes("")
|
||||
binder := newUntypedRequestBinder(op2, nil, strfmt.Default)
|
||||
|
||||
confirmed := true
|
||||
name := "thomas"
|
||||
friend := map[string]interface{}{"name": "toby", "age": json.Number("32")}
|
||||
id, age, score, factor := int64(7575), int32(348), float32(5.309), float64(37.403)
|
||||
requestID := 19394858
|
||||
tags := []string{"one", "two", "three"}
|
||||
dt1 := time.Date(2014, 8, 9, 0, 0, 0, 0, time.UTC)
|
||||
planned := strfmt.Date(dt1)
|
||||
dt2 := time.Date(2014, 10, 12, 8, 5, 5, 0, time.UTC)
|
||||
delivered := strfmt.DateTime(dt2)
|
||||
picture := base64.URLEncoding.EncodeToString([]byte("hello"))
|
||||
uri, _ := url.Parse("http://localhost:8002/hello/7575")
|
||||
qs := uri.Query()
|
||||
qs.Add("name", name)
|
||||
qs.Add("confirmed", "true")
|
||||
qs.Add("age", "348")
|
||||
qs.Add("score", "5.309")
|
||||
qs.Add("factor", "37.403")
|
||||
qs.Add("tags", strings.Join(tags, ","))
|
||||
qs.Add("planned", planned.String())
|
||||
qs.Add("delivered", delivered.String())
|
||||
qs.Add("picture", picture)
|
||||
|
||||
req, _ := http.NewRequest("POST", uri.String()+"?"+qs.Encode(), bytes.NewBuffer([]byte(`{"name":"toby","age":32}`)))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Request-Id", "19394858")
|
||||
|
||||
data := make(map[string]interface{})
|
||||
err := binder.Bind(req, RouteParams([]RouteParam{{"id", "7575"}}), runtime.JSONConsumer(), &data)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, id, data["id"])
|
||||
assert.Equal(t, name, data["name"])
|
||||
assert.Equal(t, friend, data["friend"])
|
||||
assert.EqualValues(t, requestID, data["X-Request-Id"])
|
||||
assert.Equal(t, tags, data["tags"])
|
||||
assert.Equal(t, planned, data["planned"])
|
||||
assert.Equal(t, delivered, data["delivered"])
|
||||
assert.Equal(t, confirmed, data["confirmed"])
|
||||
assert.Equal(t, age, data["age"])
|
||||
assert.Equal(t, factor, data["factor"])
|
||||
assert.Equal(t, score, data["score"])
|
||||
pb, _ := base64.URLEncoding.DecodeString(picture)
|
||||
assert.EqualValues(t, pb, data["picture"].(strfmt.Base64))
|
||||
|
||||
}
|
||||
141
vendor/github.com/go-openapi/runtime/middleware/validation.go
generated
vendored
Normal file
141
vendor/github.com/go-openapi/runtime/middleware/validation.go
generated
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"mime"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// NewValidation starts a new validation middleware
|
||||
func newValidation(ctx *Context, next http.Handler) http.Handler {
|
||||
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
matched, _ := ctx.RouteInfo(r)
|
||||
if matched == nil {
|
||||
ctx.NotFound(rw, r)
|
||||
return
|
||||
}
|
||||
_, result := ctx.BindAndValidate(r, matched)
|
||||
|
||||
if result != nil {
|
||||
ctx.Respond(rw, r, matched.Produces, matched, result)
|
||||
return
|
||||
}
|
||||
|
||||
debugLog("no result for %s %s", r.Method, r.URL.EscapedPath())
|
||||
next.ServeHTTP(rw, r)
|
||||
})
|
||||
}
|
||||
|
||||
type validation struct {
|
||||
context *Context
|
||||
result []error
|
||||
request *http.Request
|
||||
route *MatchedRoute
|
||||
bound map[string]interface{}
|
||||
}
|
||||
|
||||
type untypedBinder map[string]interface{}
|
||||
|
||||
func (ub untypedBinder) BindRequest(r *http.Request, route *MatchedRoute, consumer runtime.Consumer) error {
|
||||
if err := route.Binder.Bind(r, route.Params, consumer, ub); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContentType validates the content type of a request
|
||||
func validateContentType(allowed []string, actual string) error {
|
||||
debugLog("validating content type for %q against [%s]", actual, strings.Join(allowed, ", "))
|
||||
if len(allowed) == 0 {
|
||||
return nil
|
||||
}
|
||||
mt, _, err := mime.ParseMediaType(actual)
|
||||
if err != nil {
|
||||
return errors.InvalidContentType(actual, allowed)
|
||||
}
|
||||
if swag.ContainsStringsCI(allowed, mt) {
|
||||
return nil
|
||||
}
|
||||
return errors.InvalidContentType(actual, allowed)
|
||||
}
|
||||
|
||||
func validateRequest(ctx *Context, request *http.Request, route *MatchedRoute) *validation {
|
||||
debugLog("validating request %s %s", request.Method, request.URL.EscapedPath())
|
||||
validate := &validation{
|
||||
context: ctx,
|
||||
request: request,
|
||||
route: route,
|
||||
bound: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
validate.contentType()
|
||||
if len(validate.result) == 0 {
|
||||
validate.responseFormat()
|
||||
}
|
||||
if len(validate.result) == 0 {
|
||||
validate.parameters()
|
||||
}
|
||||
|
||||
return validate
|
||||
}
|
||||
|
||||
func (v *validation) parameters() {
|
||||
debugLog("validating request parameters for %s %s", v.request.Method, v.request.URL.EscapedPath())
|
||||
if result := v.route.Binder.Bind(v.request, v.route.Params, v.route.Consumer, v.bound); result != nil {
|
||||
if result.Error() == "validation failure list" {
|
||||
for _, e := range result.(*errors.Validation).Value.([]interface{}) {
|
||||
v.result = append(v.result, e.(error))
|
||||
}
|
||||
return
|
||||
}
|
||||
v.result = append(v.result, result)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
v.result = append(v.result, err)
|
||||
}
|
||||
if len(v.result) == 0 {
|
||||
if err := validateContentType(v.route.Consumes, ct); err != nil {
|
||||
v.result = append(v.result, err)
|
||||
}
|
||||
}
|
||||
if ct != "" && v.route.Consumer == nil {
|
||||
cons, ok := v.route.Consumers[ct]
|
||||
if !ok {
|
||||
v.result = append(v.result, errors.New(500, "no consumer registered for %s", ct))
|
||||
} else {
|
||||
v.route.Consumer = cons
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (v *validation) responseFormat() {
|
||||
if str := v.context.ResponseFormat(v.request, v.route.Produces); str == "" && runtime.HasBody(v.request) {
|
||||
v.result = append(v.result, errors.InvalidResponseFormat(v.request.Header.Get(runtime.HeaderAccept), v.route.Produces))
|
||||
}
|
||||
}
|
||||
130
vendor/github.com/go-openapi/runtime/middleware/validation_test.go
generated
vendored
Normal file
130
vendor/github.com/go-openapi/runtime/middleware/validation_test.go
generated
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/go-openapi/runtime/internal/testing/petstore"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestContentTypeValidation(t *testing.T) {
|
||||
spec, api := petstore.NewAPI(t)
|
||||
context := NewContext(spec, api, nil)
|
||||
context.router = DefaultRouter(spec, context.api)
|
||||
mw := newValidation(context, http.HandlerFunc(terminator))
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/api/pets", nil)
|
||||
request.Header.Add("Accept", "*/*")
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, http.StatusOK, recorder.Code)
|
||||
|
||||
recorder = httptest.NewRecorder()
|
||||
request, _ = http.NewRequest("POST", "/api/pets", nil)
|
||||
request.Header.Add("content-type", "application(")
|
||||
request.ContentLength = 1
|
||||
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, http.StatusBadRequest, recorder.Code)
|
||||
assert.Equal(t, "application/json", recorder.Header().Get("content-type"))
|
||||
|
||||
recorder = httptest.NewRecorder()
|
||||
request, _ = http.NewRequest("POST", "/api/pets", nil)
|
||||
request.Header.Add("Accept", "application/json")
|
||||
request.Header.Add("content-type", "text/html")
|
||||
request.ContentLength = 1
|
||||
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, http.StatusUnsupportedMediaType, recorder.Code)
|
||||
assert.Equal(t, "application/json", recorder.Header().Get("content-type"))
|
||||
|
||||
recorder = httptest.NewRecorder()
|
||||
request, _ = http.NewRequest("POST", "/api/pets", nil)
|
||||
request.Header.Add("Accept", "application/json")
|
||||
request.Header.Add("content-type", "text/html")
|
||||
request.TransferEncoding = []string{"chunked"}
|
||||
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, http.StatusUnsupportedMediaType, recorder.Code)
|
||||
assert.Equal(t, "application/json", recorder.Header().Get("content-type"))
|
||||
|
||||
recorder = httptest.NewRecorder()
|
||||
request, _ = http.NewRequest("POST", "/api/pets", nil)
|
||||
request.Header.Add("Accept", "application/json")
|
||||
request.Header.Add("content-type", "text/html")
|
||||
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, 422, recorder.Code)
|
||||
assert.Equal(t, "application/json", recorder.Header().Get("content-type"))
|
||||
}
|
||||
|
||||
func TestResponseFormatValidation(t *testing.T) {
|
||||
spec, api := petstore.NewAPI(t)
|
||||
context := NewContext(spec, api, nil)
|
||||
context.router = DefaultRouter(spec, context.api)
|
||||
mw := newValidation(context, http.HandlerFunc(terminator))
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("POST", "/api/pets", bytes.NewBuffer([]byte(`name: Dog`)))
|
||||
request.Header.Set(runtime.HeaderContentType, "application/x-yaml")
|
||||
request.Header.Set(runtime.HeaderAccept, "application/x-yaml")
|
||||
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, 200, recorder.Code, recorder.Body.String())
|
||||
|
||||
recorder = httptest.NewRecorder()
|
||||
request, _ = http.NewRequest("POST", "/api/pets", bytes.NewBuffer([]byte(`name: Dog`)))
|
||||
request.Header.Set(runtime.HeaderContentType, "application/x-yaml")
|
||||
request.Header.Set(runtime.HeaderAccept, "application/sml")
|
||||
|
||||
mw.ServeHTTP(recorder, request)
|
||||
assert.Equal(t, http.StatusNotAcceptable, recorder.Code)
|
||||
}
|
||||
|
||||
func TestValidateContentType(t *testing.T) {
|
||||
data := []struct {
|
||||
hdr string
|
||||
allowed []string
|
||||
err *errors.Validation
|
||||
}{
|
||||
{"application/json", []string{"application/json"}, nil},
|
||||
{"application/json", []string{"application/x-yaml", "text/html"}, errors.InvalidContentType("application/json", []string{"application/x-yaml", "text/html"})},
|
||||
{"text/html; charset=utf-8", []string{"text/html"}, nil},
|
||||
{"text/html;charset=utf-8", []string{"text/html"}, nil},
|
||||
{"", []string{"application/json"}, errors.InvalidContentType("", []string{"application/json"})},
|
||||
{"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"})},
|
||||
}
|
||||
|
||||
for _, v := range data {
|
||||
err := validateContentType(v.allowed, v.hdr)
|
||||
if v.err == nil {
|
||||
assert.NoError(t, err, "input: %q", v.hdr)
|
||||
} else {
|
||||
assert.Error(t, err, "input: %q", v.hdr)
|
||||
assert.IsType(t, &errors.Validation{}, err, "input: %q", v.hdr)
|
||||
assert.Equal(t, v.err.Error(), err.Error(), "input: %q", v.hdr)
|
||||
assert.EqualValues(t, http.StatusUnsupportedMediaType, err.(*errors.Validation).Code())
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user