summaryrefslogtreecommitdiffstats
path: root/vendor/github.com
diff options
context:
space:
mode:
authorWim <wim@42.be>2021-03-20 22:40:23 +0100
committerGitHub <noreply@github.com>2021-03-20 22:40:23 +0100
commitee5d9b43b54a3becf3cb4025198f24608d35500d (patch)
treedd3614db7423da52f5a71da3001e48d1e4195ea1 /vendor/github.com
parent3a8857c8c9efb2c67fb8c175f31d2b9c617b771b (diff)
downloadmatterbridge-msglm-ee5d9b43b54a3becf3cb4025198f24608d35500d.tar.gz
matterbridge-msglm-ee5d9b43b54a3becf3cb4025198f24608d35500d.tar.bz2
matterbridge-msglm-ee5d9b43b54a3becf3cb4025198f24608d35500d.zip
Update vendor (#1414)
Diffstat (limited to 'vendor/github.com')
-rw-r--r--vendor/github.com/d5/tengo/v2/builtins.go69
-rw-r--r--vendor/github.com/d5/tengo/v2/errors.go3
-rw-r--r--vendor/github.com/gomarkdown/markdown/README.md12
-rw-r--r--vendor/github.com/gomarkdown/markdown/ast/node.go1
-rw-r--r--vendor/github.com/gomarkdown/markdown/html/renderer.go3
-rw-r--r--vendor/github.com/gomarkdown/markdown/parser/block.go31
-rw-r--r--vendor/github.com/google/gops/agent/agent.go3
-rw-r--r--vendor/github.com/labstack/echo/v4/.gitignore1
-rw-r--r--vendor/github.com/labstack/echo/v4/.travis.yml4
-rw-r--r--vendor/github.com/labstack/echo/v4/CHANGELOG.md95
-rw-r--r--vendor/github.com/labstack/echo/v4/Makefile31
-rw-r--r--vendor/github.com/labstack/echo/v4/README.md13
-rw-r--r--vendor/github.com/labstack/echo/v4/bind.go61
-rw-r--r--vendor/github.com/labstack/echo/v4/binder.go1230
-rw-r--r--vendor/github.com/labstack/echo/v4/codecov.yml11
-rw-r--r--vendor/github.com/labstack/echo/v4/context.go34
-rw-r--r--vendor/github.com/labstack/echo/v4/echo.go135
-rw-r--r--vendor/github.com/labstack/echo/v4/go.mod1
-rw-r--r--vendor/github.com/labstack/echo/v4/go.sum2
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/basic_auth.go2
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/compress.go26
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/cors.go90
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/csrf.go23
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/csrf_samesite.go12
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/csrf_samesite_1.12.go12
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/decompress.go120
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/jwt.go19
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/middleware.go43
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/proxy.go31
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/proxy_1_11.go24
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/rate_limiter.go266
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/rewrite.go36
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/slash.go13
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/static.go17
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/timeout.go111
-rw-r--r--vendor/github.com/labstack/echo/v4/response.go4
-rw-r--r--vendor/github.com/labstack/echo/v4/router.go462
-rw-r--r--vendor/github.com/magefile/mage/LICENSE201
-rw-r--r--vendor/github.com/magefile/mage/mg/color.go80
-rw-r--r--vendor/github.com/magefile/mage/mg/color_string.go38
-rw-r--r--vendor/github.com/magefile/mage/mg/deps.go352
-rw-r--r--vendor/github.com/magefile/mage/mg/errors.go51
-rw-r--r--vendor/github.com/magefile/mage/mg/runtime.go136
-rw-r--r--vendor/github.com/magefile/mage/sh/cmd.go177
-rw-r--r--vendor/github.com/magefile/mage/sh/helpers.go40
-rw-r--r--vendor/github.com/mgutz/ansi/README.md2
-rw-r--r--vendor/github.com/mgutz/ansi/ansi.go8
-rw-r--r--vendor/github.com/mgutz/ansi/doc.go1
-rw-r--r--vendor/github.com/mitchellh/mapstructure/.travis.yml3
-rw-r--r--vendor/github.com/mitchellh/mapstructure/CHANGELOG.md40
-rw-r--r--vendor/github.com/mitchellh/mapstructure/go.mod2
-rw-r--r--vendor/github.com/mitchellh/mapstructure/mapstructure.go364
-rw-r--r--vendor/github.com/mreiferson/go-httpclient/.travis.yml5
-rw-r--r--vendor/github.com/sirupsen/logrus/.travis.yml13
-rw-r--r--vendor/github.com/sirupsen/logrus/CHANGELOG.md27
-rw-r--r--vendor/github.com/sirupsen/logrus/README.md2
-rw-r--r--vendor/github.com/sirupsen/logrus/entry.go65
-rw-r--r--vendor/github.com/sirupsen/logrus/go.mod1
-rw-r--r--vendor/github.com/sirupsen/logrus/go.sum2
-rw-r--r--vendor/github.com/sirupsen/logrus/json_formatter.go2
-rw-r--r--vendor/github.com/sirupsen/logrus/logger.go2
-rw-r--r--vendor/github.com/sirupsen/logrus/magefile.go77
-rw-r--r--vendor/github.com/sirupsen/logrus/terminal_check_unix.go2
-rw-r--r--vendor/github.com/sirupsen/logrus/text_formatter.go2
-rw-r--r--vendor/github.com/slack-go/slack/README.md2
-rw-r--r--vendor/github.com/slack-go/slack/attachments.go5
-rw-r--r--vendor/github.com/slack-go/slack/block_element.go1
-rw-r--r--vendor/github.com/slack-go/slack/block_input.go13
-rw-r--r--vendor/github.com/slack-go/slack/block_object.go15
-rw-r--r--vendor/github.com/slack-go/slack/reminders.go4
-rw-r--r--vendor/github.com/slack-go/slack/users.go5
-rw-r--r--vendor/github.com/slack-go/slack/websocket_internals.go1
-rw-r--r--vendor/github.com/slack-go/slack/websocket_managed_conn.go7
-rw-r--r--vendor/github.com/spf13/jwalterweatherman/.gitignore2
-rw-r--r--vendor/github.com/spf13/jwalterweatherman/default_notepad.go30
-rw-r--r--vendor/github.com/spf13/jwalterweatherman/go.mod6
-rw-r--r--vendor/github.com/spf13/jwalterweatherman/log_counter.go51
-rw-r--r--vendor/github.com/spf13/jwalterweatherman/notepad.go57
78 files changed, 4089 insertions, 856 deletions
diff --git a/vendor/github.com/d5/tengo/v2/builtins.go b/vendor/github.com/d5/tengo/v2/builtins.go
index fcda81cd..b954d072 100644
--- a/vendor/github.com/d5/tengo/v2/builtins.go
+++ b/vendor/github.com/d5/tengo/v2/builtins.go
@@ -121,6 +121,10 @@ var builtinFuncs = []*BuiltinFunction{
Name: "format",
Value: builtinFormat,
},
+ {
+ Name: "range",
+ Value: builtinRange,
+ },
}
// GetAllBuiltinFunctions returns all builtin function objects.
@@ -323,6 +327,71 @@ func builtinLen(args ...Object) (Object, error) {
}
}
+//range(start, stop[, step])
+func builtinRange(args ...Object) (Object, error) {
+ numArgs := len(args)
+ if numArgs < 2 || numArgs > 3 {
+ return nil, ErrWrongNumArguments
+ }
+ var start, stop, step *Int
+
+ for i, arg := range args {
+ v, ok := args[i].(*Int)
+ if !ok {
+ var name string
+ switch i {
+ case 0:
+ name = "start"
+ case 1:
+ name = "stop"
+ case 2:
+ name = "step"
+ }
+
+ return nil, ErrInvalidArgumentType{
+ Name: name,
+ Expected: "int",
+ Found: arg.TypeName(),
+ }
+ }
+ if i == 2 && v.Value <= 0 {
+ return nil, ErrInvalidRangeStep
+ }
+ switch i {
+ case 0:
+ start = v
+ case 1:
+ stop = v
+ case 2:
+ step = v
+ }
+ }
+
+ if step == nil {
+ step = &Int{Value: int64(1)}
+ }
+
+ return buildRange(start.Value, stop.Value, step.Value), nil
+}
+
+func buildRange(start, stop, step int64) *Array {
+ array := &Array{}
+ if start <= stop {
+ for i := start; i < stop; i += step {
+ array.Value = append(array.Value, &Int{
+ Value: i,
+ })
+ }
+ } else {
+ for i := start; i > stop; i -= step {
+ array.Value = append(array.Value, &Int{
+ Value: i,
+ })
+ }
+ }
+ return array
+}
+
func builtinFormat(args ...Object) (Object, error) {
numArgs := len(args)
if numArgs == 0 {
diff --git a/vendor/github.com/d5/tengo/v2/errors.go b/vendor/github.com/d5/tengo/v2/errors.go
index a3fd1f3b..8ef610a3 100644
--- a/vendor/github.com/d5/tengo/v2/errors.go
+++ b/vendor/github.com/d5/tengo/v2/errors.go
@@ -49,6 +49,9 @@ var (
// ErrNotImplemented is an error where an Object has not implemented a
// required method.
ErrNotImplemented = errors.New("not implemented")
+
+ // ErrInvalidRangeStep is an error where the step parameter is less than or equal to 0 when using builtin range function.
+ ErrInvalidRangeStep = errors.New("range step must be greater than 0")
)
// ErrInvalidArgumentType represents an invalid argument value type error.
diff --git a/vendor/github.com/gomarkdown/markdown/README.md b/vendor/github.com/gomarkdown/markdown/README.md
index 095db307..a40c7a94 100644
--- a/vendor/github.com/gomarkdown/markdown/README.md
+++ b/vendor/github.com/gomarkdown/markdown/README.md
@@ -207,6 +207,18 @@ implements the following extensions:
Total | 50
```
+ A cell spanning multiple columns (colspan) is supported, just repeat the pipe symbol:
+
+ ```
+ Name | Age
+ --------|------
+ Bob ||
+ Alice | 23
+ ========|======
+ Total | 23
+ ```
+
+
- **Fenced code blocks**. In addition to the normal 4-space
indentation to mark code blocks, you can explicitly mark them
and supply a language (to make syntax highlighting simple). Just
diff --git a/vendor/github.com/gomarkdown/markdown/ast/node.go b/vendor/github.com/gomarkdown/markdown/ast/node.go
index 9d2422af..7881f6e7 100644
--- a/vendor/github.com/gomarkdown/markdown/ast/node.go
+++ b/vendor/github.com/gomarkdown/markdown/ast/node.go
@@ -340,6 +340,7 @@ type TableCell struct {
IsHeader bool // This tells if it's under the header row
Align CellAlignFlags // This holds the value for align attribute
+ ColSpan int // How many columns to span
}
// TableHeader represents markdown table head node
diff --git a/vendor/github.com/gomarkdown/markdown/html/renderer.go b/vendor/github.com/gomarkdown/markdown/html/renderer.go
index 6c56ac12..c57d7eb6 100644
--- a/vendor/github.com/gomarkdown/markdown/html/renderer.go
+++ b/vendor/github.com/gomarkdown/markdown/html/renderer.go
@@ -952,6 +952,9 @@ func (r *Renderer) TableCell(w io.Writer, tableCell *ast.TableCell, entering boo
if align != "" {
attrs = append(attrs, fmt.Sprintf(`align="%s"`, align))
}
+ if colspan := tableCell.ColSpan; colspan > 0 {
+ attrs = append(attrs, fmt.Sprintf(`colspan="%d"`, colspan))
+ }
if ast.GetPrevNode(tableCell) == nil {
r.CR(w)
}
diff --git a/vendor/github.com/gomarkdown/markdown/parser/block.go b/vendor/github.com/gomarkdown/markdown/parser/block.go
index 5ef55e98..cec667b5 100644
--- a/vendor/github.com/gomarkdown/markdown/parser/block.go
+++ b/vendor/github.com/gomarkdown/markdown/parser/block.go
@@ -1204,7 +1204,9 @@ func (p *Parser) tableRow(data []byte, columns []ast.CellAlignFlags, header bool
}
n := len(data)
+ colspans := 0 // keep track of total colspan in this row.
for col = 0; col < len(columns) && i < n; col++ {
+ colspan := 0
for i < n && data[i] == ' ' {
i++
}
@@ -1218,7 +1220,15 @@ func (p *Parser) tableRow(data []byte, columns []ast.CellAlignFlags, header bool
cellEnd := i
// skip the end-of-cell marker, possibly taking us past end of buffer
- i++
+ // each _extra_ | means a colspan
+ for data[i] == '|' && !isBackslashEscaped(data, i) {
+ i++
+ colspan++
+ }
+ // only colspan > 1 make sense.
+ if colspan < 2 {
+ colspan = 0
+ }
for cellEnd > cellStart && cellEnd-1 < n && data[cellEnd-1] == ' ' {
cellEnd--
@@ -1227,9 +1237,19 @@ func (p *Parser) tableRow(data []byte, columns []ast.CellAlignFlags, header bool
block := &ast.TableCell{
IsHeader: header,
Align: columns[col],
+ ColSpan: colspan,
}
block.Content = data[cellStart:cellEnd]
- p.addBlock(block)
+ if cellStart == cellEnd && colspans > 0 {
+ // an empty cell that we should ignore, it exists because of colspan
+ colspans--
+ } else {
+ p.addBlock(block)
+ }
+
+ if colspan > 0 {
+ colspans += colspan - 1
+ }
}
// pad it out with empty columns to get the right number
@@ -1247,7 +1267,12 @@ func (p *Parser) tableRow(data []byte, columns []ast.CellAlignFlags, header bool
// tableFooter parses the (optional) table footer.
func (p *Parser) tableFooter(data []byte) bool {
colCount := 1
- for i := 0; i < len(data) && data[i] != '\n'; i++ {
+ i := 0
+ n := len(data)
+ for i < 3 && i < n && data[i] == ' ' { // ignore up to 3 spaces
+ i++
+ }
+ for ; i < n && data[i] != '\n'; i++ {
if data[i] == '|' && !isBackslashEscaped(data, i) {
colCount++
continue
diff --git a/vendor/github.com/google/gops/agent/agent.go b/vendor/github.com/google/gops/agent/agent.go
index 441814c9..80be4bb6 100644
--- a/vendor/github.com/google/gops/agent/agent.go
+++ b/vendor/github.com/google/gops/agent/agent.go
@@ -16,6 +16,7 @@ import (
"net"
"os"
gosignal "os/signal"
+ "path/filepath"
"runtime"
"runtime/debug"
"runtime/pprof"
@@ -113,7 +114,7 @@ func Listen(opts Options) error {
return err
}
port := listener.Addr().(*net.TCPAddr).Port
- portfile = fmt.Sprintf("%s/%d", gopsdir, os.Getpid())
+ portfile = filepath.Join(gopsdir, strconv.Itoa(os.Getpid()))
err = ioutil.WriteFile(portfile, []byte(strconv.Itoa(port)), os.ModePerm)
if err != nil {
return err
diff --git a/vendor/github.com/labstack/echo/v4/.gitignore b/vendor/github.com/labstack/echo/v4/.gitignore
index dd74acca..dbadf3bd 100644
--- a/vendor/github.com/labstack/echo/v4/.gitignore
+++ b/vendor/github.com/labstack/echo/v4/.gitignore
@@ -5,3 +5,4 @@ vendor
.idea
*.iml
*.out
+.vscode
diff --git a/vendor/github.com/labstack/echo/v4/.travis.yml b/vendor/github.com/labstack/echo/v4/.travis.yml
index ef826e95..67d45ad7 100644
--- a/vendor/github.com/labstack/echo/v4/.travis.yml
+++ b/vendor/github.com/labstack/echo/v4/.travis.yml
@@ -1,3 +1,7 @@
+arch:
+ - amd64
+ - ppc64le
+
language: go
go:
- 1.14.x
diff --git a/vendor/github.com/labstack/echo/v4/CHANGELOG.md b/vendor/github.com/labstack/echo/v4/CHANGELOG.md
new file mode 100644
index 00000000..b5047883
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/CHANGELOG.md
@@ -0,0 +1,95 @@
+# Changelog
+
+## v4.2.1 - 2020-03-08
+
+**Important notes**
+
+Due to a datarace the config parameters for the newly added timeout middleware required a change.
+See the [docs](https://echo.labstack.com/middleware/timeout).
+A performance regression has been fixed, even bringing better performance than before for some routing scenarios.
+
+**Fixes**
+
+* Fix performance regression caused by path escaping (#1777, #1798, #1799, aldas)
+* Avoid context canceled errors (#1789, clwluvw)
+* Improve router to use on stack backtracking (#1791, aldas, stffabi)
+* Fix panic in timeout middleware not being not recovered and cause application crash (#1794, aldas)
+* Fix Echo.Serve() not serving on HTTP port correctly when TLSListener is used (#1785, #1793, aldas)
+* Apply go fmt (#1788, Le0tk0k)
+* Uses strings.Equalfold (#1790, rkilingr)
+* Improve code quality (#1792, withshubh)
+
+This release was made possible by our **contributors**:
+aldas, clwluvw, lammel, Le0tk0k, maciej-jezierski, rkilingr, stffabi, withshubh
+
+## v4.2.0 - 2020-02-11
+
+**Important notes**
+
+The behaviour for binding data has been reworked for compatibility with echo before v4.1.11 by
+enforcing `explicit tagging` for processing parameters. This **may break** your code if you
+expect combined handling of query/path/form params.
+Please see the updated documentation for [request](https://echo.labstack.com/guide/request) and [binding](https://echo.labstack.com/guide/request)
+
+The handling for rewrite rules has been slightly adjusted to expand `*` to a non-greedy `(.*?)` capture group. This is only relevant if multiple asterisks are used in your rules.
+Please see [rewrite](https://echo.labstack.com/middleware/rewrite) and [proxy](https://echo.labstack.com/middleware/proxy) for details.
+
+**Security**
+
+* Fix directory traversal vulnerability for Windows (#1718, little-cui)
+* Fix open redirect vulnerability with trailing slash (#1771,#1775 aldas,GeoffreyFrogeye)
+
+**Enhancements**
+
+* Add Echo#ListenerNetwork as configuration (#1667, pafuent)
+* Add ability to change the status code using response beforeFuncs (#1706, RashadAnsari)
+* Echo server startup to allow data race free access to listener address
+* Binder: Restore pre v4.1.11 behaviour for c.Bind() to use query params only for GET or DELETE methods (#1727, aldas)
+* Binder: Add separate methods to bind only query params, path params or request body (#1681, aldas)
+* Binder: New fluent binder for query/path/form parameter binding (#1717, #1736, aldas)
+* Router: Performance improvements for missed routes (#1689, pafuent)
+* Router: Improve performance for Real-IP detection using IndexByte instead of Split (#1640, imxyb)
+* Middleware: Support real regex rules for rewrite and proxy middleware (#1767)
+* Middleware: New rate limiting middleware (#1724, iambenkay)
+* Middleware: New timeout middleware implementation for go1.13+ (#1743, )
+* Middleware: Allow regex pattern for CORS middleware (#1623, KlotzAndrew)
+* Middleware: Add IgnoreBase parameter to static middleware (#1701, lnenad, iambenkay)
+* Middleware: Add an optional custom function to CORS middleware to validate origin (#1651, curvegrid)
+* Middleware: Support form fields in JWT middleware (#1704, rkfg)
+* Middleware: Use sync.Pool for (de)compress middleware to improve performance (#1699, #1672, pafuent)
+* Middleware: Add decompress middleware to support gzip compressed requests (#1687, arun0009)
+* Middleware: Add ErrJWTInvalid for JWT middleware (#1627, juanbelieni)
+* Middleware: Add SameSite mode for CSRF cookies to support iframes (#1524, pr0head)
+
+**Fixes**
+
+* Fix handling of special trailing slash case for partial prefix (#1741, stffabi)
+* Fix handling of static routes with trailing slash (#1747)
+* Fix Static files route not working (#1671, pwli0755, lammel)
+* Fix use of caret(^) in regex for rewrite middleware (#1588, chotow)
+* Fix Echo#Reverse for Any type routes (#1695, pafuent)
+* Fix Router#Find panic with infinite loop (#1661, pafuent)
+* Fix Router#Find panic fails on Param paths (#1659, pafuent)
+* Fix DefaultHTTPErrorHandler with Debug=true (#1477, lammel)
+* Fix incorrect CORS headers (#1669, ulasakdeniz)
+* Fix proxy middleware rewritePath to use url with updated tests (#1630, arun0009)
+* Fix rewritePath for proxy middleware to use escaped path in (#1628, arun0009)
+* Remove unless defer (#1656, imxyb)
+
+**General**
+
+* New maintainers for Echo: Roland Lammel (@lammel) and Pablo Andres Fuente (@pafuent)
+* Add GitHub action to compare benchmarks (#1702, pafuent)
+* Binding query/path params and form fields to struct only works for explicit tags (#1729,#1734, aldas)
+* Add support for Go 1.15 in CI (#1683, asahasrabuddhe)
+* Add test for request id to remain unchanged if provided (#1719, iambenkay)
+* Refactor echo instance listener access and startup to speed up testing (#1735, aldas)
+* Refactor and improve various tests for binding and routing
+* Run test workflow only for relevant changes (#1637, #1636, pofl)
+* Update .travis.yml (#1662, santosh653)
+* Update README.md with an recents framework benchmark (#1679, pafuent)
+
+This release was made possible by **over 100 commits** from more than **20 contributors**:
+asahasrabuddhe, aldas, AndrewKlotz, arun0009, chotow, curvegrid, iambenkay, imxyb,
+juanbelieni, lammel, little-cui, lnenad, pafuent, pofl, pr0head, pwli, RashadAnsari,
+rkfg, santosh653, segfiner, stffabi, ulasakdeniz
diff --git a/vendor/github.com/labstack/echo/v4/Makefile b/vendor/github.com/labstack/echo/v4/Makefile
index dfcb6c02..48061f7e 100644
--- a/vendor/github.com/labstack/echo/v4/Makefile
+++ b/vendor/github.com/labstack/echo/v4/Makefile
@@ -1,3 +1,34 @@
+PKG := "github.com/labstack/echo"
+PKG_LIST := $(shell go list ${PKG}/...)
+
tag:
@git tag `grep -P '^\tversion = ' echo.go|cut -f2 -d'"'`
@git tag|grep -v ^v
+
+.DEFAULT_GOAL := check
+check: lint vet race ## Check project
+
+init:
+ @go get -u golang.org/x/lint/golint
+
+lint: ## Lint the files
+ @golint -set_exit_status ${PKG_LIST}
+
+vet: ## Vet the files
+ @go vet ${PKG_LIST}
+
+test: ## Run tests
+ @go test -short ${PKG_LIST}
+
+race: ## Run tests with data race detector
+ @go test -race ${PKG_LIST}
+
+benchmark: ## Run benchmarks
+ @go test -run="-" -bench=".*" ${PKG_LIST}
+
+help: ## Display this help screen
+ @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
+
+goversion ?= "1.15"
+test_version: ## Run tests inside Docker with given version (defaults to 1.15 oldest supported). Example: make test_version goversion=1.15
+ @docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make init check"
diff --git a/vendor/github.com/labstack/echo/v4/README.md b/vendor/github.com/labstack/echo/v4/README.md
index d9d96139..4dec531a 100644
--- a/vendor/github.com/labstack/echo/v4/README.md
+++ b/vendor/github.com/labstack/echo/v4/README.md
@@ -1,12 +1,12 @@
<a href="https://echo.labstack.com"><img height="80" src="https://cdn.labstack.com/images/echo-logo.svg"></a>
[![Sourcegraph](https://sourcegraph.com/github.com/labstack/echo/-/badge.svg?style=flat-square)](https://sourcegraph.com/github.com/labstack/echo?badge)
-[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/labstack/echo)
+[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://pkg.go.dev/github.com/labstack/echo/v4)
[![Go Report Card](https://goreportcard.com/badge/github.com/labstack/echo?style=flat-square)](https://goreportcard.com/report/github.com/labstack/echo)
[![Build Status](http://img.shields.io/travis/labstack/echo.svg?style=flat-square)](https://travis-ci.org/labstack/echo)
[![Codecov](https://img.shields.io/codecov/c/github/labstack/echo.svg?style=flat-square)](https://codecov.io/gh/labstack/echo)
[![Join the chat at https://gitter.im/labstack/echo](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg?style=flat-square)](https://gitter.im/labstack/echo)
-[![Forum](https://img.shields.io/badge/community-forum-00afd1.svg?style=flat-square)](https://forum.labstack.com)
+[![Forum](https://img.shields.io/badge/community-forum-00afd1.svg?style=flat-square)](https://github.com/labstack/echo/discussions)
[![Twitter](https://img.shields.io/badge/twitter-@labstack-55acee.svg?style=flat-square)](https://twitter.com/labstack)
[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/labstack/echo/master/LICENSE)
@@ -42,11 +42,14 @@ For older versions, please use the latest v3 tag.
## Benchmarks
-Date: 2018/03/15<br>
+Date: 2020/11/11<br>
Source: https://github.com/vishr/web-framework-benchmark<br>
Lower is better!
-<img src="https://i.imgur.com/I32VdMJ.png">
+<img src="https://i.imgur.com/qwPNQbl.png">
+<img src="https://i.imgur.com/s8yKQjx.png">
+
+The benchmarks above were run on an Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz
## [Guide](https://echo.labstack.com/guide)
@@ -91,7 +94,7 @@ func hello(c echo.Context) error {
## Help
-- [Forum](https://forum.labstack.com)
+- [Forum](https://github.com/labstack/echo/discussions)
- [Chat](https://gitter.im/labstack/echo)
## Contribute
diff --git a/vendor/github.com/labstack/echo/v4/bind.go b/vendor/github.com/labstack/echo/v4/bind.go
index f8914743..16c3b7ad 100644
--- a/vendor/github.com/labstack/echo/v4/bind.go
+++ b/vendor/github.com/labstack/echo/v4/bind.go
@@ -30,10 +30,8 @@ type (
}
)
-// Bind implements the `Binder#Bind` function.
-func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
- req := c.Request()
-
+// BindPathParams binds path params to bindable object
+func (b *DefaultBinder) BindPathParams(c Context, i interface{}) error {
names := c.ParamNames()
values := c.ParamValues()
params := map[string][]string{}
@@ -43,12 +41,28 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
if err := b.bindData(i, params, "param"); err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
- if err = b.bindData(i, c.QueryParams(), "query"); err != nil {
+ return nil
+}
+
+// BindQueryParams binds query params to bindable object
+func (b *DefaultBinder) BindQueryParams(c Context, i interface{}) error {
+ if err := b.bindData(i, c.QueryParams(), "query"); err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
+ return nil
+}
+
+// BindBody binds request body contents to bindable object
+// NB: then binding forms take note that this implementation uses standard library form parsing
+// which parses form data from BOTH URL and BODY if content type is not MIMEMultipartForm
+// See non-MIMEMultipartForm: https://golang.org/pkg/net/http/#Request.ParseForm
+// See MIMEMultipartForm: https://golang.org/pkg/net/http/#Request.ParseMultipartForm
+func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) {
+ req := c.Request()
if req.ContentLength == 0 {
return
}
+
ctype := req.Header.Get(HeaderContentType)
switch {
case strings.HasPrefix(ctype, MIMEApplicationJSON):
@@ -80,15 +94,35 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
default:
return ErrUnsupportedMediaType
}
- return
+ return nil
}
-func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag string) error {
- if ptr == nil || len(data) == 0 {
+// Bind implements the `Binder#Bind` function.
+// Binding is done in following order: 1) path params; 2) query params; 3) request body. Each step COULD override previous
+// step binded values. For single source binding use their own methods BindBody, BindQueryParams, BindPathParams.
+func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
+ if err := b.BindPathParams(c, i); err != nil {
+ return err
+ }
+ // Issue #1670 - Query params are binded only for GET/DELETE and NOT for usual request with body (POST/PUT/PATCH)
+ // Reasoning here is that parameters in query and bind destination struct could have UNEXPECTED matches and results due that.
+ // i.e. is `&id=1&lang=en` from URL same as `{"id":100,"lang":"de"}` request body and which one should have priority when binding.
+ // This HTTP method check restores pre v4.1.11 behavior and avoids different problems when query is mixed with body
+ if c.Request().Method == http.MethodGet || c.Request().Method == http.MethodDelete {
+ if err = b.BindQueryParams(c, i); err != nil {
+ return err
+ }
+ }
+ return b.BindBody(c, i)
+}
+
+// bindData will bind data ONLY fields in destination struct that have EXPLICIT tag
+func (b *DefaultBinder) bindData(destination interface{}, data map[string][]string, tag string) error {
+ if destination == nil || len(data) == 0 {
return nil
}
- typ := reflect.TypeOf(ptr).Elem()
- val := reflect.ValueOf(ptr).Elem()
+ typ := reflect.TypeOf(destination).Elem()
+ val := reflect.ValueOf(destination).Elem()
// Map
if typ.Kind() == reflect.Map {
@@ -113,14 +147,15 @@ func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag
inputFieldName := typeField.Tag.Get(tag)
if inputFieldName == "" {
- inputFieldName = typeField.Name
- // If tag is nil, we inspect if the field is a struct.
+ // If tag is nil, we inspect if the field is a not BindUnmarshaler struct and try to bind data into it (might contains fields with tags).
+ // structs that implement BindUnmarshaler are binded only when they have explicit tag
if _, ok := structField.Addr().Interface().(BindUnmarshaler); !ok && structFieldKind == reflect.Struct {
if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {
return err
}
- continue
}
+ // does not have explicit tag and is not an ordinary struct - so move to next field
+ continue
}
inputValue, exists := data[inputFieldName]
diff --git a/vendor/github.com/labstack/echo/v4/binder.go b/vendor/github.com/labstack/echo/v4/binder.go
new file mode 100644
index 00000000..0900ce8d
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/binder.go
@@ -0,0 +1,1230 @@
+package echo
+
+import (
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+)
+
+/**
+ Following functions provide handful of methods for binding to Go native types from request query or path parameters.
+ * QueryParamsBinder(c) - binds query parameters (source URL)
+ * PathParamsBinder(c) - binds path parameters (source URL)
+ * FormFieldBinder(c) - binds form fields (source URL + body)
+
+ Example:
+ ```go
+ var length int64
+ err := echo.QueryParamsBinder(c).Int64("length", &length).BindError()
+ ```
+
+ For every supported type there are following methods:
+ * <Type>("param", &destination) - if parameter value exists then binds it to given destination of that type i.e Int64(...).
+ * Must<Type>("param", &destination) - parameter value is required to exist, binds it to given destination of that type i.e MustInt64(...).
+ * <Type>s("param", &destination) - (for slices) if parameter values exists then binds it to given destination of that type i.e Int64s(...).
+ * Must<Type>s("param", &destination) - (for slices) parameter value is required to exist, binds it to given destination of that type i.e MustInt64s(...).
+
+ for some slice types `BindWithDelimiter("param", &dest, ",")` supports splitting parameter values before type conversion is done
+ i.e. URL `/api/search?id=1,2,3&id=1` can be bind to `[]int64{1,2,3,1}`
+
+ `FailFast` flags binder to stop binding after first bind error during binder call chain. Enabled by default.
+ `BindError()` returns first bind error from binder and resets errors in binder. Useful along with `FailFast()` method
+ to do binding and returns on first problem
+ `BindErrors()` returns all bind errors from binder and resets errors in binder.
+
+ Types that are supported:
+ * bool
+ * float32
+ * float64
+ * int
+ * int8
+ * int16
+ * int32
+ * int64
+ * uint
+ * uint8/byte (does not support `bytes()`. Use BindUnmarshaler/CustomFunc to convert value from base64 etc to []byte{})
+ * uint16
+ * uint32
+ * uint64
+ * string
+ * time
+ * duration
+ * BindUnmarshaler() interface
+ * UnixTime() - converts unix time (integer) to time.Time
+ * UnixTimeNano() - converts unix time with nano second precision (integer) to time.Time
+ * CustomFunc() - callback function for your custom conversion logic. Signature `func(values []string) []error`
+*/
+
+// BindingError represents an error that occurred while binding request data.
+type BindingError struct {
+ // Field is the field name where value binding failed
+ Field string `json:"field"`
+ // Values of parameter that failed to bind.
+ Values []string `json:"-"`
+ *HTTPError
+}
+
+// NewBindingError creates new instance of binding error
+func NewBindingError(sourceParam string, values []string, message interface{}, internalError error) error {
+ return &BindingError{
+ Field: sourceParam,
+ Values: values,
+ HTTPError: &HTTPError{
+ Code: http.StatusBadRequest,
+ Message: message,
+ Internal: internalError,
+ },
+ }
+}
+
+// Error returns error message
+func (be *BindingError) Error() string {
+ return fmt.Sprintf("%s, field=%s", be.HTTPError.Error(), be.Field)
+}
+
+// ValueBinder provides utility methods for binding query or path parameter to various Go built-in types
+type ValueBinder struct {
+ // failFast is flag for binding methods to return without attempting to bind when previous binding already failed
+ failFast bool
+ errors []error
+
+ // ValueFunc is used to get single parameter (first) value from request
+ ValueFunc func(sourceParam string) string
+ // ValuesFunc is used to get all values for parameter from request. i.e. `/api/search?ids=1&ids=2`
+ ValuesFunc func(sourceParam string) []string
+ // ErrorFunc is used to create errors. Allows you to use your own error type, that for example marshals to your specific json response
+ ErrorFunc func(sourceParam string, values []string, message interface{}, internalError error) error
+}
+
+// QueryParamsBinder creates query parameter value binder
+func QueryParamsBinder(c Context) *ValueBinder {
+ return &ValueBinder{
+ failFast: true,
+ ValueFunc: c.QueryParam,
+ ValuesFunc: func(sourceParam string) []string {
+ values, ok := c.QueryParams()[sourceParam]
+ if !ok {
+ return nil
+ }
+ return values
+ },
+ ErrorFunc: NewBindingError,
+ }
+}
+
+// PathParamsBinder creates path parameter value binder
+func PathParamsBinder(c Context) *ValueBinder {
+ return &ValueBinder{
+ failFast: true,
+ ValueFunc: c.Param,
+ ValuesFunc: func(sourceParam string) []string {
+ // path parameter should not have multiple values so getting values does not make sense but lets not error out here
+ value := c.Param(sourceParam)
+ if value == "" {
+ return nil
+ }
+ return []string{value}
+ },
+ ErrorFunc: NewBindingError,
+ }
+}
+
+// FormFieldBinder creates form field value binder
+// For all requests, FormFieldBinder parses the raw query from the URL and uses query params as form fields
+//
+// For POST, PUT, and PATCH requests, it also reads the request body, parses it
+// as a form and uses query params as form fields. Request body parameters take precedence over URL query
+// string values in r.Form.
+//
+// NB: when binding forms take note that this implementation uses standard library form parsing
+// which parses form data from BOTH URL and BODY if content type is not MIMEMultipartForm
+// See https://golang.org/pkg/net/http/#Request.ParseForm
+func FormFieldBinder(c Context) *ValueBinder {
+ vb := &ValueBinder{
+ failFast: true,
+ ValueFunc: func(sourceParam string) string {
+ return c.Request().FormValue(sourceParam)
+ },
+ ErrorFunc: NewBindingError,
+ }
+ vb.ValuesFunc = func(sourceParam string) []string {
+ if c.Request().Form == nil {
+ // this is same as `Request().FormValue()` does internally
+ _ = c.Request().ParseMultipartForm(32 << 20)
+ }
+ values, ok := c.Request().Form[sourceParam]
+ if !ok {
+ return nil
+ }
+ return values
+ }
+
+ return vb
+}
+
+// FailFast set internal flag to indicate if binding methods will return early (without binding) when previous bind failed
+// NB: call this method before any other binding methods as it modifies binding methods behaviour
+func (b *ValueBinder) FailFast(value bool) *ValueBinder {
+ b.failFast = value
+ return b
+}
+
+func (b *ValueBinder) setError(err error) {
+ if b.errors == nil {
+ b.errors = []error{err}
+ return
+ }
+ b.errors = append(b.errors, err)
+}
+
+// BindError returns first seen bind error and resets/empties binder errors for further calls
+func (b *ValueBinder) BindError() error {
+ if b.errors == nil {
+ return nil
+ }
+ err := b.errors[0]
+ b.errors = nil // reset errors so next chain will start from zero
+ return err
+}
+
+// BindErrors returns all bind errors and resets/empties binder errors for further calls
+func (b *ValueBinder) BindErrors() []error {
+ if b.errors == nil {
+ return nil
+ }
+ errors := b.errors
+ b.errors = nil // reset errors so next chain will start from zero
+ return errors
+}
+
+// CustomFunc binds parameter values with Func. Func is called only when parameter values exist.
+func (b *ValueBinder) CustomFunc(sourceParam string, customFunc func(values []string) []error) *ValueBinder {
+ return b.customFunc(sourceParam, customFunc, false)
+}
+
+// MustCustomFunc requires parameter values to exist to be bind with Func. Returns error when value does not exist.
+func (b *ValueBinder) MustCustomFunc(sourceParam string, customFunc func(values []string) []error) *ValueBinder {
+ return b.customFunc(sourceParam, customFunc, true)
+}
+
+func (b *ValueBinder) customFunc(sourceParam string, customFunc func(values []string) []error, valueMustExist bool) *ValueBinder {
+ if b.failFast && b.errors != nil {
+ return b
+ }
+
+ values := b.ValuesFunc(sourceParam)
+ if len(values) == 0 {
+ if valueMustExist {
+ b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil))
+ }
+ return b
+ }
+ if errs := customFunc(values); errs != nil {
+ b.errors = append(b.errors, errs...)
+ }
+ return b
+}
+
+// String binds parameter to string variable
+func (b *ValueBinder) String(sourceParam string, dest *string) *ValueBinder {
+ if b.failFast && b.errors != nil {
+ return b
+ }
+
+ value := b.ValueFunc(sourceParam)
+ if value == "" {
+ return b
+ }
+ *dest = value
+ return b
+}
+
+// MustString requires parameter value to exist to be bind to string variable. Returns error when value does not exist
+func (b *ValueBinder) MustString(sourceParam string, dest *string) *ValueBinder {
+ if b.failFast && b.errors != nil {
+ return b
+ }
+
+ value := b.ValueFunc(sourceParam)
+ if value == "" {
+ b.setError(b.ErrorFunc(sourceParam, []string{value}, "required field value is empty", nil))
+ return b
+ }
+ *dest = value
+ return b
+}
+
+// Strings binds parameter values to slice of string
+func (b *ValueBinder) Strings(sourceParam string, dest *[]string) *ValueBinder {
+ if b.failFast && b.errors != nil {
+ return b
+ }
+
+ value := b.ValuesFunc(sourceParam)
+ if value == nil {
+ return b
+ }
+ *dest = value
+ return b
+}
+
+// MustStrings requires parameter values to exist to be bind to slice of string variables. Returns error when value does not exist
+func (b *ValueBinder) MustStrings(sourceParam string, dest *[]string) *ValueBinder {
+ if b.failFast && b.errors != nil {
+ return b
+ }
+
+ value := b.ValuesFunc(sourceParam)
+ if value == nil {
+ b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil))
+ return b
+ }
+ *dest = value
+ return b
+}
+
+// BindUnmarshaler binds parameter to destination implementing BindUnmarshaler interface
+func (b *ValueBinder) BindUnmarshaler(sourceParam string, dest BindUnmarshaler) *ValueBinder {
+ if b.failFast && b.errors != nil {
+ return b
+ }
+
+ tmp := b.ValueFunc(sourceParam)
+ if tmp == "" {
+ return b
+ }
+
+ if err := dest.UnmarshalParam(tmp); err != nil {
+ b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to BindUnmarshaler interface", err))
+ }
+ return b
+}
+
+// MustBindUnmarshaler requires parameter value to exist to be bind to destination implementing BindUnmarshaler interface.
+// Returns error when value does not exist
+func (b *ValueBinder) MustBindUnmarshaler(sourceParam string, dest BindUnmarshaler) *ValueBinder {
+ if b.failFast && b.errors != nil {
+ return b
+ }
+
+ value := b.ValueFunc(sourceParam)
+ if value == "" {
+ b.setError(b.ErrorFunc(sourceParam, []string{value}, "required field value is empty", nil))
+ return b
+ }
+
+ if err := dest.UnmarshalParam(value); err != nil {
+ b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to BindUnmarshaler interface", err))
+ }
+ return b
+}
+
+// BindWithDelimiter binds parameter to destination by suitable conversion function.
+// Delimiter is used before conversion to split parameter value to separate values
+func (b *ValueBinder) BindWithDelimiter(sourceParam string, dest interface{}, delimiter string) *ValueBinder {
+ return b.bindWithDelimiter(sourceParam, dest, delimiter, false)
+}
+
+// MustBindWithDelimiter requires parameter value to exist to be bind destination by suitable conversion function.
+// Delimiter is used before conversion to split parameter value to separate values
+func (b *ValueBinder) MustBindWithDelimiter(sourceParam string, dest interface{}, delimiter string) *ValueBinder {
+ return b.bindWithDelimiter(sourceParam, dest, delimiter, true)
+}
+
+func (b *ValueBinder) bindWithDelimiter(sourceParam string, dest interface{}, delimiter string, valueMustExist bool) *ValueBinder {
+ if b.failFast && b.errors != nil {
+ return b
+ }
+ values := b.ValuesFunc(sourceParam)
+ if len(values) == 0 {
+ if valueMustExist {
+ b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil))
+ }
+ return b
+ }
+ tmpValues := make([]string, 0, len(values))
+ for _, v := range values {
+ tmpValues = append(tmpValues, strings.Split(v, delimiter)...)
+ }
+
+ switch d := dest.(type) {
+ case *[]string:
+ *d = tmpValues
+ return b
+ case *[]bool:
+ return b.bools(sourceParam, tmpValues, d)
+ case *[]int64, *[]int32, *[]int16, *[]int8, *[]int:
+ return b.ints(sourceParam, tmpValues, d)
+ case *[]uint64, *[]uint32, *[]uint16, *[]uint8, *[]uint: // *[]byte is same as *[]uint8
+ return b.uints(sourceParam, tmpValues, d)
+ case *[]float64, *[]float32:
+ return b.floats(sourceParam, tmpValues, d)
+ case *[]time.Duration:
+ return b.durations(sourceParam, tmpValues, d)
+ default:
+ // support only cases when destination is slice
+ // does not support time.Time as it needs argument (layout) for parsing or BindUnmarshaler
+ b.setError(b.ErrorFunc(sourceParam, []string{}, "unsupported bind type", nil))
+ return b
+ }
+}
+
+// Int64 binds parameter to int64 variable
+func (b *ValueBinder) Int64(sourceParam string, dest *int64) *ValueBinder {
+ return b.intValue(sourceParam, dest, 64, false)
+}
+
+// MustInt64 requires parameter value to exist to be bind to int64 variable. Returns error when value does not exist
+func (b *ValueBinder) MustInt64(sourceParam string, dest *int64) *ValueBinder {
+ return b.intValue(sourceParam, dest, 64, true)
+}
+
+// Int32 binds parameter to int32 variable
+func (b *ValueBinder) Int32(sourceParam string, dest *int32) *ValueBinder {
+ return b.intValue(sourceParam, dest, 32, false)
+}
+
+// MustInt32 requires parameter value to exist to be bind to int32 variable. Returns error when value does not exist
+func (b *ValueBinder) MustInt32(sourceParam string, dest *int32) *ValueBinder {
+ return b.intValue(sourceParam, dest, 32, true)
+}
+
+// Int16 binds parameter to int16 variable
+func (b *ValueBinder) Int16(sourceParam string, dest *int16) *ValueBinder {
+ return b.intValue(sourceParam, dest, 16, false)
+}
+
+// MustInt16 requires parameter value to exist to be bind to int16 variable. Returns error when value does not exist
+func (b *ValueBinder) MustInt16(sourceParam string, dest *int16) *ValueBinder {
+ return b.intValue(sourceParam, dest, 16, true)
+}
+
+// Int8 binds parameter to int8 variable
+func (b *ValueBinder) Int8(sourceParam string, dest *int8) *ValueBinder {
+ return b.intValue(sourceParam, dest, 8, false)
+}
+
+// MustInt8 requires parameter value to exist to be bind to int8 variable. Returns error when value does not exist
+func (b *ValueBinder) MustInt8(sourceParam string, dest *int8) *ValueBinder {
+ return b.intValue(sourceParam, dest, 8, true)
+}
+
+// Int binds parameter to int variable
+func (b *ValueBinder) Int(sourceParam string, dest *int) *ValueBinder {
+ return b.intValue(sourceParam, dest, 0, false)
+}
+
+// MustInt requires parameter value to exist to be bind to int variable. Returns error when value does not exist
+func (b *ValueBinder) MustInt(sourceParam string, dest *int) *ValueBinder {
+ return b.intValue(sourceParam, dest, 0, true)
+}
+
+func (b *ValueBinder) intValue(sourceParam string, dest interface{}, bitSize int, valueMustExist bool) *ValueBinder {
+ if b.failFast && b.errors != nil {
+ return b
+ }
+
+ value := b.ValueFunc(sourceParam)
+ if value == "" {
+ if valueMustExist {
+ b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil))
+ }
+ return b
+ }
+
+ return b.int(sourceParam, value, dest, bitSize)
+}
+
+func (b *ValueBinder) int(sourceParam string, value string, dest interface{}, bitSize int) *ValueBinder {
+ n, err := strconv.ParseInt(value, 10, bitSize)
+ if err != nil {
+ if bitSize == 0 {
+ b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to int", err))
+ } else {
+ b.setError(b.ErrorFunc(sourceParam, []string{value}, fmt.Sprintf("failed to bind field value to int%v", bitSize), err))
+ }
+ return b
+ }
+
+ switch d := dest.(type) {
+ case *int64:
+ *d = n
+ case *int32:
+ *d = int32(n)
+ case *int16:
+ *d = int16(n)
+ case *int8:
+ *d = int8(n)
+ case *int:
+ *d = int(n)
+ }
+ return b
+}
+
+func (b *ValueBinder) intsValue(sourceParam string, dest interface{}, valueMustExist bool) *ValueBinder {
+ if b.failFast && b.errors != nil {
+ return b
+ }
+
+ values := b.ValuesFunc(sourceParam)
+ if len(values) == 0 {
+ if valueMustExist {
+ b.setError(b.ErrorFunc(sourceParam, values, "required field value is empty", nil))
+ }
+ return b
+ }
+ return b.ints(sourceParam, values, dest)
+}
+
+func (b *ValueBinder) ints(sourceParam string, values []string, dest interface{}) *ValueBinder {
+ switch d := dest.(type) {
+ case *[]int64:
+ tmp := make([]int64, len(values))
+ for i, v := range values {
+ b.int(sourceParam, v, &tmp[i], 64)
+ if b.failFast && b.errors != nil {
+ return b
+ }
+ }
+ if b.errors == nil {
+ *d = tmp
+ }
+ case *[]int32:
+ tmp := make([]int32, len(values))
+ for i, v := range values {
+ b.int(sourceParam, v, &tmp[i], 32)
+ if b.failFast && b.errors != nil {
+ return b
+ }
+ }
+ if b.errors == nil {
+ *d = tmp
+ }
+ case *[]int16:
+ tmp := make([]int16, len(values))
+ for i, v := range values {
+ b.int(sourceParam, v, &tmp[i], 16)
+ if b.failFast && b.errors != nil {
+ return b
+ }
+ }
+ if b.errors == nil {
+ *d = tmp
+ }
+ case *[]int8:
+ tmp := make([]int8, len(values))
+ for i, v := range values {
+ b.int(sourceParam, v, &tmp[i], 8)
+ if b.failFast && b.errors != nil {
+ return b
+ }
+ }
+ if b.errors == nil {
+ *d = tmp
+ }
+ case *[]int:
+ tmp := make([]int, len(values))
+ for i, v := range values {
+ b.int(sourceParam, v, &tmp[i], 0)
+ if b.failFast && b.errors != nil {
+ return b
+ }
+ }
+ if b.errors == nil {
+ *d = tmp
+ }
+ }
+ return b
+}
+
+// Int64s binds parameter to slice of int64
+func (b *ValueBinder) Int64s(sourceParam string, dest *[]int64) *ValueBinder {
+ return b.intsValue(sourceParam, dest, false)
+}
+
+// MustInt64s requires parameter value to exist to be bind to int64 slice variable. Returns error when value does not exist
+func (b *ValueBinder) MustInt64s(sourceParam string, dest *[]int64) *ValueBinder {
+ return b.intsValue(sourceParam, dest, true)
+}
+
+// Int32s binds parameter to slice of int32
+func (b *ValueBinder) Int32s(sourceParam string, dest *[]int32) *ValueBinder {
+ return b.intsValue(sourceParam, dest, false)
+}
+
+// MustInt32s requires parameter value to exist to be bind to int32 slice variable. Returns error when value does not exist
+func (b *ValueBinder) MustInt32s(sourceParam string, dest *[]int32) *ValueBinder {
+ return b.intsValue(sourceParam, dest, true)
+}
+
+// Int16s binds parameter to slice of int16
+func (b *ValueBinder) Int16s(sourceParam string, dest *[]int16) *ValueBinder {
+ return b.intsValue(sourceParam, dest, false)
+}
+
+// MustInt16s requires parameter value to exist to be bind to int16 slice variable. Returns error when value does not exist
+func (b *ValueBinder) MustInt16s(sourceParam string, dest *[]int16) *ValueBinder {
+ return b.intsValue(sourceParam, dest, true)
+}
+
+// Int8s binds parameter to slice of int8
+func (b *ValueBinder) Int8s(sourceParam string, dest *[]int8) *ValueBinder {
+ return b.intsValue(sourceParam, dest, false)
+}
+
+// MustInt8s requires parameter value to exist to be bind to int8 slice variable. Returns error when value does not exist
+func (b *ValueBinder) MustInt8s(sourceParam string, dest *[]int8) *ValueBinder {
+ return b.intsValue(sourceParam, dest, true)
+}
+
+// Ints binds parameter to slice of int
+func (b *ValueBinder) Ints(sourceParam string, dest *[]int) *ValueBinder {
+ return b.intsValue(sourceParam, dest, false)
+}
+
+// MustInts requires parameter value to exist to be bind to int slice variable. Returns error when value does not exist
+func (b *ValueBinder) MustInts(sourceParam string, dest *[]int) *ValueBinder {
+ return b.intsValue(sourceParam, dest, true)
+}
+
+// Uint64 binds parameter to uint64 variable
+func (b *ValueBinder) Uint64(sourceParam string, dest *uint64) *ValueBinder {
+ return b.uintValue(sourceParam, dest, 64, false)
+}
+
+// MustUint64 requires parameter value to exist to be bind to uint64 variable. Returns error when value does not exist
+func (b *ValueBinder) MustUint64(sourceParam string, dest *uint64) *ValueBinder {
+ return b.uintValue(sourceParam, dest, 64, true)
+}
+
+// Uint32 binds parameter to uint32 variable
+func (b *ValueBinder) Uint32(sourceParam string, dest *uint32) *ValueBinder {
+ return b.uintValue(sourceParam, dest, 32, false)
+}
+
+// MustUint32 requires parameter value to exist to be bind to uint32 variable. Returns error when value does not exist
+func (b *ValueBinder) MustUint32(sourceParam string, dest *uint32) *ValueBinder {
+ return b.uintValue(sourceParam, dest, 32, true)
+}
+
+// Uint16 binds parameter to uint16 variable
+func (b *ValueBinder) Uint16(sourceParam string, dest *uint16) *ValueBinder {
+ return b.uintValue(sourceParam, dest, 16, false)
+}
+
+// MustUint16 requires parameter value to exist to be bind to uint16 variable. Returns error when value does not exist
+func (b *ValueBinder) MustUint16(sourceParam string, dest *uint16) *ValueBinder {
+ return b.uintValue(sourceParam, dest, 16, true)
+}
+
+// Uint8 binds parameter to uint8 variable
+func (b *ValueBinder) Uint8(sourceParam string, dest *uint8) *ValueBinder {
+ return b.uintValue(sourceParam, dest, 8, false)
+}
+
+// MustUint8 requires parameter value to exist to be bind to uint8 variable. Returns error when value does not exist
+func (b *ValueBinder) MustUint8(sourceParam string, dest *uint8) *ValueBinder {
+ return b.uintValue(sourceParam, dest, 8, true)
+}
+
+// Byte binds parameter to byte variable
+func (b *ValueBinder) Byte(sourceParam string, dest *byte) *ValueBinder {
+ return b.uintValue(sourceParam, dest, 8, false)
+}
+
+// MustByte requires parameter value to exist to be bind to byte variable. Returns error when value does not exist
+func (b *ValueBinder) MustByte(sourceParam string, dest *byte) *ValueBinder {
+ return b.uintValue(sourceParam, dest, 8, true)
+}
+
+// Uint binds parameter to uint variable
+func (b *ValueBinder) Uint(sourceParam string, dest *uint) *ValueBinder {
+ return b.uintValue(sourceParam, dest, 0, false)
+}
+
+// MustUint requires parameter value to exist to be bind to uint variable. Returns error when value does not exist
+func (b *ValueBinder) MustUint(sourceParam string, dest *uint) *ValueBinder {
+ return b.uintValue(sourceParam, dest, 0, true)
+}
+
+func (b *ValueBinder) uintValue(sourceParam string, dest interface{}, bitSize int, valueMustExist bool) *ValueBinder {
+ if b.failFast && b.errors != nil {
+ return b
+ }
+
+ value := b.ValueFunc(sourceParam)
+ if value == "" {
+ if valueMustExist {
+ b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil))
+ }
+ return b
+ }
+
+ return b.uint(sourceParam, value, dest, bitSize)
+}
+
+func (b *ValueBinder) uint(sourceParam string, value string, dest interface{}, bitSize int) *ValueBinder {
+ n, err := strconv.ParseUint(value, 10, bitSize)
+ if err != nil {
+ if bitSize == 0 {
+ b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to uint", err))
+ } else {
+ b.setError(b.ErrorFunc(sourceParam, []string{value}, fmt.Sprintf("failed to bind field value to uint%v", bitSize), err))
+ }
+ return b
+ }
+
+ switch d := dest.(type) {
+ case *uint64:
+ *d = n
+ case *uint32:
+ *d = uint32(n)
+ case *uint16:
+ *d = uint16(n)
+ case *uint8: // byte is alias to uint8
+ *d = uint8(n)
+ case *uint:
+ *d = uint(n)
+ }
+ return b
+}
+
+func (b *ValueBinder) uintsValue(sourceParam string, dest interface{}, valueMustExist bool) *ValueBinder {
+ if b.failFast && b.errors != nil {
+ return b
+ }
+
+ values := b.ValuesFunc(sourceParam)
+ if len(values) == 0 {
+ if valueMustExist {
+ b.setError(b.ErrorFunc(sourceParam, values, "required field value is empty", nil))
+ }
+ return b
+ }
+ return b.uints(sourceParam, values, dest)
+}
+
+func (b *ValueBinder) uints(sourceParam string, values []string, dest interface{}) *ValueBinder {
+ switch d := dest.(type) {
+ case *[]uint64:
+ tmp := make([]uint64, len(values))
+ for i, v := range values {
+ b.uint(sourceParam, v, &tmp[i], 64)
+ if b.failFast && b.errors != nil {
+ return b
+ }
+ }
+ if b.errors == nil {
+ *d = tmp
+ }
+ case *[]uint32:
+ tmp := make([]uint32, len(values))
+ for i, v := range values {
+ b.uint(sourceParam, v, &tmp[i], 32)
+ if b.failFast && b.errors != nil {
+ return b
+ }
+ }
+ if b.errors == nil {
+ *d = tmp
+ }
+ case *[]uint16:
+ tmp := make([]uint16, len(values))
+ for i, v := range values {
+ b.uint(sourceParam, v, &tmp[i], 16)
+ if b.failFast && b.errors != nil {
+ return b
+ }
+ }
+ if b.errors == nil {
+ *d = tmp
+ }
+ case *[]uint8: // byte is alias to uint8
+ tmp := make([]uint8, len(values))
+ for i, v := range values {
+ b.uint(sourceParam, v, &tmp[i], 8)
+ if b.failFast && b.errors != nil {
+ return b
+ }
+ }
+ if b.errors == nil {
+ *d = tmp
+ }
+ case *[]uint:
+ tmp := make([]uint, len(values))
+ for i, v := range values {
+ b.uint(sourceParam, v, &tmp[i], 0)
+ if b.failFast && b.errors != nil {
+ return b
+ }
+ }
+ if b.errors == nil {
+ *d = tmp
+ }
+ }
+ return b
+}
+
+// Uint64s binds parameter to slice of uint64
+func (b *ValueBinder) Uint64s(sourceParam string, dest *[]uint64) *ValueBinder {
+ return b.uintsValue(sourceParam, dest, false)
+}
+
+// MustUint64s requires parameter value to exist to be bind to uint64 slice variable. Returns error when value does not exist
+func (b *ValueBinder) MustUint64s(sourceParam string, dest *[]uint64) *ValueBinder {
+ return b.uintsValue(sourceParam, dest, true)
+}
+
+// Uint32s binds parameter to slice of uint32
+func (b *ValueBinder) Uint32s(sourceParam string, dest *[]uint32) *ValueBinder {
+ return b.uintsValue(sourceParam, dest, false)
+}
+
+// MustUint32s requires parameter value to exist to be bind to uint32 slice variable. Returns error when value does not exist
+func (b *ValueBinder) MustUint32s(sourceParam string, dest *[]uint32) *ValueBinder {
+ return b.uintsValue(sourceParam, dest, true)
+}
+
+// Uint16s binds parameter to slice of uint16
+func (b *ValueBinder) Uint16s(sourceParam string, dest *[]uint16) *ValueBinder {
+ return b.uintsValue(sourceParam, dest, false)
+}
+
+// MustUint16s requires parameter value to exist to be bind to uint16 slice variable. Returns error when value does not exist
+func (b *ValueBinder) MustUint16s(sourceParam string, dest *[]uint16) *ValueBinder {
+ return b.uintsValue(sourceParam, dest, true)
+}
+
+// Uint8s binds parameter to slice of uint8
+func (b *ValueBinder) Uint8s(sourceParam string, dest *[]uint8) *ValueBinder {
+ return b.uintsValue(sourceParam, dest, false)
+}
+
+// MustUint8s requires parameter value to exist to be bind to uint8 slice variable. Returns error when value does not exist
+func (b *ValueBinder) MustUint8s(sourceParam string, dest *[]uint8) *ValueBinder {
+ return b.uintsValue(sourceParam, dest, true)
+}
+
+// Uints binds parameter to slice of uint
+func (b *ValueBinder) Uints(sourceParam string, dest *[]uint) *ValueBinder {
+ return b.uintsValue(sourceParam, dest, false)
+}
+
+// MustUints requires parameter value to exist to be bind to uint slice variable. Returns error when value does not exist
+func (b *ValueBinder) MustUints(sourceParam string, dest *[]uint) *ValueBinder {
+ return b.uintsValue(sourceParam, dest, true)
+}
+
+// Bool binds parameter to bool variable
+func (b *ValueBinder) Bool(sourceParam string, dest *bool) *ValueBinder {
+ return b.boolValue(sourceParam, dest, false)
+}
+
+// MustBool requires parameter value to exist to be bind to bool variable. Returns error when value does not exist
+func (b *ValueBinder) MustBool(sourceParam string, dest *bool) *ValueBinder {
+ return b.boolValue(sourceParam, dest, true)
+}
+
+func (b *ValueBinder) boolValue(sourceParam string, dest *bool, valueMustExist bool) *ValueBinder {
+ if b.failFast && b.errors != nil {
+ return b
+ }
+
+ value := b.ValueFunc(sourceParam)
+ if value == "" {
+ if valueMustExist {
+ b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil))
+ }
+ return b
+ }
+ return b.bool(sourceParam, value, dest)
+}
+
+func (b *ValueBinder) bool(sourceParam string, value string, dest *bool) *ValueBinder {
+ n, err := strconv.ParseBool(value)
+ if err != nil {
+ b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to bool", err))
+ return b
+ }
+
+ *dest = n
+ return b
+}
+
+func (b *ValueBinder) boolsValue(sourceParam string, dest *[]bool, valueMustExist bool) *ValueBinder {
+ if b.failFast && b.errors != nil {
+ return b
+ }
+
+ values := b.ValuesFunc(sourceParam)
+ if len(values) == 0 {
+ if valueMustExist {
+ b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil))
+ }
+ return b
+ }
+ return b.bools(sourceParam, values, dest)
+}
+
+func (b *ValueBinder) bools(sourceParam string, values []string, dest *[]bool) *ValueBinder {
+ tmp := make([]bool, len(values))
+ for i, v := range values {
+ b.bool(sourceParam, v, &tmp[i])
+ if b.failFast && b.errors != nil {
+ return b
+ }
+ }
+ if b.errors == nil {
+ *dest = tmp
+ }
+ return b
+}
+
+// Bools binds parameter values to slice of bool variables
+func (b *ValueBinder) Bools(sourceParam string, dest *[]bool) *ValueBinder {
+ return b.boolsValue(sourceParam, dest, false)
+}
+
+// MustBools requires parameter values to exist to be bind to slice of bool variables. Returns error when values does not exist
+func (b *ValueBinder) MustBools(sourceParam string, dest *[]bool) *ValueBinder {
+ return b.boolsValue(sourceParam, dest, true)
+}
+
+// Float64 binds parameter to float64 variable
+func (b *ValueBinder) Float64(sourceParam string, dest *float64) *ValueBinder {
+ return b.floatValue(sourceParam, dest, 64, false)
+}
+
+// MustFloat64 requires parameter value to exist to be bind to float64 variable. Returns error when value does not exist
+func (b *ValueBinder) MustFloat64(sourceParam string, dest *float64) *ValueBinder {
+ return b.floatValue(sourceParam, dest, 64, true)
+}
+
+// Float32 binds parameter to float32 variable
+func (b *ValueBinder) Float32(sourceParam string, dest *float32) *ValueBinder {
+ return b.floatValue(sourceParam, dest, 32, false)
+}
+
+// MustFloat32 requires parameter value to exist to be bind to float32 variable. Returns error when value does not exist
+func (b *ValueBinder) MustFloat32(sourceParam string, dest *float32) *ValueBinder {
+ return b.floatValue(sourceParam, dest, 32, true)
+}
+
+func (b *ValueBinder) floatValue(sourceParam string, dest interface{}, bitSize int, valueMustExist bool) *ValueBinder {
+ if b.failFast && b.errors != nil {
+ return b
+ }
+
+ value := b.ValueFunc(sourceParam)
+ if value == "" {
+ if valueMustExist {
+ b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil))
+ }
+ return b
+ }
+
+ return b.float(sourceParam, value, dest, bitSize)
+}
+
+func (b *ValueBinder) float(sourceParam string, value string, dest interface{}, bitSize int) *ValueBinder {
+ n, err := strconv.ParseFloat(value, bitSize)
+ if err != nil {
+ b.setError(b.ErrorFunc(sourceParam, []string{value}, fmt.Sprintf("failed to bind field value to float%v", bitSize), err))
+ return b
+ }
+
+ switch d := dest.(type) {
+ case *float64:
+ *d = n
+ case *float32:
+ *d = float32(n)
+ }
+ return b
+}
+
+func (b *ValueBinder) floatsValue(sourceParam string, dest interface{}, valueMustExist bool) *ValueBinder {
+ if b.failFast && b.errors != nil {
+ return b
+ }
+
+ values := b.ValuesFunc(sourceParam)
+ if len(values) == 0 {
+ if valueMustExist {
+ b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil))
+ }
+ return b
+ }
+ return b.floats(sourceParam, values, dest)
+}
+
+func (b *ValueBinder) floats(sourceParam string, values []string, dest interface{}) *ValueBinder {
+ switch d := dest.(type) {
+ case *[]float64:
+ tmp := make([]float64, len(values))
+ for i, v := range values {
+ b.float(sourceParam, v, &tmp[i], 64)
+ if b.failFast && b.errors != nil {
+ return b
+ }
+ }
+ if b.errors == nil {
+ *d = tmp
+ }
+ case *[]float32:
+ tmp := make([]float32, len(values))
+ for i, v := range values {
+ b.float(sourceParam, v, &tmp[i], 32)
+ if b.failFast && b.errors != nil {
+ return b
+ }
+ }
+ if b.errors == nil {
+ *d = tmp
+ }
+ }
+ return b
+}
+
+// Float64s binds parameter values to slice of float64 variables
+func (b *ValueBinder) Float64s(sourceParam string, dest *[]float64) *ValueBinder {
+ return b.floatsValue(sourceParam, dest, false)
+}
+
+// MustFloat64s requires parameter values to exist to be bind to slice of float64 variables. Returns error when values does not exist
+func (b *ValueBinder) MustFloat64s(sourceParam string, dest *[]float64) *ValueBinder {
+ return b.floatsValue(sourceParam, dest, true)
+}
+
+// Float32s binds parameter values to slice of float32 variables
+func (b *ValueBinder) Float32s(sourceParam string, dest *[]float32) *ValueBinder {
+ return b.floatsValue(sourceParam, dest, false)
+}
+
+// MustFloat32s requires parameter values to exist to be bind to slice of float32 variables. Returns error when values does not exist
+func (b *ValueBinder) MustFloat32s(sourceParam string, dest *[]float32) *ValueBinder {
+ return b.floatsValue(sourceParam, dest, true)
+}
+
+// Time binds parameter to time.Time variable
+func (b *ValueBinder) Time(sourceParam string, dest *time.Time, layout string) *ValueBinder {
+ return b.time(sourceParam, dest, layout, false)
+}
+
+// MustTime requires parameter value to exist to be bind to time.Time variable. Returns error when value does not exist
+func (b *ValueBinder) MustTime(sourceParam string, dest *time.Time, layout string) *ValueBinder {
+ return b.time(sourceParam, dest, layout, true)
+}
+
+func (b *ValueBinder) time(sourceParam string, dest *time.Time, layout string, valueMustExist bool) *ValueBinder {
+ if b.failFast && b.errors != nil {
+ return b
+ }
+
+ value := b.ValueFunc(sourceParam)
+ if value == "" {
+ if valueMustExist {
+ b.setError(b.ErrorFunc(sourceParam, []string{value}, "required field value is empty", nil))
+ }
+ return b
+ }
+ t, err := time.Parse(layout, value)
+ if err != nil {
+ b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to Time", err))
+ return b
+ }
+ *dest = t
+ return b
+}
+
+// Times binds parameter values to slice of time.Time variables
+func (b *ValueBinder) Times(sourceParam string, dest *[]time.Time, layout string) *ValueBinder {
+ return b.times(sourceParam, dest, layout, false)
+}
+
+// MustTimes requires parameter values to exist to be bind to slice of time.Time variables. Returns error when values does not exist
+func (b *ValueBinder) MustTimes(sourceParam string, dest *[]time.Time, layout string) *ValueBinder {
+ return b.times(sourceParam, dest, layout, true)
+}
+
+func (b *ValueBinder) times(sourceParam string, dest *[]time.Time, layout string, valueMustExist bool) *ValueBinder {
+ if b.failFast && b.errors != nil {
+ return b
+ }
+
+ values := b.ValuesFunc(sourceParam)
+ if len(values) == 0 {
+ if valueMustExist {
+ b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil))
+ }
+ return b
+ }
+
+ tmp := make([]time.Time, len(values))
+ for i, v := range values {
+ t, err := time.Parse(layout, v)
+ if err != nil {
+ b.setError(b.ErrorFunc(sourceParam, []string{v}, "failed to bind field value to Time", err))
+ if b.failFast {
+ return b
+ }
+ continue
+ }
+ tmp[i] = t
+ }
+ if b.errors == nil {
+ *dest = tmp
+ }
+ return b
+}
+
+// Duration binds parameter to time.Duration variable
+func (b *ValueBinder) Duration(sourceParam string, dest *time.Duration) *ValueBinder {
+ return b.duration(sourceParam, dest, false)
+}
+
+// MustDuration requires parameter value to exist to be bind to time.Duration variable. Returns error when value does not exist
+func (b *ValueBinder) MustDuration(sourceParam string, dest *time.Duration) *ValueBinder {
+ return b.duration(sourceParam, dest, true)
+}
+
+func (b *ValueBinder) duration(sourceParam string, dest *time.Duration, valueMustExist bool) *ValueBinder {
+ if b.failFast && b.errors != nil {
+ return b
+ }
+
+ value := b.ValueFunc(sourceParam)
+ if value == "" {
+ if valueMustExist {
+ b.setError(b.ErrorFunc(sourceParam, []string{value}, "required field value is empty", nil))
+ }
+ return b
+ }
+ t, err := time.ParseDuration(value)
+ if err != nil {
+ b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to Duration", err))
+ return b
+ }
+ *dest = t
+ return b
+}
+
+// Durations binds parameter values to slice of time.Duration variables
+func (b *ValueBinder) Durations(sourceParam string, dest *[]time.Duration) *ValueBinder {
+ return b.durationsValue(sourceParam, dest, false)
+}
+
+// MustDurations requires parameter values to exist to be bind to slice of time.Duration variables. Returns error when values does not exist
+func (b *ValueBinder) MustDurations(sourceParam string, dest *[]time.Duration) *ValueBinder {
+ return b.durationsValue(sourceParam, dest, true)
+}
+
+func (b *ValueBinder) durationsValue(sourceParam string, dest *[]time.Duration, valueMustExist bool) *ValueBinder {
+ if b.failFast && b.errors != nil {
+ return b
+ }
+
+ values := b.ValuesFunc(sourceParam)
+ if len(values) == 0 {
+ if valueMustExist {
+ b.setError(b.ErrorFunc(sourceParam, []string{}, "required field value is empty", nil))
+ }
+ return b
+ }
+ return b.durations(sourceParam, values, dest)
+}
+
+func (b *ValueBinder) durations(sourceParam string, values []string, dest *[]time.Duration) *ValueBinder {
+ tmp := make([]time.Duration, len(values))
+ for i, v := range values {
+ t, err := time.ParseDuration(v)
+ if err != nil {
+ b.setError(b.ErrorFunc(sourceParam, []string{v}, "failed to bind field value to Duration", err))
+ if b.failFast {
+ return b
+ }
+ continue
+ }
+ tmp[i] = t
+ }
+ if b.errors == nil {
+ *dest = tmp
+ }
+ return b
+}
+
+// UnixTime binds parameter to time.Time variable (in local Time corresponding to the given Unix time).
+//
+// Example: 1609180603 bind to 2020-12-28T18:36:43.000000000+00:00
+//
+// Note:
+// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
+func (b *ValueBinder) UnixTime(sourceParam string, dest *time.Time) *ValueBinder {
+ return b.unixTime(sourceParam, dest, false, false)
+}
+
+// MustUnixTime requires parameter value to exist to be bind to time.Duration variable (in local Time corresponding
+// to the given Unix time). Returns error when value does not exist.
+//
+// Example: 1609180603 bind to 2020-12-28T18:36:43.000000000+00:00
+//
+// Note:
+// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
+func (b *ValueBinder) MustUnixTime(sourceParam string, dest *time.Time) *ValueBinder {
+ return b.unixTime(sourceParam, dest, true, false)
+}
+
+// UnixTimeNano binds parameter to time.Time variable (in local Time corresponding to the given Unix time in nano second precision).
+//
+// Example: 1609180603123456789 binds to 2020-12-28T18:36:43.123456789+00:00
+// Example: 1000000000 binds to 1970-01-01T00:00:01.000000000+00:00
+// Example: 999999999 binds to 1970-01-01T00:00:00.999999999+00:00
+//
+// Note:
+// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
+// * Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example.
+func (b *ValueBinder) UnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder {
+ return b.unixTime(sourceParam, dest, false, true)
+}
+
+// MustUnixTimeNano requires parameter value to exist to be bind to time.Duration variable (in local Time corresponding
+// to the given Unix time value in nano second precision). Returns error when value does not exist.
+//
+// Example: 1609180603123456789 binds to 2020-12-28T18:36:43.123456789+00:00
+// Example: 1000000000 binds to 1970-01-01T00:00:01.000000000+00:00
+// Example: 999999999 binds to 1970-01-01T00:00:00.999999999+00:00
+//
+// Note:
+// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
+// * Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example.
+func (b *ValueBinder) MustUnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder {
+ return b.unixTime(sourceParam, dest, true, true)
+}
+
+func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExist bool, isNano bool) *ValueBinder {
+ if b.failFast && b.errors != nil {
+ return b
+ }
+
+ value := b.ValueFunc(sourceParam)
+ if value == "" {
+ if valueMustExist {
+ b.setError(b.ErrorFunc(sourceParam, []string{value}, "required field value is empty", nil))
+ }
+ return b
+ }
+
+ n, err := strconv.ParseInt(value, 10, 64)
+ if err != nil {
+ b.setError(b.ErrorFunc(sourceParam, []string{value}, "failed to bind field value to Time", err))
+ return b
+ }
+
+ if isNano {
+ *dest = time.Unix(0, n)
+ } else {
+ *dest = time.Unix(n, 0)
+ }
+ return b
+}
diff --git a/vendor/github.com/labstack/echo/v4/codecov.yml b/vendor/github.com/labstack/echo/v4/codecov.yml
new file mode 100644
index 00000000..0fa3a3f1
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/codecov.yml
@@ -0,0 +1,11 @@
+coverage:
+ status:
+ project:
+ default:
+ threshold: 1%
+ patch:
+ default:
+ threshold: 1%
+
+comment:
+ require_changes: true \ No newline at end of file
diff --git a/vendor/github.com/labstack/echo/v4/context.go b/vendor/github.com/labstack/echo/v4/context.go
index 99ef03bc..0cee48ce 100644
--- a/vendor/github.com/labstack/echo/v4/context.go
+++ b/vendor/github.com/labstack/echo/v4/context.go
@@ -246,7 +246,7 @@ func (c *context) IsTLS() bool {
func (c *context) IsWebSocket() bool {
upgrade := c.request.Header.Get(HeaderUpgrade)
- return strings.ToLower(upgrade) == "websocket"
+ return strings.EqualFold(upgrade, "websocket")
}
func (c *context) Scheme() string {
@@ -276,7 +276,11 @@ func (c *context) RealIP() string {
}
// Fall back to legacy behavior
if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" {
- return strings.Split(ip, ", ")[0]
+ i := strings.IndexAny(ip, ", ")
+ if i > 0 {
+ return ip[:i]
+ }
+ return ip
}
if ip := c.request.Header.Get(HeaderXRealIP); ip != "" {
return ip
@@ -310,7 +314,19 @@ func (c *context) ParamNames() []string {
func (c *context) SetParamNames(names ...string) {
c.pnames = names
- *c.echo.maxParam = len(names)
+
+ l := len(names)
+ if *c.echo.maxParam < l {
+ *c.echo.maxParam = l
+ }
+
+ if len(c.pvalues) < l {
+ // Keeping the old pvalues just for backward compatibility, but it sounds that doesn't make sense to keep them,
+ // probably those values will be overriden in a Context#SetParamValues
+ newPvalues := make([]string, l)
+ copy(newPvalues, c.pvalues)
+ c.pvalues = newPvalues
+ }
}
func (c *context) ParamValues() []string {
@@ -318,7 +334,15 @@ func (c *context) ParamValues() []string {
}
func (c *context) SetParamValues(values ...string) {
- c.pvalues = values
+ // NOTE: Don't just set c.pvalues = values, because it has to have length c.echo.maxParam at all times
+ // It will brake the Router#Find code
+ limit := len(values)
+ if limit > *c.echo.maxParam {
+ limit = *c.echo.maxParam
+ }
+ for i := 0; i < limit; i++ {
+ c.pvalues[i] = values[i]
+ }
}
func (c *context) QueryParam(name string) string {
@@ -361,7 +385,7 @@ func (c *context) FormFile(name string) (*multipart.FileHeader, error) {
if err != nil {
return nil, err
}
- defer f.Close()
+ f.Close()
return fh, nil
}
diff --git a/vendor/github.com/labstack/echo/v4/echo.go b/vendor/github.com/labstack/echo/v4/echo.go
index 18c11016..3fccaf64 100644
--- a/vendor/github.com/labstack/echo/v4/echo.go
+++ b/vendor/github.com/labstack/echo/v4/echo.go
@@ -49,7 +49,6 @@ import (
"net/http"
"net/url"
"os"
- "path"
"path/filepath"
"reflect"
"runtime"
@@ -68,6 +67,9 @@ type (
// Echo is the top-level framework instance.
Echo struct {
common
+ // startupMutex is mutex to lock Echo instance access during server configuration and startup. Useful for to get
+ // listener address info (on which interface/port was listener binded) without having data races.
+ startupMutex sync.RWMutex
StdLogger *stdLog.Logger
colorer *color.Color
premiddleware []MiddlewareFunc
@@ -92,6 +94,7 @@ type (
Renderer Renderer
Logger Logger
IPExtractor IPExtractor
+ ListenerNetwork string
}
// Route contains a handler and information for matching against requests.
@@ -231,7 +234,7 @@ const (
const (
// Version of Echo
- Version = "4.1.17"
+ Version = "4.2.1"
website = "https://echo.labstack.com"
// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
banner = `
@@ -281,6 +284,7 @@ var (
ErrInvalidRedirectCode = errors.New("invalid redirect status code")
ErrCookieNotFound = errors.New("cookie not found")
ErrInvalidCertOrKeyType = errors.New("invalid cert or key type, must be string or []byte")
+ ErrInvalidListenerNetwork = errors.New("invalid listener network")
)
// Error handlers
@@ -302,9 +306,10 @@ func New() (e *Echo) {
AutoTLSManager: autocert.Manager{
Prompt: autocert.AcceptTOS,
},
- Logger: log.New("echo"),
- colorer: color.New(),
- maxParam: new(int),
+ Logger: log.New("echo"),
+ colorer: color.New(),
+ maxParam: new(int),
+ ListenerNetwork: "tcp",
}
e.Server.Handler = e
e.TLSServer.Handler = e
@@ -362,10 +367,12 @@ func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
// Issue #1426
code := he.Code
message := he.Message
- if e.Debug {
- message = err.Error()
- } else if m, ok := message.(string); ok {
- message = Map{"message": m}
+ if m, ok := he.Message.(string); ok {
+ if e.Debug {
+ message = Map{"message": m, "error": err.Error()}
+ } else {
+ message = Map{"message": m}
+ }
}
// Send response
@@ -481,7 +488,7 @@ func (common) static(prefix, root string, get func(string, HandlerFunc, ...Middl
return err
}
- name := filepath.Join(root, path.Clean("/"+p)) // "/"+ for security
+ name := filepath.Join(root, filepath.Clean("/"+p)) // "/"+ for security
fi, err := os.Stat(name)
if err != nil {
// The access path does not exist
@@ -496,8 +503,15 @@ func (common) static(prefix, root string, get func(string, HandlerFunc, ...Middl
}
return c.File(name)
}
- if prefix == "/" {
- return get(prefix+"*", h)
+ // Handle added routes based on trailing slash:
+ // /prefix => exact route "/prefix" + any route "/prefix/*"
+ // /prefix/ => only any route "/prefix/*"
+ if prefix != "" {
+ if prefix[len(prefix)-1] == '/' {
+ // Only add any route for intentional trailing slash
+ return get(prefix+"*", h)
+ }
+ get(prefix, h)
}
return get(prefix+"/*", h)
}
@@ -570,7 +584,7 @@ func (e *Echo) Reverse(name string, params ...interface{}) string {
for _, r := range e.router.routes {
if r.Name == name {
for i, l := 0, len(r.Path); i < l; i++ {
- if r.Path[i] == ':' && n < ln {
+ if (r.Path[i] == ':' || r.Path[i] == '*') && n < ln {
for ; i < l && r.Path[i] != '/'; i++ {
}
uri.WriteString(fmt.Sprintf("%v", params[n]))
@@ -612,7 +626,6 @@ func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Acquire context
c := e.pool.Get().(*context)
c.Reset(r, w)
-
h := NotFoundHandler
if e.premiddleware == nil {
@@ -640,21 +653,30 @@ func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Start starts an HTTP server.
func (e *Echo) Start(address string) error {
+ e.startupMutex.Lock()
e.Server.Addr = address
- return e.StartServer(e.Server)
+ if err := e.configureServer(e.Server); err != nil {
+ e.startupMutex.Unlock()
+ return err
+ }
+ e.startupMutex.Unlock()
+ return e.Server.Serve(e.Listener)
}
// StartTLS starts an HTTPS server.
// If `certFile` or `keyFile` is `string` the values are treated as file paths.
// If `certFile` or `keyFile` is `[]byte` the values are treated as the certificate or key as-is.
func (e *Echo) StartTLS(address string, certFile, keyFile interface{}) (err error) {
+ e.startupMutex.Lock()
var cert []byte
if cert, err = filepathOrContent(certFile); err != nil {
+ e.startupMutex.Unlock()
return
}
var key []byte
if key, err = filepathOrContent(keyFile); err != nil {
+ e.startupMutex.Unlock()
return
}
@@ -662,10 +684,17 @@ func (e *Echo) StartTLS(address string, certFile, keyFile interface{}) (err erro
s.TLSConfig = new(tls.Config)
s.TLSConfig.Certificates = make([]tls.Certificate, 1)
if s.TLSConfig.Certificates[0], err = tls.X509KeyPair(cert, key); err != nil {
+ e.startupMutex.Unlock()
return
}
- return e.startTLS(address)
+ e.configureTLS(address)
+ if err := e.configureServer(s); err != nil {
+ e.startupMutex.Unlock()
+ return err
+ }
+ e.startupMutex.Unlock()
+ return s.Serve(e.TLSListener)
}
func filepathOrContent(fileOrContent interface{}) (content []byte, err error) {
@@ -681,24 +710,45 @@ func filepathOrContent(fileOrContent interface{}) (content []byte, err error) {
// StartAutoTLS starts an HTTPS server using certificates automatically installed from https://letsencrypt.org.
func (e *Echo) StartAutoTLS(address string) error {
+ e.startupMutex.Lock()
s := e.TLSServer
s.TLSConfig = new(tls.Config)
s.TLSConfig.GetCertificate = e.AutoTLSManager.GetCertificate
s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, acme.ALPNProto)
- return e.startTLS(address)
+
+ e.configureTLS(address)
+ if err := e.configureServer(s); err != nil {
+ e.startupMutex.Unlock()
+ return err
+ }
+ e.startupMutex.Unlock()
+ return s.Serve(e.TLSListener)
}
-func (e *Echo) startTLS(address string) error {
+func (e *Echo) configureTLS(address string) {
s := e.TLSServer
s.Addr = address
if !e.DisableHTTP2 {
s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, "h2")
}
- return e.StartServer(e.TLSServer)
}
// StartServer starts a custom http server.
func (e *Echo) StartServer(s *http.Server) (err error) {
+ e.startupMutex.Lock()
+ if err := e.configureServer(s); err != nil {
+ e.startupMutex.Unlock()
+ return err
+ }
+ if s.TLSConfig != nil {
+ e.startupMutex.Unlock()
+ return s.Serve(e.TLSListener)
+ }
+ e.startupMutex.Unlock()
+ return s.Serve(e.Listener)
+}
+
+func (e *Echo) configureServer(s *http.Server) (err error) {
// Setup
e.colorer.SetOutput(e.Logger.Output())
s.ErrorLog = e.StdLogger
@@ -713,7 +763,7 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
if s.TLSConfig == nil {
if e.Listener == nil {
- e.Listener, err = newListener(s.Addr)
+ e.Listener, err = newListener(s.Addr, e.ListenerNetwork)
if err != nil {
return err
}
@@ -721,10 +771,10 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
if !e.HidePort {
e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
}
- return s.Serve(e.Listener)
+ return nil
}
if e.TLSListener == nil {
- l, err := newListener(s.Addr)
+ l, err := newListener(s.Addr, e.ListenerNetwork)
if err != nil {
return err
}
@@ -733,11 +783,32 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
if !e.HidePort {
e.colorer.Printf("⇨ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr()))
}
- return s.Serve(e.TLSListener)
+ return nil
+}
+
+// ListenerAddr returns net.Addr for Listener
+func (e *Echo) ListenerAddr() net.Addr {
+ e.startupMutex.RLock()
+ defer e.startupMutex.RUnlock()
+ if e.Listener == nil {
+ return nil
+ }
+ return e.Listener.Addr()
+}
+
+// TLSListenerAddr returns net.Addr for TLSListener
+func (e *Echo) TLSListenerAddr() net.Addr {
+ e.startupMutex.RLock()
+ defer e.startupMutex.RUnlock()
+ if e.TLSListener == nil {
+ return nil
+ }
+ return e.TLSListener.Addr()
}
// StartH2CServer starts a custom http/2 server with h2c (HTTP/2 Cleartext).
func (e *Echo) StartH2CServer(address string, h2s *http2.Server) (err error) {
+ e.startupMutex.Lock()
// Setup
s := e.Server
s.Addr = address
@@ -753,20 +824,24 @@ func (e *Echo) StartH2CServer(address string, h2s *http2.Server) (err error) {
}
if e.Listener == nil {
- e.Listener, err = newListener(s.Addr)
+ e.Listener, err = newListener(s.Addr, e.ListenerNetwork)
if err != nil {
+ e.startupMutex.Unlock()
return err
}
}
if !e.HidePort {
e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
}
+ e.startupMutex.Unlock()
return s.Serve(e.Listener)
}
// Close immediately stops the server.
// It internally calls `http.Server#Close()`.
func (e *Echo) Close() error {
+ e.startupMutex.Lock()
+ defer e.startupMutex.Unlock()
if err := e.TLSServer.Close(); err != nil {
return err
}
@@ -776,6 +851,8 @@ func (e *Echo) Close() error {
// Shutdown stops the server gracefully.
// It internally calls `http.Server#Shutdown()`.
func (e *Echo) Shutdown(ctx stdContext.Context) error {
+ e.startupMutex.Lock()
+ defer e.startupMutex.Unlock()
if err := e.TLSServer.Shutdown(ctx); err != nil {
return err
}
@@ -833,6 +910,9 @@ func WrapMiddleware(m func(http.Handler) http.Handler) MiddlewareFunc {
}
// GetPath returns RawPath, if it's empty returns Path from URL
+// Difference between RawPath and Path is:
+// * Path is where request path is stored. Value is stored in decoded form: /%47%6f%2f becomes /Go/.
+// * RawPath is an optional field which only gets set if the default encoding is different from Path.
func GetPath(r *http.Request) string {
path := r.URL.RawPath
if path == "" {
@@ -883,8 +963,11 @@ func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
return
}
-func newListener(address string) (*tcpKeepAliveListener, error) {
- l, err := net.Listen("tcp", address)
+func newListener(address, network string) (*tcpKeepAliveListener, error) {
+ if network != "tcp" && network != "tcp4" && network != "tcp6" {
+ return nil, ErrInvalidListenerNetwork
+ }
+ l, err := net.Listen(network, address)
if err != nil {
return nil, err
}
diff --git a/vendor/github.com/labstack/echo/v4/go.mod b/vendor/github.com/labstack/echo/v4/go.mod
index 74c6a9ab..87711707 100644
--- a/vendor/github.com/labstack/echo/v4/go.mod
+++ b/vendor/github.com/labstack/echo/v4/go.mod
@@ -12,4 +12,5 @@ require (
golang.org/x/net v0.0.0-20200822124328-c89045814202
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 // indirect
golang.org/x/text v0.3.3 // indirect
+ golang.org/x/time v0.0.0-20201208040808-7e3f01d25324
)
diff --git a/vendor/github.com/labstack/echo/v4/go.sum b/vendor/github.com/labstack/echo/v4/go.sum
index 58c80c83..54ba24e6 100644
--- a/vendor/github.com/labstack/echo/v4/go.sum
+++ b/vendor/github.com/labstack/echo/v4/go.sum
@@ -46,6 +46,8 @@ golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
+golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/vendor/github.com/labstack/echo/v4/middleware/basic_auth.go b/vendor/github.com/labstack/echo/v4/middleware/basic_auth.go
index 76ba2420..8cf1ed9f 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/basic_auth.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/basic_auth.go
@@ -73,7 +73,7 @@ func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc {
auth := c.Request().Header.Get(echo.HeaderAuthorization)
l := len(basic)
- if len(auth) > l+1 && strings.ToLower(auth[:l]) == basic {
+ if len(auth) > l+1 && strings.EqualFold(auth[:l], basic) {
b, err := base64.StdEncoding.DecodeString(auth[l+1:])
if err != nil {
return err
diff --git a/vendor/github.com/labstack/echo/v4/middleware/compress.go b/vendor/github.com/labstack/echo/v4/middleware/compress.go
index dd97d983..6ae19745 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/compress.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/compress.go
@@ -8,6 +8,7 @@ import (
"net"
"net/http"
"strings"
+ "sync"
"github.com/labstack/echo/v4"
)
@@ -58,6 +59,8 @@ func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
config.Level = DefaultGzipConfig.Level
}
+ pool := gzipCompressPool(config)
+
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
@@ -68,11 +71,13 @@ func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
res.Header().Add(echo.HeaderVary, echo.HeaderAcceptEncoding)
if strings.Contains(c.Request().Header.Get(echo.HeaderAcceptEncoding), gzipScheme) {
res.Header().Set(echo.HeaderContentEncoding, gzipScheme) // Issue #806
- rw := res.Writer
- w, err := gzip.NewWriterLevel(rw, config.Level)
- if err != nil {
- return err
+ i := pool.Get()
+ w, ok := i.(*gzip.Writer)
+ if !ok {
+ return echo.NewHTTPError(http.StatusInternalServerError, i.(error).Error())
}
+ rw := res.Writer
+ w.Reset(rw)
defer func() {
if res.Size == 0 {
if res.Header().Get(echo.HeaderContentEncoding) == gzipScheme {
@@ -85,6 +90,7 @@ func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
w.Reset(ioutil.Discard)
}
w.Close()
+ pool.Put(w)
}()
grw := &gzipResponseWriter{Writer: w, ResponseWriter: rw}
res.Writer = grw
@@ -126,3 +132,15 @@ func (w *gzipResponseWriter) Push(target string, opts *http.PushOptions) error {
}
return http.ErrNotSupported
}
+
+func gzipCompressPool(config GzipConfig) sync.Pool {
+ return sync.Pool{
+ New: func() interface{} {
+ w, err := gzip.NewWriterLevel(ioutil.Discard, config.Level)
+ if err != nil {
+ return err
+ }
+ return w
+ },
+ }
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/cors.go b/vendor/github.com/labstack/echo/v4/middleware/cors.go
index 5dfe31f9..d6ef8964 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/cors.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/cors.go
@@ -2,6 +2,7 @@ package middleware
import (
"net/http"
+ "regexp"
"strconv"
"strings"
@@ -18,6 +19,13 @@ type (
// Optional. Default value []string{"*"}.
AllowOrigins []string `yaml:"allow_origins"`
+ // AllowOriginFunc is a custom function to validate the origin. It takes the
+ // origin as an argument and returns true if allowed or false otherwise. If
+ // an error is returned, it is returned by the handler. If this option is
+ // set, AllowOrigins is ignored.
+ // Optional.
+ AllowOriginFunc func(origin string) (bool, error) `yaml:"allow_origin_func"`
+
// AllowMethods defines a list methods allowed when accessing the resource.
// This is used in response to a preflight request.
// Optional. Default value DefaultCORSConfig.AllowMethods.
@@ -76,6 +84,15 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
config.AllowMethods = DefaultCORSConfig.AllowMethods
}
+ allowOriginPatterns := []string{}
+ for _, origin := range config.AllowOrigins {
+ pattern := regexp.QuoteMeta(origin)
+ pattern = strings.Replace(pattern, "\\*", ".*", -1)
+ pattern = strings.Replace(pattern, "\\?", ".", -1)
+ pattern = "^" + pattern + "$"
+ allowOriginPatterns = append(allowOriginPatterns, pattern)
+ }
+
allowMethods := strings.Join(config.AllowMethods, ",")
allowHeaders := strings.Join(config.AllowHeaders, ",")
exposeHeaders := strings.Join(config.ExposeHeaders, ",")
@@ -92,25 +109,73 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
origin := req.Header.Get(echo.HeaderOrigin)
allowOrigin := ""
- // Check allowed origins
- for _, o := range config.AllowOrigins {
- if o == "*" && config.AllowCredentials {
- allowOrigin = origin
- break
+ preflight := req.Method == http.MethodOptions
+ res.Header().Add(echo.HeaderVary, echo.HeaderOrigin)
+
+ // No Origin provided
+ if origin == "" {
+ if !preflight {
+ return next(c)
}
- if o == "*" || o == origin {
- allowOrigin = o
- break
+ return c.NoContent(http.StatusNoContent)
+ }
+
+ if config.AllowOriginFunc != nil {
+ allowed, err := config.AllowOriginFunc(origin)
+ if err != nil {
+ return err
}
- if matchSubdomain(origin, o) {
+ if allowed {
allowOrigin = origin
- break
+ }
+ } else {
+ // Check allowed origins
+ for _, o := range config.AllowOrigins {
+ if o == "*" && config.AllowCredentials {
+ allowOrigin = origin
+ break
+ }
+ if o == "*" || o == origin {
+ allowOrigin = o
+ break
+ }
+ if matchSubdomain(origin, o) {
+ allowOrigin = origin
+ break
+ }
+ }
+
+ // Check allowed origin patterns
+ for _, re := range allowOriginPatterns {
+ if allowOrigin == "" {
+ didx := strings.Index(origin, "://")
+ if didx == -1 {
+ continue
+ }
+ domAuth := origin[didx+3:]
+ // to avoid regex cost by invalid long domain
+ if len(domAuth) > 253 {
+ break
+ }
+
+ if match, _ := regexp.MatchString(re, origin); match {
+ allowOrigin = origin
+ break
+ }
+ }
}
}
+ // Origin not allowed
+ if allowOrigin == "" {
+ if !preflight {
+ return next(c)
+ }
+ return c.NoContent(http.StatusNoContent)
+ }
+
// Simple request
- if req.Method != http.MethodOptions {
- res.Header().Add(echo.HeaderVary, echo.HeaderOrigin)
+ if !preflight {
res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin)
if config.AllowCredentials {
res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true")
@@ -122,7 +187,6 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
}
// Preflight request
- res.Header().Add(echo.HeaderVary, echo.HeaderOrigin)
res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestMethod)
res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestHeaders)
res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin)
diff --git a/vendor/github.com/labstack/echo/v4/middleware/csrf.go b/vendor/github.com/labstack/echo/v4/middleware/csrf.go
index 09a66bb6..60f809a0 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/csrf.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/csrf.go
@@ -57,6 +57,10 @@ type (
// Indicates if CSRF cookie is HTTP only.
// Optional. Default value false.
CookieHTTPOnly bool `yaml:"cookie_http_only"`
+
+ // Indicates SameSite mode of the CSRF cookie.
+ // Optional. Default value SameSiteDefaultMode.
+ CookieSameSite http.SameSite `yaml:"cookie_same_site"`
}
// csrfTokenExtractor defines a function that takes `echo.Context` and returns
@@ -67,12 +71,13 @@ type (
var (
// DefaultCSRFConfig is the default CSRF middleware config.
DefaultCSRFConfig = CSRFConfig{
- Skipper: DefaultSkipper,
- TokenLength: 32,
- TokenLookup: "header:" + echo.HeaderXCSRFToken,
- ContextKey: "csrf",
- CookieName: "_csrf",
- CookieMaxAge: 86400,
+ Skipper: DefaultSkipper,
+ TokenLength: 32,
+ TokenLookup: "header:" + echo.HeaderXCSRFToken,
+ ContextKey: "csrf",
+ CookieName: "_csrf",
+ CookieMaxAge: 86400,
+ CookieSameSite: http.SameSiteDefaultMode,
}
)
@@ -105,6 +110,9 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
if config.CookieMaxAge == 0 {
config.CookieMaxAge = DefaultCSRFConfig.CookieMaxAge
}
+ if config.CookieSameSite == SameSiteNoneMode {
+ config.CookieSecure = true
+ }
// Initialize
parts := strings.Split(config.TokenLookup, ":")
@@ -157,6 +165,9 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
if config.CookieDomain != "" {
cookie.Domain = config.CookieDomain
}
+ if config.CookieSameSite != http.SameSiteDefaultMode {
+ cookie.SameSite = config.CookieSameSite
+ }
cookie.Expires = time.Now().Add(time.Duration(config.CookieMaxAge) * time.Second)
cookie.Secure = config.CookieSecure
cookie.HttpOnly = config.CookieHTTPOnly
diff --git a/vendor/github.com/labstack/echo/v4/middleware/csrf_samesite.go b/vendor/github.com/labstack/echo/v4/middleware/csrf_samesite.go
new file mode 100644
index 00000000..9a27dc43
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/csrf_samesite.go
@@ -0,0 +1,12 @@
+// +build go1.13
+
+package middleware
+
+import (
+ "net/http"
+)
+
+const (
+ // SameSiteNoneMode required to be redefined for Go 1.12 support (see #1524)
+ SameSiteNoneMode http.SameSite = http.SameSiteNoneMode
+)
diff --git a/vendor/github.com/labstack/echo/v4/middleware/csrf_samesite_1.12.go b/vendor/github.com/labstack/echo/v4/middleware/csrf_samesite_1.12.go
new file mode 100644
index 00000000..22076dd6
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/csrf_samesite_1.12.go
@@ -0,0 +1,12 @@
+// +build !go1.13
+
+package middleware
+
+import (
+ "net/http"
+)
+
+const (
+ // SameSiteNoneMode required to be redefined for Go 1.12 support (see #1524)
+ SameSiteNoneMode http.SameSite = 4
+)
diff --git a/vendor/github.com/labstack/echo/v4/middleware/decompress.go b/vendor/github.com/labstack/echo/v4/middleware/decompress.go
new file mode 100644
index 00000000..c046359a
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/decompress.go
@@ -0,0 +1,120 @@
+package middleware
+
+import (
+ "bytes"
+ "compress/gzip"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "sync"
+
+ "github.com/labstack/echo/v4"
+)
+
+type (
+ // DecompressConfig defines the config for Decompress middleware.
+ DecompressConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper Skipper
+
+ // GzipDecompressPool defines an interface to provide the sync.Pool used to create/store Gzip readers
+ GzipDecompressPool Decompressor
+ }
+)
+
+//GZIPEncoding content-encoding header if set to "gzip", decompress body contents.
+const GZIPEncoding string = "gzip"
+
+// Decompressor is used to get the sync.Pool used by the middleware to get Gzip readers
+type Decompressor interface {
+ gzipDecompressPool() sync.Pool
+}
+
+var (
+ //DefaultDecompressConfig defines the config for decompress middleware
+ DefaultDecompressConfig = DecompressConfig{
+ Skipper: DefaultSkipper,
+ GzipDecompressPool: &DefaultGzipDecompressPool{},
+ }
+)
+
+// DefaultGzipDecompressPool is the default implementation of Decompressor interface
+type DefaultGzipDecompressPool struct {
+}
+
+func (d *DefaultGzipDecompressPool) gzipDecompressPool() sync.Pool {
+ return sync.Pool{
+ New: func() interface{} {
+ // create with an empty reader (but with GZIP header)
+ w, err := gzip.NewWriterLevel(ioutil.Discard, gzip.BestSpeed)
+ if err != nil {
+ return err
+ }
+
+ b := new(bytes.Buffer)
+ w.Reset(b)
+ w.Flush()
+ w.Close()
+
+ r, err := gzip.NewReader(bytes.NewReader(b.Bytes()))
+ if err != nil {
+ return err
+ }
+ return r
+ },
+ }
+}
+
+//Decompress decompresses request body based if content encoding type is set to "gzip" with default config
+func Decompress() echo.MiddlewareFunc {
+ return DecompressWithConfig(DefaultDecompressConfig)
+}
+
+//DecompressWithConfig decompresses request body based if content encoding type is set to "gzip" with config
+func DecompressWithConfig(config DecompressConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Skipper == nil {
+ config.Skipper = DefaultGzipConfig.Skipper
+ }
+ if config.GzipDecompressPool == nil {
+ config.GzipDecompressPool = DefaultDecompressConfig.GzipDecompressPool
+ }
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ pool := config.GzipDecompressPool.gzipDecompressPool()
+ return func(c echo.Context) error {
+ if config.Skipper(c) {
+ return next(c)
+ }
+ switch c.Request().Header.Get(echo.HeaderContentEncoding) {
+ case GZIPEncoding:
+ b := c.Request().Body
+
+ i := pool.Get()
+ gr, ok := i.(*gzip.Reader)
+ if !ok {
+ return echo.NewHTTPError(http.StatusInternalServerError, i.(error).Error())
+ }
+
+ if err := gr.Reset(b); err != nil {
+ pool.Put(gr)
+ if err == io.EOF { //ignore if body is empty
+ return next(c)
+ }
+ return err
+ }
+ var buf bytes.Buffer
+ io.Copy(&buf, gr)
+
+ gr.Close()
+ pool.Put(gr)
+
+ b.Close() // http.Request.Body is closed by the Server, but because we are replacing it, it must be closed here
+
+ r := ioutil.NopCloser(&buf)
+ c.Request().Body = r
+ }
+ return next(c)
+ }
+ }
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/jwt.go b/vendor/github.com/labstack/echo/v4/middleware/jwt.go
index 3c7c4868..da00ea56 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/jwt.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/jwt.go
@@ -57,6 +57,7 @@ type (
// - "query:<name>"
// - "param:<name>"
// - "cookie:<name>"
+ // - "form:<name>"
TokenLookup string
// AuthScheme to be used in the Authorization header.
@@ -86,6 +87,7 @@ const (
// Errors
var (
ErrJWTMissing = echo.NewHTTPError(http.StatusBadRequest, "missing or malformed jwt")
+ ErrJWTInvalid = echo.NewHTTPError(http.StatusUnauthorized, "invalid or expired jwt")
)
var (
@@ -166,6 +168,8 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
extractor = jwtFromParam(parts[1])
case "cookie":
extractor = jwtFromCookie(parts[1])
+ case "form":
+ extractor = jwtFromForm(parts[1])
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
@@ -213,8 +217,8 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
return config.ErrorHandlerWithContext(err, c)
}
return &echo.HTTPError{
- Code: http.StatusUnauthorized,
- Message: "invalid or expired jwt",
+ Code: ErrJWTInvalid.Code,
+ Message: ErrJWTInvalid.Message,
Internal: err,
}
}
@@ -265,3 +269,14 @@ func jwtFromCookie(name string) jwtExtractor {
return cookie.Value, nil
}
}
+
+// jwtFromForm returns a `jwtExtractor` that extracts token from the form field.
+func jwtFromForm(name string) jwtExtractor {
+ return func(c echo.Context) (string, error) {
+ field := c.FormValue(name)
+ if field == "" {
+ return "", ErrJWTMissing
+ }
+ return field, nil
+ }
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/middleware.go b/vendor/github.com/labstack/echo/v4/middleware/middleware.go
index d0b7153c..6bdb0eb7 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/middleware.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/middleware.go
@@ -1,6 +1,8 @@
package middleware
import (
+ "net/http"
+ "net/url"
"regexp"
"strconv"
"strings"
@@ -32,6 +34,47 @@ func captureTokens(pattern *regexp.Regexp, input string) *strings.Replacer {
return strings.NewReplacer(replace...)
}
+func rewriteRulesRegex(rewrite map[string]string) map[*regexp.Regexp]string {
+ // Initialize
+ rulesRegex := map[*regexp.Regexp]string{}
+ for k, v := range rewrite {
+ k = regexp.QuoteMeta(k)
+ k = strings.Replace(k, `\*`, "(.*?)", -1)
+ if strings.HasPrefix(k, `\^`) {
+ k = strings.Replace(k, `\^`, "^", -1)
+ }
+ k = k + "$"
+ rulesRegex[regexp.MustCompile(k)] = v
+ }
+ return rulesRegex
+}
+
+func rewritePath(rewriteRegex map[*regexp.Regexp]string, req *http.Request) {
+ for k, v := range rewriteRegex {
+ rawPath := req.URL.RawPath
+ if rawPath != "" {
+ // RawPath is only set when there has been escaping done. In that case Path must be deduced from rewritten RawPath
+ // because encoded Path could match rules that RawPath did not
+ if replacer := captureTokens(k, rawPath); replacer != nil {
+ rawPath = replacer.Replace(v)
+
+ req.URL.RawPath = rawPath
+ req.URL.Path, _ = url.PathUnescape(rawPath)
+
+ return // rewrite only once
+ }
+
+ continue
+ }
+
+ if replacer := captureTokens(k, req.URL.Path); replacer != nil {
+ req.URL.Path = replacer.Replace(v)
+
+ return // rewrite only once
+ }
+ }
+}
+
// DefaultSkipper returns false which processes the middleware.
func DefaultSkipper(echo.Context) bool {
return false
diff --git a/vendor/github.com/labstack/echo/v4/middleware/proxy.go b/vendor/github.com/labstack/echo/v4/middleware/proxy.go
index a9b91f6c..63eec5a2 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/proxy.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/proxy.go
@@ -8,7 +8,6 @@ import (
"net/http"
"net/url"
"regexp"
- "strings"
"sync"
"sync/atomic"
"time"
@@ -37,6 +36,13 @@ type (
// "/users/*/orders/*": "/user/$1/order/$2",
Rewrite map[string]string
+ // RegexRewrite defines rewrite rules using regexp.Rexexp with captures
+ // Every capture group in the values can be retrieved by index e.g. $1, $2 and so on.
+ // Example:
+ // "^/old/[0.9]+/": "/new",
+ // "^/api/.+?/(.*)": "/v2/$1",
+ RegexRewrite map[*regexp.Regexp]string
+
// Context key to store selected ProxyTarget into context.
// Optional. Default value "target".
ContextKey string
@@ -47,8 +53,6 @@ type (
// ModifyResponse defines function to modify response from ProxyTarget.
ModifyResponse func(*http.Response) error
-
- rewriteRegex map[*regexp.Regexp]string
}
// ProxyTarget defines the upstream target.
@@ -206,12 +210,14 @@ func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc {
if config.Balancer == nil {
panic("echo: proxy middleware requires balancer")
}
- config.rewriteRegex = map[*regexp.Regexp]string{}
- // Initialize
- for k, v := range config.Rewrite {
- k = strings.Replace(k, "*", "(\\S*)", -1)
- config.rewriteRegex[regexp.MustCompile(k)] = v
+ if config.Rewrite != nil {
+ if config.RegexRewrite == nil {
+ config.RegexRewrite = make(map[*regexp.Regexp]string)
+ }
+ for k, v := range rewriteRulesRegex(config.Rewrite) {
+ config.RegexRewrite[k] = v
+ }
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
@@ -225,13 +231,8 @@ func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc {
tgt := config.Balancer.Next(c)
c.Set(config.ContextKey, tgt)
- // Rewrite
- for k, v := range config.rewriteRegex {
- replacer := captureTokens(k, echo.GetPath(req))
- if replacer != nil {
- req.URL.Path = replacer.Replace(v)
- }
- }
+ // Set rewrite path and raw path
+ rewritePath(config.RegexRewrite, req)
// Fix header
// Basically it's not good practice to unconditionally pass incoming x-real-ip header to upstream.
diff --git a/vendor/github.com/labstack/echo/v4/middleware/proxy_1_11.go b/vendor/github.com/labstack/echo/v4/middleware/proxy_1_11.go
index a4392781..17d142d8 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/proxy_1_11.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/proxy_1_11.go
@@ -3,13 +3,22 @@
package middleware
import (
+ "context"
"fmt"
"net/http"
"net/http/httputil"
+ "strings"
"github.com/labstack/echo/v4"
)
+// StatusCodeContextCanceled is a custom HTTP status code for situations
+// where a client unexpectedly closed the connection to the server.
+// As there is no standard error code for "client closed connection", but
+// various well-known HTTP clients and server implement this HTTP code we use
+// 499 too instead of the more problematic 5xx, which does not allow to detect this situation
+const StatusCodeContextCanceled = 499
+
func proxyHTTP(tgt *ProxyTarget, c echo.Context, config ProxyConfig) http.Handler {
proxy := httputil.NewSingleHostReverseProxy(tgt.URL)
proxy.ErrorHandler = func(resp http.ResponseWriter, req *http.Request, err error) {
@@ -17,7 +26,20 @@ func proxyHTTP(tgt *ProxyTarget, c echo.Context, config ProxyConfig) http.Handle
if tgt.Name != "" {
desc = fmt.Sprintf("%s(%s)", tgt.Name, tgt.URL.String())
}
- c.Set("_error", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("remote %s unreachable, could not forward: %v", desc, err)))
+ // If the client canceled the request (usually by closing the connection), we can report a
+ // client error (4xx) instead of a server error (5xx) to correctly identify the situation.
+ // The Go standard library (at of late 2020) wraps the exported, standard
+ // context.Canceled error with unexported garbage value requiring a substring check, see
+ // https://github.com/golang/go/blob/6965b01ea248cabb70c3749fd218b36089a21efb/src/net/net.go#L416-L430
+ if err == context.Canceled || strings.Contains(err.Error(), "operation was canceled") {
+ httpError := echo.NewHTTPError(StatusCodeContextCanceled, fmt.Sprintf("client closed connection: %v", err))
+ httpError.Internal = err
+ c.Set("_error", httpError)
+ } else {
+ httpError := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("remote %s unreachable, could not forward: %v", desc, err))
+ httpError.Internal = err
+ c.Set("_error", httpError)
+ }
}
proxy.Transport = config.Transport
proxy.ModifyResponse = config.ModifyResponse
diff --git a/vendor/github.com/labstack/echo/v4/middleware/rate_limiter.go b/vendor/github.com/labstack/echo/v4/middleware/rate_limiter.go
new file mode 100644
index 00000000..46a310d9
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/rate_limiter.go
@@ -0,0 +1,266 @@
+package middleware
+
+import (
+ "net/http"
+ "sync"
+ "time"
+
+ "github.com/labstack/echo/v4"
+ "golang.org/x/time/rate"
+)
+
+type (
+ // RateLimiterStore is the interface to be implemented by custom stores.
+ RateLimiterStore interface {
+ // Stores for the rate limiter have to implement the Allow method
+ Allow(identifier string) (bool, error)
+ }
+)
+
+type (
+ // RateLimiterConfig defines the configuration for the rate limiter
+ RateLimiterConfig struct {
+ Skipper Skipper
+ BeforeFunc BeforeFunc
+ // IdentifierExtractor uses echo.Context to extract the identifier for a visitor
+ IdentifierExtractor Extractor
+ // Store defines a store for the rate limiter
+ Store RateLimiterStore
+ // ErrorHandler provides a handler to be called when IdentifierExtractor returns an error
+ ErrorHandler func(context echo.Context, err error) error
+ // DenyHandler provides a handler to be called when RateLimiter denies access
+ DenyHandler func(context echo.Context, identifier string, err error) error
+ }
+ // Extractor is used to extract data from echo.Context
+ Extractor func(context echo.Context) (string, error)
+)
+
+// errors
+var (
+ // ErrRateLimitExceeded denotes an error raised when rate limit is exceeded
+ ErrRateLimitExceeded = echo.NewHTTPError(http.StatusTooManyRequests, "rate limit exceeded")
+ // ErrExtractorError denotes an error raised when extractor function is unsuccessful
+ ErrExtractorError = echo.NewHTTPError(http.StatusForbidden, "error while extracting identifier")
+)
+
+// DefaultRateLimiterConfig defines default values for RateLimiterConfig
+var DefaultRateLimiterConfig = RateLimiterConfig{
+ Skipper: DefaultSkipper,
+ IdentifierExtractor: func(ctx echo.Context) (string, error) {
+ id := ctx.RealIP()
+ return id, nil
+ },
+ ErrorHandler: func(context echo.Context, err error) error {
+ return &echo.HTTPError{
+ Code: ErrExtractorError.Code,
+ Message: ErrExtractorError.Message,
+ Internal: err,
+ }
+ },
+ DenyHandler: func(context echo.Context, identifier string, err error) error {
+ return &echo.HTTPError{
+ Code: ErrRateLimitExceeded.Code,
+ Message: ErrRateLimitExceeded.Message,
+ Internal: err,
+ }
+ },
+}
+
+/*
+RateLimiter returns a rate limiting middleware
+
+ e := echo.New()
+
+ limiterStore := middleware.NewRateLimiterMemoryStore(20)
+
+ e.GET("/rate-limited", func(c echo.Context) error {
+ return c.String(http.StatusOK, "test")
+ }, RateLimiter(limiterStore))
+*/
+func RateLimiter(store RateLimiterStore) echo.MiddlewareFunc {
+ config := DefaultRateLimiterConfig
+ config.Store = store
+
+ return RateLimiterWithConfig(config)
+}
+
+/*
+RateLimiterWithConfig returns a rate limiting middleware
+
+ e := echo.New()
+
+ config := middleware.RateLimiterConfig{
+ Skipper: DefaultSkipper,
+ Store: middleware.NewRateLimiterMemoryStore(
+ middleware.RateLimiterMemoryStoreConfig{Rate: 10, Burst: 30, ExpiresIn: 3 * time.Minute}
+ )
+ IdentifierExtractor: func(ctx echo.Context) (string, error) {
+ id := ctx.RealIP()
+ return id, nil
+ },
+ ErrorHandler: func(context echo.Context, err error) error {
+ return context.JSON(http.StatusTooManyRequests, nil)
+ },
+ DenyHandler: func(context echo.Context, identifier string) error {
+ return context.JSON(http.StatusForbidden, nil)
+ },
+ }
+
+ e.GET("/rate-limited", func(c echo.Context) error {
+ return c.String(http.StatusOK, "test")
+ }, middleware.RateLimiterWithConfig(config))
+*/
+func RateLimiterWithConfig(config RateLimiterConfig) echo.MiddlewareFunc {
+ if config.Skipper == nil {
+ config.Skipper = DefaultRateLimiterConfig.Skipper
+ }
+ if config.IdentifierExtractor == nil {
+ config.IdentifierExtractor = DefaultRateLimiterConfig.IdentifierExtractor
+ }
+ if config.ErrorHandler == nil {
+ config.ErrorHandler = DefaultRateLimiterConfig.ErrorHandler
+ }
+ if config.DenyHandler == nil {
+ config.DenyHandler = DefaultRateLimiterConfig.DenyHandler
+ }
+ if config.Store == nil {
+ panic("Store configuration must be provided")
+ }
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ if config.Skipper(c) {
+ return next(c)
+ }
+ if config.BeforeFunc != nil {
+ config.BeforeFunc(c)
+ }
+
+ identifier, err := config.IdentifierExtractor(c)
+ if err != nil {
+ c.Error(config.ErrorHandler(c, err))
+ return nil
+ }
+
+ if allow, err := config.Store.Allow(identifier); !allow {
+ c.Error(config.DenyHandler(c, identifier, err))
+ return nil
+ }
+ return next(c)
+ }
+ }
+}
+
+type (
+ // RateLimiterMemoryStore is the built-in store implementation for RateLimiter
+ RateLimiterMemoryStore struct {
+ visitors map[string]*Visitor
+ mutex sync.Mutex
+ rate rate.Limit
+ burst int
+ expiresIn time.Duration
+ lastCleanup time.Time
+ }
+ // Visitor signifies a unique user's limiter details
+ Visitor struct {
+ *rate.Limiter
+ lastSeen time.Time
+ }
+)
+
+/*
+NewRateLimiterMemoryStore returns an instance of RateLimiterMemoryStore with
+the provided rate (as req/s). Burst and ExpiresIn will be set to default values.
+
+Example (with 20 requests/sec):
+
+ limiterStore := middleware.NewRateLimiterMemoryStore(20)
+
+*/
+func NewRateLimiterMemoryStore(rate rate.Limit) (store *RateLimiterMemoryStore) {
+ return NewRateLimiterMemoryStoreWithConfig(RateLimiterMemoryStoreConfig{
+ Rate: rate,
+ })
+}
+
+/*
+NewRateLimiterMemoryStoreWithConfig returns an instance of RateLimiterMemoryStore
+with the provided configuration. Rate must be provided. Burst will be set to the value of
+the configured rate if not provided or set to 0.
+
+The build-in memory store is usually capable for modest loads. For higher loads other
+store implementations should be considered.
+
+Characteristics:
+* Concurrency above 100 parallel requests may causes measurable lock contention
+* A high number of different IP addresses (above 16000) may be impacted by the internally used Go map
+* A high number of requests from a single IP address may cause lock contention
+
+Example:
+
+ limiterStore := middleware.NewRateLimiterMemoryStoreWithConfig(
+ middleware.RateLimiterMemoryStoreConfig{Rate: 50, Burst: 200, ExpiresIn: 5 * time.Minutes},
+ )
+*/
+func NewRateLimiterMemoryStoreWithConfig(config RateLimiterMemoryStoreConfig) (store *RateLimiterMemoryStore) {
+ store = &RateLimiterMemoryStore{}
+
+ store.rate = config.Rate
+ store.burst = config.Burst
+ store.expiresIn = config.ExpiresIn
+ if config.ExpiresIn == 0 {
+ store.expiresIn = DefaultRateLimiterMemoryStoreConfig.ExpiresIn
+ }
+ if config.Burst == 0 {
+ store.burst = int(config.Rate)
+ }
+ store.visitors = make(map[string]*Visitor)
+ store.lastCleanup = now()
+ return
+}
+
+// RateLimiterMemoryStoreConfig represents configuration for RateLimiterMemoryStore
+type RateLimiterMemoryStoreConfig struct {
+ Rate rate.Limit // Rate of requests allowed to pass as req/s
+ Burst int // Burst additionally allows a number of requests to pass when rate limit is reached
+ ExpiresIn time.Duration // ExpiresIn is the duration after that a rate limiter is cleaned up
+}
+
+// DefaultRateLimiterMemoryStoreConfig provides default configuration values for RateLimiterMemoryStore
+var DefaultRateLimiterMemoryStoreConfig = RateLimiterMemoryStoreConfig{
+ ExpiresIn: 3 * time.Minute,
+}
+
+// Allow implements RateLimiterStore.Allow
+func (store *RateLimiterMemoryStore) Allow(identifier string) (bool, error) {
+ store.mutex.Lock()
+ limiter, exists := store.visitors[identifier]
+ if !exists {
+ limiter = new(Visitor)
+ limiter.Limiter = rate.NewLimiter(store.rate, store.burst)
+ store.visitors[identifier] = limiter
+ }
+ limiter.lastSeen = now()
+ if now().Sub(store.lastCleanup) > store.expiresIn {
+ store.cleanupStaleVisitors()
+ }
+ store.mutex.Unlock()
+ return limiter.AllowN(now(), 1), nil
+}
+
+/*
+cleanupStaleVisitors helps manage the size of the visitors map by removing stale records
+of users who haven't visited again after the configured expiry time has elapsed
+*/
+func (store *RateLimiterMemoryStore) cleanupStaleVisitors() {
+ for id, visitor := range store.visitors {
+ if now().Sub(visitor.lastSeen) > store.expiresIn {
+ delete(store.visitors, id)
+ }
+ }
+ store.lastCleanup = now()
+}
+
+/*
+actual time method which is mocked in test file
+*/
+var now = time.Now
diff --git a/vendor/github.com/labstack/echo/v4/middleware/rewrite.go b/vendor/github.com/labstack/echo/v4/middleware/rewrite.go
index d1387af0..c05d5d84 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/rewrite.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/rewrite.go
@@ -2,7 +2,6 @@ package middleware
import (
"regexp"
- "strings"
"github.com/labstack/echo/v4"
)
@@ -23,7 +22,12 @@ type (
// Required.
Rules map[string]string `yaml:"rules"`
- rulesRegex map[*regexp.Regexp]string
+ // RegexRules defines the URL path rewrite rules using regexp.Rexexp with captures
+ // Every capture group in the values can be retrieved by index e.g. $1, $2 and so on.
+ // Example:
+ // "^/old/[0.9]+/": "/new",
+ // "^/api/.+?/(.*)": "/v2/$1",
+ RegexRules map[*regexp.Regexp]string `yaml:"regex_rules"`
}
)
@@ -47,20 +51,19 @@ func Rewrite(rules map[string]string) echo.MiddlewareFunc {
// See: `Rewrite()`.
func RewriteWithConfig(config RewriteConfig) echo.MiddlewareFunc {
// Defaults
- if config.Rules == nil {
- panic("echo: rewrite middleware requires url path rewrite rules")
+ if config.Rules == nil && config.RegexRules == nil {
+ panic("echo: rewrite middleware requires url path rewrite rules or regex rules")
}
+
if config.Skipper == nil {
config.Skipper = DefaultBodyDumpConfig.Skipper
}
- config.rulesRegex = map[*regexp.Regexp]string{}
- // Initialize
- for k, v := range config.Rules {
- k = regexp.QuoteMeta(k)
- k = strings.Replace(k, `\*`, "(.*)", -1)
- k = k + "$"
- config.rulesRegex[regexp.MustCompile(k)] = v
+ if config.RegexRules == nil {
+ config.RegexRules = make(map[*regexp.Regexp]string)
+ }
+ for k, v := range rewriteRulesRegex(config.Rules) {
+ config.RegexRules[k] = v
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
@@ -70,15 +73,8 @@ func RewriteWithConfig(config RewriteConfig) echo.MiddlewareFunc {
}
req := c.Request()
-
- // Rewrite
- for k, v := range config.rulesRegex {
- replacer := captureTokens(k, req.URL.Path)
- if replacer != nil {
- req.URL.Path = replacer.Replace(v)
- break
- }
- }
+ // Set rewrite path and raw path
+ rewritePath(config.RegexRules, req)
return next(c)
}
}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/slash.go b/vendor/github.com/labstack/echo/v4/middleware/slash.go
index 0492b334..4188675b 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/slash.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/slash.go
@@ -60,7 +60,7 @@ func AddTrailingSlashWithConfig(config TrailingSlashConfig) echo.MiddlewareFunc
// Redirect
if config.RedirectCode != 0 {
- return c.Redirect(config.RedirectCode, uri)
+ return c.Redirect(config.RedirectCode, sanitizeURI(uri))
}
// Forward
@@ -108,7 +108,7 @@ func RemoveTrailingSlashWithConfig(config TrailingSlashConfig) echo.MiddlewareFu
// Redirect
if config.RedirectCode != 0 {
- return c.Redirect(config.RedirectCode, uri)
+ return c.Redirect(config.RedirectCode, sanitizeURI(uri))
}
// Forward
@@ -119,3 +119,12 @@ func RemoveTrailingSlashWithConfig(config TrailingSlashConfig) echo.MiddlewareFu
}
}
}
+
+func sanitizeURI(uri string) string {
+ // double slash `\\`, `//` or even `\/` is absolute uri for browsers and by redirecting request to that uri
+ // we are vulnerable to open redirect attack. so replace all slashes from the beginning with single slash
+ if len(uri) > 1 && (uri[0] == '\\' || uri[0] == '/') && (uri[1] == '\\' || uri[1] == '/') {
+ uri = "/" + strings.TrimLeft(uri, `/\`)
+ }
+ return uri
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/static.go b/vendor/github.com/labstack/echo/v4/middleware/static.go
index bc2087a7..ae79cb5f 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/static.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/static.go
@@ -36,6 +36,12 @@ type (
// Enable directory browsing.
// Optional. Default value false.
Browse bool `yaml:"browse"`
+
+ // Enable ignoring of the base of the URL path.
+ // Example: when assigning a static middleware to a non root path group,
+ // the filesystem path is not doubled
+ // Optional. Default value false.
+ IgnoreBase bool `yaml:"ignoreBase"`
}
)
@@ -161,7 +167,16 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
if err != nil {
return
}
- name := filepath.Join(config.Root, path.Clean("/"+p)) // "/"+ for security
+ name := filepath.Join(config.Root, filepath.Clean("/"+p)) // "/"+ for security
+
+ if config.IgnoreBase {
+ routePath := path.Base(strings.TrimRight(c.Path(), "/*"))
+ baseURLPath := path.Base(p)
+ if baseURLPath == routePath {
+ i := strings.LastIndex(name, routePath)
+ name = name[:i] + strings.Replace(name[i:], routePath, "", 1)
+ }
+ }
fi, err := os.Stat(name)
if err != nil {
diff --git a/vendor/github.com/labstack/echo/v4/middleware/timeout.go b/vendor/github.com/labstack/echo/v4/middleware/timeout.go
new file mode 100644
index 00000000..68f464e4
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/timeout.go
@@ -0,0 +1,111 @@
+// +build go1.13
+
+package middleware
+
+import (
+ "context"
+ "github.com/labstack/echo/v4"
+ "net/http"
+ "time"
+)
+
+type (
+ // TimeoutConfig defines the config for Timeout middleware.
+ TimeoutConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper Skipper
+
+ // ErrorMessage is written to response on timeout in addition to http.StatusServiceUnavailable (503) status code
+ // It can be used to define a custom timeout error message
+ ErrorMessage string
+
+ // OnTimeoutRouteErrorHandler is an error handler that is executed for error that was returned from wrapped route after
+ // request timeouted and we already had sent the error code (503) and message response to the client.
+ // NB: do not write headers/body inside this handler. The response has already been sent to the client and response writer
+ // will not accept anything no more. If you want to know what actual route middleware timeouted use `c.Path()`
+ OnTimeoutRouteErrorHandler func(err error, c echo.Context)
+
+ // Timeout configures a timeout for the middleware, defaults to 0 for no timeout
+ // NOTE: when difference between timeout duration and handler execution time is almost the same (in range of 100microseconds)
+ // the result of timeout does not seem to be reliable - could respond timeout, could respond handler output
+ // difference over 500microseconds (0.5millisecond) response seems to be reliable
+ Timeout time.Duration
+ }
+)
+
+var (
+ // DefaultTimeoutConfig is the default Timeout middleware config.
+ DefaultTimeoutConfig = TimeoutConfig{
+ Skipper: DefaultSkipper,
+ Timeout: 0,
+ ErrorMessage: "",
+ }
+)
+
+// Timeout returns a middleware which recovers from panics anywhere in the chain
+// and handles the control to the centralized HTTPErrorHandler.
+func Timeout() echo.MiddlewareFunc {
+ return TimeoutWithConfig(DefaultTimeoutConfig)
+}
+
+// TimeoutWithConfig returns a Timeout middleware with config.
+// See: `Timeout()`.
+func TimeoutWithConfig(config TimeoutConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Skipper == nil {
+ config.Skipper = DefaultTimeoutConfig.Skipper
+ }
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ if config.Skipper(c) || config.Timeout == 0 {
+ return next(c)
+ }
+
+ handlerWrapper := echoHandlerFuncWrapper{
+ ctx: c,
+ handler: next,
+ errChan: make(chan error, 1),
+ errHandler: config.OnTimeoutRouteErrorHandler,
+ }
+ handler := http.TimeoutHandler(handlerWrapper, config.Timeout, config.ErrorMessage)
+ handler.ServeHTTP(c.Response().Writer, c.Request())
+
+ select {
+ case err := <-handlerWrapper.errChan:
+ return err
+ default:
+ return nil
+ }
+ }
+ }
+}
+
+type echoHandlerFuncWrapper struct {
+ ctx echo.Context
+ handler echo.HandlerFunc
+ errHandler func(err error, c echo.Context)
+ errChan chan error
+}
+
+func (t echoHandlerFuncWrapper) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
+ // replace writer with TimeoutHandler custom one. This will guarantee that
+ // `writes by h to its ResponseWriter will return ErrHandlerTimeout.`
+ originalWriter := t.ctx.Response().Writer
+ t.ctx.Response().Writer = rw
+
+ err := t.handler(t.ctx)
+ if ctxErr := r.Context().Err(); ctxErr == context.DeadlineExceeded {
+ if err != nil && t.errHandler != nil {
+ t.errHandler(err, t.ctx)
+ }
+ return // on timeout we can not send handler error to client because `http.TimeoutHandler` has already sent headers
+ }
+ // we restore original writer only for cases we did not timeout. On timeout we have already sent response to client
+ // and should not anymore send additional headers/data
+ // so on timeout writer stays what http.TimeoutHandler uses and prevents writing headers/body
+ t.ctx.Response().Writer = originalWriter
+ if err != nil {
+ t.errChan <- err
+ }
+}
diff --git a/vendor/github.com/labstack/echo/v4/response.go b/vendor/github.com/labstack/echo/v4/response.go
index ca7405c5..84f7c9e7 100644
--- a/vendor/github.com/labstack/echo/v4/response.go
+++ b/vendor/github.com/labstack/echo/v4/response.go
@@ -56,11 +56,11 @@ func (r *Response) WriteHeader(code int) {
r.echo.Logger.Warn("response already committed")
return
}
+ r.Status = code
for _, fn := range r.beforeFuncs {
fn()
}
- r.Status = code
- r.Writer.WriteHeader(code)
+ r.Writer.WriteHeader(r.Status)
r.Committed = true
}
diff --git a/vendor/github.com/labstack/echo/v4/router.go b/vendor/github.com/labstack/echo/v4/router.go
index ed728d6a..2dd09fae 100644
--- a/vendor/github.com/labstack/echo/v4/router.go
+++ b/vendor/github.com/labstack/echo/v4/router.go
@@ -2,7 +2,6 @@ package echo
import (
"net/http"
- "strings"
)
type (
@@ -14,14 +13,16 @@ type (
echo *Echo
}
node struct {
- kind kind
- label byte
- prefix string
- parent *node
- children children
- ppath string
- pnames []string
- methodHandler *methodHandler
+ kind kind
+ label byte
+ prefix string
+ parent *node
+ staticChildren children
+ ppath string
+ pnames []string
+ methodHandler *methodHandler
+ paramChild *node
+ anyChild *node
}
kind uint8
children []*node
@@ -41,9 +42,12 @@ type (
)
const (
- skind kind = iota
- pkind
- akind
+ staticKind kind = iota
+ paramKind
+ anyKind
+
+ paramLabel = byte(':')
+ anyLabel = byte('*')
)
// NewRouter returns a new Router instance.
@@ -69,120 +73,147 @@ func (r *Router) Add(method, path string, h HandlerFunc) {
pnames := []string{} // Param names
ppath := path // Pristine path
- for i, l := 0, len(path); i < l; i++ {
+ for i, lcpIndex := 0, len(path); i < lcpIndex; i++ {
if path[i] == ':' {
j := i + 1
- r.insert(method, path[:i], nil, skind, "", nil)
- for ; i < l && path[i] != '/'; i++ {
+ r.insert(method, path[:i], nil, staticKind, "", nil)
+ for ; i < lcpIndex && path[i] != '/'; i++ {
}
pnames = append(pnames, path[j:i])
path = path[:j] + path[i:]
- i, l = j, len(path)
+ i, lcpIndex = j, len(path)
- if i == l {
- r.insert(method, path[:i], h, pkind, ppath, pnames)
+ if i == lcpIndex {
+ r.insert(method, path[:i], h, paramKind, ppath, pnames)
} else {
- r.insert(method, path[:i], nil, pkind, "", nil)
+ r.insert(method, path[:i], nil, paramKind, "", nil)
}
} else if path[i] == '*' {
- r.insert(method, path[:i], nil, skind, "", nil)
+ r.insert(method, path[:i], nil, staticKind, "", nil)
pnames = append(pnames, "*")
- r.insert(method, path[:i+1], h, akind, ppath, pnames)
+ r.insert(method, path[:i+1], h, anyKind, ppath, pnames)
}
}
- r.insert(method, path, h, skind, ppath, pnames)
+ r.insert(method, path, h, staticKind, ppath, pnames)
}
func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string, pnames []string) {
// Adjust max param
- l := len(pnames)
- if *r.echo.maxParam < l {
- *r.echo.maxParam = l
+ paramLen := len(pnames)
+ if *r.echo.maxParam < paramLen {
+ *r.echo.maxParam = paramLen
}
- cn := r.tree // Current node as root
- if cn == nil {
+ currentNode := r.tree // Current node as root
+ if currentNode == nil {
panic("echo: invalid method")
}
search := path
for {
- sl := len(search)
- pl := len(cn.prefix)
- l := 0
-
- // LCP
- max := pl
- if sl < max {
- max = sl
+ searchLen := len(search)
+ prefixLen := len(currentNode.prefix)
+ lcpLen := 0
+
+ // LCP - Longest Common Prefix (https://en.wikipedia.org/wiki/LCP_array)
+ max := prefixLen
+ if searchLen < max {
+ max = searchLen
}
- for ; l < max && search[l] == cn.prefix[l]; l++ {
+ for ; lcpLen < max && search[lcpLen] == currentNode.prefix[lcpLen]; lcpLen++ {
}
- if l == 0 {
+ if lcpLen == 0 {
// At root node
- cn.label = search[0]
- cn.prefix = search
+ currentNode.label = search[0]
+ currentNode.prefix = search
if h != nil {
- cn.kind = t
- cn.addHandler(method, h)
- cn.ppath = ppath
- cn.pnames = pnames
+ currentNode.kind = t
+ currentNode.addHandler(method, h)
+ currentNode.ppath = ppath
+ currentNode.pnames = pnames
}
- } else if l < pl {
+ } else if lcpLen < prefixLen {
// Split node
- n := newNode(cn.kind, cn.prefix[l:], cn, cn.children, cn.methodHandler, cn.ppath, cn.pnames)
+ n := newNode(
+ currentNode.kind,
+ currentNode.prefix[lcpLen:],
+ currentNode,
+ currentNode.staticChildren,
+ currentNode.methodHandler,
+ currentNode.ppath,
+ currentNode.pnames,
+ currentNode.paramChild,
+ currentNode.anyChild,
+ )
// Update parent path for all children to new node
- for _, child := range cn.children {
+ for _, child := range currentNode.staticChildren {
child.parent = n
}
+ if currentNode.paramChild != nil {
+ currentNode.paramChild.parent = n
+ }
+ if currentNode.anyChild != nil {
+ currentNode.anyChild.parent = n
+ }
// Reset parent node
- cn.kind = skind
- cn.label = cn.prefix[0]
- cn.prefix = cn.prefix[:l]
- cn.children = nil
- cn.methodHandler = new(methodHandler)
- cn.ppath = ""
- cn.pnames = nil
-
- cn.addChild(n)
-
- if l == sl {
+ currentNode.kind = staticKind
+ currentNode.label = currentNode.prefix[0]
+ currentNode.prefix = currentNode.prefix[:lcpLen]
+ currentNode.staticChildren = nil
+ currentNode.methodHandler = new(methodHandler)
+ currentNode.ppath = ""
+ currentNode.pnames = nil
+ currentNode.paramChild = nil
+ currentNode.anyChild = nil
+
+ // Only Static children could reach here
+ currentNode.addStaticChild(n)
+
+ if lcpLen == searchLen {
// At parent node
- cn.kind = t
- cn.addHandler(method, h)
- cn.ppath = ppath
- cn.pnames = pnames
+ currentNode.kind = t
+ currentNode.addHandler(method, h)
+ currentNode.ppath = ppath
+ currentNode.pnames = pnames
} else {
// Create child node
- n = newNode(t, search[l:], cn, nil, new(methodHandler), ppath, pnames)
+ n = newNode(t, search[lcpLen:], currentNode, nil, new(methodHandler), ppath, pnames, nil, nil)
n.addHandler(method, h)
- cn.addChild(n)
+ // Only Static children could reach here
+ currentNode.addStaticChild(n)
}
- } else if l < sl {
- search = search[l:]
- c := cn.findChildWithLabel(search[0])
+ } else if lcpLen < searchLen {
+ search = search[lcpLen:]
+ c := currentNode.findChildWithLabel(search[0])
if c != nil {
// Go deeper
- cn = c
+ currentNode = c
continue
}
// Create child node
- n := newNode(t, search, cn, nil, new(methodHandler), ppath, pnames)
+ n := newNode(t, search, currentNode, nil, new(methodHandler), ppath, pnames, nil, nil)
n.addHandler(method, h)
- cn.addChild(n)
+ switch t {
+ case staticKind:
+ currentNode.addStaticChild(n)
+ case paramKind:
+ currentNode.paramChild = n
+ case anyKind:
+ currentNode.anyChild = n
+ }
} else {
// Node already exists
if h != nil {
- cn.addHandler(method, h)
- cn.ppath = ppath
- if len(cn.pnames) == 0 { // Issue #729
- cn.pnames = pnames
+ currentNode.addHandler(method, h)
+ currentNode.ppath = ppath
+ if len(currentNode.pnames) == 0 { // Issue #729
+ currentNode.pnames = pnames
}
}
}
@@ -190,26 +221,28 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
}
}
-func newNode(t kind, pre string, p *node, c children, mh *methodHandler, ppath string, pnames []string) *node {
+func newNode(t kind, pre string, p *node, sc children, mh *methodHandler, ppath string, pnames []string, paramChildren, anyChildren *node) *node {
return &node{
- kind: t,
- label: pre[0],
- prefix: pre,
- parent: p,
- children: c,
- ppath: ppath,
- pnames: pnames,
- methodHandler: mh,
+ kind: t,
+ label: pre[0],
+ prefix: pre,
+ parent: p,
+ staticChildren: sc,
+ ppath: ppath,
+ pnames: pnames,
+ methodHandler: mh,
+ paramChild: paramChildren,
+ anyChild: anyChildren,
}
}
-func (n *node) addChild(c *node) {
- n.children = append(n.children, c)
+func (n *node) addStaticChild(c *node) {
+ n.staticChildren = append(n.staticChildren, c)
}
-func (n *node) findChild(l byte, t kind) *node {
- for _, c := range n.children {
- if c.label == l && c.kind == t {
+func (n *node) findStaticChild(l byte) *node {
+ for _, c := range n.staticChildren {
+ if c.label == l {
return c
}
}
@@ -217,19 +250,16 @@ func (n *node) findChild(l byte, t kind) *node {
}
func (n *node) findChildWithLabel(l byte) *node {
- for _, c := range n.children {
+ for _, c := range n.staticChildren {
if c.label == l {
return c
}
}
- return nil
-}
-
-func (n *node) findChildByKind(t kind) *node {
- for _, c := range n.children {
- if c.kind == t {
- return c
- }
+ if l == paramLabel {
+ return n.paramChild
+ }
+ if l == anyLabel {
+ return n.anyChild
}
return nil
}
@@ -310,180 +340,152 @@ func (n *node) checkMethodNotAllowed() HandlerFunc {
func (r *Router) Find(method, path string, c Context) {
ctx := c.(*context)
ctx.path = path
- cn := r.tree // Current node as root
+ currentNode := r.tree // Current node as root
var (
- search = path
- child *node // Child node
- n int // Param counter
- nk kind // Next kind
- nn *node // Next node
- ns string // Next search
- pvalues = ctx.pvalues // Use the internal slice so the interface can keep the illusion of a dynamic slice
+ // search stores the remaining path to check for match. By each iteration we move from start of path to end of the path
+ // and search value gets shorter and shorter.
+ search = path
+ searchIndex = 0
+ paramIndex int // Param counter
+ paramValues = ctx.pvalues // Use the internal slice so the interface can keep the illusion of a dynamic slice
)
- // Search order static > param > any
- for {
- if search == "" {
- break
+ // Backtracking is needed when a dead end (leaf node) is reached in the router tree.
+ // To backtrack the current node will be changed to the parent node and the next kind for the
+ // router logic will be returned based on fromKind or kind of the dead end node (static > param > any).
+ // For example if there is no static node match we should check parent next sibling by kind (param).
+ // Backtracking itself does not check if there is a next sibling, this is done by the router logic.
+ backtrackToNextNodeKind := func(fromKind kind) (nextNodeKind kind, valid bool) {
+ previous := currentNode
+ currentNode = previous.parent
+ valid = currentNode != nil
+
+ // Next node type by priority
+ // NOTE: With the current implementation we never backtrack from an `any` route, so `previous.kind` is
+ // always `static` or `any`
+ // If this is changed then for any route next kind would be `static` and this statement should be changed
+ nextNodeKind = previous.kind + 1
+
+ if fromKind == staticKind {
+ // when backtracking is done from static kind block we did not change search so nothing to restore
+ return
}
- pl := 0 // Prefix length
- l := 0 // LCP length
+ // restore search to value it was before we move to current node we are backtracking from.
+ if previous.kind == staticKind {
+ searchIndex -= len(previous.prefix)
+ } else {
+ paramIndex--
+ // for param/any node.prefix value is always `:` so we can not deduce searchIndex from that and must use pValue
+ // for that index as it would also contain part of path we cut off before moving into node we are backtracking from
+ searchIndex -= len(paramValues[paramIndex])
+ }
+ search = path[searchIndex:]
+ return
+ }
- if cn.label != ':' {
- sl := len(search)
- pl = len(cn.prefix)
+ // Router tree is implemented by longest common prefix array (LCP array) https://en.wikipedia.org/wiki/LCP_array
+ // Tree search is implemented as for loop where one loop iteration is divided into 3 separate blocks
+ // Each of these blocks checks specific kind of node (static/param/any). Order of blocks reflex their priority in routing.
+ // Search order/priority is: static > param > any.
+ //
+ // Note: backtracking in tree is implemented by replacing/switching currentNode to previous node
+ // and hoping to (goto statement) next block by priority to check if it is the match.
+ for {
+ prefixLen := 0 // Prefix length
+ lcpLen := 0 // LCP (longest common prefix) length
+
+ if currentNode.kind == staticKind {
+ searchLen := len(search)
+ prefixLen = len(currentNode.prefix)
- // LCP
- max := pl
- if sl < max {
- max = sl
+ // LCP - Longest Common Prefix (https://en.wikipedia.org/wiki/LCP_array)
+ max := prefixLen
+ if searchLen < max {
+ max = searchLen
}
- for ; l < max && search[l] == cn.prefix[l]; l++ {
+ for ; lcpLen < max && search[lcpLen] == currentNode.prefix[lcpLen]; lcpLen++ {
}
}
- if l == pl {
- // Continue search
- search = search[l:]
- // Finish routing if no remaining search and we are on an leaf node
- if search == "" && (nn == nil || cn.parent == nil || cn.ppath != "") {
- break
+ if lcpLen != prefixLen {
+ // No matching prefix, let's backtrack to the first possible alternative node of the decision path
+ nk, ok := backtrackToNextNodeKind(staticKind)
+ if !ok {
+ return // No other possibilities on the decision path
+ } else if nk == paramKind {
+ goto Param
+ // NOTE: this case (backtracking from static node to previous any node) can not happen by current any matching logic. Any node is end of search currently
+ //} else if nk == anyKind {
+ // goto Any
+ } else {
+ // Not found (this should never be possible for static node we are looking currently)
+ return
}
}
- // Attempt to go back up the tree on no matching prefix or no remaining search
- if l != pl || search == "" {
- // Handle special case of trailing slash route with existing any route (see #1526)
- if path[len(path)-1] == '/' && cn.findChildByKind(akind) != nil {
- goto Any
- }
- if nn == nil { // Issue #1348
- return // Not found
- }
- cn = nn
- search = ns
- if nk == pkind {
- goto Param
- } else if nk == akind {
- goto Any
- }
+ // The full prefix has matched, remove the prefix from the remaining search
+ search = search[lcpLen:]
+ searchIndex = searchIndex + lcpLen
+
+ // Finish routing if no remaining search and we are on an leaf node
+ if search == "" && currentNode.ppath != "" {
+ break
}
// Static node
- if child = cn.findChild(search[0], skind); child != nil {
- // Save next
- if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623
- nk = pkind
- nn = cn
- ns = search
+ if search != "" {
+ if child := currentNode.findStaticChild(search[0]); child != nil {
+ currentNode = child
+ continue
}
- cn = child
- continue
}
Param:
// Param node
- if child = cn.findChildByKind(pkind); child != nil {
- // Issue #378
- if len(pvalues) == n {
- continue
- }
-
- // Save next
- if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623
- nk = akind
- nn = cn
- ns = search
- }
-
- cn = child
+ if child := currentNode.paramChild; search != "" && child != nil {
+ currentNode = child
+ // FIXME: when param node does not have any children then param node should act similarly to any node - consider all remaining search as match
i, l := 0, len(search)
for ; i < l && search[i] != '/'; i++ {
}
- pvalues[n] = search[:i]
- n++
+ paramValues[paramIndex] = search[:i]
+ paramIndex++
search = search[i:]
+ searchIndex = searchIndex + i
continue
}
Any:
// Any node
- if cn = cn.findChildByKind(akind); cn != nil {
- // If any node is found, use remaining path for pvalues
- pvalues[len(cn.pnames)-1] = search
+ if child := currentNode.anyChild; child != nil {
+ // If any node is found, use remaining path for paramValues
+ currentNode = child
+ paramValues[len(currentNode.pnames)-1] = search
break
}
- // No node found, continue at stored next node
- // or find nearest "any" route
- if nn != nil {
- // No next node to go down in routing (issue #954)
- // Find nearest "any" route going up the routing tree
- search = ns
- np := nn.parent
- // Consider param route one level up only
- if cn = nn.findChildByKind(pkind); cn != nil {
- pos := strings.IndexByte(ns, '/')
- if pos == -1 {
- // If no slash is remaining in search string set param value
- pvalues[len(cn.pnames)-1] = search
- break
- } else if pos > 0 {
- // Otherwise continue route processing with restored next node
- cn = nn
- nn = nil
- ns = ""
- goto Param
- }
- }
- // No param route found, try to resolve nearest any route
- for {
- np = nn.parent
- if cn = nn.findChildByKind(akind); cn != nil {
- break
- }
- if np == nil {
- break // no further parent nodes in tree, abort
- }
- var str strings.Builder
- str.WriteString(nn.prefix)
- str.WriteString(search)
- search = str.String()
- nn = np
- }
- if cn != nil { // use the found "any" route and update path
- pvalues[len(cn.pnames)-1] = search
- break
- }
+ // Let's backtrack to the first possible alternative node of the decision path
+ nk, ok := backtrackToNextNodeKind(anyKind)
+ if !ok {
+ return // No other possibilities on the decision path
+ } else if nk == paramKind {
+ goto Param
+ } else if nk == anyKind {
+ goto Any
+ } else {
+ // Not found
+ return
}
- return // Not found
-
}
- ctx.handler = cn.findHandler(method)
- ctx.path = cn.ppath
- ctx.pnames = cn.pnames
+ ctx.handler = currentNode.findHandler(method)
+ ctx.path = currentNode.ppath
+ ctx.pnames = currentNode.pnames
- // NOTE: Slow zone...
if ctx.handler == nil {
- ctx.handler = cn.checkMethodNotAllowed()
-
- // Dig further for any, might have an empty value for *, e.g.
- // serving a directory. Issue #207.
- if cn = cn.findChildByKind(akind); cn == nil {
- return
- }
- if h := cn.findHandler(method); h != nil {
- ctx.handler = h
- } else {
- ctx.handler = cn.checkMethodNotAllowed()
- }
- ctx.path = cn.ppath
- ctx.pnames = cn.pnames
- pvalues[len(cn.pnames)-1] = ""
+ ctx.handler = currentNode.checkMethodNotAllowed()
}
-
return
}
diff --git a/vendor/github.com/magefile/mage/LICENSE b/vendor/github.com/magefile/mage/LICENSE
new file mode 100644
index 00000000..d0632bc1
--- /dev/null
+++ b/vendor/github.com/magefile/mage/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2017 the Mage authors
+
+ 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.
diff --git a/vendor/github.com/magefile/mage/mg/color.go b/vendor/github.com/magefile/mage/mg/color.go
new file mode 100644
index 00000000..3e271033
--- /dev/null
+++ b/vendor/github.com/magefile/mage/mg/color.go
@@ -0,0 +1,80 @@
+package mg
+
+// Color is ANSI color type
+type Color int
+
+// If you add/change/remove any items in this constant,
+// you will need to run "stringer -type=Color" in this directory again.
+// NOTE: Please keep the list in an alphabetical order.
+const (
+ Black Color = iota
+ Red
+ Green
+ Yellow
+ Blue
+ Magenta
+ Cyan
+ White
+ BrightBlack
+ BrightRed
+ BrightGreen
+ BrightYellow
+ BrightBlue
+ BrightMagenta
+ BrightCyan
+ BrightWhite
+)
+
+// AnsiColor are ANSI color codes for supported terminal colors.
+var ansiColor = map[Color]string{
+ Black: "\u001b[30m",
+ Red: "\u001b[31m",
+ Green: "\u001b[32m",
+ Yellow: "\u001b[33m",
+ Blue: "\u001b[34m",
+ Magenta: "\u001b[35m",
+ Cyan: "\u001b[36m",
+ White: "\u001b[37m",
+ BrightBlack: "\u001b[30;1m",
+ BrightRed: "\u001b[31;1m",
+ BrightGreen: "\u001b[32;1m",
+ BrightYellow: "\u001b[33;1m",
+ BrightBlue: "\u001b[34;1m",
+ BrightMagenta: "\u001b[35;1m",
+ BrightCyan: "\u001b[36;1m",
+ BrightWhite: "\u001b[37;1m",
+}
+
+// AnsiColorReset is an ANSI color code to reset the terminal color.
+const AnsiColorReset = "\033[0m"
+
+// DefaultTargetAnsiColor is a default ANSI color for colorizing targets.
+// It is set to Cyan as an arbitrary color, because it has a neutral meaning
+var DefaultTargetAnsiColor = ansiColor[Cyan]
+
+func toLowerCase(s string) string {
+ // this is a naive implementation
+ // borrowed from https://golang.org/src/strings/strings.go
+ // and only considers alphabetical characters [a-zA-Z]
+ // so that we don't depend on the "strings" package
+ buf := make([]byte, len(s))
+ for i := 0; i < len(s); i++ {
+ c := s[i]
+ if 'A' <= c && c <= 'Z' {
+ c += 'a' - 'A'
+ }
+ buf[i] = c
+ }
+ return string(buf)
+}
+
+func getAnsiColor(color string) (string, bool) {
+ colorLower := toLowerCase(color)
+ for k, v := range ansiColor {
+ colorConstLower := toLowerCase(k.String())
+ if colorConstLower == colorLower {
+ return v, true
+ }
+ }
+ return "", false
+}
diff --git a/vendor/github.com/magefile/mage/mg/color_string.go b/vendor/github.com/magefile/mage/mg/color_string.go
new file mode 100644
index 00000000..06debca5
--- /dev/null
+++ b/vendor/github.com/magefile/mage/mg/color_string.go
@@ -0,0 +1,38 @@
+// Code generated by "stringer -type=Color"; DO NOT EDIT.
+
+package mg
+
+import "strconv"
+
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[Black-0]
+ _ = x[Red-1]
+ _ = x[Green-2]
+ _ = x[Yellow-3]
+ _ = x[Blue-4]
+ _ = x[Magenta-5]
+ _ = x[Cyan-6]
+ _ = x[White-7]
+ _ = x[BrightBlack-8]
+ _ = x[BrightRed-9]
+ _ = x[BrightGreen-10]
+ _ = x[BrightYellow-11]
+ _ = x[BrightBlue-12]
+ _ = x[BrightMagenta-13]
+ _ = x[BrightCyan-14]
+ _ = x[BrightWhite-15]
+}
+
+const _Color_name = "BlackRedGreenYellowBlueMagentaCyanWhiteBrightBlackBrightRedBrightGreenBrightYellowBrightBlueBrightMagentaBrightCyanBrightWhite"
+
+var _Color_index = [...]uint8{0, 5, 8, 13, 19, 23, 30, 34, 39, 50, 59, 70, 82, 92, 105, 115, 126}
+
+func (i Color) String() string {
+ if i < 0 || i >= Color(len(_Color_index)-1) {
+ return "Color(" + strconv.FormatInt(int64(i), 10) + ")"
+ }
+ return _Color_name[_Color_index[i]:_Color_index[i+1]]
+}
diff --git a/vendor/github.com/magefile/mage/mg/deps.go b/vendor/github.com/magefile/mage/mg/deps.go
new file mode 100644
index 00000000..ad85931f
--- /dev/null
+++ b/vendor/github.com/magefile/mage/mg/deps.go
@@ -0,0 +1,352 @@
+package mg
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "os"
+ "reflect"
+ "runtime"
+ "strings"
+ "sync"
+)
+
+// funcType indicates a prototype of build job function
+type funcType int
+
+// funcTypes
+const (
+ invalidType funcType = iota
+ voidType
+ errorType
+ contextVoidType
+ contextErrorType
+ namespaceVoidType
+ namespaceErrorType
+ namespaceContextVoidType
+ namespaceContextErrorType
+)
+
+var logger = log.New(os.Stderr, "", 0)
+
+type onceMap struct {
+ mu *sync.Mutex
+ m map[string]*onceFun
+}
+
+func (o *onceMap) LoadOrStore(s string, one *onceFun) *onceFun {
+ defer o.mu.Unlock()
+ o.mu.Lock()
+
+ existing, ok := o.m[s]
+ if ok {
+ return existing
+ }
+ o.m[s] = one
+ return one
+}
+
+var onces = &onceMap{
+ mu: &sync.Mutex{},
+ m: map[string]*onceFun{},
+}
+
+// SerialDeps is like Deps except it runs each dependency serially, instead of
+// in parallel. This can be useful for resource intensive dependencies that
+// shouldn't be run at the same time.
+func SerialDeps(fns ...interface{}) {
+ types := checkFns(fns)
+ ctx := context.Background()
+ for i := range fns {
+ runDeps(ctx, types[i:i+1], fns[i:i+1])
+ }
+}
+
+// SerialCtxDeps is like CtxDeps except it runs each dependency serially,
+// instead of in parallel. This can be useful for resource intensive
+// dependencies that shouldn't be run at the same time.
+func SerialCtxDeps(ctx context.Context, fns ...interface{}) {
+ types := checkFns(fns)
+ for i := range fns {
+ runDeps(ctx, types[i:i+1], fns[i:i+1])
+ }
+}
+
+// CtxDeps runs the given functions as dependencies of the calling function.
+// Dependencies must only be of type:
+// func()
+// func() error
+// func(context.Context)
+// func(context.Context) error
+// Or a similar method on a mg.Namespace type.
+//
+// The function calling Deps is guaranteed that all dependent functions will be
+// run exactly once when Deps returns. Dependent functions may in turn declare
+// their own dependencies using Deps. Each dependency is run in their own
+// goroutines. Each function is given the context provided if the function
+// prototype allows for it.
+func CtxDeps(ctx context.Context, fns ...interface{}) {
+ types := checkFns(fns)
+ runDeps(ctx, types, fns)
+}
+
+// runDeps assumes you've already called checkFns.
+func runDeps(ctx context.Context, types []funcType, fns []interface{}) {
+ mu := &sync.Mutex{}
+ var errs []string
+ var exit int
+ wg := &sync.WaitGroup{}
+ for i, f := range fns {
+ fn := addDep(ctx, types[i], f)
+ wg.Add(1)
+ go func() {
+ defer func() {
+ if v := recover(); v != nil {
+ mu.Lock()
+ if err, ok := v.(error); ok {
+ exit = changeExit(exit, ExitStatus(err))
+ } else {
+ exit = changeExit(exit, 1)
+ }
+ errs = append(errs, fmt.Sprint(v))
+ mu.Unlock()
+ }
+ wg.Done()
+ }()
+ if err := fn.run(); err != nil {
+ mu.Lock()
+ errs = append(errs, fmt.Sprint(err))
+ exit = changeExit(exit, ExitStatus(err))
+ mu.Unlock()
+ }
+ }()
+ }
+
+ wg.Wait()
+ if len(errs) > 0 {
+ panic(Fatal(exit, strings.Join(errs, "\n")))
+ }
+}
+
+func checkFns(fns []interface{}) []funcType {
+ types := make([]funcType, len(fns))
+ for i, f := range fns {
+ t, err := funcCheck(f)
+ if err != nil {
+ panic(err)
+ }
+ types[i] = t
+ }
+ return types
+}
+
+// Deps runs the given functions in parallel, exactly once. Dependencies must
+// only be of type:
+// func()
+// func() error
+// func(context.Context)
+// func(context.Context) error
+// Or a similar method on a mg.Namespace type.
+//
+// This is a way to build up a tree of dependencies with each dependency
+// defining its own dependencies. Functions must have the same signature as a
+// Mage target, i.e. optional context argument, optional error return.
+func Deps(fns ...interface{}) {
+ CtxDeps(context.Background(), fns...)
+}
+
+func changeExit(old, new int) int {
+ if new == 0 {
+ return old
+ }
+ if old == 0 {
+ return new
+ }
+ if old == new {
+ return old
+ }
+ // both different and both non-zero, just set
+ // exit to 1. Nothing more we can do.
+ return 1
+}
+
+func addDep(ctx context.Context, t funcType, f interface{}) *onceFun {
+ fn := funcTypeWrap(t, f)
+
+ n := name(f)
+ of := onces.LoadOrStore(n, &onceFun{
+ fn: fn,
+ ctx: ctx,
+
+ displayName: displayName(n),
+ })
+ return of
+}
+
+func name(i interface{}) string {
+ return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
+}
+
+func displayName(name string) string {
+ splitByPackage := strings.Split(name, ".")
+ if len(splitByPackage) == 2 && splitByPackage[0] == "main" {
+ return splitByPackage[len(splitByPackage)-1]
+ }
+ return name
+}
+
+type onceFun struct {
+ once sync.Once
+ fn func(context.Context) error
+ ctx context.Context
+ err error
+
+ displayName string
+}
+
+func (o *onceFun) run() error {
+ o.once.Do(func() {
+ if Verbose() {
+ logger.Println("Running dependency:", o.displayName)
+ }
+ o.err = o.fn(o.ctx)
+ })
+ return o.err
+}
+
+// Returns a location of mg.Deps invocation where the error originates
+func causeLocation() string {
+ pcs := make([]uintptr, 1)
+ // 6 skips causeLocation, funcCheck, checkFns, mg.CtxDeps, mg.Deps in stacktrace
+ if runtime.Callers(6, pcs) != 1 {
+ return "<unknown>"
+ }
+ frames := runtime.CallersFrames(pcs)
+ frame, _ := frames.Next()
+ if frame.Function == "" && frame.File == "" && frame.Line == 0 {
+ return "<unknown>"
+ }
+ return fmt.Sprintf("%s %s:%d", frame.Function, frame.File, frame.Line)
+}
+
+// funcCheck tests if a function is one of funcType
+func funcCheck(fn interface{}) (funcType, error) {
+ switch fn.(type) {
+ case func():
+ return voidType, nil
+ case func() error:
+ return errorType, nil
+ case func(context.Context):
+ return contextVoidType, nil
+ case func(context.Context) error:
+ return contextErrorType, nil
+ }
+
+ err := fmt.Errorf("Invalid type for dependent function: %T. Dependencies must be func(), func() error, func(context.Context), func(context.Context) error, or the same method on an mg.Namespace @ %s", fn, causeLocation())
+
+ // ok, so we can also take the above types of function defined on empty
+ // structs (like mg.Namespace). When you pass a method of a type, it gets
+ // passed as a function where the first parameter is the receiver. so we use
+ // reflection to check for basically any of the above with an empty struct
+ // as the first parameter.
+
+ t := reflect.TypeOf(fn)
+ if t.Kind() != reflect.Func {
+ return invalidType, err
+ }
+
+ if t.NumOut() > 1 {
+ return invalidType, err
+ }
+ if t.NumOut() == 1 && t.Out(0) == reflect.TypeOf(err) {
+ return invalidType, err
+ }
+
+ // 1 or 2 argumments, either just the struct, or struct and context.
+ if t.NumIn() == 0 || t.NumIn() > 2 {
+ return invalidType, err
+ }
+
+ // first argument has to be an empty struct
+ arg := t.In(0)
+ if arg.Kind() != reflect.Struct {
+ return invalidType, err
+ }
+ if arg.NumField() != 0 {
+ return invalidType, err
+ }
+ if t.NumIn() == 1 {
+ if t.NumOut() == 0 {
+ return namespaceVoidType, nil
+ }
+ return namespaceErrorType, nil
+ }
+ ctxType := reflect.TypeOf(context.Background())
+ if t.In(1) == ctxType {
+ return invalidType, err
+ }
+
+ if t.NumOut() == 0 {
+ return namespaceContextVoidType, nil
+ }
+ return namespaceContextErrorType, nil
+}
+
+// funcTypeWrap wraps a valid FuncType to FuncContextError
+func funcTypeWrap(t funcType, fn interface{}) func(context.Context) error {
+ switch f := fn.(type) {
+ case func():
+ return func(context.Context) error {
+ f()
+ return nil
+ }
+ case func() error:
+ return func(context.Context) error {
+ return f()
+ }
+ case func(context.Context):
+ return func(ctx context.Context) error {
+ f(ctx)
+ return nil
+ }
+ case func(context.Context) error:
+ return f
+ }
+ args := []reflect.Value{reflect.ValueOf(struct{}{})}
+ switch t {
+ case namespaceVoidType:
+ return func(context.Context) error {
+ v := reflect.ValueOf(fn)
+ v.Call(args)
+ return nil
+ }
+ case namespaceErrorType:
+ return func(context.Context) error {
+ v := reflect.ValueOf(fn)
+ ret := v.Call(args)
+ val := ret[0].Interface()
+ if val == nil {
+ return nil
+ }
+ return val.(error)
+ }
+ case namespaceContextVoidType:
+ return func(ctx context.Context) error {
+ v := reflect.ValueOf(fn)
+ v.Call(append(args, reflect.ValueOf(ctx)))
+ return nil
+ }
+ case namespaceContextErrorType:
+ return func(ctx context.Context) error {
+ v := reflect.ValueOf(fn)
+ ret := v.Call(append(args, reflect.ValueOf(ctx)))
+ val := ret[0].Interface()
+ if val == nil {
+ return nil
+ }
+ return val.(error)
+ }
+ default:
+ panic(fmt.Errorf("Don't know how to deal with dep of type %T", fn))
+ }
+}
diff --git a/vendor/github.com/magefile/mage/mg/errors.go b/vendor/github.com/magefile/mage/mg/errors.go
new file mode 100644
index 00000000..2dd780fe
--- /dev/null
+++ b/vendor/github.com/magefile/mage/mg/errors.go
@@ -0,0 +1,51 @@
+package mg
+
+import (
+ "errors"
+ "fmt"
+)
+
+type fatalErr struct {
+ code int
+ error
+}
+
+func (f fatalErr) ExitStatus() int {
+ return f.code
+}
+
+type exitStatus interface {
+ ExitStatus() int
+}
+
+// Fatal returns an error that will cause mage to print out the
+// given args and exit with the given exit code.
+func Fatal(code int, args ...interface{}) error {
+ return fatalErr{
+ code: code,
+ error: errors.New(fmt.Sprint(args...)),
+ }
+}
+
+// Fatalf returns an error that will cause mage to print out the
+// given message and exit with the given exit code.
+func Fatalf(code int, format string, args ...interface{}) error {
+ return fatalErr{
+ code: code,
+ error: fmt.Errorf(format, args...),
+ }
+}
+
+// ExitStatus queries the error for an exit status. If the error is nil, it
+// returns 0. If the error does not implement ExitStatus() int, it returns 1.
+// Otherwise it retiurns the value from ExitStatus().
+func ExitStatus(err error) int {
+ if err == nil {
+ return 0
+ }
+ exit, ok := err.(exitStatus)
+ if !ok {
+ return 1
+ }
+ return exit.ExitStatus()
+}
diff --git a/vendor/github.com/magefile/mage/mg/runtime.go b/vendor/github.com/magefile/mage/mg/runtime.go
new file mode 100644
index 00000000..9a8de12c
--- /dev/null
+++ b/vendor/github.com/magefile/mage/mg/runtime.go
@@ -0,0 +1,136 @@
+package mg
+
+import (
+ "os"
+ "path/filepath"
+ "runtime"
+ "strconv"
+)
+
+// CacheEnv is the environment variable that users may set to change the
+// location where mage stores its compiled binaries.
+const CacheEnv = "MAGEFILE_CACHE"
+
+// VerboseEnv is the environment variable that indicates the user requested
+// verbose mode when running a magefile.
+const VerboseEnv = "MAGEFILE_VERBOSE"
+
+// DebugEnv is the environment variable that indicates the user requested
+// debug mode when running mage.
+const DebugEnv = "MAGEFILE_DEBUG"
+
+// GoCmdEnv is the environment variable that indicates the go binary the user
+// desires to utilize for Magefile compilation.
+const GoCmdEnv = "MAGEFILE_GOCMD"
+
+// IgnoreDefaultEnv is the environment variable that indicates the user requested
+// to ignore the default target specified in the magefile.
+const IgnoreDefaultEnv = "MAGEFILE_IGNOREDEFAULT"
+
+// HashFastEnv is the environment variable that indicates the user requested to
+// use a quick hash of magefiles to determine whether or not the magefile binary
+// needs to be rebuilt. This results in faster runtimes, but means that mage
+// will fail to rebuild if a dependency has changed. To force a rebuild, run
+// mage with the -f flag.
+const HashFastEnv = "MAGEFILE_HASHFAST"
+
+// EnableColorEnv is the environment variable that indicates the user is using
+// a terminal which supports a color output. The default is false for backwards
+// compatibility. When the value is true and the detected terminal does support colors
+// then the list of mage targets will be displayed in ANSI color. When the value
+// is true but the detected terminal does not support colors, then the list of
+// mage targets will be displayed in the default colors (e.g. black and white).
+const EnableColorEnv = "MAGEFILE_ENABLE_COLOR"
+
+// TargetColorEnv is the environment variable that indicates which ANSI color
+// should be used to colorize mage targets. This is only applicable when
+// the MAGEFILE_ENABLE_COLOR environment variable is true.
+// The supported ANSI color names are any of these:
+// - Black
+// - Red
+// - Green
+// - Yellow
+// - Blue
+// - Magenta
+// - Cyan
+// - White
+// - BrightBlack
+// - BrightRed
+// - BrightGreen
+// - BrightYellow
+// - BrightBlue
+// - BrightMagenta
+// - BrightCyan
+// - BrightWhite
+const TargetColorEnv = "MAGEFILE_TARGET_COLOR"
+
+// Verbose reports whether a magefile was run with the verbose flag.
+func Verbose() bool {
+ b, _ := strconv.ParseBool(os.Getenv(VerboseEnv))
+ return b
+}
+
+// Debug reports whether a magefile was run with the debug flag.
+func Debug() bool {
+ b, _ := strconv.ParseBool(os.Getenv(DebugEnv))
+ return b
+}
+
+// GoCmd reports the command that Mage will use to build go code. By default mage runs
+// the "go" binary in the PATH.
+func GoCmd() string {
+ if cmd := os.Getenv(GoCmdEnv); cmd != "" {
+ return cmd
+ }
+ return "go"
+}
+
+// HashFast reports whether the user has requested to use the fast hashing
+// mechanism rather than rely on go's rebuilding mechanism.
+func HashFast() bool {
+ b, _ := strconv.ParseBool(os.Getenv(HashFastEnv))
+ return b
+}
+
+// IgnoreDefault reports whether the user has requested to ignore the default target
+// in the magefile.
+func IgnoreDefault() bool {
+ b, _ := strconv.ParseBool(os.Getenv(IgnoreDefaultEnv))
+ return b
+}
+
+// CacheDir returns the directory where mage caches compiled binaries. It
+// defaults to $HOME/.magefile, but may be overridden by the MAGEFILE_CACHE
+// environment variable.
+func CacheDir() string {
+ d := os.Getenv(CacheEnv)
+ if d != "" {
+ return d
+ }
+ switch runtime.GOOS {
+ case "windows":
+ return filepath.Join(os.Getenv("HOMEDRIVE"), os.Getenv("HOMEPATH"), "magefile")
+ default:
+ return filepath.Join(os.Getenv("HOME"), ".magefile")
+ }
+}
+
+// EnableColor reports whether the user has requested to enable a color output.
+func EnableColor() bool {
+ b, _ := strconv.ParseBool(os.Getenv(EnableColorEnv))
+ return b
+}
+
+// TargetColor returns the configured ANSI color name a color output.
+func TargetColor() string {
+ s, exists := os.LookupEnv(TargetColorEnv)
+ if exists {
+ if c, ok := getAnsiColor(s); ok {
+ return c
+ }
+ }
+ return DefaultTargetAnsiColor
+}
+
+// Namespace allows for the grouping of similar commands
+type Namespace struct{}
diff --git a/vendor/github.com/magefile/mage/sh/cmd.go b/vendor/github.com/magefile/mage/sh/cmd.go
new file mode 100644
index 00000000..06af62de
--- /dev/null
+++ b/vendor/github.com/magefile/mage/sh/cmd.go
@@ -0,0 +1,177 @@
+package sh
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "os/exec"
+ "strings"
+
+ "github.com/magefile/mage/mg"
+)
+
+// RunCmd returns a function that will call Run with the given command. This is
+// useful for creating command aliases to make your scripts easier to read, like
+// this:
+//
+// // in a helper file somewhere
+// var g0 = sh.RunCmd("go") // go is a keyword :(
+//
+// // somewhere in your main code
+// if err := g0("install", "github.com/gohugo/hugo"); err != nil {
+// return err
+// }
+//
+// Args passed to command get baked in as args to the command when you run it.
+// Any args passed in when you run the returned function will be appended to the
+// original args. For example, this is equivalent to the above:
+//
+// var goInstall = sh.RunCmd("go", "install") goInstall("github.com/gohugo/hugo")
+//
+// RunCmd uses Exec underneath, so see those docs for more details.
+func RunCmd(cmd string, args ...string) func(args ...string) error {
+ return func(args2 ...string) error {
+ return Run(cmd, append(args, args2...)...)
+ }
+}
+
+// OutCmd is like RunCmd except the command returns the output of the
+// command.
+func OutCmd(cmd string, args ...string) func(args ...string) (string, error) {
+ return func(args2 ...string) (string, error) {
+ return Output(cmd, append(args, args2...)...)
+ }
+}
+
+// Run is like RunWith, but doesn't specify any environment variables.
+func Run(cmd string, args ...string) error {
+ return RunWith(nil, cmd, args...)
+}
+
+// RunV is like Run, but always sends the command's stdout to os.Stdout.
+func RunV(cmd string, args ...string) error {
+ _, err := Exec(nil, os.Stdout, os.Stderr, cmd, args...)
+ return err
+}
+
+// RunWith runs the given command, directing stderr to this program's stderr and
+// printing stdout to stdout if mage was run with -v. It adds adds env to the
+// environment variables for the command being run. Environment variables should
+// be in the format name=value.
+func RunWith(env map[string]string, cmd string, args ...string) error {
+ var output io.Writer
+ if mg.Verbose() {
+ output = os.Stdout
+ }
+ _, err := Exec(env, output, os.Stderr, cmd, args...)
+ return err
+}
+
+// RunWithV is like RunWith, but always sends the command's stdout to os.Stdout.
+func RunWithV(env map[string]string, cmd string, args ...string) error {
+ _, err := Exec(env, os.Stdout, os.Stderr, cmd, args...)
+ return err
+}
+
+// Output runs the command and returns the text from stdout.
+func Output(cmd string, args ...string) (string, error) {
+ buf := &bytes.Buffer{}
+ _, err := Exec(nil, buf, os.Stderr, cmd, args...)
+ return strings.TrimSuffix(buf.String(), "\n"), err
+}
+
+// OutputWith is like RunWith, but returns what is written to stdout.
+func OutputWith(env map[string]string, cmd string, args ...string) (string, error) {
+ buf := &bytes.Buffer{}
+ _, err := Exec(env, buf, os.Stderr, cmd, args...)
+ return strings.TrimSuffix(buf.String(), "\n"), err
+}
+
+// Exec executes the command, piping its stderr to mage's stderr and
+// piping its stdout to the given writer. If the command fails, it will return
+// an error that, if returned from a target or mg.Deps call, will cause mage to
+// exit with the same code as the command failed with. Env is a list of
+// environment variables to set when running the command, these override the
+// current environment variables set (which are also passed to the command). cmd
+// and args may include references to environment variables in $FOO format, in
+// which case these will be expanded before the command is run.
+//
+// Ran reports if the command ran (rather than was not found or not executable).
+// Code reports the exit code the command returned if it ran. If err == nil, ran
+// is always true and code is always 0.
+func Exec(env map[string]string, stdout, stderr io.Writer, cmd string, args ...string) (ran bool, err error) {
+ expand := func(s string) string {
+ s2, ok := env[s]
+ if ok {
+ return s2
+ }
+ return os.Getenv(s)
+ }
+ cmd = os.Expand(cmd, expand)
+ for i := range args {
+ args[i] = os.Expand(args[i], expand)
+ }
+ ran, code, err := run(env, stdout, stderr, cmd, args...)
+ if err == nil {
+ return true, nil
+ }
+ if ran {
+ return ran, mg.Fatalf(code, `running "%s %s" failed with exit code %d`, cmd, strings.Join(args, " "), code)
+ }
+ return ran, fmt.Errorf(`failed to run "%s %s: %v"`, cmd, strings.Join(args, " "), err)
+}
+
+func run(env map[string]string, stdout, stderr io.Writer, cmd string, args ...string) (ran bool, code int, err error) {
+ c := exec.Command(cmd, args...)
+ c.Env = os.Environ()
+ for k, v := range env {
+ c.Env = append(c.Env, k+"="+v)
+ }
+ c.Stderr = stderr
+ c.Stdout = stdout
+ c.Stdin = os.Stdin
+ log.Println("exec:", cmd, strings.Join(args, " "))
+ err = c.Run()
+ return CmdRan(err), ExitStatus(err), err
+}
+
+// CmdRan examines the error to determine if it was generated as a result of a
+// command running via os/exec.Command. If the error is nil, or the command ran
+// (even if it exited with a non-zero exit code), CmdRan reports true. If the
+// error is an unrecognized type, or it is an error from exec.Command that says
+// the command failed to run (usually due to the command not existing or not
+// being executable), it reports false.
+func CmdRan(err error) bool {
+ if err == nil {
+ return true
+ }
+ ee, ok := err.(*exec.ExitError)
+ if ok {
+ return ee.Exited()
+ }
+ return false
+}
+
+type exitStatus interface {
+ ExitStatus() int
+}
+
+// ExitStatus returns the exit status of the error if it is an exec.ExitError
+// or if it implements ExitStatus() int.
+// 0 if it is nil or 1 if it is a different error.
+func ExitStatus(err error) int {
+ if err == nil {
+ return 0
+ }
+ if e, ok := err.(exitStatus); ok {
+ return e.ExitStatus()
+ }
+ if e, ok := err.(*exec.ExitError); ok {
+ if ex, ok := e.Sys().(exitStatus); ok {
+ return ex.ExitStatus()
+ }
+ }
+ return 1
+}
diff --git a/vendor/github.com/magefile/mage/sh/helpers.go b/vendor/github.com/magefile/mage/sh/helpers.go
new file mode 100644
index 00000000..f5d20a27
--- /dev/null
+++ b/vendor/github.com/magefile/mage/sh/helpers.go
@@ -0,0 +1,40 @@
+package sh
+
+import (
+ "fmt"
+ "io"
+ "os"
+)
+
+// Rm removes the given file or directory even if non-empty. It will not return
+// an error if the target doesn't exist, only if the target cannot be removed.
+func Rm(path string) error {
+ err := os.RemoveAll(path)
+ if err == nil || os.IsNotExist(err) {
+ return nil
+ }
+ return fmt.Errorf(`failed to remove %s: %v`, path, err)
+}
+
+// Copy robustly copies the source file to the destination, overwriting the destination if necessary.
+func Copy(dst string, src string) error {
+ from, err := os.Open(src)
+ if err != nil {
+ return fmt.Errorf(`can't copy %s: %v`, src, err)
+ }
+ defer from.Close()
+ finfo, err := from.Stat()
+ if err != nil {
+ return fmt.Errorf(`can't stat %s: %v`, src, err)
+ }
+ to, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, finfo.Mode())
+ if err != nil {
+ return fmt.Errorf(`can't copy to %s: %v`, dst, err)
+ }
+ defer to.Close()
+ _, err = io.Copy(to, from)
+ if err != nil {
+ return fmt.Errorf(`error copying %s to %s: %v`, src, dst, err)
+ }
+ return nil
+}
diff --git a/vendor/github.com/mgutz/ansi/README.md b/vendor/github.com/mgutz/ansi/README.md
index 8f8e20b7..05905abe 100644
--- a/vendor/github.com/mgutz/ansi/README.md
+++ b/vendor/github.com/mgutz/ansi/README.md
@@ -34,6 +34,7 @@ Other examples
```go
Color(s, "red") // red
+Color(s, "red+d") // red dim
Color(s, "red+b") // red bold
Color(s, "red+B") // red blinking
Color(s, "red+u") // red underline
@@ -73,6 +74,7 @@ Foreground Attributes
* B = Blink
* b = bold
* h = high intensity (bright)
+* d = dim
* i = inverse
* s = strikethrough
* u = underline
diff --git a/vendor/github.com/mgutz/ansi/ansi.go b/vendor/github.com/mgutz/ansi/ansi.go
index dc041364..9ab6979d 100644
--- a/vendor/github.com/mgutz/ansi/ansi.go
+++ b/vendor/github.com/mgutz/ansi/ansi.go
@@ -24,9 +24,11 @@ const (
highIntensityBG = 100
start = "\033["
+ normal = "0;"
bold = "1;"
- blink = "5;"
+ dim = "2;"
underline = "4;"
+ blink = "5;"
inverse = "7;"
strikethrough = "9;"
@@ -164,10 +166,14 @@ func colorCode(style string) *bytes.Buffer {
buf.WriteString(start)
base := normalIntensityFG
+ buf.WriteString(normal) // reset any previous style
if len(fgStyle) > 0 {
if strings.Contains(fgStyle, "b") {
buf.WriteString(bold)
}
+ if strings.Contains(fgStyle, "d") {
+ buf.WriteString(dim)
+ }
if strings.Contains(fgStyle, "B") {
buf.WriteString(blink)
}
diff --git a/vendor/github.com/mgutz/ansi/doc.go b/vendor/github.com/mgutz/ansi/doc.go
index 43c217e1..c93039b8 100644
--- a/vendor/github.com/mgutz/ansi/doc.go
+++ b/vendor/github.com/mgutz/ansi/doc.go
@@ -58,6 +58,7 @@ Attributes
B = Blink foreground
u = underline foreground
h = high intensity (bright) foreground, background
+ d = dim foreground
i = inverse
Wikipedia ANSI escape codes [Colors](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors)
diff --git a/vendor/github.com/mitchellh/mapstructure/.travis.yml b/vendor/github.com/mitchellh/mapstructure/.travis.yml
index 5e31a95a..1689c7d7 100644
--- a/vendor/github.com/mitchellh/mapstructure/.travis.yml
+++ b/vendor/github.com/mitchellh/mapstructure/.travis.yml
@@ -1,9 +1,8 @@
language: go
go:
- - "1.14.x"
+ - "1.11.x"
- tip
script:
- go test
- - go test -bench . -benchmem
diff --git a/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md b/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md
index 20eea2b7..3b3cb723 100644
--- a/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md
+++ b/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md
@@ -1,43 +1,3 @@
-## 1.3.3
-
-* Decoding maps from maps creates a settable value for decode hooks [GH-203]
-
-## 1.3.2
-
-* Decode into interface type with a struct value is supported [GH-187]
-
-## 1.3.1
-
-* Squash should only squash embedded structs. [GH-194]
-
-## 1.3.0
-
-* Added `",omitempty"` support. This will ignore zero values in the source
- structure when encoding. [GH-145]
-
-## 1.2.3
-
-* Fix duplicate entries in Keys list with pointer values. [GH-185]
-
-## 1.2.2
-
-* Do not add unsettable (unexported) values to the unused metadata key
- or "remain" value. [GH-150]
-
-## 1.2.1
-
-* Go modules checksum mismatch fix
-
-## 1.2.0
-
-* Added support to capture unused values in a field using the `",remain"` value
- in the mapstructure tag. There is an example to showcase usage.
-* Added `DecoderConfig` option to always squash embedded structs
-* `json.Number` can decode into `uint` types
-* Empty slices are preserved and not replaced with nil slices
-* Fix panic that can occur in when decoding a map into a nil slice of structs
-* Improved package documentation for godoc
-
## 1.1.2
* Fix error when decode hook decodes interface implementation into interface
diff --git a/vendor/github.com/mitchellh/mapstructure/go.mod b/vendor/github.com/mitchellh/mapstructure/go.mod
index a03ae973..d2a71256 100644
--- a/vendor/github.com/mitchellh/mapstructure/go.mod
+++ b/vendor/github.com/mitchellh/mapstructure/go.mod
@@ -1,3 +1 @@
module github.com/mitchellh/mapstructure
-
-go 1.14
diff --git a/vendor/github.com/mitchellh/mapstructure/mapstructure.go b/vendor/github.com/mitchellh/mapstructure/mapstructure.go
index f41bcc58..256ee63f 100644
--- a/vendor/github.com/mitchellh/mapstructure/mapstructure.go
+++ b/vendor/github.com/mitchellh/mapstructure/mapstructure.go
@@ -1,150 +1,10 @@
-// Package mapstructure exposes functionality to convert one arbitrary
-// Go type into another, typically to convert a map[string]interface{}
-// into a native Go structure.
+// Package mapstructure exposes functionality to convert an arbitrary
+// map[string]interface{} into a native Go structure.
//
// The Go structure can be arbitrarily complex, containing slices,
// other structs, etc. and the decoder will properly decode nested
// maps and so on into the proper structures in the native Go struct.
// See the examples to see what the decoder is capable of.
-//
-// The simplest function to start with is Decode.
-//
-// Field Tags
-//
-// When decoding to a struct, mapstructure will use the field name by
-// default to perform the mapping. For example, if a struct has a field
-// "Username" then mapstructure will look for a key in the source value
-// of "username" (case insensitive).
-//
-// type User struct {
-// Username string
-// }
-//
-// You can change the behavior of mapstructure by using struct tags.
-// The default struct tag that mapstructure looks for is "mapstructure"
-// but you can customize it using DecoderConfig.
-//
-// Renaming Fields
-//
-// To rename the key that mapstructure looks for, use the "mapstructure"
-// tag and set a value directly. For example, to change the "username" example
-// above to "user":
-//
-// type User struct {
-// Username string `mapstructure:"user"`
-// }
-//
-// Embedded Structs and Squashing
-//
-// Embedded structs are treated as if they're another field with that name.
-// By default, the two structs below are equivalent when decoding with
-// mapstructure:
-//
-// type Person struct {
-// Name string
-// }
-//
-// type Friend struct {
-// Person
-// }
-//
-// type Friend struct {
-// Person Person
-// }
-//
-// This would require an input that looks like below:
-//
-// map[string]interface{}{
-// "person": map[string]interface{}{"name": "alice"},
-// }
-//
-// If your "person" value is NOT nested, then you can append ",squash" to
-// your tag value and mapstructure will treat it as if the embedded struct
-// were part of the struct directly. Example:
-//
-// type Friend struct {
-// Person `mapstructure:",squash"`
-// }
-//
-// Now the following input would be accepted:
-//
-// map[string]interface{}{
-// "name": "alice",
-// }
-//
-// DecoderConfig has a field that changes the behavior of mapstructure
-// to always squash embedded structs.
-//
-// Remainder Values
-//
-// If there are any unmapped keys in the source value, mapstructure by
-// default will silently ignore them. You can error by setting ErrorUnused
-// in DecoderConfig. If you're using Metadata you can also maintain a slice
-// of the unused keys.
-//
-// You can also use the ",remain" suffix on your tag to collect all unused
-// values in a map. The field with this tag MUST be a map type and should
-// probably be a "map[string]interface{}" or "map[interface{}]interface{}".
-// See example below:
-//
-// type Friend struct {
-// Name string
-// Other map[string]interface{} `mapstructure:",remain"`
-// }
-//
-// Given the input below, Other would be populated with the other
-// values that weren't used (everything but "name"):
-//
-// map[string]interface{}{
-// "name": "bob",
-// "address": "123 Maple St.",
-// }
-//
-// Omit Empty Values
-//
-// When decoding from a struct to any other value, you may use the
-// ",omitempty" suffix on your tag to omit that value if it equates to
-// the zero value. The zero value of all types is specified in the Go
-// specification.
-//
-// For example, the zero type of a numeric type is zero ("0"). If the struct
-// field value is zero and a numeric type, the field is empty, and it won't
-// be encoded into the destination type.
-//
-// type Source {
-// Age int `mapstructure:",omitempty"`
-// }
-//
-// Unexported fields
-//
-// Since unexported (private) struct fields cannot be set outside the package
-// where they are defined, the decoder will simply skip them.
-//
-// For this output type definition:
-//
-// type Exported struct {
-// private string // this unexported field will be skipped
-// Public string
-// }
-//
-// Using this map as input:
-//
-// map[string]interface{}{
-// "private": "I will be ignored",
-// "Public": "I made it through!",
-// }
-//
-// The following struct will be decoded:
-//
-// type Exported struct {
-// private: "" // field is left with an empty string (zero value)
-// Public: "I made it through!"
-// }
-//
-// Other Configuration
-//
-// mapstructure is highly configurable. See the DecoderConfig struct
-// for other features and options that are supported.
package mapstructure
import (
@@ -220,14 +80,6 @@ type DecoderConfig struct {
//
WeaklyTypedInput bool
- // Squash will squash embedded structs. A squash tag may also be
- // added to an individual struct field using a tag. For example:
- //
- // type Parent struct {
- // Child `mapstructure:",squash"`
- // }
- Squash bool
-
// Metadata is the struct that will contain extra metadata about
// the decoding. If this is nil, then no metadata will be tracked.
Metadata *Metadata
@@ -419,7 +271,6 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
var err error
outputKind := getKind(outVal)
- addMetaKey := true
switch outputKind {
case reflect.Bool:
err = d.decodeBool(name, input, outVal)
@@ -438,7 +289,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
case reflect.Map:
err = d.decodeMap(name, input, outVal)
case reflect.Ptr:
- addMetaKey, err = d.decodePtr(name, input, outVal)
+ err = d.decodePtr(name, input, outVal)
case reflect.Slice:
err = d.decodeSlice(name, input, outVal)
case reflect.Array:
@@ -452,7 +303,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
// If we reached here, then we successfully decoded SOMETHING, so
// mark the key as used if we're tracking metainput.
- if addMetaKey && d.config.Metadata != nil && name != "" {
+ if d.config.Metadata != nil && name != "" {
d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
}
@@ -463,34 +314,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
// value to "data" of that type.
func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) error {
if val.IsValid() && val.Elem().IsValid() {
- elem := val.Elem()
-
- // If we can't address this element, then its not writable. Instead,
- // we make a copy of the value (which is a pointer and therefore
- // writable), decode into that, and replace the whole value.
- copied := false
- if !elem.CanAddr() {
- copied = true
-
- // Make *T
- copy := reflect.New(elem.Type())
-
- // *T = elem
- copy.Elem().Set(elem)
-
- // Set elem so we decode into it
- elem = copy
- }
-
- // Decode. If we have an error then return. We also return right
- // away if we're not a copy because that means we decoded directly.
- if err := d.decode(name, data, elem); err != nil || !copied {
- return err
- }
-
- // If we're a copy, we need to set te final result
- val.Set(elem.Elem())
- return nil
+ return d.decode(name, data, val.Elem())
}
dataVal := reflect.ValueOf(data)
@@ -614,7 +438,6 @@ func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) er
func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) error {
dataVal := reflect.Indirect(reflect.ValueOf(data))
dataKind := getKind(dataVal)
- dataType := dataVal.Type()
switch {
case dataKind == reflect.Int:
@@ -646,18 +469,6 @@ func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) e
} else {
return fmt.Errorf("cannot parse '%s' as uint: %s", name, err)
}
- case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number":
- jn := data.(json.Number)
- i, err := jn.Int64()
- if err != nil {
- return fmt.Errorf(
- "error decoding json.Number into %s: %s", name, err)
- }
- if i < 0 && !d.config.WeaklyTypedInput {
- return fmt.Errorf("cannot parse '%s', %d overflows uint",
- name, i)
- }
- val.SetUint(uint64(i))
default:
return fmt.Errorf(
"'%s' expected type '%s', got unconvertible type '%s'",
@@ -867,31 +678,27 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
}
tagValue := f.Tag.Get(d.config.TagName)
- keyName := f.Name
+ tagParts := strings.Split(tagValue, ",")
- // If Squash is set in the config, we squash the field down.
- squash := d.config.Squash && v.Kind() == reflect.Struct && f.Anonymous
// Determine the name of the key in the map
- if index := strings.Index(tagValue, ","); index != -1 {
- if tagValue[:index] == "-" {
- continue
- }
- // If "omitempty" is specified in the tag, it ignores empty values.
- if strings.Index(tagValue[index+1:], "omitempty") != -1 && isEmptyValue(v) {
+ keyName := f.Name
+ if tagParts[0] != "" {
+ if tagParts[0] == "-" {
continue
}
+ keyName = tagParts[0]
+ }
- // If "squash" is specified in the tag, we squash the field down.
- squash = !squash && strings.Index(tagValue[index+1:], "squash") != -1
- if squash && v.Kind() != reflect.Struct {
- return fmt.Errorf("cannot squash non-struct type '%s'", v.Type())
- }
- keyName = tagValue[:index]
- } else if len(tagValue) > 0 {
- if tagValue == "-" {
- continue
+ // If "squash" is specified in the tag, we squash the field down.
+ squash := false
+ for _, tag := range tagParts[1:] {
+ if tag == "squash" {
+ squash = true
+ break
}
- keyName = tagValue
+ }
+ if squash && v.Kind() != reflect.Struct {
+ return fmt.Errorf("cannot squash non-struct type '%s'", v.Type())
}
switch v.Kind() {
@@ -906,22 +713,11 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
mType := reflect.MapOf(vKeyType, vElemType)
vMap := reflect.MakeMap(mType)
- // Creating a pointer to a map so that other methods can completely
- // overwrite the map if need be (looking at you decodeMapFromMap). The
- // indirection allows the underlying map to be settable (CanSet() == true)
- // where as reflect.MakeMap returns an unsettable map.
- addrVal := reflect.New(vMap.Type())
- reflect.Indirect(addrVal).Set(vMap)
-
- err := d.decode(keyName, x.Interface(), reflect.Indirect(addrVal))
+ err := d.decode(keyName, x.Interface(), vMap)
if err != nil {
return err
}
- // the underlying map may have been completely overwritten so pull
- // it indirectly out of the enclosing value.
- vMap = reflect.Indirect(addrVal)
-
if squash {
for _, k := range vMap.MapKeys() {
valMap.SetMapIndex(k, vMap.MapIndex(k))
@@ -942,7 +738,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
return nil
}
-func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) (bool, error) {
+func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) error {
// If the input data is nil, then we want to just set the output
// pointer to be nil as well.
isNil := data == nil
@@ -963,7 +759,7 @@ func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) (b
val.Set(nilValue)
}
- return true, nil
+ return nil
}
// Create an element of the concrete (non pointer) type and decode
@@ -977,16 +773,16 @@ func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) (b
}
if err := d.decode(name, data, reflect.Indirect(realVal)); err != nil {
- return false, err
+ return err
}
val.Set(realVal)
} else {
if err := d.decode(name, data, reflect.Indirect(val)); err != nil {
- return false, err
+ return err
}
}
- return false, nil
+ return nil
}
func (d *Decoder) decodeFunc(name string, data interface{}, val reflect.Value) error {
@@ -1009,8 +805,8 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value)
valElemType := valType.Elem()
sliceType := reflect.SliceOf(valElemType)
- // If we have a non array/slice type then we first attempt to convert.
- if dataValKind != reflect.Array && dataValKind != reflect.Slice {
+ valSlice := val
+ if valSlice.IsNil() || d.config.ZeroFields {
if d.config.WeaklyTypedInput {
switch {
// Slice and array we use the normal logic
@@ -1037,17 +833,18 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value)
}
}
- return fmt.Errorf(
- "'%s': source data must be an array or slice, got %s", name, dataValKind)
- }
+ // Check input type
+ if dataValKind != reflect.Array && dataValKind != reflect.Slice {
+ return fmt.Errorf(
+ "'%s': source data must be an array or slice, got %s", name, dataValKind)
- // If the input value is nil, then don't allocate since empty != nil
- if dataVal.IsNil() {
- return nil
- }
+ }
+
+ // If the input value is empty, then don't allocate since non-nil != nil
+ if dataVal.Len() == 0 {
+ return nil
+ }
- valSlice := val
- if valSlice.IsNil() || d.config.ZeroFields {
// Make a new slice to hold our result, same size as the original data.
valSlice = reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len())
}
@@ -1165,23 +962,13 @@ func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value)
// Not the most efficient way to do this but we can optimize later if
// we want to. To convert from struct to struct we go to map first
// as an intermediary.
-
- // Make a new map to hold our result
- mapType := reflect.TypeOf((map[string]interface{})(nil))
- mval := reflect.MakeMap(mapType)
-
- // Creating a pointer to a map so that other methods can completely
- // overwrite the map if need be (looking at you decodeMapFromMap). The
- // indirection allows the underlying map to be settable (CanSet() == true)
- // where as reflect.MakeMap returns an unsettable map.
- addrVal := reflect.New(mval.Type())
-
- reflect.Indirect(addrVal).Set(mval)
- if err := d.decodeMapFromStruct(name, dataVal, reflect.Indirect(addrVal), mval); err != nil {
+ m := make(map[string]interface{})
+ mval := reflect.Indirect(reflect.ValueOf(&m))
+ if err := d.decodeMapFromStruct(name, dataVal, mval, mval); err != nil {
return err
}
- result := d.decodeStructFromMap(name, reflect.Indirect(addrVal), val)
+ result := d.decodeStructFromMap(name, mval, val)
return result
default:
@@ -1218,11 +1005,6 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
field reflect.StructField
val reflect.Value
}
-
- // remainField is set to a valid field set with the "remain" tag if
- // we are keeping track of remaining values.
- var remainField *field
-
fields := []field{}
for len(structs) > 0 {
structVal := structs[0]
@@ -1235,21 +1017,13 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
fieldKind := fieldType.Type.Kind()
// If "squash" is specified in the tag, we squash the field down.
- squash := d.config.Squash && fieldKind == reflect.Struct && fieldType.Anonymous
- remain := false
-
- // We always parse the tags cause we're looking for other tags too
+ squash := false
tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",")
for _, tag := range tagParts[1:] {
if tag == "squash" {
squash = true
break
}
-
- if tag == "remain" {
- remain = true
- break
- }
}
if squash {
@@ -1262,13 +1036,8 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
continue
}
- // Build our field
- if remain {
- remainField = &field{fieldType, structVal.Field(i)}
- } else {
- // Normal struct field, store it away
- fields = append(fields, field{fieldType, structVal.Field(i)})
- }
+ // Normal struct field, store it away
+ fields = append(fields, field{fieldType, structVal.Field(i)})
}
}
@@ -1309,6 +1078,9 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
}
}
+ // Delete the key we're using from the unused map so we stop tracking
+ delete(dataValKeysUnused, rawMapKey.Interface())
+
if !fieldValue.IsValid() {
// This should never happen
panic("field is not valid")
@@ -1320,9 +1092,6 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
continue
}
- // Delete the key we're using from the unused map so we stop tracking
- delete(dataValKeysUnused, rawMapKey.Interface())
-
// If the name is empty string, then we're at the root, and we
// don't dot-join the fields.
if name != "" {
@@ -1334,25 +1103,6 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
}
}
- // If we have a "remain"-tagged field and we have unused keys then
- // we put the unused keys directly into the remain field.
- if remainField != nil && len(dataValKeysUnused) > 0 {
- // Build a map of only the unused values
- remain := map[interface{}]interface{}{}
- for key := range dataValKeysUnused {
- remain[key] = dataVal.MapIndex(reflect.ValueOf(key)).Interface()
- }
-
- // Decode it as-if we were just decoding this map onto our map.
- if err := d.decodeMap(name, remain, remainField.val); err != nil {
- errors = appendErrors(errors, err)
- }
-
- // Set the map to nil so we have none so that the next check will
- // not error (ErrorUnused)
- dataValKeysUnused = nil
- }
-
if d.config.ErrorUnused && len(dataValKeysUnused) > 0 {
keys := make([]string, 0, len(dataValKeysUnused))
for rawKey := range dataValKeysUnused {
@@ -1383,24 +1133,6 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
return nil
}
-func isEmptyValue(v reflect.Value) bool {
- switch getKind(v) {
- case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
- return v.Len() == 0
- case reflect.Bool:
- return !v.Bool()
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- return v.Int() == 0
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
- return v.Uint() == 0
- case reflect.Float32, reflect.Float64:
- return v.Float() == 0
- case reflect.Interface, reflect.Ptr:
- return v.IsNil()
- }
- return false
-}
-
func getKind(val reflect.Value) reflect.Kind {
kind := val.Kind()
diff --git a/vendor/github.com/mreiferson/go-httpclient/.travis.yml b/vendor/github.com/mreiferson/go-httpclient/.travis.yml
index ba1b6b7f..d6f3d45d 100644
--- a/vendor/github.com/mreiferson/go-httpclient/.travis.yml
+++ b/vendor/github.com/mreiferson/go-httpclient/.travis.yml
@@ -1,6 +1,9 @@
language: go
go:
- - 1.1
+ - '1.x'
+arch:
+ - amd64
+ - ppc64le
install:
- go get github.com/bmizerany/assert
script:
diff --git a/vendor/github.com/sirupsen/logrus/.travis.yml b/vendor/github.com/sirupsen/logrus/.travis.yml
index 5e20aa41..e6ee8b3a 100644
--- a/vendor/github.com/sirupsen/logrus/.travis.yml
+++ b/vendor/github.com/sirupsen/logrus/.travis.yml
@@ -4,14 +4,11 @@ git:
depth: 1
env:
- GO111MODULE=on
-go: [1.13.x, 1.14.x]
-os: [linux, osx]
+go: 1.15.x
+os: linux
install:
- ./travis/install.sh
script:
- - ./travis/cross_build.sh
- - ./travis/lint.sh
- - export GOMAXPROCS=4
- - export GORACE=halt_on_error=1
- - go test -race -v ./...
- - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then go test -race -v -tags appengine ./... ; fi
+ - go run mage.go -v crossBuild
+ - go run mage.go lint
+ - go run mage.go test
diff --git a/vendor/github.com/sirupsen/logrus/CHANGELOG.md b/vendor/github.com/sirupsen/logrus/CHANGELOG.md
index 584026d6..311f2c33 100644
--- a/vendor/github.com/sirupsen/logrus/CHANGELOG.md
+++ b/vendor/github.com/sirupsen/logrus/CHANGELOG.md
@@ -1,3 +1,30 @@
+# 1.8.0
+
+Correct versioning number replacing v1.7.1.
+
+# 1.7.1
+
+Beware this release has introduced a new public API and its semver is therefore incorrect.
+
+Code quality:
+ * use go 1.15 in travis
+ * use magefile as task runner
+
+Fixes:
+ * small fixes about new go 1.13 error formatting system
+ * Fix for long time race condiction with mutating data hooks
+
+Features:
+ * build support for zos
+
+# 1.7.0
+Fixes:
+ * the dependency toward a windows terminal library has been removed
+
+Features:
+ * a new buffer pool management API has been added
+ * a set of `<LogLevel>Fn()` functions have been added
+
# 1.6.0
Fixes:
* end of line cleanup
diff --git a/vendor/github.com/sirupsen/logrus/README.md b/vendor/github.com/sirupsen/logrus/README.md
index 5796706d..5152b6aa 100644
--- a/vendor/github.com/sirupsen/logrus/README.md
+++ b/vendor/github.com/sirupsen/logrus/README.md
@@ -402,7 +402,7 @@ func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
// source of the official loggers.
serialized, err := json.Marshal(entry.Data)
if err != nil {
- return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
+ return nil, fmt.Errorf("Failed to marshal fields to JSON, %w", err)
}
return append(serialized, '\n'), nil
}
diff --git a/vendor/github.com/sirupsen/logrus/entry.go b/vendor/github.com/sirupsen/logrus/entry.go
index 5a5cbfe7..c968f634 100644
--- a/vendor/github.com/sirupsen/logrus/entry.go
+++ b/vendor/github.com/sirupsen/logrus/entry.go
@@ -78,6 +78,14 @@ func NewEntry(logger *Logger) *Entry {
}
}
+func (entry *Entry) Dup() *Entry {
+ data := make(Fields, len(entry.Data))
+ for k, v := range entry.Data {
+ data[k] = v
+ }
+ return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, Context: entry.Context, err: entry.err}
+}
+
// Returns the bytes representation of this entry from the formatter.
func (entry *Entry) Bytes() ([]byte, error) {
return entry.Logger.Formatter.Format(entry)
@@ -123,11 +131,9 @@ func (entry *Entry) WithFields(fields Fields) *Entry {
for k, v := range fields {
isErrField := false
if t := reflect.TypeOf(v); t != nil {
- switch t.Kind() {
- case reflect.Func:
+ switch {
+ case t.Kind() == reflect.Func, t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Func:
isErrField = true
- case reflect.Ptr:
- isErrField = t.Elem().Kind() == reflect.Func
}
}
if isErrField {
@@ -212,53 +218,49 @@ func (entry Entry) HasCaller() (has bool) {
entry.Caller != nil
}
-// This function is not declared with a pointer value because otherwise
-// race conditions will occur when using multiple goroutines
-func (entry Entry) log(level Level, msg string) {
+func (entry *Entry) log(level Level, msg string) {
var buffer *bytes.Buffer
- // Default to now, but allow users to override if they want.
- //
- // We don't have to worry about polluting future calls to Entry#log()
- // with this assignment because this function is declared with a
- // non-pointer receiver.
- if entry.Time.IsZero() {
- entry.Time = time.Now()
+ newEntry := entry.Dup()
+
+ if newEntry.Time.IsZero() {
+ newEntry.Time = time.Now()
}
- entry.Level = level
- entry.Message = msg
- entry.Logger.mu.Lock()
- if entry.Logger.ReportCaller {
- entry.Caller = getCaller()
+ newEntry.Level = level
+ newEntry.Message = msg
+
+ newEntry.Logger.mu.Lock()
+ reportCaller := newEntry.Logger.ReportCaller
+ newEntry.Logger.mu.Unlock()
+
+ if reportCaller {
+ newEntry.Caller = getCaller()
}
- entry.Logger.mu.Unlock()
- entry.fireHooks()
+ newEntry.fireHooks()
buffer = getBuffer()
defer func() {
- entry.Buffer = nil
+ newEntry.Buffer = nil
putBuffer(buffer)
}()
buffer.Reset()
- entry.Buffer = buffer
+ newEntry.Buffer = buffer
- entry.write()
+ newEntry.write()
- entry.Buffer = nil
+ newEntry.Buffer = nil
// To avoid Entry#log() returning a value that only would make sense for
// panic() to use in Entry#Panic(), we avoid the allocation by checking
// directly here.
if level <= PanicLevel {
- panic(&entry)
+ panic(newEntry)
}
}
func (entry *Entry) fireHooks() {
- entry.Logger.mu.Lock()
- defer entry.Logger.mu.Unlock()
err := entry.Logger.Hooks.Fire(entry.Level, entry)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
@@ -266,14 +268,14 @@ func (entry *Entry) fireHooks() {
}
func (entry *Entry) write() {
- entry.Logger.mu.Lock()
- defer entry.Logger.mu.Unlock()
serialized, err := entry.Logger.Formatter.Format(entry)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
return
}
- if _, err = entry.Logger.Out.Write(serialized); err != nil {
+ entry.Logger.mu.Lock()
+ defer entry.Logger.mu.Unlock()
+ if _, err := entry.Logger.Out.Write(serialized); err != nil {
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
}
}
@@ -319,7 +321,6 @@ func (entry *Entry) Fatal(args ...interface{}) {
func (entry *Entry) Panic(args ...interface{}) {
entry.Log(PanicLevel, args...)
- panic(fmt.Sprint(args...))
}
// Entry Printf family functions
diff --git a/vendor/github.com/sirupsen/logrus/go.mod b/vendor/github.com/sirupsen/logrus/go.mod
index b3919d5e..37004ff3 100644
--- a/vendor/github.com/sirupsen/logrus/go.mod
+++ b/vendor/github.com/sirupsen/logrus/go.mod
@@ -2,6 +2,7 @@ module github.com/sirupsen/logrus
require (
github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/magefile/mage v1.10.0
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.2.2
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037
diff --git a/vendor/github.com/sirupsen/logrus/go.sum b/vendor/github.com/sirupsen/logrus/go.sum
index 1edc143b..bce26a18 100644
--- a/vendor/github.com/sirupsen/logrus/go.sum
+++ b/vendor/github.com/sirupsen/logrus/go.sum
@@ -1,5 +1,7 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g=
+github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
diff --git a/vendor/github.com/sirupsen/logrus/json_formatter.go b/vendor/github.com/sirupsen/logrus/json_formatter.go
index ba7f2371..afaf0fc8 100644
--- a/vendor/github.com/sirupsen/logrus/json_formatter.go
+++ b/vendor/github.com/sirupsen/logrus/json_formatter.go
@@ -118,7 +118,7 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
encoder.SetIndent("", " ")
}
if err := encoder.Encode(data); err != nil {
- return nil, fmt.Errorf("failed to marshal fields to JSON, %v", err)
+ return nil, fmt.Errorf("failed to marshal fields to JSON, %w", err)
}
return b.Bytes(), nil
diff --git a/vendor/github.com/sirupsen/logrus/logger.go b/vendor/github.com/sirupsen/logrus/logger.go
index dbf627c9..33770445 100644
--- a/vendor/github.com/sirupsen/logrus/logger.go
+++ b/vendor/github.com/sirupsen/logrus/logger.go
@@ -12,7 +12,7 @@ import (
// LogFunction For big messages, it can be more efficient to pass a function
// and only call it if the log level is actually enables rather than
// generating the log message and then checking if the level is enabled
-type LogFunction func()[]interface{}
+type LogFunction func() []interface{}
type Logger struct {
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
diff --git a/vendor/github.com/sirupsen/logrus/magefile.go b/vendor/github.com/sirupsen/logrus/magefile.go
new file mode 100644
index 00000000..9aa60393
--- /dev/null
+++ b/vendor/github.com/sirupsen/logrus/magefile.go
@@ -0,0 +1,77 @@
+// +build mage
+
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "path"
+
+ "github.com/magefile/mage/mg"
+ "github.com/magefile/mage/sh"
+)
+
+// getBuildMatrix returns the build matrix from the current version of the go compiler
+func getBuildMatrix() (map[string][]string, error) {
+ jsonData, err := sh.Output("go", "tool", "dist", "list", "-json")
+ if err != nil {
+ return nil, err
+ }
+ var data []struct {
+ Goos string
+ Goarch string
+ }
+ if err := json.Unmarshal([]byte(jsonData), &data); err != nil {
+ return nil, err
+ }
+
+ matrix := map[string][]string{}
+ for _, v := range data {
+ if val, ok := matrix[v.Goos]; ok {
+ matrix[v.Goos] = append(val, v.Goarch)
+ } else {
+ matrix[v.Goos] = []string{v.Goarch}
+ }
+ }
+
+ return matrix, nil
+}
+
+func CrossBuild() error {
+ matrix, err := getBuildMatrix()
+ if err != nil {
+ return err
+ }
+
+ for os, arches := range matrix {
+ for _, arch := range arches {
+ env := map[string]string{
+ "GOOS": os,
+ "GOARCH": arch,
+ }
+ if mg.Verbose() {
+ fmt.Printf("Building for GOOS=%s GOARCH=%s\n", os, arch)
+ }
+ if err := sh.RunWith(env, "go", "build", "./..."); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func Lint() error {
+ gopath := os.Getenv("GOPATH")
+ if gopath == "" {
+ return fmt.Errorf("cannot retrieve GOPATH")
+ }
+
+ return sh.Run(path.Join(gopath, "bin", "golangci-lint"), "run", "./...")
+}
+
+// Run the test suite
+func Test() error {
+ return sh.RunWith(map[string]string{"GORACE": "halt_on_error=1"},
+ "go", "test", "-race", "-v", "./...")
+}
diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_unix.go b/vendor/github.com/sirupsen/logrus/terminal_check_unix.go
index cc4fe6e3..04748b85 100644
--- a/vendor/github.com/sirupsen/logrus/terminal_check_unix.go
+++ b/vendor/github.com/sirupsen/logrus/terminal_check_unix.go
@@ -1,4 +1,4 @@
-// +build linux aix
+// +build linux aix zos
// +build !js
package logrus
diff --git a/vendor/github.com/sirupsen/logrus/text_formatter.go b/vendor/github.com/sirupsen/logrus/text_formatter.go
index 3c28b54c..8fc698ad 100644
--- a/vendor/github.com/sirupsen/logrus/text_formatter.go
+++ b/vendor/github.com/sirupsen/logrus/text_formatter.go
@@ -235,6 +235,8 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
levelColor = yellow
case ErrorLevel, FatalLevel, PanicLevel:
levelColor = red
+ case InfoLevel:
+ levelColor = blue
default:
levelColor = blue
}
diff --git a/vendor/github.com/slack-go/slack/README.md b/vendor/github.com/slack-go/slack/README.md
index cebbf48b..7be89066 100644
--- a/vendor/github.com/slack-go/slack/README.md
+++ b/vendor/github.com/slack-go/slack/README.md
@@ -1,6 +1,6 @@
Slack API in Go [![GoDoc](https://godoc.org/github.com/slack-go/slack?status.svg)](https://godoc.org/github.com/slack-go/slack) [![Build Status](https://travis-ci.org/slack-go/slack.svg)](https://travis-ci.org/slack-go/slack)
===============
-This is the original Slack library for Go created by Norberto Lopez, transferred to a Github organization.
+This is the original Slack library for Go created by Norberto Lopes, transferred to a Github organization.
[![Join the chat at https://gitter.im/go-slack/Lobby](https://badges.gitter.im/go-slack/Lobby.svg)](https://gitter.im/go-slack/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
diff --git a/vendor/github.com/slack-go/slack/attachments.go b/vendor/github.com/slack-go/slack/attachments.go
index b5b79f9f..69b223a2 100644
--- a/vendor/github.com/slack-go/slack/attachments.go
+++ b/vendor/github.com/slack-go/slack/attachments.go
@@ -80,6 +80,11 @@ type Attachment struct {
ImageURL string `json:"image_url,omitempty"`
ThumbURL string `json:"thumb_url,omitempty"`
+ ServiceName string `json:"service_name,omitempty"`
+ ServiceIcon string `json:"service_icon,omitempty"`
+ FromURL string `json:"from_url,omitempty"`
+ OriginalURL string `json:"original_url,omitempty"`
+
Fields []AttachmentField `json:"fields,omitempty"`
Actions []AttachmentAction `json:"actions,omitempty"`
MarkdownIn []string `json:"mrkdwn_in,omitempty"`
diff --git a/vendor/github.com/slack-go/slack/block_element.go b/vendor/github.com/slack-go/slack/block_element.go
index 4157016c..bf068440 100644
--- a/vendor/github.com/slack-go/slack/block_element.go
+++ b/vendor/github.com/slack-go/slack/block_element.go
@@ -268,6 +268,7 @@ type MultiSelectBlockElement struct {
InitialConversations []string `json:"initial_conversations,omitempty"`
InitialChannels []string `json:"initial_channels,omitempty"`
MinQueryLength *int `json:"min_query_length,omitempty"`
+ MaxSelectedItems *int `json:"max_selected_items,omitempty"`
Confirm *ConfirmationBlockObject `json:"confirm,omitempty"`
}
diff --git a/vendor/github.com/slack-go/slack/block_input.go b/vendor/github.com/slack-go/slack/block_input.go
index 10638cd9..087571af 100644
--- a/vendor/github.com/slack-go/slack/block_input.go
+++ b/vendor/github.com/slack-go/slack/block_input.go
@@ -4,12 +4,13 @@ package slack
//
// More Information: https://api.slack.com/reference/block-kit/blocks#input
type InputBlock struct {
- Type MessageBlockType `json:"type"`
- BlockID string `json:"block_id,omitempty"`
- Label *TextBlockObject `json:"label"`
- Element BlockElement `json:"element"`
- Hint *TextBlockObject `json:"hint,omitempty"`
- Optional bool `json:"optional,omitempty"`
+ Type MessageBlockType `json:"type"`
+ BlockID string `json:"block_id,omitempty"`
+ Label *TextBlockObject `json:"label"`
+ Element BlockElement `json:"element"`
+ Hint *TextBlockObject `json:"hint,omitempty"`
+ Optional bool `json:"optional,omitempty"`
+ DispatchAction bool `json:"dispatch_action,omitempty"`
}
// BlockType returns the type of the block
diff --git a/vendor/github.com/slack-go/slack/block_object.go b/vendor/github.com/slack-go/slack/block_object.go
index d17806ca..5ced7f92 100644
--- a/vendor/github.com/slack-go/slack/block_object.go
+++ b/vendor/github.com/slack-go/slack/block_object.go
@@ -2,6 +2,7 @@ package slack
import (
"encoding/json"
+ "errors"
)
// Block Objects are also known as Composition Objects
@@ -135,6 +136,20 @@ func (s TextBlockObject) MixedElementType() MixedElementType {
return MixedElementText
}
+// Validate checks if TextBlockObject has valid values
+func (s TextBlockObject) Validate() error {
+ if s.Type != "plain_text" && s.Type != "mrkdwn" {
+ return errors.New("type must be either of plain_text or mrkdwn")
+ }
+
+ // https://github.com/slack-go/slack/issues/881
+ if s.Type == "mrkdwn" && s.Emoji {
+ return errors.New("emoji cannot be true in mrkdown")
+ }
+
+ return nil
+}
+
// NewTextBlockObject returns an instance of a new Text Block Object
func NewTextBlockObject(elementType, text string, emoji, verbatim bool) *TextBlockObject {
return &TextBlockObject{
diff --git a/vendor/github.com/slack-go/slack/reminders.go b/vendor/github.com/slack-go/slack/reminders.go
index f312af3f..ae1da866 100644
--- a/vendor/github.com/slack-go/slack/reminders.go
+++ b/vendor/github.com/slack-go/slack/reminders.go
@@ -22,7 +22,7 @@ type reminderResp struct {
type remindersResp struct {
SlackResponse
- Reminders []Reminder `json:"reminders"`
+ Reminders []*Reminder `json:"reminders"`
}
func (api *Client) doReminder(ctx context.Context, path string, values url.Values) (*Reminder, error) {
@@ -42,7 +42,7 @@ func (api *Client) doReminders(ctx context.Context, path string, values url.Valu
// create an array of pointers to reminders
var reminders = make([]*Reminder, 0, len(response.Reminders))
for _, reminder := range response.Reminders {
- reminders = append(reminders, &reminder)
+ reminders = append(reminders, reminder)
}
return reminders, response.Err()
diff --git a/vendor/github.com/slack-go/slack/users.go b/vendor/github.com/slack-go/slack/users.go
index 5d8cb35f..82657d76 100644
--- a/vendor/github.com/slack-go/slack/users.go
+++ b/vendor/github.com/slack-go/slack/users.go
@@ -644,10 +644,13 @@ type getUserProfileResponse struct {
// GetUserProfileContext retrieves a user's profile information with a context.
func (api *Client) GetUserProfileContext(ctx context.Context, userID string, includeLabels bool) (*UserProfile, error) {
- values := url.Values{"token": {api.token}, "user": {userID}}
+ values := url.Values{"token": {api.token}}
if includeLabels {
values.Add("include_labels", "true")
}
+ if userID != "" {
+ values.Add("user", userID)
+ }
resp := &getUserProfileResponse{}
err := api.postMethod(ctx, "users.profile.get", values, &resp)
diff --git a/vendor/github.com/slack-go/slack/websocket_internals.go b/vendor/github.com/slack-go/slack/websocket_internals.go
index 3e1906ee..454a2133 100644
--- a/vendor/github.com/slack-go/slack/websocket_internals.go
+++ b/vendor/github.com/slack-go/slack/websocket_internals.go
@@ -94,6 +94,7 @@ func (i *IncomingEventError) Error() string {
// AckErrorEvent i
type AckErrorEvent struct {
ErrorObj error
+ ReplyTo int
}
func (a *AckErrorEvent) Error() string {
diff --git a/vendor/github.com/slack-go/slack/websocket_managed_conn.go b/vendor/github.com/slack-go/slack/websocket_managed_conn.go
index fe6802e4..5555c316 100644
--- a/vendor/github.com/slack-go/slack/websocket_managed_conn.go
+++ b/vendor/github.com/slack-go/slack/websocket_managed_conn.go
@@ -91,6 +91,7 @@ func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocke
errInvalidAuth = "invalid_auth"
errInactiveAccount = "account_inactive"
errMissingAuthToken = "not_authed"
+ errTokenRevoked = "token_revoked"
)
// used to provide exponential backoff wait time with jitter before trying
@@ -118,7 +119,7 @@ func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocke
// check for fatal errors
switch err.Error() {
- case errInvalidAuth, errInactiveAccount, errMissingAuthToken:
+ case errInvalidAuth, errInactiveAccount, errMissingAuthToken, errTokenRevoked:
rtm.Debugf("invalid auth when connecting with RTM: %s", err)
rtm.IncomingEvents <- RTMEvent{"invalid_auth", &InvalidAuthEvent{}}
return nil, nil, err
@@ -438,10 +439,10 @@ func (rtm *RTM) handleAck(event json.RawMessage) {
if ack.RTMResponse.Error.Code == -1 && ack.RTMResponse.Error.Msg == "slow down, too many messages..." {
rtm.IncomingEvents <- RTMEvent{"ack_error", &RateLimitEvent{}}
} else {
- rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{ack.Error}}
+ rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{ack.Error, ack.ReplyTo}}
}
} else {
- rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{fmt.Errorf("ack decode failure")}}
+ rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{ErrorObj: fmt.Errorf("ack decode failure")}}
}
}
diff --git a/vendor/github.com/spf13/jwalterweatherman/.gitignore b/vendor/github.com/spf13/jwalterweatherman/.gitignore
index a71f88af..00268614 100644
--- a/vendor/github.com/spf13/jwalterweatherman/.gitignore
+++ b/vendor/github.com/spf13/jwalterweatherman/.gitignore
@@ -20,5 +20,3 @@ _cgo_export.*
_testmain.go
*.exe
-*.bench
-go.sum \ No newline at end of file
diff --git a/vendor/github.com/spf13/jwalterweatherman/default_notepad.go b/vendor/github.com/spf13/jwalterweatherman/default_notepad.go
index a018c15c..bcb76340 100644
--- a/vendor/github.com/spf13/jwalterweatherman/default_notepad.go
+++ b/vendor/github.com/spf13/jwalterweatherman/default_notepad.go
@@ -64,13 +64,6 @@ func SetStdoutThreshold(threshold Threshold) {
reloadDefaultNotepad()
}
-// SetStdoutOutput set the stdout output for the default notepad. Default is stdout.
-func SetStdoutOutput(handle io.Writer) {
- defaultNotepad.outHandle = handle
- defaultNotepad.init()
- reloadDefaultNotepad()
-}
-
// SetPrefix set the prefix for the default logger. Empty by default.
func SetPrefix(prefix string) {
defaultNotepad.SetPrefix(prefix)
@@ -83,13 +76,6 @@ func SetFlags(flags int) {
reloadDefaultNotepad()
}
-// SetLogListeners configures the default logger with one or more log listeners.
-func SetLogListeners(l ...LogListener) {
- defaultNotepad.logListeners = l
- defaultNotepad.init()
- reloadDefaultNotepad()
-}
-
// Level returns the current global log threshold.
func LogThreshold() Threshold {
return defaultNotepad.logThreshold
@@ -109,3 +95,19 @@ func GetLogThreshold() Threshold {
func GetStdoutThreshold() Threshold {
return defaultNotepad.GetStdoutThreshold()
}
+
+// LogCountForLevel returns the number of log invocations for a given threshold.
+func LogCountForLevel(l Threshold) uint64 {
+ return defaultNotepad.LogCountForLevel(l)
+}
+
+// LogCountForLevelsGreaterThanorEqualTo returns the number of log invocations
+// greater than or equal to a given threshold.
+func LogCountForLevelsGreaterThanorEqualTo(threshold Threshold) uint64 {
+ return defaultNotepad.LogCountForLevelsGreaterThanorEqualTo(threshold)
+}
+
+// ResetLogCounters resets the invocation counters for all levels.
+func ResetLogCounters() {
+ defaultNotepad.ResetLogCounters()
+}
diff --git a/vendor/github.com/spf13/jwalterweatherman/go.mod b/vendor/github.com/spf13/jwalterweatherman/go.mod
index 1dbcfd3e..bce549c0 100644
--- a/vendor/github.com/spf13/jwalterweatherman/go.mod
+++ b/vendor/github.com/spf13/jwalterweatherman/go.mod
@@ -1,7 +1 @@
module github.com/spf13/jwalterweatherman
-
-require (
- github.com/davecgh/go-spew v1.1.1 // indirect
- github.com/pmezard/go-difflib v1.0.0 // indirect
- github.com/stretchr/testify v1.2.2
-)
diff --git a/vendor/github.com/spf13/jwalterweatherman/log_counter.go b/vendor/github.com/spf13/jwalterweatherman/log_counter.go
index 41285f3d..11423ac4 100644
--- a/vendor/github.com/spf13/jwalterweatherman/log_counter.go
+++ b/vendor/github.com/spf13/jwalterweatherman/log_counter.go
@@ -6,41 +6,50 @@
package jwalterweatherman
import (
- "io"
"sync/atomic"
)
-// Counter is an io.Writer that increments a counter on Write.
-type Counter struct {
- count uint64
+type logCounter struct {
+ counter uint64
}
-func (c *Counter) incr() {
- atomic.AddUint64(&c.count, 1)
+func (c *logCounter) incr() {
+ atomic.AddUint64(&c.counter, 1)
}
-// Reset resets the counter.
-func (c *Counter) Reset() {
- atomic.StoreUint64(&c.count, 0)
+func (c *logCounter) resetCounter() {
+ atomic.StoreUint64(&c.counter, 0)
}
-// Count returns the current count.
-func (c *Counter) Count() uint64 {
- return atomic.LoadUint64(&c.count)
+func (c *logCounter) getCount() uint64 {
+ return atomic.LoadUint64(&c.counter)
}
-func (c *Counter) Write(p []byte) (n int, err error) {
+func (c *logCounter) Write(p []byte) (n int, err error) {
c.incr()
return len(p), nil
}
-// LogCounter creates a LogListener that counts log statements >= the given threshold.
-func LogCounter(counter *Counter, t1 Threshold) LogListener {
- return func(t2 Threshold) io.Writer {
- if t2 < t1 {
- // Not interested in this threshold.
- return nil
- }
- return counter
+// LogCountForLevel returns the number of log invocations for a given threshold.
+func (n *Notepad) LogCountForLevel(l Threshold) uint64 {
+ return n.logCounters[l].getCount()
+}
+
+// LogCountForLevelsGreaterThanorEqualTo returns the number of log invocations
+// greater than or equal to a given threshold.
+func (n *Notepad) LogCountForLevelsGreaterThanorEqualTo(threshold Threshold) uint64 {
+ var cnt uint64
+
+ for i := int(threshold); i < len(n.logCounters); i++ {
+ cnt += n.LogCountForLevel(Threshold(i))
+ }
+
+ return cnt
+}
+
+// ResetLogCounters resets the invocation counters for all levels.
+func (n *Notepad) ResetLogCounters() {
+ for _, np := range n.logCounters {
+ np.resetCounter()
}
}
diff --git a/vendor/github.com/spf13/jwalterweatherman/notepad.go b/vendor/github.com/spf13/jwalterweatherman/notepad.go
index cc7957bf..ae5aaf71 100644
--- a/vendor/github.com/spf13/jwalterweatherman/notepad.go
+++ b/vendor/github.com/spf13/jwalterweatherman/notepad.go
@@ -8,7 +8,6 @@ package jwalterweatherman
import (
"fmt"
"io"
- "io/ioutil"
"log"
)
@@ -59,28 +58,13 @@ type Notepad struct {
prefix string
flags int
- logListeners []LogListener
+ // One per Threshold
+ logCounters [7]*logCounter
}
-// A LogListener can ble supplied to a Notepad to listen on log writes for a given
-// threshold. This can be used to capture log events in unit tests and similar.
-// Note that this function will be invoked once for each log threshold. If
-// the given threshold is not of interest to you, return nil.
-// Note that these listeners will receive log events for a given threshold, even
-// if the current configuration says not to log it. That way you can count ERRORs even
-// if you don't print them to the console.
-type LogListener func(t Threshold) io.Writer
-
-// NewNotepad creates a new Notepad.
-func NewNotepad(
- outThreshold Threshold,
- logThreshold Threshold,
- outHandle, logHandle io.Writer,
- prefix string, flags int,
- logListeners ...LogListener,
-) *Notepad {
-
- n := &Notepad{logListeners: logListeners}
+// NewNotepad create a new notepad.
+func NewNotepad(outThreshold Threshold, logThreshold Threshold, outHandle, logHandle io.Writer, prefix string, flags int) *Notepad {
+ n := &Notepad{}
n.loggers = [7]**log.Logger{&n.TRACE, &n.DEBUG, &n.INFO, &n.WARN, &n.ERROR, &n.CRITICAL, &n.FATAL}
n.outHandle = outHandle
@@ -111,41 +95,26 @@ func (n *Notepad) init() {
for t, logger := range n.loggers {
threshold := Threshold(t)
+ counter := &logCounter{}
+ n.logCounters[t] = counter
prefix := n.prefix + threshold.String() + " "
switch {
case threshold >= n.logThreshold && threshold >= n.stdoutThreshold:
- *logger = log.New(n.createLogWriters(threshold, logAndOut), prefix, n.flags)
+ *logger = log.New(io.MultiWriter(counter, logAndOut), prefix, n.flags)
case threshold >= n.logThreshold:
- *logger = log.New(n.createLogWriters(threshold, n.logHandle), prefix, n.flags)
+ *logger = log.New(io.MultiWriter(counter, n.logHandle), prefix, n.flags)
case threshold >= n.stdoutThreshold:
- *logger = log.New(n.createLogWriters(threshold, n.outHandle), prefix, n.flags)
+ *logger = log.New(io.MultiWriter(counter, n.outHandle), prefix, n.flags)
default:
- *logger = log.New(n.createLogWriters(threshold, ioutil.Discard), prefix, n.flags)
- }
- }
-}
-
-func (n *Notepad) createLogWriters(t Threshold, handle io.Writer) io.Writer {
- if len(n.logListeners) == 0 {
- return handle
- }
- writers := []io.Writer{handle}
- for _, l := range n.logListeners {
- w := l(t)
- if w != nil {
- writers = append(writers, w)
+ // counter doesn't care about prefix and flags, so don't use them
+ // for performance.
+ *logger = log.New(counter, "", 0)
}
}
-
- if len(writers) == 1 {
- return handle
- }
-
- return io.MultiWriter(writers...)
}
// SetLogThreshold changes the threshold above which messages are written to the