Merge pull request #177 from A-Hilaly/git-version

Michael Muré created

Check git version in repo RmConfigs

Change summary

Gopkg.lock                                                             |   9 
Gopkg.toml                                                             |   6 
repository/git.go                                                      |  91 
repository/git_test.go                                                 |   2 
vendor/github.com/99designs/gqlgen/api/generate.go                     |   2 
vendor/github.com/99designs/gqlgen/codegen/args.go                     |  16 
vendor/github.com/99designs/gqlgen/codegen/args.gotpl                  |  20 
vendor/github.com/99designs/gqlgen/codegen/config/binder.go            |  24 
vendor/github.com/99designs/gqlgen/codegen/config/config.go            |  96 
vendor/github.com/99designs/gqlgen/codegen/data.go                     |   7 
vendor/github.com/99designs/gqlgen/codegen/directive.go                |  63 
vendor/github.com/99designs/gqlgen/codegen/directives.gotpl            | 137 
vendor/github.com/99designs/gqlgen/codegen/field.go                    |  23 
vendor/github.com/99designs/gqlgen/codegen/field.gotpl                 |  94 
vendor/github.com/99designs/gqlgen/codegen/generated!.gotpl            |  59 
vendor/github.com/99designs/gqlgen/codegen/input.gotpl                 |  24 
vendor/github.com/99designs/gqlgen/graphql/version.go                  |   2 
vendor/github.com/99designs/gqlgen/handler/graphql.go                  |  93 
vendor/github.com/99designs/gqlgen/handler/websocket.go                |  10 
vendor/github.com/99designs/gqlgen/plugin/modelgen/models.go           |   5 
vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.go      |   1 
vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.gotpl   |   6 
vendor/github.com/99designs/gqlgen/plugin/schemaconfig/schemaconfig.go |  93 
vendor/github.com/blang/semver/.travis.yml                             |  25 
vendor/github.com/blang/semver/LICENSE                                 |  22 
vendor/github.com/blang/semver/README.md                               | 194 
vendor/github.com/blang/semver/go.mod                                  |   1 
vendor/github.com/blang/semver/json.go                                 |  23 
vendor/github.com/blang/semver/package.json                            |  17 
vendor/github.com/blang/semver/range.go                                | 416 
vendor/github.com/blang/semver/semver.go                               | 455 
vendor/github.com/blang/semver/sort.go                                 |  28 
vendor/github.com/blang/semver/sql.go                                  |  30 
vendor/github.com/gorilla/mux/.travis.yml                              |  24 
vendor/github.com/gorilla/mux/ISSUE_TEMPLATE.md                        |  11 
vendor/github.com/gorilla/mux/README.md                                |  69 
vendor/github.com/gorilla/mux/doc.go                                   |   2 
vendor/github.com/gorilla/mux/middleware.go                            |  61 
38 files changed, 2,045 insertions(+), 216 deletions(-)

Detailed changes

Gopkg.lock 🔗

@@ -42,6 +42,14 @@
   revision = "3d21ba515fe27b856f230847e856431ae1724adc"
   version = "v1.0.0"
 
+[[projects]]
+  digest = "1:b6d886569181ec96ca83d529f4d6ba0cbf92ace7bb6f633f90c5f34d9bba7aab"
+  name = "github.com/blang/semver"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "ba2c2ddd89069b46a7011d4106f6868f17ee1705"
+  version = "v3.6.1"
+
 [[projects]]
   branch = "master"
   digest = "1:f438d91be142877c3ad83157992c91de787ddfbddcc2a7da1ef6ef61606cadc4"
@@ -442,6 +450,7 @@
     "github.com/99designs/gqlgen/graphql/introspection",
     "github.com/99designs/gqlgen/handler",
     "github.com/MichaelMure/gocui",
+    "github.com/blang/semver",
     "github.com/cheekybits/genny/generic",
     "github.com/dustin/go-humanize",
     "github.com/fatih/color",

Gopkg.toml 🔗

@@ -66,4 +66,8 @@
 
 [[override]]
   name = "golang.org/x/tools"
-  revision = "7e5bf9270d7061560865b8847c378236480f47e3"
+  revision = "7e5bf9270d7061560865b8847c378236480f47e3"
+
+[[constraint]]
+  name = "github.com/blang/semver"
+  version = "3.6.1"

repository/git.go 🔗

@@ -3,7 +3,6 @@ package repository
 
 import (
 	"bytes"
-	"errors"
 	"fmt"
 	"io"
 	"os/exec"
@@ -11,6 +10,9 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/blang/semver"
+	"github.com/pkg/errors"
+
 	"github.com/MichaelMure/git-bug/util/git"
 	"github.com/MichaelMure/git-bug/util/lamport"
 )
@@ -259,16 +261,89 @@ func (repo *GitRepo) ReadConfigString(key string) (string, error) {
 	return lines[0], nil
 }
 
+func (repo *GitRepo) rmSection(keyPrefix string) error {
+	_, err := repo.runGitCommand("config", "--remove-section", keyPrefix)
+	return err
+}
+
+func (repo *GitRepo) unsetAll(keyPrefix string) error {
+	_, err := repo.runGitCommand("config", "--unset-all", keyPrefix)
+	return err
+}
+
+// return keyPrefix section
+// example: sectionFromKey(a.b.c.d) return a.b.c
+func sectionFromKey(keyPrefix string) string {
+	s := strings.Split(keyPrefix, ".")
+	if len(s) == 1 {
+		return keyPrefix
+	}
+
+	return strings.Join(s[:len(s)-1], ".")
+}
+
+// rmConfigs with git version lesser than 2.18
+func (repo *GitRepo) rmConfigsGitVersionLT218(keyPrefix string) error {
+	// try to remove key/value pair by key
+	err := repo.unsetAll(keyPrefix)
+	if err != nil {
+		return repo.rmSection(keyPrefix)
+	}
+
+	m, err := repo.ReadConfigs(sectionFromKey(keyPrefix))
+	if err != nil {
+		return err
+	}
+
+	// if section doesn't have any left key/value remove the section
+	if len(m) == 0 {
+		return repo.rmSection(sectionFromKey(keyPrefix))
+	}
+
+	return nil
+}
+
 // RmConfigs remove all key/value pair matching the key prefix
 func (repo *GitRepo) RmConfigs(keyPrefix string) error {
-	// try to remove key/value pair by key
-	_, err := repo.runGitCommand("config", "--unset-all", keyPrefix)
+	// starting from git 2.18.0 sections are automatically deleted when the last existing
+	// key/value is removed. Before 2.18.0 we should remove the section
+	// see https://github.com/git/git/blob/master/Documentation/RelNotes/2.18.0.txt#L379
+	lt218, err := repo.gitVersionLT218()
 	if err != nil {
-		// try to remove section
-		_, err = repo.runGitCommand("config", "--remove-section", keyPrefix)
+		return errors.Wrap(err, "getting git version")
 	}
 
-	return err
+	if lt218 {
+		return repo.rmConfigsGitVersionLT218(keyPrefix)
+	}
+
+	err = repo.unsetAll(keyPrefix)
+	if err != nil {
+		return repo.rmSection(keyPrefix)
+	}
+
+	return nil
+}
+
+func (repo *GitRepo) gitVersionLT218() (bool, error) {
+	versionOut, err := repo.runGitCommand("version")
+	if err != nil {
+		return false, err
+	}
+
+	versionString := strings.Fields(versionOut)[2]
+	version, err := semver.Make(versionString)
+	if err != nil {
+		return false, err
+	}
+
+	version218string := "2.18.0"
+	gitVersion218, err := semver.Make(version218string)
+	if err != nil {
+		return false, err
+	}
+
+	return version.LT(gitVersion218), nil
 }
 
 // FetchRefs fetch git refs from a remote
@@ -428,7 +503,7 @@ func (repo *GitRepo) FindCommonAncestor(hash1 git.Hash, hash2 git.Hash) (git.Has
 	stdout, err := repo.runGitCommand("merge-base", string(hash1), string(hash2))
 
 	if err != nil {
-		return "", nil
+		return "", err
 	}
 
 	return git.Hash(stdout), nil
@@ -439,7 +514,7 @@ func (repo *GitRepo) GetTreeHash(commit git.Hash) (git.Hash, error) {
 	stdout, err := repo.runGitCommand("rev-parse", string(commit)+"^{tree}")
 
 	if err != nil {
-		return "", nil
+		return "", err
 	}
 
 	return git.Hash(stdout), nil

repository/git_test.go 🔗

@@ -55,7 +55,7 @@ func TestConfig(t *testing.T) {
 	assert.Error(t, err)
 
 	err = repo.RmConfigs("section")
-	assert.NoError(t, err)
+	assert.Error(t, err)
 
 	_, err = repo.ReadConfigString("section.key")
 	assert.Error(t, err)

vendor/github.com/99designs/gqlgen/api/generate.go 🔗

@@ -8,6 +8,7 @@ import (
 	"github.com/99designs/gqlgen/plugin"
 	"github.com/99designs/gqlgen/plugin/modelgen"
 	"github.com/99designs/gqlgen/plugin/resolvergen"
+	"github.com/99designs/gqlgen/plugin/schemaconfig"
 	"github.com/pkg/errors"
 	"golang.org/x/tools/go/packages"
 )
@@ -17,6 +18,7 @@ func Generate(cfg *config.Config, option ...Option) error {
 	_ = syscall.Unlink(cfg.Model.Filename)
 
 	plugins := []plugin.Plugin{
+		schemaconfig.New(),
 		modelgen.New(),
 		resolvergen.New(),
 	}

vendor/github.com/99designs/gqlgen/codegen/args.go 🔗

@@ -26,6 +26,22 @@ type FieldArgument struct {
 	Value         interface{} // value set in Data
 }
 
+//ImplDirectives get not Builtin and location ARGUMENT_DEFINITION directive
+func (f *FieldArgument) ImplDirectives() []*Directive {
+	d := make([]*Directive, 0)
+	for i := range f.Directives {
+		if !f.Directives[i].Builtin && f.Directives[i].IsLocation(ast.LocationArgumentDefinition) {
+			d = append(d, f.Directives[i])
+		}
+	}
+
+	return d
+}
+
+func (f *FieldArgument) DirectiveObjName() string {
+	return "rawArgs"
+}
+
 func (f *FieldArgument) Stream() bool {
 	return f.Object != nil && f.Object.Stream
 }

vendor/github.com/99designs/gqlgen/codegen/args.gotpl 🔗

@@ -5,22 +5,10 @@ func (ec *executionContext) {{ $name }}(ctx context.Context, rawArgs map[string]
 	{{- range $i, $arg := . }}
 		var arg{{$i}} {{ $arg.TypeReference.GO | ref}}
 		if tmp, ok := rawArgs[{{$arg.Name|quote}}]; ok {
-			{{- if $arg.Directives }}
-				getArg0 := func(ctx context.Context) (interface{}, error) { return ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, tmp) }
-
-				{{- range $i, $directive := $arg.Directives }}
-					getArg{{add $i 1}} := func(ctx context.Context) (res interface{}, err error) {
-						{{- range $dArg := $directive.Args }}
-							{{- if and $dArg.TypeReference.IsPtr ( notNil "Value" $dArg ) }}
-								{{ $dArg.VarName }} := {{ $dArg.Value | dump }}
-							{{- end }}
-						{{- end }}
-						n := getArg{{$i}}
-						return ec.directives.{{$directive.Name|ucFirst}}({{$directive.ResolveArgs "tmp" "n" }})
-					}
-				{{- end }}
-
-				tmp, err = getArg{{$arg.Directives|len}}(ctx)
+			{{- if $arg.ImplDirectives }}
+				directive0 := func(ctx context.Context) (interface{}, error) { return ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, tmp) }
+				{{ template "implDirectives" $arg }}
+				tmp, err = directive{{$arg.ImplDirectives|len}}(ctx)
 				if err != nil {
 					return nil, err
 				}

vendor/github.com/99designs/gqlgen/codegen/config/binder.go 🔗

@@ -14,7 +14,7 @@ import (
 
 // Binder connects graphql types to golang types using static analysis
 type Binder struct {
-	pkgs       []*packages.Package
+	pkgs       map[string]*packages.Package
 	schema     *ast.Schema
 	cfg        *Config
 	References []*TypeReference
@@ -26,7 +26,9 @@ func (c *Config) NewBinder(s *ast.Schema) (*Binder, error) {
 		return nil, err
 	}
 
+	mp := map[string]*packages.Package{}
 	for _, p := range pkgs {
+		populatePkg(mp, p)
 		for _, e := range p.Errors {
 			if e.Kind == packages.ListError {
 				return nil, p.Errors[0]
@@ -35,12 +37,23 @@ func (c *Config) NewBinder(s *ast.Schema) (*Binder, error) {
 	}
 
 	return &Binder{
-		pkgs:   pkgs,
+		pkgs:   mp,
 		schema: s,
 		cfg:    c,
 	}, nil
 }
 
+func populatePkg(mp map[string]*packages.Package, p *packages.Package) {
+	imp := code.NormalizeVendor(p.PkgPath)
+	if _, ok := mp[imp]; ok {
+		return
+	}
+	mp[imp] = p
+	for _, p := range p.Imports {
+		populatePkg(mp, p)
+	}
+}
+
 func (b *Binder) TypePosition(typ types.Type) token.Position {
 	named, isNamed := typ.(*types.Named)
 	if !isNamed {
@@ -75,10 +88,9 @@ func (b *Binder) FindType(pkgName string, typeName string) (types.Type, error) {
 }
 
 func (b *Binder) getPkg(find string) *packages.Package {
-	for _, p := range b.pkgs {
-		if code.NormalizeVendor(find) == code.NormalizeVendor(p.PkgPath) {
-			return p
-		}
+	imp := code.NormalizeVendor(find)
+	if p, ok := b.pkgs[imp]; ok {
+		return p
 	}
 	return nil
 }

vendor/github.com/99designs/gqlgen/codegen/config/config.go 🔗

@@ -6,9 +6,12 @@ import (
 	"io/ioutil"
 	"os"
 	"path/filepath"
+	"regexp"
 	"sort"
 	"strings"
 
+	"golang.org/x/tools/go/packages"
+
 	"github.com/99designs/gqlgen/internal/code"
 	"github.com/pkg/errors"
 	"github.com/vektah/gqlparser"
@@ -17,12 +20,14 @@ import (
 )
 
 type Config struct {
-	SchemaFilename StringList    `yaml:"schema,omitempty"`
-	Exec           PackageConfig `yaml:"exec"`
-	Model          PackageConfig `yaml:"model"`
-	Resolver       PackageConfig `yaml:"resolver,omitempty"`
-	Models         TypeMap       `yaml:"models,omitempty"`
-	StructTag      string        `yaml:"struct_tag,omitempty"`
+	SchemaFilename StringList                 `yaml:"schema,omitempty"`
+	Exec           PackageConfig              `yaml:"exec"`
+	Model          PackageConfig              `yaml:"model"`
+	Resolver       PackageConfig              `yaml:"resolver,omitempty"`
+	AutoBind       []string                   `yaml:"autobind"`
+	Models         TypeMap                    `yaml:"models,omitempty"`
+	StructTag      string                     `yaml:"struct_tag,omitempty"`
+	Directives     map[string]DirectiveConfig `yaml:"directives,omitempty"`
 }
 
 var cfgFilenames = []string{".gqlgen.yml", "gqlgen.yml", "gqlgen.yaml"}
@@ -33,6 +38,17 @@ func DefaultConfig() *Config {
 		SchemaFilename: StringList{"schema.graphql"},
 		Model:          PackageConfig{Filename: "models_gen.go"},
 		Exec:           PackageConfig{Filename: "generated.go"},
+		Directives: map[string]DirectiveConfig{
+			"skip": {
+				SkipRuntime: true,
+			},
+			"include": {
+				SkipRuntime: true,
+			},
+			"deprecated": {
+				SkipRuntime: true,
+			},
+		},
 	}
 }
 
@@ -51,6 +67,13 @@ func LoadConfigFromDefaultLocations() (*Config, error) {
 	return LoadConfig(cfgFile)
 }
 
+var path2regex = strings.NewReplacer(
+	`.`, `\.`,
+	`*`, `.+`,
+	`\`, `[\\/]`,
+	`/`, `[\\/]`,
+)
+
 // LoadConfig reads the gqlgen.yml config file
 func LoadConfig(filename string) (*Config, error) {
 	config := DefaultConfig()
@@ -67,9 +90,35 @@ func LoadConfig(filename string) (*Config, error) {
 	preGlobbing := config.SchemaFilename
 	config.SchemaFilename = StringList{}
 	for _, f := range preGlobbing {
-		matches, err := filepath.Glob(f)
-		if err != nil {
-			return nil, errors.Wrapf(err, "failed to glob schema filename %s", f)
+		var matches []string
+
+		// for ** we want to override default globbing patterns and walk all
+		// subdirectories to match schema files.
+		if strings.Contains(f, "**") {
+			pathParts := strings.SplitN(f, "**", 2)
+			rest := strings.TrimPrefix(strings.TrimPrefix(pathParts[1], `\`), `/`)
+			// turn the rest of the glob into a regex, anchored only at the end because ** allows
+			// for any number of dirs in between and walk will let us match against the full path name
+			globRe := regexp.MustCompile(path2regex.Replace(rest) + `$`)
+
+			if err := filepath.Walk(pathParts[0], func(path string, info os.FileInfo, err error) error {
+				if err != nil {
+					return err
+				}
+
+				if globRe.MatchString(strings.TrimPrefix(path, pathParts[0])) {
+					matches = append(matches, path)
+				}
+
+				return nil
+			}); err != nil {
+				return nil, errors.Wrapf(err, "failed to walk schema at root %s", pathParts[0])
+			}
+		} else {
+			matches, err = filepath.Glob(f)
+			if err != nil {
+				return nil, errors.Wrapf(err, "failed to glob schema filename %s", f)
+			}
 		}
 
 		for _, m := range matches {
@@ -265,6 +314,10 @@ func (tm TypeMap) Add(Name string, goType string) {
 	tm[Name] = modelCfg
 }
 
+type DirectiveConfig struct {
+	SkipRuntime bool `yaml:"skip_runtime"`
+}
+
 func inStrSlice(haystack []string, needle string) bool {
 	for _, v := range haystack {
 		if needle == v {
@@ -329,6 +382,31 @@ func (c *Config) normalize() error {
 	return nil
 }
 
+func (c *Config) Autobind(s *ast.Schema) error {
+	if len(c.AutoBind) == 0 {
+		return nil
+	}
+	ps, err := packages.Load(&packages.Config{Mode: packages.LoadTypes}, c.AutoBind...)
+	if err != nil {
+		return err
+	}
+
+	for _, t := range s.Types {
+		if c.Models.UserDefined(t.Name) {
+			continue
+		}
+
+		for _, p := range ps {
+			if t := p.Types.Scope().Lookup(t.Name); t != nil {
+				c.Models.Add(t.Name(), t.Pkg().Path()+"."+t.Name())
+				break
+			}
+		}
+	}
+
+	return nil
+}
+
 func (c *Config) InjectBuiltins(s *ast.Schema) {
 	builtins := TypeMap{
 		"__Directive":         {Model: StringList{"github.com/99designs/gqlgen/graphql/introspection.Directive"}},

vendor/github.com/99designs/gqlgen/codegen/data.go 🔗

@@ -15,7 +15,7 @@ type Data struct {
 	Config          *config.Config
 	Schema          *ast.Schema
 	SchemaStr       map[string]string
-	Directives      map[string]*Directive
+	Directives      DirectiveList
 	Objects         Objects
 	Inputs          Objects
 	Interfaces      map[string]*Interface
@@ -51,6 +51,11 @@ func BuildData(cfg *config.Config) (*Data, error) {
 		return nil, err
 	}
 
+	err = cfg.Autobind(b.Schema)
+	if err != nil {
+		return nil, err
+	}
+
 	cfg.InjectBuiltins(b.Schema)
 
 	b.Binder, err = b.Config.NewBinder(b.Schema)

vendor/github.com/99designs/gqlgen/codegen/directive.go 🔗

@@ -10,12 +10,43 @@ import (
 	"github.com/vektah/gqlparser/ast"
 )
 
+type DirectiveList map[string]*Directive
+
+//LocationDirectives filter directives by location
+func (dl DirectiveList) LocationDirectives(location string) DirectiveList {
+	return locationDirectives(dl, ast.DirectiveLocation(location))
+}
+
 type Directive struct {
+	*ast.DirectiveDefinition
 	Name    string
 	Args    []*FieldArgument
 	Builtin bool
 }
 
+//IsLocation check location directive
+func (d *Directive) IsLocation(location ...ast.DirectiveLocation) bool {
+	for _, l := range d.Locations {
+		for _, a := range location {
+			if l == a {
+				return true
+			}
+		}
+	}
+
+	return false
+}
+
+func locationDirectives(directives DirectiveList, location ...ast.DirectiveLocation) map[string]*Directive {
+	mDirectives := make(map[string]*Directive)
+	for name, d := range directives {
+		if d.IsLocation(location...) {
+			mDirectives[name] = d
+		}
+	}
+	return mDirectives
+}
+
 func (b *builder) buildDirectives() (map[string]*Directive, error) {
 	directives := make(map[string]*Directive, len(b.Schema.Directives))
 
@@ -24,11 +55,6 @@ func (b *builder) buildDirectives() (map[string]*Directive, error) {
 			return nil, errors.Errorf("directive with name %s already exists", name)
 		}
 
-		var builtin bool
-		if name == "skip" || name == "include" || name == "deprecated" {
-			builtin = true
-		}
-
 		var args []*FieldArgument
 		for _, arg := range dir.Arguments {
 			tr, err := b.Binder.TypeReference(arg.Type, nil)
@@ -53,9 +79,10 @@ func (b *builder) buildDirectives() (map[string]*Directive, error) {
 		}
 
 		directives[name] = &Directive{
-			Name:    name,
-			Args:    args,
-			Builtin: builtin,
+			DirectiveDefinition: dir,
+			Name:                name,
+			Args:                args,
+			Builtin:             b.Config.Directives[name].SkipRuntime,
 		}
 	}
 
@@ -92,8 +119,10 @@ func (b *builder) getDirectives(list ast.DirectiveList) ([]*Directive, error) {
 			})
 		}
 		dirs[i] = &Directive{
-			Name: d.Name,
-			Args: args,
+			Name:                d.Name,
+			Args:                args,
+			DirectiveDefinition: list[i].Definition,
+			Builtin:             b.Config.Directives[d.Name].SkipRuntime,
 		}
 
 	}
@@ -119,18 +148,12 @@ func (d *Directive) CallArgs() string {
 	return strings.Join(args, ", ")
 }
 
-func (d *Directive) ResolveArgs(obj string, next string) string {
-	args := []string{"ctx", obj, next}
+func (d *Directive) ResolveArgs(obj string, next int) string {
+	args := []string{"ctx", obj, fmt.Sprintf("directive%d", next)}
 
 	for _, arg := range d.Args {
-		dArg := "&" + arg.VarName
-		if !arg.TypeReference.IsPtr() {
-			if arg.Value != nil {
-				dArg = templates.Dump(arg.Value)
-			} else {
-				dArg = templates.Dump(arg.Default)
-			}
-		} else if arg.Value == nil && arg.Default == nil {
+		dArg := arg.VarName
+		if arg.Value == nil && arg.Default == nil {
 			dArg = "nil"
 		}
 

vendor/github.com/99designs/gqlgen/codegen/directives.gotpl 🔗

@@ -0,0 +1,137 @@
+{{ define "implDirectives" }}{{ $in := .DirectiveObjName }}
+	{{- range $i, $directive := .ImplDirectives -}}
+		directive{{add $i 1}} := func(ctx context.Context) (interface{}, error) {
+			{{- range $arg := $directive.Args }}
+				{{- if notNil "Value" $arg }}
+						{{ $arg.VarName }}, err := ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, {{ $arg.Value | dump }})
+						if err != nil{
+							return nil, err
+						}
+					{{- else if notNil "Default" $arg }}
+						{{ $arg.VarName }}, err := ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, {{ $arg.Default | dump }})
+						if err != nil{
+							return nil, err
+						}
+					{{- end }}
+			{{- end }}
+			return ec.directives.{{$directive.Name|ucFirst}}({{$directive.ResolveArgs $in $i }})
+		}
+	{{- end -}}
+{{ end }}
+
+{{define "queryDirectives"}}
+	for _, d := range obj.Directives {
+		switch d.Name {
+		{{- range $directive := . }}
+		case "{{$directive.Name}}":
+			{{- if $directive.Args }}
+				rawArgs := d.ArgumentMap(ec.Variables)
+				args, err := ec.{{ $directive.ArgsFunc }}(ctx,rawArgs)
+				if err != nil {
+					ec.Error(ctx, err)
+					return graphql.Null
+				}
+			{{- end }}
+			n := next
+			next = func(ctx context.Context) (interface{}, error) {
+				return ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}})
+			}
+		{{- end }}
+		}
+	}
+	tmp, err := next(ctx)
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if data, ok := tmp.(graphql.Marshaler); ok {
+		return data
+	}
+	ec.Errorf(ctx, `unexpected type %T from directive, should be graphql.Marshaler`, tmp)
+	return graphql.Null
+{{end}}
+
+{{ if .Directives.LocationDirectives "QUERY" }}
+func (ec *executionContext) _queryMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (interface{}, error)) graphql.Marshaler {
+	{{ template "queryDirectives" .Directives.LocationDirectives "QUERY" }}
+}
+{{ end }}
+
+{{ if .Directives.LocationDirectives "MUTATION" }}
+func (ec *executionContext) _mutationMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (interface{}, error)) graphql.Marshaler {
+	{{ template "queryDirectives" .Directives.LocationDirectives "MUTATION" }}
+}
+{{ end }}
+
+{{ if .Directives.LocationDirectives "SUBSCRIPTION" }}
+func (ec *executionContext) _subscriptionMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (interface{}, error)) func() graphql.Marshaler {
+	for _, d := range obj.Directives {
+		switch d.Name {
+		{{- range $directive := .Directives.LocationDirectives "SUBSCRIPTION" }}
+		case "{{$directive.Name}}":
+			{{- if $directive.Args }}
+				rawArgs := d.ArgumentMap(ec.Variables)
+				args, err := ec.{{ $directive.ArgsFunc }}(ctx,rawArgs)
+				if err != nil {
+					ec.Error(ctx, err)
+					return func() graphql.Marshaler {
+						return graphql.Null
+					}
+				}
+			{{- end }}
+			n := next
+			next = func(ctx context.Context) (interface{}, error) {
+				return ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}})
+			}
+		{{- end }}
+		}
+	}
+	tmp, err := next(ctx)
+	if err != nil {
+		ec.Error(ctx, err)
+		return func() graphql.Marshaler {
+			return graphql.Null
+		}
+	}
+	if data, ok := tmp.(func() graphql.Marshaler); ok {
+		return data
+	}
+	ec.Errorf(ctx, `unexpected type %T from directive, should be graphql.Marshaler`, tmp)
+	return func() graphql.Marshaler {
+		return graphql.Null
+	}
+}
+{{ end }}
+
+{{ if .Directives.LocationDirectives "FIELD" }}
+	func (ec *executionContext) _fieldMiddleware(ctx context.Context, obj interface{}, next graphql.Resolver) interface{} {
+		{{- if .Directives.LocationDirectives "FIELD" }}
+		rctx := graphql.GetResolverContext(ctx)
+		for _, d := range rctx.Field.Directives {
+			switch d.Name {
+			{{- range $directive := .Directives.LocationDirectives "FIELD" }}
+			case "{{$directive.Name}}":
+				{{- if $directive.Args }}
+					rawArgs := d.ArgumentMap(ec.Variables)
+					args, err := ec.{{ $directive.ArgsFunc }}(ctx,rawArgs)
+					if err != nil {
+						ec.Error(ctx, err)
+						return nil
+					}
+				{{- end }}
+				n := next
+				next = func(ctx context.Context) (interface{}, error) {
+					return ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}})
+				}
+			{{- end }}
+			}
+		}
+		{{- end }}
+		res, err := ec.ResolverMiddleware(ctx, next)
+		if err != nil {
+			ec.Error(ctx, err)
+			return nil
+		}
+		return res
+	}
+{{ end }}

vendor/github.com/99designs/gqlgen/codegen/field.go 🔗

@@ -284,7 +284,28 @@ func (b *builder) findBindStructTarget(strukt *types.Struct, name string) (types
 }
 
 func (f *Field) HasDirectives() bool {
-	return len(f.Directives) > 0
+	return len(f.ImplDirectives()) > 0
+}
+
+func (f *Field) DirectiveObjName() string {
+	if f.Object.Root {
+		return "nil"
+	}
+	return f.GoReceiverName
+}
+
+func (f *Field) ImplDirectives() []*Directive {
+	var d []*Directive
+	loc := ast.LocationFieldDefinition
+	if f.Object.IsInputType() {
+		loc = ast.LocationInputFieldDefinition
+	}
+	for i := range f.Directives {
+		if !f.Directives[i].Builtin && f.Directives[i].IsLocation(loc) {
+			d = append(d, f.Directives[i])
+		}
+	}
+	return d
 }
 
 func (f *Field) IsReserved() bool {

vendor/github.com/99designs/gqlgen/codegen/field.gotpl 🔗

@@ -37,9 +37,15 @@
 		}
 	}
 {{ else }}
-	func (ec *executionContext) _{{$object.Name}}_{{$field.Name}}(ctx context.Context, field graphql.CollectedField{{ if not $object.Root }}, obj {{$object.Reference | ref}}{{end}}) graphql.Marshaler {
+	func (ec *executionContext) _{{$object.Name}}_{{$field.Name}}(ctx context.Context, field graphql.CollectedField{{ if not $object.Root }}, obj {{$object.Reference | ref}}{{end}}) (ret graphql.Marshaler) {
 		ctx = ec.Tracer.StartFieldExecution(ctx, field)
-		defer func () { ec.Tracer.EndFieldExecution(ctx) }()
+		defer func () {
+			if r := recover(); r != nil {
+				ec.Error(ctx, ec.Recover(ctx, r))
+				ret = graphql.Null
+			}
+			ec.Tracer.EndFieldExecution(ctx)
+		}()
 		rctx := &graphql.ResolverContext{
 			Object: {{$object.Name|quote}},
 			Field: field,
@@ -57,31 +63,19 @@
 			rctx.Args = args
 		{{- end }}
 		ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
-		resTmp := ec.FieldMiddleware(ctx, {{if $object.Root}}nil{{else}}obj{{end}}, func(rctx context.Context) (interface{}, error) {
-			ctx = rctx  // use context from middleware stack in children
-			{{- if $field.IsResolver }}
-				return ec.resolvers.{{ $field.ShortInvocation }}
-			{{- else if $field.IsMap }}
-				switch v := {{$field.GoReceiverName}}[{{$field.Name|quote}}].(type) {
-				case {{$field.TypeReference.GO | ref}}:
-					return v, nil
-				case {{$field.TypeReference.Elem.GO | ref}}:
-					return &v, nil
-				case nil:
-					return ({{$field.TypeReference.GO | ref}})(nil), nil
-				default:
-					return nil, fmt.Errorf("unexpected type %T for field %s", v, {{ $field.Name | quote}})
-				}
-			{{- else if $field.IsMethod }}
-				{{- if $field.NoErr }}
-					return {{$field.GoReceiverName}}.{{$field.GoFieldName}}({{ $field.CallArgs }}), nil
-				{{- else }}
-					return {{$field.GoReceiverName}}.{{$field.GoFieldName}}({{ $field.CallArgs }})
-				{{- end }}
-			{{- else if $field.IsVariable }}
-				return {{$field.GoReceiverName}}.{{$field.GoFieldName}}, nil
-			{{- end }}
-		})
+		{{- if  $.Directives.LocationDirectives "FIELD" }}
+			resTmp := ec._fieldMiddleware(ctx, {{if $object.Root}}nil{{else}}obj{{end}}, func(rctx context.Context) (interface{}, error) {
+				{{ template "field" $field }}
+			})
+		{{ else }}
+			resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+				{{ template "field" $field }}
+			})
+			if err != nil {
+				ec.Error(ctx, err)
+				return graphql.Null
+			}
+		{{- end }}
 		if resTmp == nil {
 			{{- if $field.TypeReference.GQL.NonNull }}
 				if !ec.HasError(rctx) {
@@ -98,3 +92,49 @@
 {{ end }}
 
 {{- end }}{{- end}}
+
+{{ define "field" }}
+	{{- if .HasDirectives -}}
+		directive0 := func(rctx context.Context) (interface{}, error) {
+			ctx = rctx  // use context from middleware stack in children
+			{{ template "fieldDefinition" . }}
+		}
+		{{ template "implDirectives" . }}
+		tmp, err := directive{{.ImplDirectives|len}}(rctx)
+		if err != nil {
+			return nil, err
+		}
+		if data, ok := tmp.({{ .TypeReference.GO | ref }}) ; ok {
+			return data, nil
+		}
+		return nil, fmt.Errorf(`unexpected type %T from directive, should be {{ .TypeReference.GO }}`, tmp)
+	{{- else -}}
+		ctx = rctx  // use context from middleware stack in children
+		{{ template "fieldDefinition" . }}
+	{{- end -}}
+{{ end }}
+
+{{ define "fieldDefinition" }}
+	{{- if .IsResolver -}}
+		return ec.resolvers.{{ .ShortInvocation }}
+	{{- else if .IsMap -}}
+		switch v := {{.GoReceiverName}}[{{.Name|quote}}].(type) {
+		case {{.TypeReference.GO | ref}}:
+			return v, nil
+		case {{.TypeReference.Elem.GO | ref}}:
+			return &v, nil
+		case nil:
+			return ({{.TypeReference.GO | ref}})(nil), nil
+		default:
+			return nil, fmt.Errorf("unexpected type %T for field %s", v, {{ .Name | quote}})
+		}
+	{{- else if .IsMethod -}}
+		{{- if .NoErr -}}
+			return {{.GoReceiverName}}.{{.GoFieldName}}({{ .CallArgs }}), nil
+		{{- else -}}
+			return {{.GoReceiverName}}.{{.GoFieldName}}({{ .CallArgs }})
+		{{- end -}}
+	{{- else if .IsVariable -}}
+		return {{.GoReceiverName}}.{{.GoFieldName}}, nil
+	{{- end }}
+{{- end }}

vendor/github.com/99designs/gqlgen/codegen/generated!.gotpl 🔗

@@ -117,7 +117,13 @@ func (e *executableSchema) Query(ctx context.Context, op *ast.OperationDefinitio
 		ec := executionContext{graphql.GetRequestContext(ctx), e}
 
 		buf := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {
+		{{ if .Directives.LocationDirectives "QUERY" -}}
+			data := ec._queryMiddleware(ctx, op, func(ctx context.Context) (interface{}, error){
+				return ec._{{.QueryRoot.Name}}(ctx, op.SelectionSet), nil
+			})
+		{{- else -}}
 			data := ec._{{.QueryRoot.Name}}(ctx, op.SelectionSet)
+		{{- end }}
 			var buf bytes.Buffer
 			data.MarshalGQL(&buf)
 			return buf.Bytes()
@@ -138,7 +144,13 @@ func (e *executableSchema) Mutation(ctx context.Context, op *ast.OperationDefini
 		ec := executionContext{graphql.GetRequestContext(ctx), e}
 
 		buf := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte {
+		{{ if .Directives.LocationDirectives "MUTATION" -}}
+			data := ec._mutationMiddleware(ctx, op, func(ctx context.Context) (interface{}, error){
+				return ec._{{.MutationRoot.Name}}(ctx, op.SelectionSet), nil
+			})
+		{{- else -}}
 			data := ec._{{.MutationRoot.Name}}(ctx, op.SelectionSet)
+		{{- end }}
 			var buf bytes.Buffer
 			data.MarshalGQL(&buf)
 			return buf.Bytes()
@@ -158,7 +170,13 @@ func (e *executableSchema) Subscription(ctx context.Context, op *ast.OperationDe
 	{{- if .SubscriptionRoot }}
 		ec := executionContext{graphql.GetRequestContext(ctx), e}
 
-		next := ec._{{.SubscriptionRoot.Name}}(ctx, op.SelectionSet)
+		{{ if .Directives.LocationDirectives "SUBSCRIPTION" -}}
+			next := ec._subscriptionMiddleware(ctx, op, func(ctx context.Context) (interface{}, error){
+				return ec._{{.SubscriptionRoot.Name}}(ctx, op.SelectionSet),nil
+			})
+		{{- else -}}
+			next := ec._{{.SubscriptionRoot.Name}}(ctx, op.SelectionSet)
+		{{- end }}
 		if ec.Errors != nil {
 			return graphql.OneShot(&graphql.Response{Data: []byte("null"), Errors: ec.Errors})
 		}
@@ -196,45 +214,6 @@ type executionContext struct {
 	*executableSchema
 }
 
-func (ec *executionContext) FieldMiddleware(ctx context.Context, obj interface{}, next graphql.Resolver) (ret interface{}) {
-	defer func() {
-		if r := recover(); r != nil {
-			ec.Error(ctx, ec.Recover(ctx, r))
-			ret = nil
-		}
-	}()
-	{{- if .Directives }}
-	rctx := graphql.GetResolverContext(ctx)
-	for _, d := range rctx.Field.Definition.Directives {
-		switch d.Name {
-		{{- range $directive := .Directives }}
-		case "{{$directive.Name}}":
-			if ec.directives.{{$directive.Name|ucFirst}} != nil {
-				{{- if $directive.Args }}
-					rawArgs := d.ArgumentMap(ec.Variables)
-					args, err := ec.{{ $directive.ArgsFunc }}(ctx,rawArgs)
-					if err != nil {
-						ec.Error(ctx, err)
-						return nil
-					}
-				{{- end }}
-				n := next
-				next = func(ctx context.Context) (interface{}, error) {
-					return ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}})
-				}
-			}
-		{{- end }}
-		}
-	}
-	{{- end }}
-	res, err := ec.ResolverMiddleware(ctx, next)
-	if err != nil {
-		ec.Error(ctx, err)
-		return nil
-	}
-	return res
-}
-
 func (ec *executionContext) introspectSchema() (*introspection.Schema, error) {
 	if ec.DisableIntrospection {
 		return nil, errors.New("introspection disabled")

vendor/github.com/99designs/gqlgen/codegen/input.gotpl 🔗

@@ -1,8 +1,8 @@
 {{- range $input := .Inputs }}
 	{{- if not .HasUnmarshal }}
-	func (ec *executionContext) unmarshalInput{{ .Name }}(ctx context.Context, v interface{}) ({{.Type | ref}}, error) {
+	func (ec *executionContext) unmarshalInput{{ .Name }}(ctx context.Context, obj interface{}) ({{.Type | ref}}, error) {
 		var it {{.Type | ref}}
-		var asMap = v.(map[string]interface{})
+		var asMap = obj.(map[string]interface{})
 		{{ range $field := .Fields}}
 			{{- if $field.Default}}
 				if _, present := asMap[{{$field.Name|quote}}] ; !present {
@@ -16,22 +16,10 @@
 			{{- range $field := .Fields }}
 			case {{$field.Name|quote}}:
 				var err error
-				{{- if $field.Directives }}
-					getField0 := func(ctx context.Context) (interface{}, error) { return ec.{{ $field.TypeReference.UnmarshalFunc }}(ctx, v) }
-
-					{{- range $i, $directive := $field.Directives }}
-						getField{{add $i 1}} := func(ctx context.Context) (res interface{}, err error) {
-							{{- range $dArg := $directive.Args }}
-								{{- if and $dArg.TypeReference.IsPtr ( notNil "Value" $dArg ) }}
-									{{ $dArg.VarName }} := {{ $dArg.Value | dump }}
-								{{- end }}
-							{{- end }}
-							n := getField{{$i}}
-							return ec.directives.{{$directive.Name|ucFirst}}({{$directive.ResolveArgs "it" "n" }})
-						}
-					{{- end }}
-
-					tmp, err := getField{{$field.Directives|len}}(ctx)
+				{{- if $field.ImplDirectives }}
+					directive0 := func(ctx context.Context) (interface{}, error) { return ec.{{ $field.TypeReference.UnmarshalFunc }}(ctx, v) }
+					{{ template "implDirectives" $field }}
+					tmp, err := directive{{$field.ImplDirectives|len}}(ctx)
 					if err != nil {
 						return it, err
 					}

vendor/github.com/99designs/gqlgen/handler/graphql.go 🔗

@@ -2,6 +2,8 @@ package handler
 
 import (
 	"context"
+	"crypto/sha256"
+	"encoding/hex"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -28,8 +30,30 @@ type params struct {
 	Query         string                 `json:"query"`
 	OperationName string                 `json:"operationName"`
 	Variables     map[string]interface{} `json:"variables"`
+	Extensions    *extensions            `json:"extensions"`
 }
 
+type extensions struct {
+	PersistedQuery *persistedQuery `json:"persistedQuery"`
+}
+
+type persistedQuery struct {
+	Sha256  string `json:"sha256Hash"`
+	Version int64  `json:"version"`
+}
+
+const (
+	errPersistedQueryNotSupported = "PersistedQueryNotSupported"
+	errPersistedQueryNotFound     = "PersistedQueryNotFound"
+)
+
+type PersistedQueryCache interface {
+	Add(ctx context.Context, hash string, query string)
+	Get(ctx context.Context, hash string) (string, bool)
+}
+
+type websocketInitFunc func(ctx context.Context, initPayload InitPayload) error
+
 type Config struct {
 	cacheSize                       int
 	upgrader                        websocket.Upgrader
@@ -40,10 +64,12 @@ type Config struct {
 	tracer                          graphql.Tracer
 	complexityLimit                 int
 	complexityLimitFunc             graphql.ComplexityLimitFunc
+	websocketInitFunc               websocketInitFunc
 	disableIntrospection            bool
 	connectionKeepAlivePingInterval time.Duration
 	uploadMaxMemory                 int64
 	uploadMaxSize                   int64
+	apqCache                        PersistedQueryCache
 }
 
 func (c *Config) newRequestContext(es graphql.ExecutableSchema, doc *ast.QueryDocument, op *ast.OperationDefinition, query string, variables map[string]interface{}) *graphql.RequestContext {
@@ -250,6 +276,14 @@ func (tw *tracerWrapper) EndOperationExecution(ctx context.Context) {
 	tw.tracer1.EndOperationExecution(ctx)
 }
 
+// WebsocketInitFunc is called when the server receives connection init message from the client.
+// This can be used to check initial payload to see whether to accept the websocket connection.
+func WebsocketInitFunc(websocketInitFunc func(ctx context.Context, initPayload InitPayload) error) Option {
+	return func(cfg *Config) {
+		cfg.websocketInitFunc = websocketInitFunc
+	}
+}
+
 // CacheSize sets the maximum size of the query cache.
 // If size is less than or equal to 0, the cache is disabled.
 func CacheSize(size int) Option {
@@ -285,6 +319,13 @@ func WebsocketKeepAliveDuration(duration time.Duration) Option {
 	}
 }
 
+// Add cache that will hold queries for automatic persisted queries (APQ)
+func EnablePersistedQueryCache(cache PersistedQueryCache) Option {
+	return func(cfg *Config) {
+		cfg.apqCache = cache
+	}
+}
+
 const DefaultCacheSize = 1000
 const DefaultConnectionKeepAlivePingInterval = 25 * time.Second
 
@@ -344,6 +385,11 @@ type graphqlHandler struct {
 	exec  graphql.ExecutableSchema
 }
 
+func computeQueryHash(query string) string {
+	b := sha256.Sum256([]byte(query))
+	return hex.EncodeToString(b[:])
+}
+
 func (gh *graphqlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	if r.Method == http.MethodOptions {
 		w.Header().Set("Allow", "OPTIONS, GET, POST")
@@ -369,6 +415,13 @@ func (gh *graphqlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 				return
 			}
 		}
+
+		if extensions := r.URL.Query().Get("extensions"); extensions != "" {
+			if err := jsonDecode(strings.NewReader(extensions), &reqParams.Extensions); err != nil {
+				sendErrorf(w, http.StatusBadRequest, "extensions could not be decoded")
+				return
+			}
+		}
 	case http.MethodPost:
 		mediaType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
 		if err != nil {
@@ -409,6 +462,41 @@ func (gh *graphqlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	ctx := r.Context()
 
+	var queryHash string
+	apqRegister := false
+	apq := reqParams.Extensions != nil && reqParams.Extensions.PersistedQuery != nil
+	if apq {
+		// client has enabled apq
+		queryHash = reqParams.Extensions.PersistedQuery.Sha256
+		if gh.cfg.apqCache == nil {
+			// server has disabled apq
+			sendErrorf(w, http.StatusOK, errPersistedQueryNotSupported)
+			return
+		}
+		if reqParams.Extensions.PersistedQuery.Version != 1 {
+			sendErrorf(w, http.StatusOK, "Unsupported persisted query version")
+			return
+		}
+		if reqParams.Query == "" {
+			// client sent optimistic query hash without query string
+			query, ok := gh.cfg.apqCache.Get(ctx, queryHash)
+			if !ok {
+				sendErrorf(w, http.StatusOK, errPersistedQueryNotFound)
+				return
+			}
+			reqParams.Query = query
+		} else {
+			if computeQueryHash(reqParams.Query) != queryHash {
+				sendErrorf(w, http.StatusOK, "provided sha does not match query")
+				return
+			}
+			apqRegister = true
+		}
+	} else if reqParams.Query == "" {
+		sendErrorf(w, http.StatusUnprocessableEntity, "Must provide query string")
+		return
+	}
+
 	var doc *ast.QueryDocument
 	var cacheHit bool
 	if gh.cache != nil {
@@ -463,6 +551,11 @@ func (gh *graphqlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	if apqRegister && gh.cfg.apqCache != nil {
+		// Add to persisted query cache
+		gh.cfg.apqCache.Add(ctx, queryHash, reqParams.Query)
+	}
+
 	switch op.Operation {
 	case ast.Query:
 		b, err := json.Marshal(gh.exec.Query(ctx, op))

vendor/github.com/99designs/gqlgen/handler/websocket.go 🔗

@@ -12,7 +12,7 @@ import (
 
 	"github.com/99designs/gqlgen/graphql"
 	"github.com/gorilla/websocket"
-	"github.com/hashicorp/golang-lru"
+	lru "github.com/hashicorp/golang-lru"
 	"github.com/vektah/gqlparser"
 	"github.com/vektah/gqlparser/ast"
 	"github.com/vektah/gqlparser/gqlerror"
@@ -94,6 +94,14 @@ func (c *wsConnection) init() bool {
 			}
 		}
 
+		if c.cfg.websocketInitFunc != nil {
+			if err := c.cfg.websocketInitFunc(c.ctx, c.initPayload); err != nil {
+				c.sendConnectionError(err.Error())
+				c.close(websocket.CloseNormalClosure, "terminated")
+				return false
+			}
+		}
+
 		c.write(&operationMessage{Type: connectionAckMsg})
 	case connectionTerminateMsg:
 		c.close(websocket.CloseNormalClosure, "terminated")

vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.gotpl 🔗

@@ -20,18 +20,18 @@ type {{.ResolverType}} struct {}
 {{ range $object := .Objects -}}
 	{{- if $object.HasResolvers -}}
 		func (r *{{$.ResolverType}}) {{$object.Name}}() {{ $object.ResolverInterface | ref }} {
-			return &{{lcFirst $object.Name}}Resolver{r}
+			return &{{lcFirst $object.Name}}{{ucFirst $.ResolverType}}{r}
 		}
 	{{ end -}}
 {{ end }}
 
 {{ range $object := .Objects -}}
 	{{- if $object.HasResolvers -}}
-		type {{lcFirst $object.Name}}Resolver struct { *Resolver }
+		type {{lcFirst $object.Name}}{{ucFirst $.ResolverType}} struct { *{{$.ResolverType}} }
 
 		{{ range $field := $object.Fields -}}
 			{{- if $field.IsResolver -}}
-			func (r *{{lcFirst $object.Name}}Resolver) {{$field.GoFieldName}}{{ $field.ShortResolverDeclaration }} {
+			func (r *{{lcFirst $object.Name}}{{ucFirst $.ResolverType}}) {{$field.GoFieldName}}{{ $field.ShortResolverDeclaration }} {
 				panic("not implemented")
 			}
 			{{ end -}}

vendor/github.com/99designs/gqlgen/plugin/schemaconfig/schemaconfig.go 🔗

@@ -0,0 +1,93 @@
+package schemaconfig
+
+import (
+	"github.com/99designs/gqlgen/codegen/config"
+	"github.com/99designs/gqlgen/plugin"
+	"github.com/vektah/gqlparser/ast"
+)
+
+func New() plugin.Plugin {
+	return &Plugin{}
+}
+
+type Plugin struct{}
+
+var _ plugin.ConfigMutator = &Plugin{}
+
+func (m *Plugin) Name() string {
+	return "schemaconfig"
+}
+
+func (m *Plugin) MutateConfig(cfg *config.Config) error {
+	if err := cfg.Check(); err != nil {
+		return err
+	}
+
+	schema, _, err := cfg.LoadSchema()
+	if err != nil {
+		return err
+	}
+
+	cfg.Directives["goModel"] = config.DirectiveConfig{
+		SkipRuntime: true,
+	}
+
+	cfg.Directives["goField"] = config.DirectiveConfig{
+		SkipRuntime: true,
+	}
+
+	for _, schemaType := range schema.Types {
+		if schemaType == schema.Query || schemaType == schema.Mutation || schemaType == schema.Subscription {
+			continue
+		}
+
+		if bd := schemaType.Directives.ForName("goModel"); bd != nil {
+			if ma := bd.Arguments.ForName("model"); ma != nil {
+				if mv, err := ma.Value.Value(nil); err == nil {
+					cfg.Models.Add(schemaType.Name, mv.(string))
+				}
+			}
+			if ma := bd.Arguments.ForName("models"); ma != nil {
+				if mvs, err := ma.Value.Value(nil); err == nil {
+					for _, mv := range mvs.([]interface{}) {
+						cfg.Models.Add(schemaType.Name, mv.(string))
+					}
+				}
+			}
+		}
+
+		if schemaType.Kind == ast.Object || schemaType.Kind == ast.InputObject {
+			for _, field := range schemaType.Fields {
+				if fd := field.Directives.ForName("goField"); fd != nil {
+					forceResolver := cfg.Models[schemaType.Name].Fields[field.Name].Resolver
+					fieldName := cfg.Models[schemaType.Name].Fields[field.Name].FieldName
+
+					if ra := fd.Arguments.ForName("forceResolver"); ra != nil {
+						if fr, err := ra.Value.Value(nil); err == nil {
+							forceResolver = fr.(bool)
+						}
+					}
+
+					if na := fd.Arguments.ForName("name"); na != nil {
+						if fr, err := na.Value.Value(nil); err == nil {
+							fieldName = fr.(string)
+						}
+					}
+
+					if cfg.Models[schemaType.Name].Fields == nil {
+						cfg.Models[schemaType.Name] = config.TypeMapEntry{
+							Model:  cfg.Models[schemaType.Name].Model,
+							Fields: map[string]config.TypeMapField{},
+						}
+					}
+
+					cfg.Models[schemaType.Name].Fields[field.Name] = config.TypeMapField{
+						FieldName: fieldName,
+						Resolver:  forceResolver,
+					}
+				}
+			}
+		}
+	}
+	return nil
+}

vendor/github.com/blang/semver/.travis.yml 🔗

@@ -0,0 +1,25 @@
+language: go
+matrix:
+  include:
+  - go: 1.4.x
+  - go: 1.5.x
+  - go: 1.6.x
+  - go: 1.7.x
+  - go: 1.8.x
+  - go: 1.9.x
+  - go: 1.10.x
+  - go: 1.11.x
+  - go: tip
+  allow_failures:
+  - go: tip
+install:
+- go get golang.org/x/tools/cmd/cover
+- go get github.com/mattn/goveralls
+script:
+- echo "Test and track coverage" ; $HOME/gopath/bin/goveralls -package "." -service=travis-ci
+  -repotoken=$COVERALLS_TOKEN
+- echo "Build examples" ; cd examples && go build
+- echo "Check if gofmt'd" ; diff -u <(echo -n) <(gofmt -d -s .)
+env:
+  global:
+    secure: HroGEAUQpVq9zX1b1VIkraLiywhGbzvNnTZq2TMxgK7JHP8xqNplAeF1izrR2i4QLL9nsY+9WtYss4QuPvEtZcVHUobw6XnL6radF7jS1LgfYZ9Y7oF+zogZ2I5QUMRLGA7rcxQ05s7mKq3XZQfeqaNts4bms/eZRefWuaFZbkw=

vendor/github.com/blang/semver/LICENSE 🔗

@@ -0,0 +1,22 @@
+The MIT License
+
+Copyright (c) 2014 Benedikt Lang <github at benediktlang.de>
+
+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.
+

vendor/github.com/blang/semver/README.md 🔗

@@ -0,0 +1,194 @@
+semver for golang [![Build Status](https://travis-ci.org/blang/semver.svg?branch=master)](https://travis-ci.org/blang/semver) [![GoDoc](https://godoc.org/github.com/blang/semver?status.svg)](https://godoc.org/github.com/blang/semver) [![Coverage Status](https://img.shields.io/coveralls/blang/semver.svg)](https://coveralls.io/r/blang/semver?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/blang/semver)](https://goreportcard.com/report/github.com/blang/semver)
+======
+
+semver is a [Semantic Versioning](http://semver.org/) library written in golang. It fully covers spec version `2.0.0`.
+
+Usage
+-----
+```bash
+$ go get github.com/blang/semver
+```
+Note: Always vendor your dependencies or fix on a specific version tag.
+
+```go
+import github.com/blang/semver
+v1, err := semver.Make("1.0.0-beta")
+v2, err := semver.Make("2.0.0-beta")
+v1.Compare(v2)
+```
+
+Also check the [GoDocs](http://godoc.org/github.com/blang/semver).
+
+Why should I use this lib?
+-----
+
+- Fully spec compatible
+- No reflection
+- No regex
+- Fully tested (Coverage >99%)
+- Readable parsing/validation errors
+- Fast (See [Benchmarks](#benchmarks))
+- Only Stdlib
+- Uses values instead of pointers
+- Many features, see below
+
+
+Features
+-----
+
+- Parsing and validation at all levels
+- Comparator-like comparisons
+- Compare Helper Methods
+- InPlace manipulation
+- Ranges `>=1.0.0 <2.0.0 || >=3.0.0 !3.0.1-beta.1`
+- Wildcards `>=1.x`, `<=2.5.x`
+- Sortable (implements sort.Interface)
+- database/sql compatible (sql.Scanner/Valuer)
+- encoding/json compatible (json.Marshaler/Unmarshaler)
+
+Ranges
+------
+
+A `Range` is a set of conditions which specify which versions satisfy the range.
+
+A condition is composed of an operator and a version. The supported operators are:
+
+- `<1.0.0` Less than `1.0.0`
+- `<=1.0.0` Less than or equal to `1.0.0`
+- `>1.0.0` Greater than `1.0.0`
+- `>=1.0.0` Greater than or equal to `1.0.0`
+- `1.0.0`, `=1.0.0`, `==1.0.0` Equal to `1.0.0`
+- `!1.0.0`, `!=1.0.0` Not equal to `1.0.0`. Excludes version `1.0.0`.
+
+Note that spaces between the operator and the version will be gracefully tolerated.
+
+A `Range` can link multiple `Ranges` separated by space:
+
+Ranges can be linked by logical AND:
+
+  - `>1.0.0 <2.0.0` would match between both ranges, so `1.1.1` and `1.8.7` but not `1.0.0` or `2.0.0`
+  - `>1.0.0 <3.0.0 !2.0.3-beta.2` would match every version between `1.0.0` and `3.0.0` except `2.0.3-beta.2`
+
+Ranges can also be linked by logical OR:
+
+  - `<2.0.0 || >=3.0.0` would match `1.x.x` and `3.x.x` but not `2.x.x`
+
+AND has a higher precedence than OR. It's not possible to use brackets.
+
+Ranges can be combined by both AND and OR
+
+  - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1`
+
+Range usage:
+
+```
+v, err := semver.Parse("1.2.3")
+expectedRange, err := semver.ParseRange(">1.0.0 <2.0.0 || >=3.0.0")
+if expectedRange(v) {
+    //valid
+}
+
+```
+
+Example
+-----
+
+Have a look at full examples in [examples/main.go](examples/main.go)
+
+```go
+import github.com/blang/semver
+
+v, err := semver.Make("0.0.1-alpha.preview+123.github")
+fmt.Printf("Major: %d\n", v.Major)
+fmt.Printf("Minor: %d\n", v.Minor)
+fmt.Printf("Patch: %d\n", v.Patch)
+fmt.Printf("Pre: %s\n", v.Pre)
+fmt.Printf("Build: %s\n", v.Build)
+
+// Prerelease versions array
+if len(v.Pre) > 0 {
+    fmt.Println("Prerelease versions:")
+    for i, pre := range v.Pre {
+        fmt.Printf("%d: %q\n", i, pre)
+    }
+}
+
+// Build meta data array
+if len(v.Build) > 0 {
+    fmt.Println("Build meta data:")
+    for i, build := range v.Build {
+        fmt.Printf("%d: %q\n", i, build)
+    }
+}
+
+v001, err := semver.Make("0.0.1")
+// Compare using helpers: v.GT(v2), v.LT, v.GTE, v.LTE
+v001.GT(v) == true
+v.LT(v001) == true
+v.GTE(v) == true
+v.LTE(v) == true
+
+// Or use v.Compare(v2) for comparisons (-1, 0, 1):
+v001.Compare(v) == 1
+v.Compare(v001) == -1
+v.Compare(v) == 0
+
+// Manipulate Version in place:
+v.Pre[0], err = semver.NewPRVersion("beta")
+if err != nil {
+    fmt.Printf("Error parsing pre release version: %q", err)
+}
+
+fmt.Println("\nValidate versions:")
+v.Build[0] = "?"
+
+err = v.Validate()
+if err != nil {
+    fmt.Printf("Validation failed: %s\n", err)
+}
+```
+
+
+Benchmarks
+-----
+
+    BenchmarkParseSimple-4           5000000    390    ns/op    48 B/op   1 allocs/op
+    BenchmarkParseComplex-4          1000000   1813    ns/op   256 B/op   7 allocs/op
+    BenchmarkParseAverage-4          1000000   1171    ns/op   163 B/op   4 allocs/op
+    BenchmarkStringSimple-4         20000000    119    ns/op    16 B/op   1 allocs/op
+    BenchmarkStringLarger-4         10000000    206    ns/op    32 B/op   2 allocs/op
+    BenchmarkStringComplex-4         5000000    324    ns/op    80 B/op   3 allocs/op
+    BenchmarkStringAverage-4         5000000    273    ns/op    53 B/op   2 allocs/op
+    BenchmarkValidateSimple-4      200000000      9.33 ns/op     0 B/op   0 allocs/op
+    BenchmarkValidateComplex-4       3000000    469    ns/op     0 B/op   0 allocs/op
+    BenchmarkValidateAverage-4       5000000    256    ns/op     0 B/op   0 allocs/op
+    BenchmarkCompareSimple-4       100000000     11.8  ns/op     0 B/op   0 allocs/op
+    BenchmarkCompareComplex-4       50000000     30.8  ns/op     0 B/op   0 allocs/op
+    BenchmarkCompareAverage-4       30000000     41.5  ns/op     0 B/op   0 allocs/op
+    BenchmarkSort-4                  3000000    419    ns/op   256 B/op   2 allocs/op
+    BenchmarkRangeParseSimple-4      2000000    850    ns/op   192 B/op   5 allocs/op
+    BenchmarkRangeParseAverage-4     1000000   1677    ns/op   400 B/op  10 allocs/op
+    BenchmarkRangeParseComplex-4      300000   5214    ns/op  1440 B/op  30 allocs/op
+    BenchmarkRangeMatchSimple-4     50000000     25.6  ns/op     0 B/op   0 allocs/op
+    BenchmarkRangeMatchAverage-4    30000000     56.4  ns/op     0 B/op   0 allocs/op
+    BenchmarkRangeMatchComplex-4    10000000    153    ns/op     0 B/op   0 allocs/op
+
+See benchmark cases at [semver_test.go](semver_test.go)
+
+
+Motivation
+-----
+
+I simply couldn't find any lib supporting the full spec. Others were just wrong or used reflection and regex which i don't like.
+
+
+Contribution
+-----
+
+Feel free to make a pull request. For bigger changes create a issue first to discuss about it.
+
+
+License
+-----
+
+See [LICENSE](LICENSE) file.

vendor/github.com/blang/semver/json.go 🔗

@@ -0,0 +1,23 @@
+package semver
+
+import (
+	"encoding/json"
+)
+
+// MarshalJSON implements the encoding/json.Marshaler interface.
+func (v Version) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.String())
+}
+
+// UnmarshalJSON implements the encoding/json.Unmarshaler interface.
+func (v *Version) UnmarshalJSON(data []byte) (err error) {
+	var versionString string
+
+	if err = json.Unmarshal(data, &versionString); err != nil {
+		return
+	}
+
+	*v, err = Parse(versionString)
+
+	return
+}

vendor/github.com/blang/semver/package.json 🔗

@@ -0,0 +1,17 @@
+{
+  "author": "blang",
+  "bugs": {
+    "URL": "https://github.com/blang/semver/issues",
+    "url": "https://github.com/blang/semver/issues"
+  },
+  "gx": {
+    "dvcsimport": "github.com/blang/semver"
+  },
+  "gxVersion": "0.10.0",
+  "language": "go",
+  "license": "MIT",
+  "name": "semver",
+  "releaseCmd": "git commit -a -m \"gx publish $VERSION\"",
+  "version": "3.5.1"
+}
+

vendor/github.com/blang/semver/range.go 🔗

@@ -0,0 +1,416 @@
+package semver
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+	"unicode"
+)
+
+type wildcardType int
+
+const (
+	noneWildcard  wildcardType = iota
+	majorWildcard wildcardType = 1
+	minorWildcard wildcardType = 2
+	patchWildcard wildcardType = 3
+)
+
+func wildcardTypefromInt(i int) wildcardType {
+	switch i {
+	case 1:
+		return majorWildcard
+	case 2:
+		return minorWildcard
+	case 3:
+		return patchWildcard
+	default:
+		return noneWildcard
+	}
+}
+
+type comparator func(Version, Version) bool
+
+var (
+	compEQ comparator = func(v1 Version, v2 Version) bool {
+		return v1.Compare(v2) == 0
+	}
+	compNE = func(v1 Version, v2 Version) bool {
+		return v1.Compare(v2) != 0
+	}
+	compGT = func(v1 Version, v2 Version) bool {
+		return v1.Compare(v2) == 1
+	}
+	compGE = func(v1 Version, v2 Version) bool {
+		return v1.Compare(v2) >= 0
+	}
+	compLT = func(v1 Version, v2 Version) bool {
+		return v1.Compare(v2) == -1
+	}
+	compLE = func(v1 Version, v2 Version) bool {
+		return v1.Compare(v2) <= 0
+	}
+)
+
+type versionRange struct {
+	v Version
+	c comparator
+}
+
+// rangeFunc creates a Range from the given versionRange.
+func (vr *versionRange) rangeFunc() Range {
+	return Range(func(v Version) bool {
+		return vr.c(v, vr.v)
+	})
+}
+
+// Range represents a range of versions.
+// A Range can be used to check if a Version satisfies it:
+//
+//     range, err := semver.ParseRange(">1.0.0 <2.0.0")
+//     range(semver.MustParse("1.1.1") // returns true
+type Range func(Version) bool
+
+// OR combines the existing Range with another Range using logical OR.
+func (rf Range) OR(f Range) Range {
+	return Range(func(v Version) bool {
+		return rf(v) || f(v)
+	})
+}
+
+// AND combines the existing Range with another Range using logical AND.
+func (rf Range) AND(f Range) Range {
+	return Range(func(v Version) bool {
+		return rf(v) && f(v)
+	})
+}
+
+// ParseRange parses a range and returns a Range.
+// If the range could not be parsed an error is returned.
+//
+// Valid ranges are:
+//   - "<1.0.0"
+//   - "<=1.0.0"
+//   - ">1.0.0"
+//   - ">=1.0.0"
+//   - "1.0.0", "=1.0.0", "==1.0.0"
+//   - "!1.0.0", "!=1.0.0"
+//
+// A Range can consist of multiple ranges separated by space:
+// Ranges can be linked by logical AND:
+//   - ">1.0.0 <2.0.0" would match between both ranges, so "1.1.1" and "1.8.7" but not "1.0.0" or "2.0.0"
+//   - ">1.0.0 <3.0.0 !2.0.3-beta.2" would match every version between 1.0.0 and 3.0.0 except 2.0.3-beta.2
+//
+// Ranges can also be linked by logical OR:
+//   - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x"
+//
+// AND has a higher precedence than OR. It's not possible to use brackets.
+//
+// Ranges can be combined by both AND and OR
+//
+//  - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1`
+func ParseRange(s string) (Range, error) {
+	parts := splitAndTrim(s)
+	orParts, err := splitORParts(parts)
+	if err != nil {
+		return nil, err
+	}
+	expandedParts, err := expandWildcardVersion(orParts)
+	if err != nil {
+		return nil, err
+	}
+	var orFn Range
+	for _, p := range expandedParts {
+		var andFn Range
+		for _, ap := range p {
+			opStr, vStr, err := splitComparatorVersion(ap)
+			if err != nil {
+				return nil, err
+			}
+			vr, err := buildVersionRange(opStr, vStr)
+			if err != nil {
+				return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err)
+			}
+			rf := vr.rangeFunc()
+
+			// Set function
+			if andFn == nil {
+				andFn = rf
+			} else { // Combine with existing function
+				andFn = andFn.AND(rf)
+			}
+		}
+		if orFn == nil {
+			orFn = andFn
+		} else {
+			orFn = orFn.OR(andFn)
+		}
+
+	}
+	return orFn, nil
+}
+
+// splitORParts splits the already cleaned parts by '||'.
+// Checks for invalid positions of the operator and returns an
+// error if found.
+func splitORParts(parts []string) ([][]string, error) {
+	var ORparts [][]string
+	last := 0
+	for i, p := range parts {
+		if p == "||" {
+			if i == 0 {
+				return nil, fmt.Errorf("First element in range is '||'")
+			}
+			ORparts = append(ORparts, parts[last:i])
+			last = i + 1
+		}
+	}
+	if last == len(parts) {
+		return nil, fmt.Errorf("Last element in range is '||'")
+	}
+	ORparts = append(ORparts, parts[last:])
+	return ORparts, nil
+}
+
+// buildVersionRange takes a slice of 2: operator and version
+// and builds a versionRange, otherwise an error.
+func buildVersionRange(opStr, vStr string) (*versionRange, error) {
+	c := parseComparator(opStr)
+	if c == nil {
+		return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, ""))
+	}
+	v, err := Parse(vStr)
+	if err != nil {
+		return nil, fmt.Errorf("Could not parse version %q in %q: %s", vStr, strings.Join([]string{opStr, vStr}, ""), err)
+	}
+
+	return &versionRange{
+		v: v,
+		c: c,
+	}, nil
+
+}
+
+// inArray checks if a byte is contained in an array of bytes
+func inArray(s byte, list []byte) bool {
+	for _, el := range list {
+		if el == s {
+			return true
+		}
+	}
+	return false
+}
+
+// splitAndTrim splits a range string by spaces and cleans whitespaces
+func splitAndTrim(s string) (result []string) {
+	last := 0
+	var lastChar byte
+	excludeFromSplit := []byte{'>', '<', '='}
+	for i := 0; i < len(s); i++ {
+		if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) {
+			if last < i-1 {
+				result = append(result, s[last:i])
+			}
+			last = i + 1
+		} else if s[i] != ' ' {
+			lastChar = s[i]
+		}
+	}
+	if last < len(s)-1 {
+		result = append(result, s[last:])
+	}
+
+	for i, v := range result {
+		result[i] = strings.Replace(v, " ", "", -1)
+	}
+
+	// parts := strings.Split(s, " ")
+	// for _, x := range parts {
+	// 	if s := strings.TrimSpace(x); len(s) != 0 {
+	// 		result = append(result, s)
+	// 	}
+	// }
+	return
+}
+
+// splitComparatorVersion splits the comparator from the version.
+// Input must be free of leading or trailing spaces.
+func splitComparatorVersion(s string) (string, string, error) {
+	i := strings.IndexFunc(s, unicode.IsDigit)
+	if i == -1 {
+		return "", "", fmt.Errorf("Could not get version from string: %q", s)
+	}
+	return strings.TrimSpace(s[0:i]), s[i:], nil
+}
+
+// getWildcardType will return the type of wildcard that the
+// passed version contains
+func getWildcardType(vStr string) wildcardType {
+	parts := strings.Split(vStr, ".")
+	nparts := len(parts)
+	wildcard := parts[nparts-1]
+
+	possibleWildcardType := wildcardTypefromInt(nparts)
+	if wildcard == "x" {
+		return possibleWildcardType
+	}
+
+	return noneWildcard
+}
+
+// createVersionFromWildcard will convert a wildcard version
+// into a regular version, replacing 'x's with '0's, handling
+// special cases like '1.x.x' and '1.x'
+func createVersionFromWildcard(vStr string) string {
+	// handle 1.x.x
+	vStr2 := strings.Replace(vStr, ".x.x", ".x", 1)
+	vStr2 = strings.Replace(vStr2, ".x", ".0", 1)
+	parts := strings.Split(vStr2, ".")
+
+	// handle 1.x
+	if len(parts) == 2 {
+		return vStr2 + ".0"
+	}
+
+	return vStr2
+}
+
+// incrementMajorVersion will increment the major version
+// of the passed version
+func incrementMajorVersion(vStr string) (string, error) {
+	parts := strings.Split(vStr, ".")
+	i, err := strconv.Atoi(parts[0])
+	if err != nil {
+		return "", err
+	}
+	parts[0] = strconv.Itoa(i + 1)
+
+	return strings.Join(parts, "."), nil
+}
+
+// incrementMajorVersion will increment the minor version
+// of the passed version
+func incrementMinorVersion(vStr string) (string, error) {
+	parts := strings.Split(vStr, ".")
+	i, err := strconv.Atoi(parts[1])
+	if err != nil {
+		return "", err
+	}
+	parts[1] = strconv.Itoa(i + 1)
+
+	return strings.Join(parts, "."), nil
+}
+
+// expandWildcardVersion will expand wildcards inside versions
+// following these rules:
+//
+// * when dealing with patch wildcards:
+// >= 1.2.x    will become    >= 1.2.0
+// <= 1.2.x    will become    <  1.3.0
+// >  1.2.x    will become    >= 1.3.0
+// <  1.2.x    will become    <  1.2.0
+// != 1.2.x    will become    <  1.2.0 >= 1.3.0
+//
+// * when dealing with minor wildcards:
+// >= 1.x      will become    >= 1.0.0
+// <= 1.x      will become    <  2.0.0
+// >  1.x      will become    >= 2.0.0
+// <  1.0      will become    <  1.0.0
+// != 1.x      will become    <  1.0.0 >= 2.0.0
+//
+// * when dealing with wildcards without
+// version operator:
+// 1.2.x       will become    >= 1.2.0 < 1.3.0
+// 1.x         will become    >= 1.0.0 < 2.0.0
+func expandWildcardVersion(parts [][]string) ([][]string, error) {
+	var expandedParts [][]string
+	for _, p := range parts {
+		var newParts []string
+		for _, ap := range p {
+			if strings.Contains(ap, "x") {
+				opStr, vStr, err := splitComparatorVersion(ap)
+				if err != nil {
+					return nil, err
+				}
+
+				versionWildcardType := getWildcardType(vStr)
+				flatVersion := createVersionFromWildcard(vStr)
+
+				var resultOperator string
+				var shouldIncrementVersion bool
+				switch opStr {
+				case ">":
+					resultOperator = ">="
+					shouldIncrementVersion = true
+				case ">=":
+					resultOperator = ">="
+				case "<":
+					resultOperator = "<"
+				case "<=":
+					resultOperator = "<"
+					shouldIncrementVersion = true
+				case "", "=", "==":
+					newParts = append(newParts, ">="+flatVersion)
+					resultOperator = "<"
+					shouldIncrementVersion = true
+				case "!=", "!":
+					newParts = append(newParts, "<"+flatVersion)
+					resultOperator = ">="
+					shouldIncrementVersion = true
+				}
+
+				var resultVersion string
+				if shouldIncrementVersion {
+					switch versionWildcardType {
+					case patchWildcard:
+						resultVersion, _ = incrementMinorVersion(flatVersion)
+					case minorWildcard:
+						resultVersion, _ = incrementMajorVersion(flatVersion)
+					}
+				} else {
+					resultVersion = flatVersion
+				}
+
+				ap = resultOperator + resultVersion
+			}
+			newParts = append(newParts, ap)
+		}
+		expandedParts = append(expandedParts, newParts)
+	}
+
+	return expandedParts, nil
+}
+
+func parseComparator(s string) comparator {
+	switch s {
+	case "==":
+		fallthrough
+	case "":
+		fallthrough
+	case "=":
+		return compEQ
+	case ">":
+		return compGT
+	case ">=":
+		return compGE
+	case "<":
+		return compLT
+	case "<=":
+		return compLE
+	case "!":
+		fallthrough
+	case "!=":
+		return compNE
+	}
+
+	return nil
+}
+
+// MustParseRange is like ParseRange but panics if the range cannot be parsed.
+func MustParseRange(s string) Range {
+	r, err := ParseRange(s)
+	if err != nil {
+		panic(`semver: ParseRange(` + s + `): ` + err.Error())
+	}
+	return r
+}

vendor/github.com/blang/semver/semver.go 🔗

@@ -0,0 +1,455 @@
+package semver
+
+import (
+	"errors"
+	"fmt"
+	"strconv"
+	"strings"
+)
+
+const (
+	numbers  string = "0123456789"
+	alphas          = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
+	alphanum        = alphas + numbers
+)
+
+// SpecVersion is the latest fully supported spec version of semver
+var SpecVersion = Version{
+	Major: 2,
+	Minor: 0,
+	Patch: 0,
+}
+
+// Version represents a semver compatible version
+type Version struct {
+	Major uint64
+	Minor uint64
+	Patch uint64
+	Pre   []PRVersion
+	Build []string //No Precedence
+}
+
+// Version to string
+func (v Version) String() string {
+	b := make([]byte, 0, 5)
+	b = strconv.AppendUint(b, v.Major, 10)
+	b = append(b, '.')
+	b = strconv.AppendUint(b, v.Minor, 10)
+	b = append(b, '.')
+	b = strconv.AppendUint(b, v.Patch, 10)
+
+	if len(v.Pre) > 0 {
+		b = append(b, '-')
+		b = append(b, v.Pre[0].String()...)
+
+		for _, pre := range v.Pre[1:] {
+			b = append(b, '.')
+			b = append(b, pre.String()...)
+		}
+	}
+
+	if len(v.Build) > 0 {
+		b = append(b, '+')
+		b = append(b, v.Build[0]...)
+
+		for _, build := range v.Build[1:] {
+			b = append(b, '.')
+			b = append(b, build...)
+		}
+	}
+
+	return string(b)
+}
+
+// Equals checks if v is equal to o.
+func (v Version) Equals(o Version) bool {
+	return (v.Compare(o) == 0)
+}
+
+// EQ checks if v is equal to o.
+func (v Version) EQ(o Version) bool {
+	return (v.Compare(o) == 0)
+}
+
+// NE checks if v is not equal to o.
+func (v Version) NE(o Version) bool {
+	return (v.Compare(o) != 0)
+}
+
+// GT checks if v is greater than o.
+func (v Version) GT(o Version) bool {
+	return (v.Compare(o) == 1)
+}
+
+// GTE checks if v is greater than or equal to o.
+func (v Version) GTE(o Version) bool {
+	return (v.Compare(o) >= 0)
+}
+
+// GE checks if v is greater than or equal to o.
+func (v Version) GE(o Version) bool {
+	return (v.Compare(o) >= 0)
+}
+
+// LT checks if v is less than o.
+func (v Version) LT(o Version) bool {
+	return (v.Compare(o) == -1)
+}
+
+// LTE checks if v is less than or equal to o.
+func (v Version) LTE(o Version) bool {
+	return (v.Compare(o) <= 0)
+}
+
+// LE checks if v is less than or equal to o.
+func (v Version) LE(o Version) bool {
+	return (v.Compare(o) <= 0)
+}
+
+// Compare compares Versions v to o:
+// -1 == v is less than o
+// 0 == v is equal to o
+// 1 == v is greater than o
+func (v Version) Compare(o Version) int {
+	if v.Major != o.Major {
+		if v.Major > o.Major {
+			return 1
+		}
+		return -1
+	}
+	if v.Minor != o.Minor {
+		if v.Minor > o.Minor {
+			return 1
+		}
+		return -1
+	}
+	if v.Patch != o.Patch {
+		if v.Patch > o.Patch {
+			return 1
+		}
+		return -1
+	}
+
+	// Quick comparison if a version has no prerelease versions
+	if len(v.Pre) == 0 && len(o.Pre) == 0 {
+		return 0
+	} else if len(v.Pre) == 0 && len(o.Pre) > 0 {
+		return 1
+	} else if len(v.Pre) > 0 && len(o.Pre) == 0 {
+		return -1
+	}
+
+	i := 0
+	for ; i < len(v.Pre) && i < len(o.Pre); i++ {
+		if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 {
+			continue
+		} else if comp == 1 {
+			return 1
+		} else {
+			return -1
+		}
+	}
+
+	// If all pr versions are the equal but one has further prversion, this one greater
+	if i == len(v.Pre) && i == len(o.Pre) {
+		return 0
+	} else if i == len(v.Pre) && i < len(o.Pre) {
+		return -1
+	} else {
+		return 1
+	}
+
+}
+
+// IncrementPatch increments the patch version
+func (v *Version) IncrementPatch() error {
+	if v.Major == 0 {
+		return fmt.Errorf("Patch version can not be incremented for %q", v.String())
+	}
+	v.Patch += 1
+	return nil
+}
+
+// IncrementMinor increments the minor version
+func (v *Version) IncrementMinor() error {
+	if v.Major == 0 {
+		return fmt.Errorf("Minor version can not be incremented for %q", v.String())
+	}
+	v.Minor += 1
+	v.Patch = 0
+	return nil
+}
+
+// IncrementMajor increments the major version
+func (v *Version) IncrementMajor() error {
+	if v.Major == 0 {
+		return fmt.Errorf("Major version can not be incremented for %q", v.String())
+	}
+	v.Major += 1
+	v.Minor = 0
+	v.Patch = 0
+	return nil
+}
+
+// Validate validates v and returns error in case
+func (v Version) Validate() error {
+	// Major, Minor, Patch already validated using uint64
+
+	for _, pre := range v.Pre {
+		if !pre.IsNum { //Numeric prerelease versions already uint64
+			if len(pre.VersionStr) == 0 {
+				return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr)
+			}
+			if !containsOnly(pre.VersionStr, alphanum) {
+				return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr)
+			}
+		}
+	}
+
+	for _, build := range v.Build {
+		if len(build) == 0 {
+			return fmt.Errorf("Build meta data can not be empty %q", build)
+		}
+		if !containsOnly(build, alphanum) {
+			return fmt.Errorf("Invalid character(s) found in build meta data %q", build)
+		}
+	}
+
+	return nil
+}
+
+// New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error
+func New(s string) (vp *Version, err error) {
+	v, err := Parse(s)
+	vp = &v
+	return
+}
+
+// Make is an alias for Parse, parses version string and returns a validated Version or error
+func Make(s string) (Version, error) {
+	return Parse(s)
+}
+
+// ParseTolerant allows for certain version specifications that do not strictly adhere to semver
+// specs to be parsed by this library. It does so by normalizing versions before passing them to
+// Parse(). It currently trims spaces, removes a "v" prefix, adds a 0 patch number to versions
+// with only major and minor components specified, and removes leading 0s.
+func ParseTolerant(s string) (Version, error) {
+	s = strings.TrimSpace(s)
+	s = strings.TrimPrefix(s, "v")
+
+	// Split into major.minor.(patch+pr+meta)
+	parts := strings.SplitN(s, ".", 3)
+	// Remove leading zeros.
+	for i, p := range parts {
+		if len(p) > 1 {
+			parts[i] = strings.TrimPrefix(p, "0")
+		}
+	}
+	// Fill up shortened versions.
+	if len(parts) < 3 {
+		if strings.ContainsAny(parts[len(parts)-1], "+-") {
+			return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data")
+		}
+		for len(parts) < 3 {
+			parts = append(parts, "0")
+		}
+	}
+	s = strings.Join(parts, ".")
+
+	return Parse(s)
+}
+
+// Parse parses version string and returns a validated Version or error
+func Parse(s string) (Version, error) {
+	if len(s) == 0 {
+		return Version{}, errors.New("Version string empty")
+	}
+
+	// Split into major.minor.(patch+pr+meta)
+	parts := strings.SplitN(s, ".", 3)
+	if len(parts) != 3 {
+		return Version{}, errors.New("No Major.Minor.Patch elements found")
+	}
+
+	// Major
+	if !containsOnly(parts[0], numbers) {
+		return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0])
+	}
+	if hasLeadingZeroes(parts[0]) {
+		return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0])
+	}
+	major, err := strconv.ParseUint(parts[0], 10, 64)
+	if err != nil {
+		return Version{}, err
+	}
+
+	// Minor
+	if !containsOnly(parts[1], numbers) {
+		return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1])
+	}
+	if hasLeadingZeroes(parts[1]) {
+		return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1])
+	}
+	minor, err := strconv.ParseUint(parts[1], 10, 64)
+	if err != nil {
+		return Version{}, err
+	}
+
+	v := Version{}
+	v.Major = major
+	v.Minor = minor
+
+	var build, prerelease []string
+	patchStr := parts[2]
+
+	if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 {
+		build = strings.Split(patchStr[buildIndex+1:], ".")
+		patchStr = patchStr[:buildIndex]
+	}
+
+	if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 {
+		prerelease = strings.Split(patchStr[preIndex+1:], ".")
+		patchStr = patchStr[:preIndex]
+	}
+
+	if !containsOnly(patchStr, numbers) {
+		return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr)
+	}
+	if hasLeadingZeroes(patchStr) {
+		return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr)
+	}
+	patch, err := strconv.ParseUint(patchStr, 10, 64)
+	if err != nil {
+		return Version{}, err
+	}
+
+	v.Patch = patch
+
+	// Prerelease
+	for _, prstr := range prerelease {
+		parsedPR, err := NewPRVersion(prstr)
+		if err != nil {
+			return Version{}, err
+		}
+		v.Pre = append(v.Pre, parsedPR)
+	}
+
+	// Build meta data
+	for _, str := range build {
+		if len(str) == 0 {
+			return Version{}, errors.New("Build meta data is empty")
+		}
+		if !containsOnly(str, alphanum) {
+			return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str)
+		}
+		v.Build = append(v.Build, str)
+	}
+
+	return v, nil
+}
+
+// MustParse is like Parse but panics if the version cannot be parsed.
+func MustParse(s string) Version {
+	v, err := Parse(s)
+	if err != nil {
+		panic(`semver: Parse(` + s + `): ` + err.Error())
+	}
+	return v
+}
+
+// PRVersion represents a PreRelease Version
+type PRVersion struct {
+	VersionStr string
+	VersionNum uint64
+	IsNum      bool
+}
+
+// NewPRVersion creates a new valid prerelease version
+func NewPRVersion(s string) (PRVersion, error) {
+	if len(s) == 0 {
+		return PRVersion{}, errors.New("Prerelease is empty")
+	}
+	v := PRVersion{}
+	if containsOnly(s, numbers) {
+		if hasLeadingZeroes(s) {
+			return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s)
+		}
+		num, err := strconv.ParseUint(s, 10, 64)
+
+		// Might never be hit, but just in case
+		if err != nil {
+			return PRVersion{}, err
+		}
+		v.VersionNum = num
+		v.IsNum = true
+	} else if containsOnly(s, alphanum) {
+		v.VersionStr = s
+		v.IsNum = false
+	} else {
+		return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s)
+	}
+	return v, nil
+}
+
+// IsNumeric checks if prerelease-version is numeric
+func (v PRVersion) IsNumeric() bool {
+	return v.IsNum
+}
+
+// Compare compares two PreRelease Versions v and o:
+// -1 == v is less than o
+// 0 == v is equal to o
+// 1 == v is greater than o
+func (v PRVersion) Compare(o PRVersion) int {
+	if v.IsNum && !o.IsNum {
+		return -1
+	} else if !v.IsNum && o.IsNum {
+		return 1
+	} else if v.IsNum && o.IsNum {
+		if v.VersionNum == o.VersionNum {
+			return 0
+		} else if v.VersionNum > o.VersionNum {
+			return 1
+		} else {
+			return -1
+		}
+	} else { // both are Alphas
+		if v.VersionStr == o.VersionStr {
+			return 0
+		} else if v.VersionStr > o.VersionStr {
+			return 1
+		} else {
+			return -1
+		}
+	}
+}
+
+// PreRelease version to string
+func (v PRVersion) String() string {
+	if v.IsNum {
+		return strconv.FormatUint(v.VersionNum, 10)
+	}
+	return v.VersionStr
+}
+
+func containsOnly(s string, set string) bool {
+	return strings.IndexFunc(s, func(r rune) bool {
+		return !strings.ContainsRune(set, r)
+	}) == -1
+}
+
+func hasLeadingZeroes(s string) bool {
+	return len(s) > 1 && s[0] == '0'
+}
+
+// NewBuildVersion creates a new valid build version
+func NewBuildVersion(s string) (string, error) {
+	if len(s) == 0 {
+		return "", errors.New("Buildversion is empty")
+	}
+	if !containsOnly(s, alphanum) {
+		return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s)
+	}
+	return s, nil
+}

vendor/github.com/blang/semver/sort.go 🔗

@@ -0,0 +1,28 @@
+package semver
+
+import (
+	"sort"
+)
+
+// Versions represents multiple versions.
+type Versions []Version
+
+// Len returns length of version collection
+func (s Versions) Len() int {
+	return len(s)
+}
+
+// Swap swaps two versions inside the collection by its indices
+func (s Versions) Swap(i, j int) {
+	s[i], s[j] = s[j], s[i]
+}
+
+// Less checks if version at index i is less than version at index j
+func (s Versions) Less(i, j int) bool {
+	return s[i].LT(s[j])
+}
+
+// Sort sorts a slice of versions
+func Sort(versions []Version) {
+	sort.Sort(Versions(versions))
+}

vendor/github.com/blang/semver/sql.go 🔗

@@ -0,0 +1,30 @@
+package semver
+
+import (
+	"database/sql/driver"
+	"fmt"
+)
+
+// Scan implements the database/sql.Scanner interface.
+func (v *Version) Scan(src interface{}) (err error) {
+	var str string
+	switch src := src.(type) {
+	case string:
+		str = src
+	case []byte:
+		str = string(src)
+	default:
+		return fmt.Errorf("version.Scan: cannot convert %T to string", src)
+	}
+
+	if t, err := Parse(str); err == nil {
+		*v = t
+	}
+
+	return
+}
+
+// Value implements the database/sql/driver.Valuer interface.
+func (v Version) Value() (driver.Value, error) {
+	return v.String(), nil
+}

vendor/github.com/gorilla/mux/.travis.yml 🔗

@@ -1,24 +0,0 @@
-language: go
-
-
-matrix:
-  include:
-    - go: 1.7.x
-    - go: 1.8.x
-    - go: 1.9.x
-    - go: 1.10.x
-    - go: 1.11.x
-    - go: 1.x
-      env: LATEST=true
-    - go: tip
-  allow_failures:
-    - go: tip
-
-install:
-  - # Skip
-
-script:
-  - go get -t -v ./...
-  - diff -u <(echo -n) <(gofmt -d .)
-  - if [[ "$LATEST" = true ]]; then go vet .; fi
-  - go test -v -race ./...

vendor/github.com/gorilla/mux/ISSUE_TEMPLATE.md 🔗

@@ -1,11 +0,0 @@
-**What version of Go are you running?** (Paste the output of `go version`)
-
-
-**What version of gorilla/mux are you at?** (Paste the output of `git rev-parse HEAD` inside `$GOPATH/src/github.com/gorilla/mux`)
-
-
-**Describe your problem** (and what you have tried so far)
-
-
-**Paste a minimal, runnable, reproduction of your issue below** (use backticks to format it)
-

vendor/github.com/gorilla/mux/README.md 🔗

@@ -2,6 +2,7 @@
 
 [![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux)
 [![Build Status](https://travis-ci.org/gorilla/mux.svg?branch=master)](https://travis-ci.org/gorilla/mux)
+[![CircleCI](https://circleci.com/gh/gorilla/mux.svg?style=svg)](https://circleci.com/gh/gorilla/mux)
 [![Sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge)
 
 ![Gorilla Logo](http://www.gorillatoolkit.org/static/images/gorilla-icon-64.png)
@@ -29,6 +30,7 @@ The name mux stands for "HTTP request multiplexer". Like the standard `http.Serv
 * [Walking Routes](#walking-routes)
 * [Graceful Shutdown](#graceful-shutdown)
 * [Middleware](#middleware)
+* [Handling CORS Requests](#handling-cors-requests)
 * [Testing Handlers](#testing-handlers)
 * [Full Example](#full-example)
 
@@ -491,6 +493,73 @@ r.Use(amw.Middleware)
 
 Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares _should_ write to `ResponseWriter` if they _are_ going to terminate the request, and they _should not_ write to `ResponseWriter` if they _are not_ going to terminate it.
 
+### Handling CORS Requests
+
+[CORSMethodMiddleware](https://godoc.org/github.com/gorilla/mux#CORSMethodMiddleware) intends to make it easier to strictly set the `Access-Control-Allow-Methods` response header.
+
+* You will still need to use your own CORS handler to set the other CORS headers such as `Access-Control-Allow-Origin`
+* The middleware will set the `Access-Control-Allow-Methods` header to all the method matchers (e.g. `r.Methods(http.MethodGet, http.MethodPut, http.MethodOptions)` -> `Access-Control-Allow-Methods: GET,PUT,OPTIONS`) on a route
+* If you do not specify any methods, then:
+> _Important_: there must be an `OPTIONS` method matcher for the middleware to set the headers.
+
+Here is an example of using `CORSMethodMiddleware` along with a custom `OPTIONS` handler to set all the required CORS headers:
+
+```go
+package main
+
+import (
+	"net/http"
+	"github.com/gorilla/mux"
+)
+
+func main() {
+    r := mux.NewRouter()
+
+    // IMPORTANT: you must specify an OPTIONS method matcher for the middleware to set CORS headers
+    r.HandleFunc("/foo", fooHandler).Methods(http.MethodGet, http.MethodPut, http.MethodPatch, http.MethodOptions)
+    r.Use(mux.CORSMethodMiddleware(r))
+    
+    http.ListenAndServe(":8080", r)
+}
+
+func fooHandler(w http.ResponseWriter, r *http.Request) {
+    w.Header().Set("Access-Control-Allow-Origin", "*")
+    if r.Method == http.MethodOptions {
+        return
+    }
+
+    w.Write([]byte("foo"))
+}
+```
+
+And an request to `/foo` using something like:
+
+```bash
+curl localhost:8080/foo -v
+```
+
+Would look like:
+
+```bash
+*   Trying ::1...
+* TCP_NODELAY set
+* Connected to localhost (::1) port 8080 (#0)
+> GET /foo HTTP/1.1
+> Host: localhost:8080
+> User-Agent: curl/7.59.0
+> Accept: */*
+> 
+< HTTP/1.1 200 OK
+< Access-Control-Allow-Methods: GET,PUT,PATCH,OPTIONS
+< Access-Control-Allow-Origin: *
+< Date: Fri, 28 Jun 2019 20:13:30 GMT
+< Content-Length: 3
+< Content-Type: text/plain; charset=utf-8
+< 
+* Connection #0 to host localhost left intact
+foo
+```
+
 ### Testing Handlers
 
 Testing handlers in a Go web application is straightforward, and _mux_ doesn't complicate this any further. Given two files: `endpoints.go` and `endpoints_test.go`, here's how we'd test an application using _mux_.

vendor/github.com/gorilla/mux/doc.go 🔗

@@ -295,7 +295,7 @@ A more complex authentication middleware, which maps session token to users, cou
 	r := mux.NewRouter()
 	r.HandleFunc("/", handler)
 
-	amw := authenticationMiddleware{}
+	amw := authenticationMiddleware{tokenUsers: make(map[string]string)}
 	amw.Populate()
 
 	r.Use(amw.Middleware)

vendor/github.com/gorilla/mux/middleware.go 🔗

@@ -32,37 +32,19 @@ func (r *Router) useInterface(mw middleware) {
 	r.middlewares = append(r.middlewares, mw)
 }
 
-// CORSMethodMiddleware sets the Access-Control-Allow-Methods response header
-// on a request, by matching routes based only on paths. It also handles
-// OPTIONS requests, by settings Access-Control-Allow-Methods, and then
-// returning without calling the next http handler.
+// CORSMethodMiddleware automatically sets the Access-Control-Allow-Methods response header
+// on requests for routes that have an OPTIONS method matcher to all the method matchers on
+// the route. Routes that do not explicitly handle OPTIONS requests will not be processed
+// by the middleware. See examples for usage.
 func CORSMethodMiddleware(r *Router) MiddlewareFunc {
 	return func(next http.Handler) http.Handler {
 		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
-			var allMethods []string
-
-			err := r.Walk(func(route *Route, _ *Router, _ []*Route) error {
-				for _, m := range route.matchers {
-					if _, ok := m.(*routeRegexp); ok {
-						if m.Match(req, &RouteMatch{}) {
-							methods, err := route.GetMethods()
-							if err != nil {
-								return err
-							}
-
-							allMethods = append(allMethods, methods...)
-						}
-						break
-					}
-				}
-				return nil
-			})
-
+			allMethods, err := getAllMethodsForRoute(r, req)
 			if err == nil {
-				w.Header().Set("Access-Control-Allow-Methods", strings.Join(append(allMethods, "OPTIONS"), ","))
-
-				if req.Method == "OPTIONS" {
-					return
+				for _, v := range allMethods {
+					if v == http.MethodOptions {
+						w.Header().Set("Access-Control-Allow-Methods", strings.Join(allMethods, ","))
+					}
 				}
 			}
 
@@ -70,3 +52,28 @@ func CORSMethodMiddleware(r *Router) MiddlewareFunc {
 		})
 	}
 }
+
+// getAllMethodsForRoute returns all the methods from method matchers matching a given
+// request.
+func getAllMethodsForRoute(r *Router, req *http.Request) ([]string, error) {
+	var allMethods []string
+
+	err := r.Walk(func(route *Route, _ *Router, _ []*Route) error {
+		for _, m := range route.matchers {
+			if _, ok := m.(*routeRegexp); ok {
+				if m.Match(req, &RouteMatch{}) {
+					methods, err := route.GetMethods()
+					if err != nil {
+						return err
+					}
+
+					allMethods = append(allMethods, methods...)
+				}
+				break
+			}
+		}
+		return nil
+	})
+
+	return allMethods, err
+}