Refactor whole app
This commit is contained in:
parent
1536bc9649
commit
6a01f7dbd1
239 changed files with 45044 additions and 562 deletions
|
@ -1,3 +1,5 @@
|
||||||
vendor
|
*
|
||||||
s3-manager
|
|
||||||
README.md
|
!/templates
|
||||||
|
!/entrypoint-cf.sh
|
||||||
|
!/s3manager
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,2 +1 @@
|
||||||
vendor
|
/s3manager
|
||||||
s3-manager
|
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
FROM golang:onbuild
|
FROM golang
|
||||||
|
|
||||||
|
ADD . /go/src/github.com/mastertinner/s3manager
|
||||||
|
WORKDIR /go/src/github.com/mastertinner/s3manager
|
||||||
|
|
||||||
|
RUN go build ./cmd/s3manager
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD ./s3manager -endpoint "${S3_ENDPOINT}" -access-key-id "${S3_ACCESS_KEY_ID}" -secret-access-key "${S3_SECRET_ACCESS_KEY}"
|
||||||
|
|
43
Gopkg.lock
generated
Normal file
43
Gopkg.lock
generated
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
memo = "2564b0b342a1586a24a3ff5356636c822f6351da32f8ee9497ab8694855e80b1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/davecgh/go-spew"
|
||||||
|
packages = ["spew"]
|
||||||
|
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||||
|
version = "v1.1.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/gorilla/context"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a"
|
||||||
|
version = "v1.1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/gorilla/mux"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "392c28fe23e1c45ddba891b0320b3b5df220beea"
|
||||||
|
version = "v1.3.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/mastertinner/adapters"
|
||||||
|
packages = [".","logging"]
|
||||||
|
revision = "3bfe6170b9beca289d89e4c703c7f9db68e7c158"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/minio/minio-go"
|
||||||
|
packages = [".","pkg/encrypt","pkg/policy","pkg/s3signer","pkg/s3utils","pkg/set"]
|
||||||
|
revision = "2f03abaa07d8bc57faef16cda7655ea62a7e0bed"
|
||||||
|
version = "v2.1.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/pmezard/go-difflib"
|
||||||
|
packages = ["difflib"]
|
||||||
|
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||||
|
version = "v1.0.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/stretchr/testify"
|
||||||
|
packages = ["assert"]
|
||||||
|
revision = "4d4bfba8f1d1027c4fdbe371823030df51419987"
|
76
Gopkg.toml
Normal file
76
Gopkg.toml
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
|
||||||
|
## Gopkg.toml example (these lines may be deleted)
|
||||||
|
|
||||||
|
## "required" lists a set of packages (not projects) that must be included in
|
||||||
|
## Gopkg.lock. This list is merged with the set of packages imported by the current
|
||||||
|
## project. Use it when your project needs a package it doesn't explicitly import -
|
||||||
|
## including "main" packages.
|
||||||
|
# required = ["github.com/user/thing/cmd/thing"]
|
||||||
|
|
||||||
|
## "ignored" lists a set of packages (not projects) that are ignored when
|
||||||
|
## dep statically analyzes source code. Ignored packages can be in this project,
|
||||||
|
## or in a dependency.
|
||||||
|
# ignored = ["github.com/user/project/badpkg"]
|
||||||
|
|
||||||
|
## Dependencies define constraints on dependent projects. They are respected by
|
||||||
|
## dep whether coming from the Gopkg.toml of the current project or a dependency.
|
||||||
|
# [[dependencies]]
|
||||||
|
## Required: the root import path of the project being constrained.
|
||||||
|
# name = "github.com/user/project"
|
||||||
|
#
|
||||||
|
## Recommended: the version constraint to enforce for the project.
|
||||||
|
## Only one of "branch", "version" or "revision" can be specified.
|
||||||
|
# version = "1.0.0"
|
||||||
|
# branch = "master"
|
||||||
|
# revision = "abc123"
|
||||||
|
#
|
||||||
|
## Optional: an alternate location (URL or import path) for the project's source.
|
||||||
|
# source = "https://github.com/myfork/package.git"
|
||||||
|
|
||||||
|
## Overrides have the same structure as [[dependencies]], but supercede all
|
||||||
|
## [[dependencies]] declarations from all projects. Only the current project's
|
||||||
|
## [[overrides]] are applied.
|
||||||
|
##
|
||||||
|
## Overrides are a sledgehammer. Use them only as a last resort.
|
||||||
|
# [[overrides]]
|
||||||
|
## Required: the root import path of the project being constrained.
|
||||||
|
# name = "github.com/user/project"
|
||||||
|
#
|
||||||
|
## Optional: specifying a version constraint override will cause all other
|
||||||
|
## constraints on this project to be ignored; only the overriden constraint
|
||||||
|
## need be satisfied.
|
||||||
|
## Again, only one of "branch", "version" or "revision" can be specified.
|
||||||
|
# version = "1.0.0"
|
||||||
|
# branch = "master"
|
||||||
|
# revision = "abc123"
|
||||||
|
#
|
||||||
|
## Optional: specifying an alternate source location as an override will
|
||||||
|
## enforce that the alternate location is used for that project, regardless of
|
||||||
|
## what source location any dependent projects specify.
|
||||||
|
# source = "https://github.com/myfork/package.git"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[dependencies]]
|
||||||
|
name = "github.com/davecgh/go-spew"
|
||||||
|
version = "^1.1.0"
|
||||||
|
|
||||||
|
[[dependencies]]
|
||||||
|
name = "github.com/gorilla/mux"
|
||||||
|
version = "^1.3.0"
|
||||||
|
|
||||||
|
[[dependencies]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/mastertinner/adapters"
|
||||||
|
|
||||||
|
[[dependencies]]
|
||||||
|
name = "github.com/minio/minio-go"
|
||||||
|
version = "^2.1.0"
|
||||||
|
|
||||||
|
[[dependencies]]
|
||||||
|
name = "github.com/pmezard/go-difflib"
|
||||||
|
version = "^1.0.0"
|
||||||
|
|
||||||
|
[[dependencies]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/stretchr/testify"
|
12
Makefile
Normal file
12
Makefile
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
all:
|
||||||
|
go build ./cmd/s3manager
|
||||||
|
|
||||||
|
test:
|
||||||
|
go test
|
||||||
|
|
||||||
|
build-docker:
|
||||||
|
docker run --rm -v "${PWD}:/go/src/github.com/mastertinner/s3manager" -w /go/src/github.com/mastertinner/s3manager golang go build ./cmd/s3manager
|
||||||
|
|
||||||
|
deploy-cf:
|
||||||
|
GOOS=linux GOARCH=amd64 go build ./cmd/s3manager
|
||||||
|
cf push
|
26
README.md
26
README.md
|
@ -1,21 +1,13 @@
|
||||||
# S3 Manager
|
# S3 Manager
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/mastertinner/s3-manager.svg?branch=master)](https://travis-ci.org/mastertinner/s3-manager)
|
[![Build Status](https://travis-ci.org/mastertinner/s3manager.svg?branch=master)](https://travis-ci.org/mastertinner/s3manager)
|
||||||
|
|
||||||
A Web GUI written in Go to manage S3 buckets from any provider.
|
A Web GUI written in Go to manage S3 buckets from any provider.
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
|
|
||||||
* `S3_ACCESS_KEY_ID`: Required. Your S3 access key ID
|
|
||||||
* `S3_SECRET_ACCESS_KEY`: Required. Your S3 secret access key
|
|
||||||
* `S3_ENDPOINT`: Optional. In case you are using a different S3 provider than AWS. Defaults to `s3.amazonaws.com`
|
|
||||||
* `V2_SIGNING`: Optional. In case your S3 provider still uses V2 Signing, set this to `true`
|
|
||||||
|
|
||||||
## Run locally
|
## Run locally
|
||||||
|
|
||||||
1. Run `go build`
|
1. Run `make`
|
||||||
1. Set environment variables in your env
|
1. Execute the created binary and visit <http://localhost:8080>
|
||||||
1. Execute the binary and visit <http://localhost:8080>
|
|
||||||
|
|
||||||
## Run with Docker
|
## Run with Docker
|
||||||
|
|
||||||
|
@ -25,14 +17,14 @@ A Web GUI written in Go to manage S3 buckets from any provider.
|
||||||
|
|
||||||
## Build with Docker and run anywhere
|
## Build with Docker and run anywhere
|
||||||
|
|
||||||
1. Run `docker run --rm -v "${PWD}:/go/src/github.com/mastertinner/s3-manager" -w /go/src/github.com/mastertinner/s3-manager golang curl https://glide.sh/get | sh && glide install && go build`
|
1. Run `make build-docker`
|
||||||
|
|
||||||
To cross-compile for windows, use the `-e "GOOS=windows" -e "GOARCH=amd64"` flags (depending on your system, you might have to adjust `GOARCH`)
|
To cross-compile for windows, add the `-e "GOOS=windows" -e "GOARCH=amd64"` flags to the `Makefile` (depending on your system, you might have to adjust `GOARCH`)
|
||||||
|
|
||||||
To cross-compile for macOS, use the `-e "GOOS=darwin" -e "GOARCH=amd64"` flags (depending on your system, you might have to adjust `GOARCH`)
|
To cross-compile for macOS, add the `-e "GOOS=darwin" -e "GOARCH=amd64"` flags to the `Makefile` (depending on your system, you might have to adjust `GOARCH`)
|
||||||
|
|
||||||
## Run on Cloud Foundry
|
## Run on Cloud Foundry
|
||||||
|
|
||||||
1. Set environment variables in `manifest.yml`
|
1. Change the service in `manifest.yml` to represent your S3 service (if you are using an external S3 provider, you'll have to switch the service type in `entrypoint-cf.sh` from `dynstrg` to `user-provided` and create the respective user-provided service with `cf create-user-provided-service`)
|
||||||
1. Set host that isn't taken yet in `manifest.yml`
|
1. Change `host` in `manifest.yml` to something that isn't taken yet
|
||||||
1. Run `cf push`
|
1. Run `make deploy-cf`
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package s3manager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
|
@ -9,23 +9,23 @@ import (
|
||||||
minio "github.com/minio/minio-go"
|
minio "github.com/minio/minio-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ObjectWithIcon is a minio object with an added icon
|
// objectWithIcon is a minio object with an added icon.
|
||||||
type ObjectWithIcon struct {
|
type objectWithIcon struct {
|
||||||
minio.ObjectInfo
|
minio.ObjectInfo
|
||||||
Icon string
|
Icon string
|
||||||
}
|
}
|
||||||
|
|
||||||
// BucketPage defines the details page of a bucket
|
// bucketPage defines the details page of a bucket.
|
||||||
type BucketPage struct {
|
type bucketPage struct {
|
||||||
BucketName string
|
BucketName string
|
||||||
Objects []ObjectWithIcon
|
Objects []objectWithIcon
|
||||||
}
|
}
|
||||||
|
|
||||||
// BucketViewHandler shows the details page of a bucket
|
// BucketViewHandler shows the details page of a bucket.
|
||||||
func BucketViewHandler(s3 S3Client) http.Handler {
|
func BucketViewHandler(s3 S3) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
bucketName := mux.Vars(r)["bucketName"]
|
bucketName := mux.Vars(r)["bucketName"]
|
||||||
var objs []ObjectWithIcon
|
var objs []objectWithIcon
|
||||||
|
|
||||||
l := path.Join(tmplDirectory, "layout.html.tmpl")
|
l := path.Join(tmplDirectory, "layout.html.tmpl")
|
||||||
p := path.Join(tmplDirectory, "bucket.html.tmpl")
|
p := path.Join(tmplDirectory, "bucket.html.tmpl")
|
||||||
|
@ -49,16 +49,16 @@ func BucketViewHandler(s3 S3Client) http.Handler {
|
||||||
handleHTTPError(w, code, object.Err)
|
handleHTTPError(w, code, object.Err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
objectWithIcon := ObjectWithIcon{object, icon(object.Key)}
|
obj := objectWithIcon{object, icon(object.Key)}
|
||||||
objs = append(objs, objectWithIcon)
|
objs = append(objs, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketPage := BucketPage{
|
page := bucketPage{
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
Objects: objs,
|
Objects: objs,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = t.ExecuteTemplate(w, "layout", bucketPage)
|
err = t.ExecuteTemplate(w, "layout", page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleHTTPError(w, http.StatusInternalServerError, err)
|
handleHTTPError(w, http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
|
@ -66,7 +66,7 @@ func BucketViewHandler(s3 S3Client) http.Handler {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// icon returns an icon for a file type
|
// icon returns an icon for a file type.
|
||||||
func icon(fileName string) string {
|
func icon(fileName string) string {
|
||||||
e := path.Ext(fileName)
|
e := path.Ext(fileName)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package s3manager_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -9,6 +9,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/mastertinner/s3manager"
|
||||||
minio "github.com/minio/minio-go"
|
minio "github.com/minio/minio-go"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
@ -16,14 +17,14 @@ import (
|
||||||
func TestBucketViewHandler(t *testing.T) {
|
func TestBucketViewHandler(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
tests := map[string]struct {
|
cases := map[string]struct {
|
||||||
s3 S3Client
|
s3 s3manager.S3
|
||||||
bucketName string
|
bucketName string
|
||||||
expectedStatusCode int
|
expectedStatusCode int
|
||||||
expectedBodyContains string
|
expectedBodyContains string
|
||||||
}{
|
}{
|
||||||
"success (empty bucket)": {
|
"success (empty bucket)": {
|
||||||
s3: &S3ClientMock{
|
s3: &s3Mock{
|
||||||
Buckets: []minio.BucketInfo{
|
Buckets: []minio.BucketInfo{
|
||||||
{Name: "testBucket"},
|
{Name: "testBucket"},
|
||||||
},
|
},
|
||||||
|
@ -33,7 +34,7 @@ func TestBucketViewHandler(t *testing.T) {
|
||||||
expectedBodyContains: "No objects in",
|
expectedBodyContains: "No objects in",
|
||||||
},
|
},
|
||||||
"success (with file)": {
|
"success (with file)": {
|
||||||
s3: &S3ClientMock{
|
s3: &s3Mock{
|
||||||
Buckets: []minio.BucketInfo{
|
Buckets: []minio.BucketInfo{
|
||||||
{Name: "testBucket"},
|
{Name: "testBucket"},
|
||||||
},
|
},
|
||||||
|
@ -46,7 +47,7 @@ func TestBucketViewHandler(t *testing.T) {
|
||||||
expectedBodyContains: "testBucket",
|
expectedBodyContains: "testBucket",
|
||||||
},
|
},
|
||||||
"success (archive)": {
|
"success (archive)": {
|
||||||
s3: &S3ClientMock{
|
s3: &s3Mock{
|
||||||
Buckets: []minio.BucketInfo{
|
Buckets: []minio.BucketInfo{
|
||||||
{Name: "testBucket"},
|
{Name: "testBucket"},
|
||||||
},
|
},
|
||||||
|
@ -59,7 +60,7 @@ func TestBucketViewHandler(t *testing.T) {
|
||||||
expectedBodyContains: "archive",
|
expectedBodyContains: "archive",
|
||||||
},
|
},
|
||||||
"success (image)": {
|
"success (image)": {
|
||||||
s3: &S3ClientMock{
|
s3: &s3Mock{
|
||||||
Buckets: []minio.BucketInfo{
|
Buckets: []minio.BucketInfo{
|
||||||
{Name: "testBucket"},
|
{Name: "testBucket"},
|
||||||
},
|
},
|
||||||
|
@ -72,7 +73,7 @@ func TestBucketViewHandler(t *testing.T) {
|
||||||
expectedBodyContains: "photo",
|
expectedBodyContains: "photo",
|
||||||
},
|
},
|
||||||
"success (sound)": {
|
"success (sound)": {
|
||||||
s3: &S3ClientMock{
|
s3: &s3Mock{
|
||||||
Buckets: []minio.BucketInfo{
|
Buckets: []minio.BucketInfo{
|
||||||
{Name: "testBucket"},
|
{Name: "testBucket"},
|
||||||
},
|
},
|
||||||
|
@ -85,13 +86,13 @@ func TestBucketViewHandler(t *testing.T) {
|
||||||
expectedBodyContains: "music_note",
|
expectedBodyContains: "music_note",
|
||||||
},
|
},
|
||||||
"bucket doesn't exist": {
|
"bucket doesn't exist": {
|
||||||
s3: &S3ClientMock{},
|
s3: &s3Mock{},
|
||||||
bucketName: "testBucket",
|
bucketName: "testBucket",
|
||||||
expectedStatusCode: http.StatusNotFound,
|
expectedStatusCode: http.StatusNotFound,
|
||||||
expectedBodyContains: http.StatusText(http.StatusNotFound),
|
expectedBodyContains: http.StatusText(http.StatusNotFound),
|
||||||
},
|
},
|
||||||
"s3 error": {
|
"s3 error": {
|
||||||
s3: &S3ClientMock{
|
s3: &s3Mock{
|
||||||
Err: errors.New("mocked S3 error"),
|
Err: errors.New("mocked S3 error"),
|
||||||
},
|
},
|
||||||
bucketName: "testBucket",
|
bucketName: "testBucket",
|
||||||
|
@ -100,12 +101,12 @@ func TestBucketViewHandler(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for tcID, tc := range tests {
|
for tcID, tc := range cases {
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.
|
r.
|
||||||
Methods(http.MethodGet).
|
Methods(http.MethodGet).
|
||||||
Path("/buckets/{bucketName}").
|
Path("/buckets/{bucketName}").
|
||||||
Handler(BucketViewHandler(tc.s3))
|
Handler(s3manager.BucketViewHandler(tc.s3))
|
||||||
|
|
||||||
ts := httptest.NewServer(r)
|
ts := httptest.NewServer(r)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package s3manager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
|
@ -6,8 +6,8 @@ import (
|
||||||
"path"
|
"path"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BucketsViewHandler shows all buckets
|
// BucketsViewHandler renders all buckets on an HTML page.
|
||||||
func BucketsViewHandler(s3 S3Client) http.Handler {
|
func BucketsViewHandler(s3 S3) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
l := path.Join(tmplDirectory, "layout.html.tmpl")
|
l := path.Join(tmplDirectory, "layout.html.tmpl")
|
||||||
p := path.Join(tmplDirectory, "buckets.html.tmpl")
|
p := path.Join(tmplDirectory, "buckets.html.tmpl")
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package s3manager_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -6,6 +6,7 @@ import (
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mastertinner/s3manager"
|
||||||
minio "github.com/minio/minio-go"
|
minio "github.com/minio/minio-go"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
@ -13,13 +14,13 @@ import (
|
||||||
func TestBucketsViewHandler(t *testing.T) {
|
func TestBucketsViewHandler(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
tests := map[string]struct {
|
cases := map[string]struct {
|
||||||
s3 S3Client
|
s3 s3manager.S3
|
||||||
expectedStatusCode int
|
expectedStatusCode int
|
||||||
expectedBodyContains string
|
expectedBodyContains string
|
||||||
}{
|
}{
|
||||||
"success": {
|
"success": {
|
||||||
s3: &S3ClientMock{
|
s3: &s3Mock{
|
||||||
Buckets: []minio.BucketInfo{
|
Buckets: []minio.BucketInfo{
|
||||||
{Name: "testBucket"},
|
{Name: "testBucket"},
|
||||||
},
|
},
|
||||||
|
@ -28,12 +29,12 @@ func TestBucketsViewHandler(t *testing.T) {
|
||||||
expectedBodyContains: "testBucket",
|
expectedBodyContains: "testBucket",
|
||||||
},
|
},
|
||||||
"success (bo buckets)": {
|
"success (bo buckets)": {
|
||||||
s3: &S3ClientMock{},
|
s3: &s3Mock{},
|
||||||
expectedStatusCode: http.StatusOK,
|
expectedStatusCode: http.StatusOK,
|
||||||
expectedBodyContains: "No buckets yet",
|
expectedBodyContains: "No buckets yet",
|
||||||
},
|
},
|
||||||
"s3 error": {
|
"s3 error": {
|
||||||
s3: &S3ClientMock{
|
s3: &s3Mock{
|
||||||
Err: errors.New("mocked S3 error"),
|
Err: errors.New("mocked S3 error"),
|
||||||
},
|
},
|
||||||
expectedStatusCode: http.StatusInternalServerError,
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
|
@ -41,12 +42,12 @@ func TestBucketsViewHandler(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for tcID, tc := range tests {
|
for tcID, tc := range cases {
|
||||||
req, err := http.NewRequest(http.MethodGet, "/buckets", nil)
|
req, err := http.NewRequest(http.MethodGet, "/buckets", nil)
|
||||||
assert.NoError(err, tcID)
|
assert.NoError(err, tcID)
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
handler := BucketsViewHandler(tc.s3)
|
handler := s3manager.BucketsViewHandler(tc.s3)
|
||||||
|
|
||||||
handler.ServeHTTP(rr, req)
|
handler.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
|
115
cmd/s3manager/main.go
Normal file
115
cmd/s3manager/main.go
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/mastertinner/adapters"
|
||||||
|
"github.com/mastertinner/adapters/logging"
|
||||||
|
"github.com/mastertinner/s3manager"
|
||||||
|
minio "github.com/minio/minio-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var (
|
||||||
|
port = flag.String("port", "8080", "the port the app should listen on")
|
||||||
|
endpoint = flag.String("endpoint", "s3.amazonaws.com", "the s3 endpoint to use")
|
||||||
|
accessKeyID = flag.String("access-key-id", "", "your s3 access key ID")
|
||||||
|
secretAccessKey = flag.String("secret-access-key", "", "your s3 secret access key")
|
||||||
|
v2Signing = flag.Bool("v2-signing", false, "set this flag if your S3 provider still uses V2 signing")
|
||||||
|
)
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *accessKeyID == "" || *secretAccessKey == "" {
|
||||||
|
flag.Usage()
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up S3 client
|
||||||
|
var s3 *minio.Client
|
||||||
|
var err error
|
||||||
|
if *v2Signing {
|
||||||
|
s3, err = minio.NewV2(*endpoint, *accessKeyID, *secretAccessKey, true)
|
||||||
|
} else {
|
||||||
|
s3, err = minio.New(*endpoint, *accessKeyID, *secretAccessKey, true)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up logger
|
||||||
|
logger := log.New(os.Stdout, "", log.Ldate|log.Ltime)
|
||||||
|
|
||||||
|
// Set up router
|
||||||
|
r := mux.NewRouter().StrictSlash(true)
|
||||||
|
r.
|
||||||
|
Methods(http.MethodGet).
|
||||||
|
Path("/").
|
||||||
|
Handler(adapters.Adapt(
|
||||||
|
http.RedirectHandler("/buckets", http.StatusPermanentRedirect),
|
||||||
|
logging.Handler(logger),
|
||||||
|
))
|
||||||
|
r.
|
||||||
|
Methods(http.MethodGet).
|
||||||
|
Path("/buckets").
|
||||||
|
Handler(adapters.Adapt(
|
||||||
|
s3manager.BucketsViewHandler(s3),
|
||||||
|
logging.Handler(logger),
|
||||||
|
))
|
||||||
|
r.
|
||||||
|
Methods(http.MethodGet).
|
||||||
|
Path("/buckets/{bucketName}").
|
||||||
|
Handler(adapters.Adapt(
|
||||||
|
s3manager.BucketViewHandler(s3),
|
||||||
|
logging.Handler(logger),
|
||||||
|
))
|
||||||
|
r.
|
||||||
|
Methods(http.MethodPost).
|
||||||
|
Path("/api/buckets").
|
||||||
|
Handler(adapters.Adapt(
|
||||||
|
s3manager.CreateBucketHandler(s3),
|
||||||
|
logging.Handler(logger),
|
||||||
|
))
|
||||||
|
r.
|
||||||
|
Methods(http.MethodDelete).
|
||||||
|
Path("/api/buckets/{bucketName}").
|
||||||
|
Handler(adapters.Adapt(
|
||||||
|
s3manager.DeleteBucketHandler(s3),
|
||||||
|
logging.Handler(logger),
|
||||||
|
))
|
||||||
|
r.
|
||||||
|
Methods(http.MethodPost).
|
||||||
|
Headers(s3manager.HeaderContentType, s3manager.ContentTypeJSON).
|
||||||
|
Path("/api/buckets/{bucketName}/objects").
|
||||||
|
Handler(adapters.Adapt(
|
||||||
|
s3manager.CopyObjectHandler(s3),
|
||||||
|
logging.Handler(logger),
|
||||||
|
))
|
||||||
|
r.
|
||||||
|
Methods(http.MethodPost).
|
||||||
|
HeadersRegexp(s3manager.HeaderContentType, s3manager.ContentTypeMultipartForm).
|
||||||
|
Path("/api/buckets/{bucketName}/objects").
|
||||||
|
Handler(adapters.Adapt(
|
||||||
|
s3manager.CreateObjectHandler(s3),
|
||||||
|
logging.Handler(logger),
|
||||||
|
))
|
||||||
|
r.
|
||||||
|
Methods(http.MethodGet).
|
||||||
|
Path("/api/buckets/{bucketName}/objects/{objectName}").
|
||||||
|
Handler(adapters.Adapt(
|
||||||
|
s3manager.GetObjectHandler(s3),
|
||||||
|
logging.Handler(logger),
|
||||||
|
))
|
||||||
|
r.
|
||||||
|
Methods(http.MethodDelete).
|
||||||
|
Path("/api/buckets/{bucketName}/objects/{objectName}").
|
||||||
|
Handler(adapters.Adapt(
|
||||||
|
s3manager.DeleteObjectHandler(s3),
|
||||||
|
logging.Handler(logger),
|
||||||
|
))
|
||||||
|
|
||||||
|
log.Fatal(http.ListenAndServe(":"+*port, r))
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package s3manager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -8,18 +8,18 @@ import (
|
||||||
minio "github.com/minio/minio-go"
|
minio "github.com/minio/minio-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CopyObjectInfo is the information about an object to copy
|
// copyObjectInfo is the information about an object to copy.
|
||||||
type CopyObjectInfo struct {
|
type copyObjectInfo struct {
|
||||||
BucketName string `json:"bucketName"`
|
BucketName string `json:"bucketName"`
|
||||||
ObjectName string `json:"objectName"`
|
ObjectName string `json:"objectName"`
|
||||||
SourceBucketName string `json:"sourceBucketName"`
|
SourceBucketName string `json:"sourceBucketName"`
|
||||||
SourceObjectName string `json:"sourceObjectName"`
|
SourceObjectName string `json:"sourceObjectName"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyObjectHandler allows to copy an existing object
|
// CopyObjectHandler copies an existing object under a new name.
|
||||||
func CopyObjectHandler(s3 S3Client) http.Handler {
|
func CopyObjectHandler(s3 S3) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
var copy CopyObjectInfo
|
var copy copyObjectInfo
|
||||||
|
|
||||||
err := json.NewDecoder(r.Body).Decode(©)
|
err := json.NewDecoder(r.Body).Decode(©)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -35,7 +35,7 @@ func CopyObjectHandler(s3 S3Client) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set(headerContentType, contentTypeJSON)
|
w.Header().Set(HeaderContentType, ContentTypeJSON)
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
|
||||||
err = json.NewEncoder(w).Encode(copy)
|
err = json.NewEncoder(w).Encode(copy)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package s3manager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -7,8 +7,8 @@ import (
|
||||||
minio "github.com/minio/minio-go"
|
minio "github.com/minio/minio-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreateBucketHandler creates a new bucket
|
// CreateBucketHandler creates a new bucket.
|
||||||
func CreateBucketHandler(s3 S3Client) http.Handler {
|
func CreateBucketHandler(s3 S3) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
var bucket minio.BucketInfo
|
var bucket minio.BucketInfo
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ func CreateBucketHandler(s3 S3Client) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set(headerContentType, contentTypeJSON)
|
w.Header().Set(HeaderContentType, ContentTypeJSON)
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
|
||||||
err = json.NewEncoder(w).Encode(bucket)
|
err = json.NewEncoder(w).Encode(bucket)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package s3manager_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -7,38 +7,39 @@ import (
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mastertinner/s3manager"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCreateBucketHandler(t *testing.T) {
|
func TestCreateBucketHandler(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
tests := map[string]struct {
|
cases := map[string]struct {
|
||||||
s3 S3Client
|
s3 s3manager.S3
|
||||||
body string
|
body string
|
||||||
expectedStatusCode int
|
expectedStatusCode int
|
||||||
expectedBodyContains string
|
expectedBodyContains string
|
||||||
}{
|
}{
|
||||||
"success": {
|
"success": {
|
||||||
s3: &S3ClientMock{},
|
s3: &s3Mock{},
|
||||||
body: "{\"name\":\"myBucket\"}",
|
body: "{\"name\":\"myBucket\"}",
|
||||||
expectedStatusCode: http.StatusCreated,
|
expectedStatusCode: http.StatusCreated,
|
||||||
expectedBodyContains: "{\"name\":\"myBucket\",\"creationDate\":\"0001-01-01T00:00:00Z\"}\n",
|
expectedBodyContains: "{\"name\":\"myBucket\",\"creationDate\":\"0001-01-01T00:00:00Z\"}\n",
|
||||||
},
|
},
|
||||||
"empty request": {
|
"empty request": {
|
||||||
s3: &S3ClientMock{},
|
s3: &s3Mock{},
|
||||||
body: "",
|
body: "",
|
||||||
expectedStatusCode: http.StatusUnprocessableEntity,
|
expectedStatusCode: http.StatusUnprocessableEntity,
|
||||||
expectedBodyContains: http.StatusText(http.StatusUnprocessableEntity),
|
expectedBodyContains: http.StatusText(http.StatusUnprocessableEntity),
|
||||||
},
|
},
|
||||||
"malformed request": {
|
"malformed request": {
|
||||||
s3: &S3ClientMock{},
|
s3: &s3Mock{},
|
||||||
body: "}",
|
body: "}",
|
||||||
expectedStatusCode: http.StatusUnprocessableEntity,
|
expectedStatusCode: http.StatusUnprocessableEntity,
|
||||||
expectedBodyContains: http.StatusText(http.StatusUnprocessableEntity),
|
expectedBodyContains: http.StatusText(http.StatusUnprocessableEntity),
|
||||||
},
|
},
|
||||||
"s3 error": {
|
"s3 error": {
|
||||||
s3: &S3ClientMock{
|
s3: &s3Mock{
|
||||||
Err: errors.New("mocked S3 error"),
|
Err: errors.New("mocked S3 error"),
|
||||||
},
|
},
|
||||||
body: "{\"name\":\"myBucket\"}",
|
body: "{\"name\":\"myBucket\"}",
|
||||||
|
@ -47,12 +48,12 @@ func TestCreateBucketHandler(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for tcID, tc := range tests {
|
for tcID, tc := range cases {
|
||||||
req, err := http.NewRequest(http.MethodPost, "/api/buckets", bytes.NewBufferString(tc.body))
|
req, err := http.NewRequest(http.MethodPost, "/api/buckets", bytes.NewBufferString(tc.body))
|
||||||
assert.NoError(err, tcID)
|
assert.NoError(err, tcID)
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
handler := CreateBucketHandler(tc.s3)
|
handler := s3manager.CreateBucketHandler(tc.s3)
|
||||||
|
|
||||||
handler.ServeHTTP(rr, req)
|
handler.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package s3manager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
@ -7,8 +7,8 @@ import (
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreateObjectHandler allows to upload a new object
|
// CreateObjectHandler uploads a new object.
|
||||||
func CreateObjectHandler(s3 S3Client) http.Handler {
|
func CreateObjectHandler(s3 S3) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
err := r.ParseMultipartForm(32 << 20)
|
err := r.ParseMultipartForm(32 << 20)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package s3manager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -6,8 +6,8 @@ import (
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DeleteBucketHandler deletes a bucket
|
// DeleteBucketHandler deletes a bucket.
|
||||||
func DeleteBucketHandler(s3 S3Client) http.Handler {
|
func DeleteBucketHandler(s3 S3) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
bucketName := mux.Vars(r)["bucketName"]
|
bucketName := mux.Vars(r)["bucketName"]
|
||||||
err := s3.RemoveBucket(bucketName)
|
err := s3.RemoveBucket(bucketName)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package s3manager_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -6,24 +6,25 @@ import (
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mastertinner/s3manager"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDeleteBucketHandler(t *testing.T) {
|
func TestDeleteBucketHandler(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
tests := map[string]struct {
|
cases := map[string]struct {
|
||||||
s3 S3Client
|
s3 s3manager.S3
|
||||||
expectedStatusCode int
|
expectedStatusCode int
|
||||||
expectedBodyContains string
|
expectedBodyContains string
|
||||||
}{
|
}{
|
||||||
"success": {
|
"success": {
|
||||||
s3: &S3ClientMock{},
|
s3: &s3Mock{},
|
||||||
expectedStatusCode: http.StatusNoContent,
|
expectedStatusCode: http.StatusNoContent,
|
||||||
expectedBodyContains: "",
|
expectedBodyContains: "",
|
||||||
},
|
},
|
||||||
"s3 error": {
|
"s3 error": {
|
||||||
s3: &S3ClientMock{
|
s3: &s3Mock{
|
||||||
Err: errors.New("mocked S3 error"),
|
Err: errors.New("mocked S3 error"),
|
||||||
},
|
},
|
||||||
expectedStatusCode: http.StatusInternalServerError,
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
|
@ -31,12 +32,12 @@ func TestDeleteBucketHandler(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for tcID, tc := range tests {
|
for tcID, tc := range cases {
|
||||||
req, err := http.NewRequest(http.MethodDelete, "/api/buckets/bucketName", nil)
|
req, err := http.NewRequest(http.MethodDelete, "/api/buckets/bucketName", nil)
|
||||||
assert.NoError(err, tcID)
|
assert.NoError(err, tcID)
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
handler := DeleteBucketHandler(tc.s3)
|
handler := s3manager.DeleteBucketHandler(tc.s3)
|
||||||
|
|
||||||
handler.ServeHTTP(rr, req)
|
handler.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package s3manager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -6,8 +6,8 @@ import (
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DeleteObjectHandler deletes an object
|
// DeleteObjectHandler deletes an object.
|
||||||
func DeleteObjectHandler(s3 S3Client) http.Handler {
|
func DeleteObjectHandler(s3 S3) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucketName := vars["bucketName"]
|
bucketName := vars["bucketName"]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package s3manager_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -6,24 +6,25 @@ import (
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mastertinner/s3manager"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDeleteObjectHandler(t *testing.T) {
|
func TestDeleteObjectHandler(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
tests := map[string]struct {
|
cases := map[string]struct {
|
||||||
s3 S3Client
|
s3 s3manager.S3
|
||||||
expectedStatusCode int
|
expectedStatusCode int
|
||||||
expectedBodyContains string
|
expectedBodyContains string
|
||||||
}{
|
}{
|
||||||
"success": {
|
"success": {
|
||||||
s3: &S3ClientMock{},
|
s3: &s3Mock{},
|
||||||
expectedStatusCode: http.StatusNoContent,
|
expectedStatusCode: http.StatusNoContent,
|
||||||
expectedBodyContains: "",
|
expectedBodyContains: "",
|
||||||
},
|
},
|
||||||
"s3 error": {
|
"s3 error": {
|
||||||
s3: &S3ClientMock{
|
s3: &s3Mock{
|
||||||
Err: errors.New("mocked S3 error"),
|
Err: errors.New("mocked S3 error"),
|
||||||
},
|
},
|
||||||
expectedStatusCode: http.StatusInternalServerError,
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
|
@ -31,12 +32,12 @@ func TestDeleteObjectHandler(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for tcID, tc := range tests {
|
for tcID, tc := range cases {
|
||||||
req, err := http.NewRequest(http.MethodDelete, "/api/buckets/bucketName/objects/objectName", nil)
|
req, err := http.NewRequest(http.MethodDelete, "/api/buckets/bucketName/objects/objectName", nil)
|
||||||
assert.NoError(err, tcID)
|
assert.NoError(err, tcID)
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
handler := DeleteObjectHandler(tc.s3)
|
handler := s3manager.DeleteObjectHandler(tc.s3)
|
||||||
|
|
||||||
handler.ServeHTTP(rr, req)
|
handler.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
version: '2'
|
version: '2'
|
||||||
services:
|
services:
|
||||||
|
|
||||||
s3-manager:
|
s3manager:
|
||||||
build: .
|
build: .
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
environment:
|
environment:
|
||||||
|
- S3_ENDPOINT=s3.amazonaws.com
|
||||||
- S3_ACCESS_KEY_ID=xxx
|
- S3_ACCESS_KEY_ID=xxx
|
||||||
- S3_SECRET_ACCESS_KEY=xxx
|
- S3_SECRET_ACCESS_KEY=xxx
|
||||||
|
|
30
entrypoint-cf.sh
Executable file
30
entrypoint-cf.sh
Executable file
|
@ -0,0 +1,30 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e -u
|
||||||
|
|
||||||
|
if [ -z "${PORT}" ]; then
|
||||||
|
echo "Error: No PORT found" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -z "${VCAP_SERVICES}" ]; then
|
||||||
|
echo "Error: No VCAP_SERVICES found" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# S3
|
||||||
|
s3_credentials="$(echo "${VCAP_SERVICES}" | jq -r '.["dynstrg"][0].credentials // ""')"
|
||||||
|
if [ -z "${s3_credentials}" ]; then
|
||||||
|
echo "Error: Please bind an S3 service" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
s3_endpoint="$(echo "${s3_credentials}" | jq -r '.accessHost // ""')"
|
||||||
|
s3_access_key_id="$(echo "${s3_credentials}" | jq -r '.accessKey // ""')"
|
||||||
|
s3_secret_access_key="$(echo "${s3_credentials}" | jq -r '.sharedSecret // ""')"
|
||||||
|
|
||||||
|
# Run binary
|
||||||
|
./s3manager \
|
||||||
|
-port "${PORT}" \
|
||||||
|
-endpoint "${s3_endpoint}" \
|
||||||
|
-access-key-id "${s3_access_key_id}" \
|
||||||
|
-secret-access-key "${s3_secret_access_key}" \
|
||||||
|
-v2-signing
|
|
@ -1,17 +1,17 @@
|
||||||
package main
|
package s3manager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Common error messages within the app
|
// Error codes that may be returned from an S3 backend.
|
||||||
const (
|
const (
|
||||||
ErrBucketDoesNotExist = "The specified bucket does not exist."
|
ErrBucketDoesNotExist = "The specified bucket does not exist."
|
||||||
ErrKeyDoesNotExist = "The specified key does not exist."
|
ErrKeyDoesNotExist = "The specified key does not exist."
|
||||||
)
|
)
|
||||||
|
|
||||||
// handleHTTPError handles HTTP errors
|
// handleHTTPError handles HTTP errors.
|
||||||
func handleHTTPError(w http.ResponseWriter, statusCode int, err error) {
|
func handleHTTPError(w http.ResponseWriter, statusCode int, err error) {
|
||||||
msg := http.StatusText(statusCode)
|
msg := http.StatusText(statusCode)
|
||||||
http.Error(w, msg, statusCode)
|
http.Error(w, msg, statusCode)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package s3manager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -8,8 +8,8 @@ import (
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetObjectHandler downloads an object to the client
|
// GetObjectHandler downloads an object to the client.
|
||||||
func GetObjectHandler(s3 S3Client) http.Handler {
|
func GetObjectHandler(s3 S3) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucketName := vars["bucketName"]
|
bucketName := vars["bucketName"]
|
||||||
|
@ -22,7 +22,7 @@ func GetObjectHandler(s3 S3Client) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set(headerContentDisposition, fmt.Sprintf("attachment; filename=\"%s\"", objectName))
|
w.Header().Set(headerContentDisposition, fmt.Sprintf("attachment; filename=\"%s\"", objectName))
|
||||||
w.Header().Set(headerContentType, contentTypeOctetStream)
|
w.Header().Set(HeaderContentType, contentTypeOctetStream)
|
||||||
|
|
||||||
_, err = io.Copy(w, object)
|
_, err = io.Copy(w, object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package s3manager_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -9,21 +9,22 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/mastertinner/s3manager"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetObjectHandler(t *testing.T) {
|
func TestGetObjectHandler(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
tests := map[string]struct {
|
cases := map[string]struct {
|
||||||
s3 S3Client
|
s3 s3manager.S3
|
||||||
bucketName string
|
bucketName string
|
||||||
objectName string
|
objectName string
|
||||||
expectedStatusCode int
|
expectedStatusCode int
|
||||||
expectedBodyContains string
|
expectedBodyContains string
|
||||||
}{
|
}{
|
||||||
"s3 error": {
|
"s3 error": {
|
||||||
s3: &S3ClientMock{
|
s3: &s3Mock{
|
||||||
Err: errors.New("mocked S3 error"),
|
Err: errors.New("mocked S3 error"),
|
||||||
},
|
},
|
||||||
bucketName: "testBucket",
|
bucketName: "testBucket",
|
||||||
|
@ -33,12 +34,12 @@ func TestGetObjectHandler(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for tcID, tc := range tests {
|
for tcID, tc := range cases {
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.
|
r.
|
||||||
Methods(http.MethodGet).
|
Methods(http.MethodGet).
|
||||||
Path("/buckets/{bucketName}/objects/{objectName}").
|
Path("/buckets/{bucketName}/objects/{objectName}").
|
||||||
Handler(GetObjectHandler(tc.s3))
|
Handler(s3manager.GetObjectHandler(tc.s3))
|
||||||
|
|
||||||
ts := httptest.NewServer(r)
|
ts := httptest.NewServer(r)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
31
glide.lock
generated
31
glide.lock
generated
|
@ -1,31 +0,0 @@
|
||||||
hash: 9b88c8ce183463a407bdb87f79b6b857d8343a7bb5ce4723223c539c654d6d6d
|
|
||||||
updated: 2017-05-03T10:21:06.902434164+02:00
|
|
||||||
imports:
|
|
||||||
- name: github.com/gorilla/context
|
|
||||||
version: 1ea25387ff6f684839d82767c1733ff4d4d15d0a
|
|
||||||
- name: github.com/gorilla/mux
|
|
||||||
version: 392c28fe23e1c45ddba891b0320b3b5df220beea
|
|
||||||
- name: github.com/mastertinner/adapters
|
|
||||||
version: b1cb3d82590975623f722e92951658939fdfc328
|
|
||||||
subpackages:
|
|
||||||
- logging
|
|
||||||
- name: github.com/minio/minio-go
|
|
||||||
version: dcaae9ec4d0b0a81d17f22f6d7a186491f6a55ec
|
|
||||||
subpackages:
|
|
||||||
- pkg/policy
|
|
||||||
- pkg/s3signer
|
|
||||||
- pkg/s3utils
|
|
||||||
- pkg/set
|
|
||||||
- name: github.com/stretchr/testify
|
|
||||||
version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0
|
|
||||||
subpackages:
|
|
||||||
- assert
|
|
||||||
testImports:
|
|
||||||
- name: github.com/davecgh/go-spew
|
|
||||||
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
|
|
||||||
subpackages:
|
|
||||||
- spew
|
|
||||||
- name: github.com/pmezard/go-difflib
|
|
||||||
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
|
||||||
subpackages:
|
|
||||||
- difflib
|
|
13
glide.yaml
13
glide.yaml
|
@ -1,13 +0,0 @@
|
||||||
package: github.com/mastertinner/s3-manager
|
|
||||||
import:
|
|
||||||
- package: github.com/minio/minio-go
|
|
||||||
version: ^2.0.2
|
|
||||||
- package: github.com/gorilla/mux
|
|
||||||
version: ^1.1.0
|
|
||||||
- package: github.com/stretchr/testify
|
|
||||||
version: ^1.1.4
|
|
||||||
subpackages:
|
|
||||||
- assert
|
|
||||||
- package: github.com/mastertinner/adapters
|
|
||||||
subpackages:
|
|
||||||
- logging
|
|
106
main.go
106
main.go
|
@ -1,106 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/mastertinner/adapters"
|
|
||||||
"github.com/mastertinner/adapters/logging"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
tmplDirectory = "templates"
|
|
||||||
headerContentType = "Content-Type"
|
|
||||||
headerContentDisposition = "Content-Disposition"
|
|
||||||
contentTypeJSON = "application/json"
|
|
||||||
contentTypeMultipartForm = "multipart/form-data"
|
|
||||||
contentTypeOctetStream = "application/octet-stream"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
s3, err := newMinioClient()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln("error creating s3 client:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger := log.New(os.Stdout, "", log.Ldate|log.Ltime)
|
|
||||||
|
|
||||||
r := mux.NewRouter().StrictSlash(true)
|
|
||||||
r.
|
|
||||||
Methods(http.MethodGet).
|
|
||||||
Path("/").
|
|
||||||
Handler(adapters.Adapt(
|
|
||||||
http.RedirectHandler("/buckets", http.StatusPermanentRedirect),
|
|
||||||
logging.Handler(logger),
|
|
||||||
))
|
|
||||||
r.
|
|
||||||
Methods(http.MethodGet).
|
|
||||||
Path("/buckets").
|
|
||||||
Handler(adapters.Adapt(
|
|
||||||
BucketsViewHandler(s3),
|
|
||||||
logging.Handler(logger),
|
|
||||||
))
|
|
||||||
r.
|
|
||||||
Methods(http.MethodGet).
|
|
||||||
Path("/buckets/{bucketName}").
|
|
||||||
Handler(adapters.Adapt(
|
|
||||||
BucketViewHandler(s3),
|
|
||||||
logging.Handler(logger),
|
|
||||||
))
|
|
||||||
|
|
||||||
api := r.PathPrefix("/api").Subrouter().StrictSlash(true)
|
|
||||||
|
|
||||||
br := api.PathPrefix("/buckets").Subrouter().StrictSlash(true)
|
|
||||||
br.
|
|
||||||
Methods(http.MethodPost).
|
|
||||||
Path("").
|
|
||||||
Handler(adapters.Adapt(
|
|
||||||
CreateBucketHandler(s3),
|
|
||||||
logging.Handler(logger),
|
|
||||||
))
|
|
||||||
br.
|
|
||||||
Methods(http.MethodDelete).
|
|
||||||
Path("/{bucketName}").
|
|
||||||
Handler(adapters.Adapt(
|
|
||||||
DeleteBucketHandler(s3),
|
|
||||||
logging.Handler(logger),
|
|
||||||
))
|
|
||||||
br.
|
|
||||||
Methods(http.MethodPost).
|
|
||||||
Headers(headerContentType, contentTypeJSON).
|
|
||||||
Path("/{bucketName}/objects").
|
|
||||||
Handler(adapters.Adapt(
|
|
||||||
CopyObjectHandler(s3),
|
|
||||||
logging.Handler(logger),
|
|
||||||
))
|
|
||||||
br.
|
|
||||||
Methods(http.MethodPost).
|
|
||||||
HeadersRegexp(headerContentType, contentTypeMultipartForm).
|
|
||||||
Path("/{bucketName}/objects").
|
|
||||||
Handler(adapters.Adapt(
|
|
||||||
CreateObjectHandler(s3),
|
|
||||||
logging.Handler(logger),
|
|
||||||
))
|
|
||||||
br.
|
|
||||||
Methods(http.MethodGet).
|
|
||||||
Path("/{bucketName}/objects/{objectName}").
|
|
||||||
Handler(adapters.Adapt(
|
|
||||||
GetObjectHandler(s3),
|
|
||||||
logging.Handler(logger),
|
|
||||||
))
|
|
||||||
br.
|
|
||||||
Methods(http.MethodDelete).
|
|
||||||
Path("/{bucketName}/objects/{objectName}").
|
|
||||||
Handler(adapters.Adapt(
|
|
||||||
DeleteObjectHandler(s3),
|
|
||||||
logging.Handler(logger),
|
|
||||||
))
|
|
||||||
|
|
||||||
port := os.Getenv("PORT")
|
|
||||||
if len(port) == 0 {
|
|
||||||
port = "8080"
|
|
||||||
}
|
|
||||||
log.Fatal(http.ListenAndServe(":"+port, r))
|
|
||||||
}
|
|
|
@ -3,8 +3,8 @@ applications:
|
||||||
- name: s3-manager
|
- name: s3-manager
|
||||||
host: s3-manager
|
host: s3-manager
|
||||||
memory: 64M
|
memory: 64M
|
||||||
buildpack: https://github.com/cloudfoundry/go-buildpack.git
|
buildpack: https://github.com/cloudfoundry/binary-buildpack.git
|
||||||
|
command: ./entrypoint-cf.sh
|
||||||
|
|
||||||
env:
|
services:
|
||||||
S3_ACCESS_KEY_ID: "xxx"
|
- wp-storage
|
||||||
S3_SECRET_ACCESS_KEY: "xxx"
|
|
||||||
|
|
37
minio.go
37
minio.go
|
@ -1,37 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/minio/minio-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
// newMinioClient creates a new Minio client
|
|
||||||
func newMinioClient() (*minio.Client, error) {
|
|
||||||
var err error
|
|
||||||
var c *minio.Client
|
|
||||||
|
|
||||||
s3Endpoint := os.Getenv("S3_ENDPOINT")
|
|
||||||
if s3Endpoint == "" {
|
|
||||||
s3Endpoint = "s3.amazonaws.com"
|
|
||||||
}
|
|
||||||
|
|
||||||
s3AccessKeyID := os.Getenv("S3_ACCESS_KEY_ID")
|
|
||||||
if s3AccessKeyID == "" {
|
|
||||||
return nil, errors.New("no S3_ACCESS_KEY_ID found")
|
|
||||||
}
|
|
||||||
|
|
||||||
s3SecretAccessKey := os.Getenv("S3_SECRET_ACCESS_KEY")
|
|
||||||
if s3SecretAccessKey == "" {
|
|
||||||
return nil, errors.New("no S3_SECRET_ACCESS_KEY found")
|
|
||||||
}
|
|
||||||
|
|
||||||
if os.Getenv("V2_SIGNING") == "true" {
|
|
||||||
c, err = minio.NewV2(s3Endpoint, s3AccessKeyID, s3SecretAccessKey, true)
|
|
||||||
} else {
|
|
||||||
c, err = minio.New(s3Endpoint, s3AccessKeyID, s3SecretAccessKey, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, err
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
minio "github.com/minio/minio-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
// S3ClientMock is a mocked S3 client
|
|
||||||
type S3ClientMock struct {
|
|
||||||
Buckets []minio.BucketInfo
|
|
||||||
Objects []minio.ObjectInfo
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyObject mocks minio.Client.CopyObject
|
|
||||||
func (s S3ClientMock) CopyObject(string, string, string, minio.CopyConditions) error {
|
|
||||||
return s.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetObject mocks minio.Client.GetObject
|
|
||||||
func (s S3ClientMock) GetObject(bucketName string, objectName string) (*minio.Object, error) {
|
|
||||||
if s.Err != nil {
|
|
||||||
return nil, s.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &minio.Object{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListBuckets mocks minio.Client.ListBuckets
|
|
||||||
func (s S3ClientMock) ListBuckets() ([]minio.BucketInfo, error) {
|
|
||||||
return s.Buckets, s.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListObjectsV2 mocks minio.Client.ListObjectsV2
|
|
||||||
func (s S3ClientMock) ListObjectsV2(name string, p string, r bool, d <-chan struct{}) <-chan minio.ObjectInfo {
|
|
||||||
// Add error if exists
|
|
||||||
if s.Err != nil {
|
|
||||||
s.Objects = append(s.Objects, minio.ObjectInfo{
|
|
||||||
Err: s.Err,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if bucket exists
|
|
||||||
found := false
|
|
||||||
for _, b := range s.Buckets {
|
|
||||||
if b.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
s.Objects = append(s.Objects, minio.ObjectInfo{
|
|
||||||
Err: errors.New("The specified bucket does not exist."),
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
objCh := make(chan minio.ObjectInfo, len(s.Objects))
|
|
||||||
defer close(objCh)
|
|
||||||
|
|
||||||
for _, obj := range s.Objects {
|
|
||||||
objCh <- obj
|
|
||||||
}
|
|
||||||
|
|
||||||
return objCh
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeBucket mocks minio.Client.MakeBucket
|
|
||||||
func (s S3ClientMock) MakeBucket(string, string) error {
|
|
||||||
return s.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutObject mocks minio.Client.PutObject
|
|
||||||
func (s S3ClientMock) PutObject(string, string, io.Reader, string) (int64, error) {
|
|
||||||
return 0, s.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveBucket mocks minio.Client.RemoveBucket
|
|
||||||
func (s S3ClientMock) RemoveBucket(string) error {
|
|
||||||
return s.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveObject mocks minio.Client.RemoveObject
|
|
||||||
func (s S3ClientMock) RemoveObject(string, string) error {
|
|
||||||
return s.Err
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package s3manager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
@ -6,8 +6,8 @@ import (
|
||||||
minio "github.com/minio/minio-go"
|
minio "github.com/minio/minio-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
// S3Client is a client to interact with S3 storage
|
// S3 is a client to interact with S3 storage.
|
||||||
type S3Client interface {
|
type S3 interface {
|
||||||
CopyObject(string, string, string, minio.CopyConditions) error
|
CopyObject(string, string, string, minio.CopyConditions) error
|
||||||
GetObject(string, string) (*minio.Object, error)
|
GetObject(string, string) (*minio.Object, error)
|
||||||
ListBuckets() ([]minio.BucketInfo, error)
|
ListBuckets() ([]minio.BucketInfo, error)
|
88
s3_test.go
Normal file
88
s3_test.go
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
package s3manager_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
minio "github.com/minio/minio-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// s3Mock is a mocked S3 client.
|
||||||
|
type s3Mock struct {
|
||||||
|
Buckets []minio.BucketInfo
|
||||||
|
Objects []minio.ObjectInfo
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyObject mocks minio.Client.CopyObject.
|
||||||
|
func (s *s3Mock) CopyObject(string, string, string, minio.CopyConditions) error {
|
||||||
|
return s.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetObject mocks minio.Client.GetObject.
|
||||||
|
func (s *s3Mock) GetObject(bucketName string, objectName string) (*minio.Object, error) {
|
||||||
|
if s.Err != nil {
|
||||||
|
return nil, s.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &minio.Object{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListBuckets mocks minio.Client.ListBuckets.
|
||||||
|
func (s *s3Mock) ListBuckets() ([]minio.BucketInfo, error) {
|
||||||
|
return s.Buckets, s.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListObjectsV2 mocks minio.Client.ListObjectsV2.
|
||||||
|
func (s *s3Mock) ListObjectsV2(name string, p string, r bool, d <-chan struct{}) <-chan minio.ObjectInfo {
|
||||||
|
// Add error if exists
|
||||||
|
if s.Err != nil {
|
||||||
|
s.Objects = append(s.Objects, minio.ObjectInfo{
|
||||||
|
Err: s.Err,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if bucket exists
|
||||||
|
found := false
|
||||||
|
for _, b := range s.Buckets {
|
||||||
|
if b.Name == name {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
s.Objects = append(s.Objects, minio.ObjectInfo{
|
||||||
|
Err: errors.New("The specified bucket does not exist."),
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
objCh := make(chan minio.ObjectInfo, len(s.Objects))
|
||||||
|
defer close(objCh)
|
||||||
|
|
||||||
|
for _, obj := range s.Objects {
|
||||||
|
objCh <- obj
|
||||||
|
}
|
||||||
|
|
||||||
|
return objCh
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeBucket mocks minio.Client.MakeBucket.
|
||||||
|
func (s *s3Mock) MakeBucket(string, string) error {
|
||||||
|
return s.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutObject mocks minio.Client.PutObject.
|
||||||
|
func (s *s3Mock) PutObject(string, string, io.Reader, string) (int64, error) {
|
||||||
|
return 0, s.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveBucket mocks minio.Client.RemoveBucket.
|
||||||
|
func (s *s3Mock) RemoveBucket(string) error {
|
||||||
|
return s.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveObject mocks minio.Client.RemoveObject.
|
||||||
|
func (s *s3Mock) RemoveObject(string, string) error {
|
||||||
|
return s.Err
|
||||||
|
}
|
12
s3manager.go
Normal file
12
s3manager.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// Package s3manager allows to interact with an S3 compatible storage.
|
||||||
|
package s3manager
|
||||||
|
|
||||||
|
// Constants commonly used throughout the application.
|
||||||
|
const (
|
||||||
|
HeaderContentType = "Content-Type"
|
||||||
|
ContentTypeJSON = "application/json"
|
||||||
|
ContentTypeMultipartForm = "multipart/form-data"
|
||||||
|
tmplDirectory = "templates"
|
||||||
|
headerContentDisposition = "Content-Disposition"
|
||||||
|
contentTypeOctetStream = "application/octet-stream"
|
||||||
|
)
|
|
@ -65,12 +65,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="modal-create-object" class="modal">
|
<div id="modal-create-object" class="modal">
|
||||||
|
<form action="/api/buckets/{{ .BucketName }}/objects" method="POST" id="create-object-form" enctype="multipart/form-data">
|
||||||
|
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h4>Create Object</h4>
|
<h4>Create Object</h4>
|
||||||
<br>
|
<br>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s6">
|
<div class="col s6">
|
||||||
<form action="/api/buckets/{{ .BucketName }}/objects" method="POST" id="create-object-form" enctype="multipart/form-data">
|
|
||||||
<div class="file-field input-field">
|
<div class="file-field input-field">
|
||||||
<div class="btn">
|
<div class="btn">
|
||||||
<span>File</span>
|
<span>File</span>
|
||||||
|
@ -80,30 +81,32 @@
|
||||||
<input class="file-path validate" type="text">
|
<input class="file-path validate" type="text">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="submit" form="create-object-form" class="modal-action modal-close waves-effect waves-green btn-flat">Upload</button>
|
<button type="submit" class="modal-action modal-close waves-effect waves-green btn-flat">Upload</button>
|
||||||
<button class="modal-action modal-close waves-effect waves-green btn-flat">Cancel</button>
|
<button class="modal-action modal-close waves-effect waves-green btn-flat">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function deleteObject(bucketName, objectName) {
|
function deleteObject(bucketName, objectName) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'DELETE',
|
type: 'DELETE',
|
||||||
url: '/api/buckets/' + bucketName + '/objects/' + objectName,
|
url: '/api/buckets/' + bucketName + '/objects/' + objectName,
|
||||||
success: function () { location.reload(); }
|
success: function () { location.reload(); }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
function deleteBucket(bucketName) {
|
function deleteBucket(bucketName) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'DELETE',
|
type: 'DELETE',
|
||||||
url: '/api/buckets/' + bucketName,
|
url: '/api/buckets/' + bucketName,
|
||||||
success: function () { window.location.replace('/buckets'); }
|
success: function () { window.location.replace('/buckets'); }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -48,28 +48,31 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="modal-create-bucket" class="modal">
|
<div id="modal-create-bucket" class="modal">
|
||||||
|
<form id="create-bucket-form">
|
||||||
|
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h4>Create Bucket</h4>
|
<h4>Create Bucket</h4>
|
||||||
<br>
|
<br>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s6">
|
<div class="col s6">
|
||||||
<form id="create-bucket-form">
|
|
||||||
<div class="input-field">
|
<div class="input-field">
|
||||||
<input placeholder="My Bucket" id="name" type="text" name="name">
|
<input placeholder="My Bucket" id="name" type="text" name="name">
|
||||||
<label for="name">Name</label>
|
<label for="name">Name</label>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" onclick="createBucket()" class="modal-action modal-close waves-effect waves-green btn-flat">Create</button>
|
<button type="button" onclick="createBucket()" class="modal-action modal-close waves-effect waves-green btn-flat">Create</button>
|
||||||
<button class="modal-action modal-close waves-effect waves-green btn-flat">Cancel</button>
|
<button class="modal-action modal-close waves-effect waves-green btn-flat">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function createBucket() {
|
function createBucket() {
|
||||||
var formData = {};
|
var formData = {};
|
||||||
$.each($('#create-bucket-form')
|
$.each($('#create-bucket-form')
|
||||||
.serializeArray(), function(i, field) {
|
.serializeArray(), function(i, field) {
|
||||||
|
@ -83,6 +86,6 @@
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
success: function() { location.reload(); }
|
success: function() { location.reload(); }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0"/>
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
|
||||||
<title>S3 Manager</title>
|
<title>S3 Manager</title>
|
||||||
|
|
||||||
|
|
22
vendor/github.com/davecgh/go-spew/.gitignore
generated
vendored
Normal file
22
vendor/github.com/davecgh/go-spew/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
14
vendor/github.com/davecgh/go-spew/.travis.yml
generated
vendored
Normal file
14
vendor/github.com/davecgh/go-spew/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.5.4
|
||||||
|
- 1.6.3
|
||||||
|
- 1.7
|
||||||
|
install:
|
||||||
|
- go get -v golang.org/x/tools/cmd/cover
|
||||||
|
script:
|
||||||
|
- go test -v -tags=safe ./spew
|
||||||
|
- go test -v -tags=testcgo ./spew -covermode=count -coverprofile=profile.cov
|
||||||
|
after_success:
|
||||||
|
- go get -v github.com/mattn/goveralls
|
||||||
|
- export PATH=$PATH:$HOME/gopath/bin
|
||||||
|
- goveralls -coverprofile=profile.cov -service=travis-ci
|
15
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
Normal file
15
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
ISC License
|
||||||
|
|
||||||
|
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
205
vendor/github.com/davecgh/go-spew/README.md
generated
vendored
Normal file
205
vendor/github.com/davecgh/go-spew/README.md
generated
vendored
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
go-spew
|
||||||
|
=======
|
||||||
|
|
||||||
|
[![Build Status](https://img.shields.io/travis/davecgh/go-spew.svg)]
|
||||||
|
(https://travis-ci.org/davecgh/go-spew) [![ISC License]
|
||||||
|
(http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) [![Coverage Status]
|
||||||
|
(https://img.shields.io/coveralls/davecgh/go-spew.svg)]
|
||||||
|
(https://coveralls.io/r/davecgh/go-spew?branch=master)
|
||||||
|
|
||||||
|
|
||||||
|
Go-spew implements a deep pretty printer for Go data structures to aid in
|
||||||
|
debugging. A comprehensive suite of tests with 100% test coverage is provided
|
||||||
|
to ensure proper functionality. See `test_coverage.txt` for the gocov coverage
|
||||||
|
report. Go-spew is licensed under the liberal ISC license, so it may be used in
|
||||||
|
open source or commercial projects.
|
||||||
|
|
||||||
|
If you're interested in reading about how this package came to life and some
|
||||||
|
of the challenges involved in providing a deep pretty printer, there is a blog
|
||||||
|
post about it
|
||||||
|
[here](https://web.archive.org/web/20160304013555/https://blog.cyphertite.com/go-spew-a-journey-into-dumping-go-data-structures/).
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)]
|
||||||
|
(http://godoc.org/github.com/davecgh/go-spew/spew)
|
||||||
|
|
||||||
|
Full `go doc` style documentation for the project can be viewed online without
|
||||||
|
installing this package by using the excellent GoDoc site here:
|
||||||
|
http://godoc.org/github.com/davecgh/go-spew/spew
|
||||||
|
|
||||||
|
You can also view the documentation locally once the package is installed with
|
||||||
|
the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
|
||||||
|
http://localhost:6060/pkg/github.com/davecgh/go-spew/spew
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ go get -u github.com/davecgh/go-spew/spew
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
Add this import line to the file you're working in:
|
||||||
|
|
||||||
|
```Go
|
||||||
|
import "github.com/davecgh/go-spew/spew"
|
||||||
|
```
|
||||||
|
|
||||||
|
To dump a variable with full newlines, indentation, type, and pointer
|
||||||
|
information use Dump, Fdump, or Sdump:
|
||||||
|
|
||||||
|
```Go
|
||||||
|
spew.Dump(myVar1, myVar2, ...)
|
||||||
|
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
||||||
|
str := spew.Sdump(myVar1, myVar2, ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, if you would prefer to use format strings with a compacted inline
|
||||||
|
printing style, use the convenience wrappers Printf, Fprintf, etc with %v (most
|
||||||
|
compact), %+v (adds pointer addresses), %#v (adds types), or %#+v (adds types
|
||||||
|
and pointer addresses):
|
||||||
|
|
||||||
|
```Go
|
||||||
|
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging a Web Application Example
|
||||||
|
|
||||||
|
Here is an example of how you can use `spew.Sdump()` to help debug a web application. Please be sure to wrap your output using the `html.EscapeString()` function for safety reasons. You should also only use this debugging technique in a development environment, never in production.
|
||||||
|
|
||||||
|
```Go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "text/html")
|
||||||
|
fmt.Fprintf(w, "Hi there, %s!", r.URL.Path[1:])
|
||||||
|
fmt.Fprintf(w, "<!--\n" + html.EscapeString(spew.Sdump(w)) + "\n-->")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", handler)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sample Dump Output
|
||||||
|
|
||||||
|
```
|
||||||
|
(main.Foo) {
|
||||||
|
unexportedField: (*main.Bar)(0xf84002e210)({
|
||||||
|
flag: (main.Flag) flagTwo,
|
||||||
|
data: (uintptr) <nil>
|
||||||
|
}),
|
||||||
|
ExportedField: (map[interface {}]interface {}) {
|
||||||
|
(string) "one": (bool) true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
([]uint8) {
|
||||||
|
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||||
|
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||||
|
00000020 31 32 |12|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sample Formatter Output
|
||||||
|
|
||||||
|
Double pointer to a uint8:
|
||||||
|
```
|
||||||
|
%v: <**>5
|
||||||
|
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
||||||
|
%#v: (**uint8)5
|
||||||
|
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
||||||
|
```
|
||||||
|
|
||||||
|
Pointer to circular struct with a uint8 field and a pointer to itself:
|
||||||
|
```
|
||||||
|
%v: <*>{1 <*><shown>}
|
||||||
|
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
||||||
|
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
||||||
|
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
|
||||||
|
Configuration of spew is handled by fields in the ConfigState type. For
|
||||||
|
convenience, all of the top-level functions use a global state available via the
|
||||||
|
spew.Config global.
|
||||||
|
|
||||||
|
It is also possible to create a ConfigState instance that provides methods
|
||||||
|
equivalent to the top-level functions. This allows concurrent configuration
|
||||||
|
options. See the ConfigState documentation for more details.
|
||||||
|
|
||||||
|
```
|
||||||
|
* Indent
|
||||||
|
String to use for each indentation level for Dump functions.
|
||||||
|
It is a single space by default. A popular alternative is "\t".
|
||||||
|
|
||||||
|
* MaxDepth
|
||||||
|
Maximum number of levels to descend into nested data structures.
|
||||||
|
There is no limit by default.
|
||||||
|
|
||||||
|
* DisableMethods
|
||||||
|
Disables invocation of error and Stringer interface methods.
|
||||||
|
Method invocation is enabled by default.
|
||||||
|
|
||||||
|
* DisablePointerMethods
|
||||||
|
Disables invocation of error and Stringer interface methods on types
|
||||||
|
which only accept pointer receivers from non-pointer variables. This option
|
||||||
|
relies on access to the unsafe package, so it will not have any effect when
|
||||||
|
running in environments without access to the unsafe package such as Google
|
||||||
|
App Engine or with the "safe" build tag specified.
|
||||||
|
Pointer method invocation is enabled by default.
|
||||||
|
|
||||||
|
* DisablePointerAddresses
|
||||||
|
DisablePointerAddresses specifies whether to disable the printing of
|
||||||
|
pointer addresses. This is useful when diffing data structures in tests.
|
||||||
|
|
||||||
|
* DisableCapacities
|
||||||
|
DisableCapacities specifies whether to disable the printing of capacities
|
||||||
|
for arrays, slices, maps and channels. This is useful when diffing data
|
||||||
|
structures in tests.
|
||||||
|
|
||||||
|
* ContinueOnMethod
|
||||||
|
Enables recursion into types after invoking error and Stringer interface
|
||||||
|
methods. Recursion after method invocation is disabled by default.
|
||||||
|
|
||||||
|
* SortKeys
|
||||||
|
Specifies map keys should be sorted before being printed. Use
|
||||||
|
this to have a more deterministic, diffable output. Note that
|
||||||
|
only native types (bool, int, uint, floats, uintptr and string)
|
||||||
|
and types which implement error or Stringer interfaces are supported,
|
||||||
|
with other types sorted according to the reflect.Value.String() output
|
||||||
|
which guarantees display stability. Natural map order is used by
|
||||||
|
default.
|
||||||
|
|
||||||
|
* SpewKeys
|
||||||
|
SpewKeys specifies that, as a last resort attempt, map keys should be
|
||||||
|
spewed to strings and sorted by those strings. This is only considered
|
||||||
|
if SortKeys is true.
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Unsafe Package Dependency
|
||||||
|
|
||||||
|
This package relies on the unsafe package to perform some of the more advanced
|
||||||
|
features, however it also supports a "limited" mode which allows it to work in
|
||||||
|
environments where the unsafe package is not available. By default, it will
|
||||||
|
operate in this mode on Google App Engine and when compiled with GopherJS. The
|
||||||
|
"safe" build tag may also be specified to force the package to build without
|
||||||
|
using the unsafe package.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Go-spew is licensed under the [copyfree](http://copyfree.org) ISC License.
|
22
vendor/github.com/davecgh/go-spew/cov_report.sh
generated
vendored
Normal file
22
vendor/github.com/davecgh/go-spew/cov_report.sh
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# This script uses gocov to generate a test coverage report.
|
||||||
|
# The gocov tool my be obtained with the following command:
|
||||||
|
# go get github.com/axw/gocov/gocov
|
||||||
|
#
|
||||||
|
# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH.
|
||||||
|
|
||||||
|
# Check for gocov.
|
||||||
|
if ! type gocov >/dev/null 2>&1; then
|
||||||
|
echo >&2 "This script requires the gocov tool."
|
||||||
|
echo >&2 "You may obtain it with the following command:"
|
||||||
|
echo >&2 "go get github.com/axw/gocov/gocov"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Only run the cgo tests if gcc is installed.
|
||||||
|
if type gcc >/dev/null 2>&1; then
|
||||||
|
(cd spew && gocov test -tags testcgo | gocov report)
|
||||||
|
else
|
||||||
|
(cd spew && gocov test | gocov report)
|
||||||
|
fi
|
152
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
Normal file
152
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
||||||
|
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
||||||
|
// tag is deprecated and thus should not be used.
|
||||||
|
// +build !js,!appengine,!safe,!disableunsafe
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||||
|
// not access to the unsafe package is available.
|
||||||
|
UnsafeDisabled = false
|
||||||
|
|
||||||
|
// ptrSize is the size of a pointer on the current arch.
|
||||||
|
ptrSize = unsafe.Sizeof((*byte)(nil))
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// offsetPtr, offsetScalar, and offsetFlag are the offsets for the
|
||||||
|
// internal reflect.Value fields. These values are valid before golang
|
||||||
|
// commit ecccf07e7f9d which changed the format. The are also valid
|
||||||
|
// after commit 82f48826c6c7 which changed the format again to mirror
|
||||||
|
// the original format. Code in the init function updates these offsets
|
||||||
|
// as necessary.
|
||||||
|
offsetPtr = uintptr(ptrSize)
|
||||||
|
offsetScalar = uintptr(0)
|
||||||
|
offsetFlag = uintptr(ptrSize * 2)
|
||||||
|
|
||||||
|
// flagKindWidth and flagKindShift indicate various bits that the
|
||||||
|
// reflect package uses internally to track kind information.
|
||||||
|
//
|
||||||
|
// flagRO indicates whether or not the value field of a reflect.Value is
|
||||||
|
// read-only.
|
||||||
|
//
|
||||||
|
// flagIndir indicates whether the value field of a reflect.Value is
|
||||||
|
// the actual data or a pointer to the data.
|
||||||
|
//
|
||||||
|
// These values are valid before golang commit 90a7c3c86944 which
|
||||||
|
// changed their positions. Code in the init function updates these
|
||||||
|
// flags as necessary.
|
||||||
|
flagKindWidth = uintptr(5)
|
||||||
|
flagKindShift = uintptr(flagKindWidth - 1)
|
||||||
|
flagRO = uintptr(1 << 0)
|
||||||
|
flagIndir = uintptr(1 << 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Older versions of reflect.Value stored small integers directly in the
|
||||||
|
// ptr field (which is named val in the older versions). Versions
|
||||||
|
// between commits ecccf07e7f9d and 82f48826c6c7 added a new field named
|
||||||
|
// scalar for this purpose which unfortunately came before the flag
|
||||||
|
// field, so the offset of the flag field is different for those
|
||||||
|
// versions.
|
||||||
|
//
|
||||||
|
// This code constructs a new reflect.Value from a known small integer
|
||||||
|
// and checks if the size of the reflect.Value struct indicates it has
|
||||||
|
// the scalar field. When it does, the offsets are updated accordingly.
|
||||||
|
vv := reflect.ValueOf(0xf00)
|
||||||
|
if unsafe.Sizeof(vv) == (ptrSize * 4) {
|
||||||
|
offsetScalar = ptrSize * 2
|
||||||
|
offsetFlag = ptrSize * 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit 90a7c3c86944 changed the flag positions such that the low
|
||||||
|
// order bits are the kind. This code extracts the kind from the flags
|
||||||
|
// field and ensures it's the correct type. When it's not, the flag
|
||||||
|
// order has been changed to the newer format, so the flags are updated
|
||||||
|
// accordingly.
|
||||||
|
upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag)
|
||||||
|
upfv := *(*uintptr)(upf)
|
||||||
|
flagKindMask := uintptr((1<<flagKindWidth - 1) << flagKindShift)
|
||||||
|
if (upfv&flagKindMask)>>flagKindShift != uintptr(reflect.Int) {
|
||||||
|
flagKindShift = 0
|
||||||
|
flagRO = 1 << 5
|
||||||
|
flagIndir = 1 << 6
|
||||||
|
|
||||||
|
// Commit adf9b30e5594 modified the flags to separate the
|
||||||
|
// flagRO flag into two bits which specifies whether or not the
|
||||||
|
// field is embedded. This causes flagIndir to move over a bit
|
||||||
|
// and means that flagRO is the combination of either of the
|
||||||
|
// original flagRO bit and the new bit.
|
||||||
|
//
|
||||||
|
// This code detects the change by extracting what used to be
|
||||||
|
// the indirect bit to ensure it's set. When it's not, the flag
|
||||||
|
// order has been changed to the newer format, so the flags are
|
||||||
|
// updated accordingly.
|
||||||
|
if upfv&flagIndir == 0 {
|
||||||
|
flagRO = 3 << 5
|
||||||
|
flagIndir = 1 << 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
|
||||||
|
// the typical safety restrictions preventing access to unaddressable and
|
||||||
|
// unexported data. It works by digging the raw pointer to the underlying
|
||||||
|
// value out of the protected value and generating a new unprotected (unsafe)
|
||||||
|
// reflect.Value to it.
|
||||||
|
//
|
||||||
|
// This allows us to check for implementations of the Stringer and error
|
||||||
|
// interfaces to be used for pretty printing ordinarily unaddressable and
|
||||||
|
// inaccessible values such as unexported struct fields.
|
||||||
|
func unsafeReflectValue(v reflect.Value) (rv reflect.Value) {
|
||||||
|
indirects := 1
|
||||||
|
vt := v.Type()
|
||||||
|
upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr)
|
||||||
|
rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag))
|
||||||
|
if rvf&flagIndir != 0 {
|
||||||
|
vt = reflect.PtrTo(v.Type())
|
||||||
|
indirects++
|
||||||
|
} else if offsetScalar != 0 {
|
||||||
|
// The value is in the scalar field when it's not one of the
|
||||||
|
// reference types.
|
||||||
|
switch vt.Kind() {
|
||||||
|
case reflect.Uintptr:
|
||||||
|
case reflect.Chan:
|
||||||
|
case reflect.Func:
|
||||||
|
case reflect.Map:
|
||||||
|
case reflect.Ptr:
|
||||||
|
case reflect.UnsafePointer:
|
||||||
|
default:
|
||||||
|
upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) +
|
||||||
|
offsetScalar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pv := reflect.NewAt(vt, upv)
|
||||||
|
rv = pv
|
||||||
|
for i := 0; i < indirects; i++ {
|
||||||
|
rv = rv.Elem()
|
||||||
|
}
|
||||||
|
return rv
|
||||||
|
}
|
38
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
Normal file
38
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when the code is running on Google App Engine, compiled by GopherJS, or
|
||||||
|
// "-tags safe" is added to the go build command line. The "disableunsafe"
|
||||||
|
// tag is deprecated and thus should not be used.
|
||||||
|
// +build js appengine safe disableunsafe
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||||
|
// not access to the unsafe package is available.
|
||||||
|
UnsafeDisabled = true
|
||||||
|
)
|
||||||
|
|
||||||
|
// unsafeReflectValue typically converts the passed reflect.Value into a one
|
||||||
|
// that bypasses the typical safety restrictions preventing access to
|
||||||
|
// unaddressable and unexported data. However, doing this relies on access to
|
||||||
|
// the unsafe package. This is a stub version which simply returns the passed
|
||||||
|
// reflect.Value when the unsafe package is not available.
|
||||||
|
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||||
|
return v
|
||||||
|
}
|
341
vendor/github.com/davecgh/go-spew/spew/common.go
generated
vendored
Normal file
341
vendor/github.com/davecgh/go-spew/spew/common.go
generated
vendored
Normal file
|
@ -0,0 +1,341 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Some constants in the form of bytes to avoid string overhead. This mirrors
|
||||||
|
// the technique used in the fmt package.
|
||||||
|
var (
|
||||||
|
panicBytes = []byte("(PANIC=")
|
||||||
|
plusBytes = []byte("+")
|
||||||
|
iBytes = []byte("i")
|
||||||
|
trueBytes = []byte("true")
|
||||||
|
falseBytes = []byte("false")
|
||||||
|
interfaceBytes = []byte("(interface {})")
|
||||||
|
commaNewlineBytes = []byte(",\n")
|
||||||
|
newlineBytes = []byte("\n")
|
||||||
|
openBraceBytes = []byte("{")
|
||||||
|
openBraceNewlineBytes = []byte("{\n")
|
||||||
|
closeBraceBytes = []byte("}")
|
||||||
|
asteriskBytes = []byte("*")
|
||||||
|
colonBytes = []byte(":")
|
||||||
|
colonSpaceBytes = []byte(": ")
|
||||||
|
openParenBytes = []byte("(")
|
||||||
|
closeParenBytes = []byte(")")
|
||||||
|
spaceBytes = []byte(" ")
|
||||||
|
pointerChainBytes = []byte("->")
|
||||||
|
nilAngleBytes = []byte("<nil>")
|
||||||
|
maxNewlineBytes = []byte("<max depth reached>\n")
|
||||||
|
maxShortBytes = []byte("<max>")
|
||||||
|
circularBytes = []byte("<already shown>")
|
||||||
|
circularShortBytes = []byte("<shown>")
|
||||||
|
invalidAngleBytes = []byte("<invalid>")
|
||||||
|
openBracketBytes = []byte("[")
|
||||||
|
closeBracketBytes = []byte("]")
|
||||||
|
percentBytes = []byte("%")
|
||||||
|
precisionBytes = []byte(".")
|
||||||
|
openAngleBytes = []byte("<")
|
||||||
|
closeAngleBytes = []byte(">")
|
||||||
|
openMapBytes = []byte("map[")
|
||||||
|
closeMapBytes = []byte("]")
|
||||||
|
lenEqualsBytes = []byte("len=")
|
||||||
|
capEqualsBytes = []byte("cap=")
|
||||||
|
)
|
||||||
|
|
||||||
|
// hexDigits is used to map a decimal value to a hex digit.
|
||||||
|
var hexDigits = "0123456789abcdef"
|
||||||
|
|
||||||
|
// catchPanic handles any panics that might occur during the handleMethods
|
||||||
|
// calls.
|
||||||
|
func catchPanic(w io.Writer, v reflect.Value) {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
w.Write(panicBytes)
|
||||||
|
fmt.Fprintf(w, "%v", err)
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleMethods attempts to call the Error and String methods on the underlying
|
||||||
|
// type the passed reflect.Value represents and outputes the result to Writer w.
|
||||||
|
//
|
||||||
|
// It handles panics in any called methods by catching and displaying the error
|
||||||
|
// as the formatted value.
|
||||||
|
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
|
||||||
|
// We need an interface to check if the type implements the error or
|
||||||
|
// Stringer interface. However, the reflect package won't give us an
|
||||||
|
// interface on certain things like unexported struct fields in order
|
||||||
|
// to enforce visibility rules. We use unsafe, when it's available,
|
||||||
|
// to bypass these restrictions since this package does not mutate the
|
||||||
|
// values.
|
||||||
|
if !v.CanInterface() {
|
||||||
|
if UnsafeDisabled {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
v = unsafeReflectValue(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choose whether or not to do error and Stringer interface lookups against
|
||||||
|
// the base type or a pointer to the base type depending on settings.
|
||||||
|
// Technically calling one of these methods with a pointer receiver can
|
||||||
|
// mutate the value, however, types which choose to satisify an error or
|
||||||
|
// Stringer interface with a pointer receiver should not be mutating their
|
||||||
|
// state inside these interface methods.
|
||||||
|
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
|
||||||
|
v = unsafeReflectValue(v)
|
||||||
|
}
|
||||||
|
if v.CanAddr() {
|
||||||
|
v = v.Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it an error or Stringer?
|
||||||
|
switch iface := v.Interface().(type) {
|
||||||
|
case error:
|
||||||
|
defer catchPanic(w, v)
|
||||||
|
if cs.ContinueOnMethod {
|
||||||
|
w.Write(openParenBytes)
|
||||||
|
w.Write([]byte(iface.Error()))
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
w.Write(spaceBytes)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(iface.Error()))
|
||||||
|
return true
|
||||||
|
|
||||||
|
case fmt.Stringer:
|
||||||
|
defer catchPanic(w, v)
|
||||||
|
if cs.ContinueOnMethod {
|
||||||
|
w.Write(openParenBytes)
|
||||||
|
w.Write([]byte(iface.String()))
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
w.Write(spaceBytes)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
w.Write([]byte(iface.String()))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// printBool outputs a boolean value as true or false to Writer w.
|
||||||
|
func printBool(w io.Writer, val bool) {
|
||||||
|
if val {
|
||||||
|
w.Write(trueBytes)
|
||||||
|
} else {
|
||||||
|
w.Write(falseBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// printInt outputs a signed integer value to Writer w.
|
||||||
|
func printInt(w io.Writer, val int64, base int) {
|
||||||
|
w.Write([]byte(strconv.FormatInt(val, base)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// printUint outputs an unsigned integer value to Writer w.
|
||||||
|
func printUint(w io.Writer, val uint64, base int) {
|
||||||
|
w.Write([]byte(strconv.FormatUint(val, base)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// printFloat outputs a floating point value using the specified precision,
|
||||||
|
// which is expected to be 32 or 64bit, to Writer w.
|
||||||
|
func printFloat(w io.Writer, val float64, precision int) {
|
||||||
|
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// printComplex outputs a complex value using the specified float precision
|
||||||
|
// for the real and imaginary parts to Writer w.
|
||||||
|
func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
||||||
|
r := real(c)
|
||||||
|
w.Write(openParenBytes)
|
||||||
|
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
|
||||||
|
i := imag(c)
|
||||||
|
if i >= 0 {
|
||||||
|
w.Write(plusBytes)
|
||||||
|
}
|
||||||
|
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
|
||||||
|
w.Write(iBytes)
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x'
|
||||||
|
// prefix to Writer w.
|
||||||
|
func printHexPtr(w io.Writer, p uintptr) {
|
||||||
|
// Null pointer.
|
||||||
|
num := uint64(p)
|
||||||
|
if num == 0 {
|
||||||
|
w.Write(nilAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
|
||||||
|
buf := make([]byte, 18)
|
||||||
|
|
||||||
|
// It's simpler to construct the hex string right to left.
|
||||||
|
base := uint64(16)
|
||||||
|
i := len(buf) - 1
|
||||||
|
for num >= base {
|
||||||
|
buf[i] = hexDigits[num%base]
|
||||||
|
num /= base
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
buf[i] = hexDigits[num]
|
||||||
|
|
||||||
|
// Add '0x' prefix.
|
||||||
|
i--
|
||||||
|
buf[i] = 'x'
|
||||||
|
i--
|
||||||
|
buf[i] = '0'
|
||||||
|
|
||||||
|
// Strip unused leading bytes.
|
||||||
|
buf = buf[i:]
|
||||||
|
w.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
||||||
|
// elements to be sorted.
|
||||||
|
type valuesSorter struct {
|
||||||
|
values []reflect.Value
|
||||||
|
strings []string // either nil or same len and values
|
||||||
|
cs *ConfigState
|
||||||
|
}
|
||||||
|
|
||||||
|
// newValuesSorter initializes a valuesSorter instance, which holds a set of
|
||||||
|
// surrogate keys on which the data should be sorted. It uses flags in
|
||||||
|
// ConfigState to decide if and how to populate those surrogate keys.
|
||||||
|
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
|
||||||
|
vs := &valuesSorter{values: values, cs: cs}
|
||||||
|
if canSortSimply(vs.values[0].Kind()) {
|
||||||
|
return vs
|
||||||
|
}
|
||||||
|
if !cs.DisableMethods {
|
||||||
|
vs.strings = make([]string, len(values))
|
||||||
|
for i := range vs.values {
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
if !handleMethods(cs, &b, vs.values[i]) {
|
||||||
|
vs.strings = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
vs.strings[i] = b.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if vs.strings == nil && cs.SpewKeys {
|
||||||
|
vs.strings = make([]string, len(values))
|
||||||
|
for i := range vs.values {
|
||||||
|
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vs
|
||||||
|
}
|
||||||
|
|
||||||
|
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
|
||||||
|
// directly, or whether it should be considered for sorting by surrogate keys
|
||||||
|
// (if the ConfigState allows it).
|
||||||
|
func canSortSimply(kind reflect.Kind) bool {
|
||||||
|
// This switch parallels valueSortLess, except for the default case.
|
||||||
|
switch kind {
|
||||||
|
case reflect.Bool:
|
||||||
|
return true
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
return true
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
return true
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return true
|
||||||
|
case reflect.String:
|
||||||
|
return true
|
||||||
|
case reflect.Uintptr:
|
||||||
|
return true
|
||||||
|
case reflect.Array:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of values in the slice. It is part of the
|
||||||
|
// sort.Interface implementation.
|
||||||
|
func (s *valuesSorter) Len() int {
|
||||||
|
return len(s.values)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap swaps the values at the passed indices. It is part of the
|
||||||
|
// sort.Interface implementation.
|
||||||
|
func (s *valuesSorter) Swap(i, j int) {
|
||||||
|
s.values[i], s.values[j] = s.values[j], s.values[i]
|
||||||
|
if s.strings != nil {
|
||||||
|
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// valueSortLess returns whether the first value should sort before the second
|
||||||
|
// value. It is used by valueSorter.Less as part of the sort.Interface
|
||||||
|
// implementation.
|
||||||
|
func valueSortLess(a, b reflect.Value) bool {
|
||||||
|
switch a.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return !a.Bool() && b.Bool()
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
return a.Int() < b.Int()
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
return a.Uint() < b.Uint()
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return a.Float() < b.Float()
|
||||||
|
case reflect.String:
|
||||||
|
return a.String() < b.String()
|
||||||
|
case reflect.Uintptr:
|
||||||
|
return a.Uint() < b.Uint()
|
||||||
|
case reflect.Array:
|
||||||
|
// Compare the contents of both arrays.
|
||||||
|
l := a.Len()
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
av := a.Index(i)
|
||||||
|
bv := b.Index(i)
|
||||||
|
if av.Interface() == bv.Interface() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return valueSortLess(av, bv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a.String() < b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less returns whether the value at index i should sort before the
|
||||||
|
// value at index j. It is part of the sort.Interface implementation.
|
||||||
|
func (s *valuesSorter) Less(i, j int) bool {
|
||||||
|
if s.strings == nil {
|
||||||
|
return valueSortLess(s.values[i], s.values[j])
|
||||||
|
}
|
||||||
|
return s.strings[i] < s.strings[j]
|
||||||
|
}
|
||||||
|
|
||||||
|
// sortValues is a sort function that handles both native types and any type that
|
||||||
|
// can be converted to error or Stringer. Other inputs are sorted according to
|
||||||
|
// their Value.String() value to ensure display stability.
|
||||||
|
func sortValues(values []reflect.Value, cs *ConfigState) {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sort.Sort(newValuesSorter(values, cs))
|
||||||
|
}
|
298
vendor/github.com/davecgh/go-spew/spew/common_test.go
generated
vendored
Normal file
298
vendor/github.com/davecgh/go-spew/spew/common_test.go
generated
vendored
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// custom type to test Stinger interface on non-pointer receiver.
|
||||||
|
type stringer string
|
||||||
|
|
||||||
|
// String implements the Stringer interface for testing invocation of custom
|
||||||
|
// stringers on types with non-pointer receivers.
|
||||||
|
func (s stringer) String() string {
|
||||||
|
return "stringer " + string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// custom type to test Stinger interface on pointer receiver.
|
||||||
|
type pstringer string
|
||||||
|
|
||||||
|
// String implements the Stringer interface for testing invocation of custom
|
||||||
|
// stringers on types with only pointer receivers.
|
||||||
|
func (s *pstringer) String() string {
|
||||||
|
return "stringer " + string(*s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// xref1 and xref2 are cross referencing structs for testing circular reference
|
||||||
|
// detection.
|
||||||
|
type xref1 struct {
|
||||||
|
ps2 *xref2
|
||||||
|
}
|
||||||
|
type xref2 struct {
|
||||||
|
ps1 *xref1
|
||||||
|
}
|
||||||
|
|
||||||
|
// indirCir1, indirCir2, and indirCir3 are used to generate an indirect circular
|
||||||
|
// reference for testing detection.
|
||||||
|
type indirCir1 struct {
|
||||||
|
ps2 *indirCir2
|
||||||
|
}
|
||||||
|
type indirCir2 struct {
|
||||||
|
ps3 *indirCir3
|
||||||
|
}
|
||||||
|
type indirCir3 struct {
|
||||||
|
ps1 *indirCir1
|
||||||
|
}
|
||||||
|
|
||||||
|
// embed is used to test embedded structures.
|
||||||
|
type embed struct {
|
||||||
|
a string
|
||||||
|
}
|
||||||
|
|
||||||
|
// embedwrap is used to test embedded structures.
|
||||||
|
type embedwrap struct {
|
||||||
|
*embed
|
||||||
|
e *embed
|
||||||
|
}
|
||||||
|
|
||||||
|
// panicer is used to intentionally cause a panic for testing spew properly
|
||||||
|
// handles them
|
||||||
|
type panicer int
|
||||||
|
|
||||||
|
func (p panicer) String() string {
|
||||||
|
panic("test panic")
|
||||||
|
}
|
||||||
|
|
||||||
|
// customError is used to test custom error interface invocation.
|
||||||
|
type customError int
|
||||||
|
|
||||||
|
func (e customError) Error() string {
|
||||||
|
return fmt.Sprintf("error: %d", int(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringizeWants converts a slice of wanted test output into a format suitable
|
||||||
|
// for a test error message.
|
||||||
|
func stringizeWants(wants []string) string {
|
||||||
|
s := ""
|
||||||
|
for i, want := range wants {
|
||||||
|
if i > 0 {
|
||||||
|
s += fmt.Sprintf("want%d: %s", i+1, want)
|
||||||
|
} else {
|
||||||
|
s += "want: " + want
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// testFailed returns whether or not a test failed by checking if the result
|
||||||
|
// of the test is in the slice of wanted strings.
|
||||||
|
func testFailed(result string, wants []string) bool {
|
||||||
|
for _, want := range wants {
|
||||||
|
if result == want {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortableStruct struct {
|
||||||
|
x int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss sortableStruct) String() string {
|
||||||
|
return fmt.Sprintf("ss.%d", ss.x)
|
||||||
|
}
|
||||||
|
|
||||||
|
type unsortableStruct struct {
|
||||||
|
x int
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortTestCase struct {
|
||||||
|
input []reflect.Value
|
||||||
|
expected []reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func helpTestSortValues(tests []sortTestCase, cs *spew.ConfigState, t *testing.T) {
|
||||||
|
getInterfaces := func(values []reflect.Value) []interface{} {
|
||||||
|
interfaces := []interface{}{}
|
||||||
|
for _, v := range values {
|
||||||
|
interfaces = append(interfaces, v.Interface())
|
||||||
|
}
|
||||||
|
return interfaces
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
spew.SortValues(test.input, cs)
|
||||||
|
// reflect.DeepEqual cannot really make sense of reflect.Value,
|
||||||
|
// probably because of all the pointer tricks. For instance,
|
||||||
|
// v(2.0) != v(2.0) on a 32-bits system. Turn them into interface{}
|
||||||
|
// instead.
|
||||||
|
input := getInterfaces(test.input)
|
||||||
|
expected := getInterfaces(test.expected)
|
||||||
|
if !reflect.DeepEqual(input, expected) {
|
||||||
|
t.Errorf("Sort mismatch:\n %v != %v", input, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSortValues ensures the sort functionality for relect.Value based sorting
|
||||||
|
// works as intended.
|
||||||
|
func TestSortValues(t *testing.T) {
|
||||||
|
v := reflect.ValueOf
|
||||||
|
|
||||||
|
a := v("a")
|
||||||
|
b := v("b")
|
||||||
|
c := v("c")
|
||||||
|
embedA := v(embed{"a"})
|
||||||
|
embedB := v(embed{"b"})
|
||||||
|
embedC := v(embed{"c"})
|
||||||
|
tests := []sortTestCase{
|
||||||
|
// No values.
|
||||||
|
{
|
||||||
|
[]reflect.Value{},
|
||||||
|
[]reflect.Value{},
|
||||||
|
},
|
||||||
|
// Bools.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(false), v(true), v(false)},
|
||||||
|
[]reflect.Value{v(false), v(false), v(true)},
|
||||||
|
},
|
||||||
|
// Ints.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(2), v(1), v(3)},
|
||||||
|
[]reflect.Value{v(1), v(2), v(3)},
|
||||||
|
},
|
||||||
|
// Uints.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(uint8(2)), v(uint8(1)), v(uint8(3))},
|
||||||
|
[]reflect.Value{v(uint8(1)), v(uint8(2)), v(uint8(3))},
|
||||||
|
},
|
||||||
|
// Floats.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(2.0), v(1.0), v(3.0)},
|
||||||
|
[]reflect.Value{v(1.0), v(2.0), v(3.0)},
|
||||||
|
},
|
||||||
|
// Strings.
|
||||||
|
{
|
||||||
|
[]reflect.Value{b, a, c},
|
||||||
|
[]reflect.Value{a, b, c},
|
||||||
|
},
|
||||||
|
// Array
|
||||||
|
{
|
||||||
|
[]reflect.Value{v([3]int{3, 2, 1}), v([3]int{1, 3, 2}), v([3]int{1, 2, 3})},
|
||||||
|
[]reflect.Value{v([3]int{1, 2, 3}), v([3]int{1, 3, 2}), v([3]int{3, 2, 1})},
|
||||||
|
},
|
||||||
|
// Uintptrs.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(uintptr(2)), v(uintptr(1)), v(uintptr(3))},
|
||||||
|
[]reflect.Value{v(uintptr(1)), v(uintptr(2)), v(uintptr(3))},
|
||||||
|
},
|
||||||
|
// SortableStructs.
|
||||||
|
{
|
||||||
|
// Note: not sorted - DisableMethods is set.
|
||||||
|
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||||
|
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||||
|
},
|
||||||
|
// UnsortableStructs.
|
||||||
|
{
|
||||||
|
// Note: not sorted - SpewKeys is false.
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
},
|
||||||
|
// Invalid.
|
||||||
|
{
|
||||||
|
[]reflect.Value{embedB, embedA, embedC},
|
||||||
|
[]reflect.Value{embedB, embedA, embedC},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cs := spew.ConfigState{DisableMethods: true, SpewKeys: false}
|
||||||
|
helpTestSortValues(tests, &cs, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSortValuesWithMethods ensures the sort functionality for relect.Value
|
||||||
|
// based sorting works as intended when using string methods.
|
||||||
|
func TestSortValuesWithMethods(t *testing.T) {
|
||||||
|
v := reflect.ValueOf
|
||||||
|
|
||||||
|
a := v("a")
|
||||||
|
b := v("b")
|
||||||
|
c := v("c")
|
||||||
|
tests := []sortTestCase{
|
||||||
|
// Ints.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(2), v(1), v(3)},
|
||||||
|
[]reflect.Value{v(1), v(2), v(3)},
|
||||||
|
},
|
||||||
|
// Strings.
|
||||||
|
{
|
||||||
|
[]reflect.Value{b, a, c},
|
||||||
|
[]reflect.Value{a, b, c},
|
||||||
|
},
|
||||||
|
// SortableStructs.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||||
|
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
|
||||||
|
},
|
||||||
|
// UnsortableStructs.
|
||||||
|
{
|
||||||
|
// Note: not sorted - SpewKeys is false.
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cs := spew.ConfigState{DisableMethods: false, SpewKeys: false}
|
||||||
|
helpTestSortValues(tests, &cs, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSortValuesWithSpew ensures the sort functionality for relect.Value
|
||||||
|
// based sorting works as intended when using spew to stringify keys.
|
||||||
|
func TestSortValuesWithSpew(t *testing.T) {
|
||||||
|
v := reflect.ValueOf
|
||||||
|
|
||||||
|
a := v("a")
|
||||||
|
b := v("b")
|
||||||
|
c := v("c")
|
||||||
|
tests := []sortTestCase{
|
||||||
|
// Ints.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(2), v(1), v(3)},
|
||||||
|
[]reflect.Value{v(1), v(2), v(3)},
|
||||||
|
},
|
||||||
|
// Strings.
|
||||||
|
{
|
||||||
|
[]reflect.Value{b, a, c},
|
||||||
|
[]reflect.Value{a, b, c},
|
||||||
|
},
|
||||||
|
// SortableStructs.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||||
|
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
|
||||||
|
},
|
||||||
|
// UnsortableStructs.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
[]reflect.Value{v(unsortableStruct{1}), v(unsortableStruct{2}), v(unsortableStruct{3})},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cs := spew.ConfigState{DisableMethods: true, SpewKeys: true}
|
||||||
|
helpTestSortValues(tests, &cs, t)
|
||||||
|
}
|
306
vendor/github.com/davecgh/go-spew/spew/config.go
generated
vendored
Normal file
306
vendor/github.com/davecgh/go-spew/spew/config.go
generated
vendored
Normal file
|
@ -0,0 +1,306 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigState houses the configuration options used by spew to format and
|
||||||
|
// display values. There is a global instance, Config, that is used to control
|
||||||
|
// all top-level Formatter and Dump functionality. Each ConfigState instance
|
||||||
|
// provides methods equivalent to the top-level functions.
|
||||||
|
//
|
||||||
|
// The zero value for ConfigState provides no indentation. You would typically
|
||||||
|
// want to set it to a space or a tab.
|
||||||
|
//
|
||||||
|
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
|
||||||
|
// with default settings. See the documentation of NewDefaultConfig for default
|
||||||
|
// values.
|
||||||
|
type ConfigState struct {
|
||||||
|
// Indent specifies the string to use for each indentation level. The
|
||||||
|
// global config instance that all top-level functions use set this to a
|
||||||
|
// single space by default. If you would like more indentation, you might
|
||||||
|
// set this to a tab with "\t" or perhaps two spaces with " ".
|
||||||
|
Indent string
|
||||||
|
|
||||||
|
// MaxDepth controls the maximum number of levels to descend into nested
|
||||||
|
// data structures. The default, 0, means there is no limit.
|
||||||
|
//
|
||||||
|
// NOTE: Circular data structures are properly detected, so it is not
|
||||||
|
// necessary to set this value unless you specifically want to limit deeply
|
||||||
|
// nested data structures.
|
||||||
|
MaxDepth int
|
||||||
|
|
||||||
|
// DisableMethods specifies whether or not error and Stringer interfaces are
|
||||||
|
// invoked for types that implement them.
|
||||||
|
DisableMethods bool
|
||||||
|
|
||||||
|
// DisablePointerMethods specifies whether or not to check for and invoke
|
||||||
|
// error and Stringer interfaces on types which only accept a pointer
|
||||||
|
// receiver when the current type is not a pointer.
|
||||||
|
//
|
||||||
|
// NOTE: This might be an unsafe action since calling one of these methods
|
||||||
|
// with a pointer receiver could technically mutate the value, however,
|
||||||
|
// in practice, types which choose to satisify an error or Stringer
|
||||||
|
// interface with a pointer receiver should not be mutating their state
|
||||||
|
// inside these interface methods. As a result, this option relies on
|
||||||
|
// access to the unsafe package, so it will not have any effect when
|
||||||
|
// running in environments without access to the unsafe package such as
|
||||||
|
// Google App Engine or with the "safe" build tag specified.
|
||||||
|
DisablePointerMethods bool
|
||||||
|
|
||||||
|
// DisablePointerAddresses specifies whether to disable the printing of
|
||||||
|
// pointer addresses. This is useful when diffing data structures in tests.
|
||||||
|
DisablePointerAddresses bool
|
||||||
|
|
||||||
|
// DisableCapacities specifies whether to disable the printing of capacities
|
||||||
|
// for arrays, slices, maps and channels. This is useful when diffing
|
||||||
|
// data structures in tests.
|
||||||
|
DisableCapacities bool
|
||||||
|
|
||||||
|
// ContinueOnMethod specifies whether or not recursion should continue once
|
||||||
|
// a custom error or Stringer interface is invoked. The default, false,
|
||||||
|
// means it will print the results of invoking the custom error or Stringer
|
||||||
|
// interface and return immediately instead of continuing to recurse into
|
||||||
|
// the internals of the data type.
|
||||||
|
//
|
||||||
|
// NOTE: This flag does not have any effect if method invocation is disabled
|
||||||
|
// via the DisableMethods or DisablePointerMethods options.
|
||||||
|
ContinueOnMethod bool
|
||||||
|
|
||||||
|
// SortKeys specifies map keys should be sorted before being printed. Use
|
||||||
|
// this to have a more deterministic, diffable output. Note that only
|
||||||
|
// native types (bool, int, uint, floats, uintptr and string) and types
|
||||||
|
// that support the error or Stringer interfaces (if methods are
|
||||||
|
// enabled) are supported, with other types sorted according to the
|
||||||
|
// reflect.Value.String() output which guarantees display stability.
|
||||||
|
SortKeys bool
|
||||||
|
|
||||||
|
// SpewKeys specifies that, as a last resort attempt, map keys should
|
||||||
|
// be spewed to strings and sorted by those strings. This is only
|
||||||
|
// considered if SortKeys is true.
|
||||||
|
SpewKeys bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is the active configuration of the top-level functions.
|
||||||
|
// The configuration can be changed by modifying the contents of spew.Config.
|
||||||
|
var Config = ConfigState{Indent: " "}
|
||||||
|
|
||||||
|
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the formatted string as a value that satisfies error. See NewFormatter
|
||||||
|
// for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
|
||||||
|
return fmt.Errorf(format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprint(w, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintf(w, format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintln(w, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Print(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Printf(format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Println(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Sprint(a ...interface{}) string {
|
||||||
|
return fmt.Sprint(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
|
||||||
|
return fmt.Sprintf(format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||||
|
// were passed with a Formatter interface returned by c.NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Sprintln(a ...interface{}) string {
|
||||||
|
return fmt.Sprintln(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||||
|
interface. As a result, it integrates cleanly with standard fmt package
|
||||||
|
printing functions. The formatter is useful for inline printing of smaller data
|
||||||
|
types similar to the standard %v format specifier.
|
||||||
|
|
||||||
|
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||||
|
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
|
||||||
|
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||||
|
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||||
|
the width and precision arguments (however they will still work on the format
|
||||||
|
specifiers not handled by the custom formatter).
|
||||||
|
|
||||||
|
Typically this function shouldn't be called directly. It is much easier to make
|
||||||
|
use of the custom formatter by calling one of the convenience functions such as
|
||||||
|
c.Printf, c.Println, or c.Printf.
|
||||||
|
*/
|
||||||
|
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
|
||||||
|
return newFormatter(c, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||||
|
// exactly the same as Dump.
|
||||||
|
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
|
||||||
|
fdump(c, w, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Dump displays the passed parameters to standard out with newlines, customizable
|
||||||
|
indentation, and additional debug information such as complete types and all
|
||||||
|
pointer addresses used to indirect to the final value. It provides the
|
||||||
|
following features over the built-in printing facilities provided by the fmt
|
||||||
|
package:
|
||||||
|
|
||||||
|
* Pointers are dereferenced and followed
|
||||||
|
* Circular data structures are detected and handled properly
|
||||||
|
* Custom Stringer/error interfaces are optionally invoked, including
|
||||||
|
on unexported types
|
||||||
|
* Custom types which only implement the Stringer/error interfaces via
|
||||||
|
a pointer receiver are optionally invoked when passing non-pointer
|
||||||
|
variables
|
||||||
|
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||||
|
includes offsets, byte values in hex, and ASCII output
|
||||||
|
|
||||||
|
The configuration options are controlled by modifying the public members
|
||||||
|
of c. See ConfigState for options documentation.
|
||||||
|
|
||||||
|
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||||
|
get the formatted result as a string.
|
||||||
|
*/
|
||||||
|
func (c *ConfigState) Dump(a ...interface{}) {
|
||||||
|
fdump(c, os.Stdout, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||||
|
// as Dump.
|
||||||
|
func (c *ConfigState) Sdump(a ...interface{}) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fdump(c, &buf, a...)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||||
|
// length with each argument converted to a spew Formatter interface using
|
||||||
|
// the ConfigState associated with s.
|
||||||
|
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
|
||||||
|
formatters = make([]interface{}, len(args))
|
||||||
|
for index, arg := range args {
|
||||||
|
formatters[index] = newFormatter(c, arg)
|
||||||
|
}
|
||||||
|
return formatters
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultConfig returns a ConfigState with the following default settings.
|
||||||
|
//
|
||||||
|
// Indent: " "
|
||||||
|
// MaxDepth: 0
|
||||||
|
// DisableMethods: false
|
||||||
|
// DisablePointerMethods: false
|
||||||
|
// ContinueOnMethod: false
|
||||||
|
// SortKeys: false
|
||||||
|
func NewDefaultConfig() *ConfigState {
|
||||||
|
return &ConfigState{Indent: " "}
|
||||||
|
}
|
211
vendor/github.com/davecgh/go-spew/spew/doc.go
generated
vendored
Normal file
211
vendor/github.com/davecgh/go-spew/spew/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package spew implements a deep pretty printer for Go data structures to aid in
|
||||||
|
debugging.
|
||||||
|
|
||||||
|
A quick overview of the additional features spew provides over the built-in
|
||||||
|
printing facilities for Go data types are as follows:
|
||||||
|
|
||||||
|
* Pointers are dereferenced and followed
|
||||||
|
* Circular data structures are detected and handled properly
|
||||||
|
* Custom Stringer/error interfaces are optionally invoked, including
|
||||||
|
on unexported types
|
||||||
|
* Custom types which only implement the Stringer/error interfaces via
|
||||||
|
a pointer receiver are optionally invoked when passing non-pointer
|
||||||
|
variables
|
||||||
|
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||||
|
includes offsets, byte values in hex, and ASCII output (only when using
|
||||||
|
Dump style)
|
||||||
|
|
||||||
|
There are two different approaches spew allows for dumping Go data structures:
|
||||||
|
|
||||||
|
* Dump style which prints with newlines, customizable indentation,
|
||||||
|
and additional debug information such as types and all pointer addresses
|
||||||
|
used to indirect to the final value
|
||||||
|
* A custom Formatter interface that integrates cleanly with the standard fmt
|
||||||
|
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
|
||||||
|
similar to the default %v while providing the additional functionality
|
||||||
|
outlined above and passing unsupported format verbs such as %x and %q
|
||||||
|
along to fmt
|
||||||
|
|
||||||
|
Quick Start
|
||||||
|
|
||||||
|
This section demonstrates how to quickly get started with spew. See the
|
||||||
|
sections below for further details on formatting and configuration options.
|
||||||
|
|
||||||
|
To dump a variable with full newlines, indentation, type, and pointer
|
||||||
|
information use Dump, Fdump, or Sdump:
|
||||||
|
spew.Dump(myVar1, myVar2, ...)
|
||||||
|
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
||||||
|
str := spew.Sdump(myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
Alternatively, if you would prefer to use format strings with a compacted inline
|
||||||
|
printing style, use the convenience wrappers Printf, Fprintf, etc with
|
||||||
|
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
|
||||||
|
%#+v (adds types and pointer addresses):
|
||||||
|
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
|
||||||
|
Configuration Options
|
||||||
|
|
||||||
|
Configuration of spew is handled by fields in the ConfigState type. For
|
||||||
|
convenience, all of the top-level functions use a global state available
|
||||||
|
via the spew.Config global.
|
||||||
|
|
||||||
|
It is also possible to create a ConfigState instance that provides methods
|
||||||
|
equivalent to the top-level functions. This allows concurrent configuration
|
||||||
|
options. See the ConfigState documentation for more details.
|
||||||
|
|
||||||
|
The following configuration options are available:
|
||||||
|
* Indent
|
||||||
|
String to use for each indentation level for Dump functions.
|
||||||
|
It is a single space by default. A popular alternative is "\t".
|
||||||
|
|
||||||
|
* MaxDepth
|
||||||
|
Maximum number of levels to descend into nested data structures.
|
||||||
|
There is no limit by default.
|
||||||
|
|
||||||
|
* DisableMethods
|
||||||
|
Disables invocation of error and Stringer interface methods.
|
||||||
|
Method invocation is enabled by default.
|
||||||
|
|
||||||
|
* DisablePointerMethods
|
||||||
|
Disables invocation of error and Stringer interface methods on types
|
||||||
|
which only accept pointer receivers from non-pointer variables.
|
||||||
|
Pointer method invocation is enabled by default.
|
||||||
|
|
||||||
|
* DisablePointerAddresses
|
||||||
|
DisablePointerAddresses specifies whether to disable the printing of
|
||||||
|
pointer addresses. This is useful when diffing data structures in tests.
|
||||||
|
|
||||||
|
* DisableCapacities
|
||||||
|
DisableCapacities specifies whether to disable the printing of
|
||||||
|
capacities for arrays, slices, maps and channels. This is useful when
|
||||||
|
diffing data structures in tests.
|
||||||
|
|
||||||
|
* ContinueOnMethod
|
||||||
|
Enables recursion into types after invoking error and Stringer interface
|
||||||
|
methods. Recursion after method invocation is disabled by default.
|
||||||
|
|
||||||
|
* SortKeys
|
||||||
|
Specifies map keys should be sorted before being printed. Use
|
||||||
|
this to have a more deterministic, diffable output. Note that
|
||||||
|
only native types (bool, int, uint, floats, uintptr and string)
|
||||||
|
and types which implement error or Stringer interfaces are
|
||||||
|
supported with other types sorted according to the
|
||||||
|
reflect.Value.String() output which guarantees display
|
||||||
|
stability. Natural map order is used by default.
|
||||||
|
|
||||||
|
* SpewKeys
|
||||||
|
Specifies that, as a last resort attempt, map keys should be
|
||||||
|
spewed to strings and sorted by those strings. This is only
|
||||||
|
considered if SortKeys is true.
|
||||||
|
|
||||||
|
Dump Usage
|
||||||
|
|
||||||
|
Simply call spew.Dump with a list of variables you want to dump:
|
||||||
|
|
||||||
|
spew.Dump(myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
You may also call spew.Fdump if you would prefer to output to an arbitrary
|
||||||
|
io.Writer. For example, to dump to standard error:
|
||||||
|
|
||||||
|
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
A third option is to call spew.Sdump to get the formatted output as a string:
|
||||||
|
|
||||||
|
str := spew.Sdump(myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
Sample Dump Output
|
||||||
|
|
||||||
|
See the Dump example for details on the setup of the types and variables being
|
||||||
|
shown here.
|
||||||
|
|
||||||
|
(main.Foo) {
|
||||||
|
unexportedField: (*main.Bar)(0xf84002e210)({
|
||||||
|
flag: (main.Flag) flagTwo,
|
||||||
|
data: (uintptr) <nil>
|
||||||
|
}),
|
||||||
|
ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||||
|
(string) (len=3) "one": (bool) true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
|
||||||
|
command as shown.
|
||||||
|
([]uint8) (len=32 cap=32) {
|
||||||
|
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||||
|
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||||
|
00000020 31 32 |12|
|
||||||
|
}
|
||||||
|
|
||||||
|
Custom Formatter
|
||||||
|
|
||||||
|
Spew provides a custom formatter that implements the fmt.Formatter interface
|
||||||
|
so that it integrates cleanly with standard fmt package printing functions. The
|
||||||
|
formatter is useful for inline printing of smaller data types similar to the
|
||||||
|
standard %v format specifier.
|
||||||
|
|
||||||
|
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||||
|
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||||
|
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||||
|
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||||
|
the width and precision arguments (however they will still work on the format
|
||||||
|
specifiers not handled by the custom formatter).
|
||||||
|
|
||||||
|
Custom Formatter Usage
|
||||||
|
|
||||||
|
The simplest way to make use of the spew custom formatter is to call one of the
|
||||||
|
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
|
||||||
|
functions have syntax you are most likely already familiar with:
|
||||||
|
|
||||||
|
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
spew.Println(myVar, myVar2)
|
||||||
|
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
|
||||||
|
See the Index for the full list convenience functions.
|
||||||
|
|
||||||
|
Sample Formatter Output
|
||||||
|
|
||||||
|
Double pointer to a uint8:
|
||||||
|
%v: <**>5
|
||||||
|
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
||||||
|
%#v: (**uint8)5
|
||||||
|
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
||||||
|
|
||||||
|
Pointer to circular struct with a uint8 field and a pointer to itself:
|
||||||
|
%v: <*>{1 <*><shown>}
|
||||||
|
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
||||||
|
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
||||||
|
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
||||||
|
|
||||||
|
See the Printf example for details on the setup of variables being shown
|
||||||
|
here.
|
||||||
|
|
||||||
|
Errors
|
||||||
|
|
||||||
|
Since it is possible for custom Stringer/error interfaces to panic, spew
|
||||||
|
detects them and handles them internally by printing the panic information
|
||||||
|
inline with the output. Since spew is intended to provide deep pretty printing
|
||||||
|
capabilities on structures, it intentionally does not return any errors.
|
||||||
|
*/
|
||||||
|
package spew
|
509
vendor/github.com/davecgh/go-spew/spew/dump.go
generated
vendored
Normal file
509
vendor/github.com/davecgh/go-spew/spew/dump.go
generated
vendored
Normal file
|
@ -0,0 +1,509 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// uint8Type is a reflect.Type representing a uint8. It is used to
|
||||||
|
// convert cgo types to uint8 slices for hexdumping.
|
||||||
|
uint8Type = reflect.TypeOf(uint8(0))
|
||||||
|
|
||||||
|
// cCharRE is a regular expression that matches a cgo char.
|
||||||
|
// It is used to detect character arrays to hexdump them.
|
||||||
|
cCharRE = regexp.MustCompile("^.*\\._Ctype_char$")
|
||||||
|
|
||||||
|
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
||||||
|
// char. It is used to detect unsigned character arrays to hexdump
|
||||||
|
// them.
|
||||||
|
cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$")
|
||||||
|
|
||||||
|
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
||||||
|
// It is used to detect uint8_t arrays to hexdump them.
|
||||||
|
cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$")
|
||||||
|
)
|
||||||
|
|
||||||
|
// dumpState contains information about the state of a dump operation.
|
||||||
|
type dumpState struct {
|
||||||
|
w io.Writer
|
||||||
|
depth int
|
||||||
|
pointers map[uintptr]int
|
||||||
|
ignoreNextType bool
|
||||||
|
ignoreNextIndent bool
|
||||||
|
cs *ConfigState
|
||||||
|
}
|
||||||
|
|
||||||
|
// indent performs indentation according to the depth level and cs.Indent
|
||||||
|
// option.
|
||||||
|
func (d *dumpState) indent() {
|
||||||
|
if d.ignoreNextIndent {
|
||||||
|
d.ignoreNextIndent = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackValue returns values inside of non-nil interfaces when possible.
|
||||||
|
// This is useful for data types like structs, arrays, slices, and maps which
|
||||||
|
// can contain varying types packed inside an interface.
|
||||||
|
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
|
||||||
|
if v.Kind() == reflect.Interface && !v.IsNil() {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// dumpPtr handles formatting of pointers by indirecting them as necessary.
|
||||||
|
func (d *dumpState) dumpPtr(v reflect.Value) {
|
||||||
|
// Remove pointers at or below the current depth from map used to detect
|
||||||
|
// circular refs.
|
||||||
|
for k, depth := range d.pointers {
|
||||||
|
if depth >= d.depth {
|
||||||
|
delete(d.pointers, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep list of all dereferenced pointers to show later.
|
||||||
|
pointerChain := make([]uintptr, 0)
|
||||||
|
|
||||||
|
// Figure out how many levels of indirection there are by dereferencing
|
||||||
|
// pointers and unpacking interfaces down the chain while detecting circular
|
||||||
|
// references.
|
||||||
|
nilFound := false
|
||||||
|
cycleFound := false
|
||||||
|
indirects := 0
|
||||||
|
ve := v
|
||||||
|
for ve.Kind() == reflect.Ptr {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
indirects++
|
||||||
|
addr := ve.Pointer()
|
||||||
|
pointerChain = append(pointerChain, addr)
|
||||||
|
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
||||||
|
cycleFound = true
|
||||||
|
indirects--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
d.pointers[addr] = d.depth
|
||||||
|
|
||||||
|
ve = ve.Elem()
|
||||||
|
if ve.Kind() == reflect.Interface {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ve = ve.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display type information.
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||||
|
d.w.Write([]byte(ve.Type().String()))
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
|
||||||
|
// Display pointer information.
|
||||||
|
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
for i, addr := range pointerChain {
|
||||||
|
if i > 0 {
|
||||||
|
d.w.Write(pointerChainBytes)
|
||||||
|
}
|
||||||
|
printHexPtr(d.w, addr)
|
||||||
|
}
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display dereferenced value.
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
switch {
|
||||||
|
case nilFound == true:
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
|
||||||
|
case cycleFound == true:
|
||||||
|
d.w.Write(circularBytes)
|
||||||
|
|
||||||
|
default:
|
||||||
|
d.ignoreNextType = true
|
||||||
|
d.dump(ve)
|
||||||
|
}
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
|
||||||
|
// reflection) arrays and slices are dumped in hexdump -C fashion.
|
||||||
|
func (d *dumpState) dumpSlice(v reflect.Value) {
|
||||||
|
// Determine whether this type should be hex dumped or not. Also,
|
||||||
|
// for types which should be hexdumped, try to use the underlying data
|
||||||
|
// first, then fall back to trying to convert them to a uint8 slice.
|
||||||
|
var buf []uint8
|
||||||
|
doConvert := false
|
||||||
|
doHexDump := false
|
||||||
|
numEntries := v.Len()
|
||||||
|
if numEntries > 0 {
|
||||||
|
vt := v.Index(0).Type()
|
||||||
|
vts := vt.String()
|
||||||
|
switch {
|
||||||
|
// C types that need to be converted.
|
||||||
|
case cCharRE.MatchString(vts):
|
||||||
|
fallthrough
|
||||||
|
case cUnsignedCharRE.MatchString(vts):
|
||||||
|
fallthrough
|
||||||
|
case cUint8tCharRE.MatchString(vts):
|
||||||
|
doConvert = true
|
||||||
|
|
||||||
|
// Try to use existing uint8 slices and fall back to converting
|
||||||
|
// and copying if that fails.
|
||||||
|
case vt.Kind() == reflect.Uint8:
|
||||||
|
// We need an addressable interface to convert the type
|
||||||
|
// to a byte slice. However, the reflect package won't
|
||||||
|
// give us an interface on certain things like
|
||||||
|
// unexported struct fields in order to enforce
|
||||||
|
// visibility rules. We use unsafe, when available, to
|
||||||
|
// bypass these restrictions since this package does not
|
||||||
|
// mutate the values.
|
||||||
|
vs := v
|
||||||
|
if !vs.CanInterface() || !vs.CanAddr() {
|
||||||
|
vs = unsafeReflectValue(vs)
|
||||||
|
}
|
||||||
|
if !UnsafeDisabled {
|
||||||
|
vs = vs.Slice(0, numEntries)
|
||||||
|
|
||||||
|
// Use the existing uint8 slice if it can be
|
||||||
|
// type asserted.
|
||||||
|
iface := vs.Interface()
|
||||||
|
if slice, ok := iface.([]uint8); ok {
|
||||||
|
buf = slice
|
||||||
|
doHexDump = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The underlying data needs to be converted if it can't
|
||||||
|
// be type asserted to a uint8 slice.
|
||||||
|
doConvert = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy and convert the underlying type if needed.
|
||||||
|
if doConvert && vt.ConvertibleTo(uint8Type) {
|
||||||
|
// Convert and copy each element into a uint8 byte
|
||||||
|
// slice.
|
||||||
|
buf = make([]uint8, numEntries)
|
||||||
|
for i := 0; i < numEntries; i++ {
|
||||||
|
vv := v.Index(i)
|
||||||
|
buf[i] = uint8(vv.Convert(uint8Type).Uint())
|
||||||
|
}
|
||||||
|
doHexDump = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hexdump the entire slice as needed.
|
||||||
|
if doHexDump {
|
||||||
|
indent := strings.Repeat(d.cs.Indent, d.depth)
|
||||||
|
str := indent + hex.Dump(buf)
|
||||||
|
str = strings.Replace(str, "\n", "\n"+indent, -1)
|
||||||
|
str = strings.TrimRight(str, d.cs.Indent)
|
||||||
|
d.w.Write([]byte(str))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively call dump for each item.
|
||||||
|
for i := 0; i < numEntries; i++ {
|
||||||
|
d.dump(d.unpackValue(v.Index(i)))
|
||||||
|
if i < (numEntries - 1) {
|
||||||
|
d.w.Write(commaNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
||||||
|
// value to figure out what kind of object we are dealing with and formats it
|
||||||
|
// appropriately. It is a recursive function, however circular data structures
|
||||||
|
// are detected and handled properly.
|
||||||
|
func (d *dumpState) dump(v reflect.Value) {
|
||||||
|
// Handle invalid reflect values immediately.
|
||||||
|
kind := v.Kind()
|
||||||
|
if kind == reflect.Invalid {
|
||||||
|
d.w.Write(invalidAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle pointers specially.
|
||||||
|
if kind == reflect.Ptr {
|
||||||
|
d.indent()
|
||||||
|
d.dumpPtr(v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print type information unless already handled elsewhere.
|
||||||
|
if !d.ignoreNextType {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
d.w.Write([]byte(v.Type().String()))
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
d.w.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
d.ignoreNextType = false
|
||||||
|
|
||||||
|
// Display length and capacity if the built-in len and cap functions
|
||||||
|
// work with the value's kind and the len/cap itself is non-zero.
|
||||||
|
valueLen, valueCap := 0, 0
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Slice, reflect.Chan:
|
||||||
|
valueLen, valueCap = v.Len(), v.Cap()
|
||||||
|
case reflect.Map, reflect.String:
|
||||||
|
valueLen = v.Len()
|
||||||
|
}
|
||||||
|
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
if valueLen != 0 {
|
||||||
|
d.w.Write(lenEqualsBytes)
|
||||||
|
printInt(d.w, int64(valueLen), 10)
|
||||||
|
}
|
||||||
|
if !d.cs.DisableCapacities && valueCap != 0 {
|
||||||
|
if valueLen != 0 {
|
||||||
|
d.w.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
d.w.Write(capEqualsBytes)
|
||||||
|
printInt(d.w, int64(valueCap), 10)
|
||||||
|
}
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
d.w.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call Stringer/error interfaces if they exist and the handle methods flag
|
||||||
|
// is enabled
|
||||||
|
if !d.cs.DisableMethods {
|
||||||
|
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||||
|
if handled := handleMethods(d.cs, d.w, v); handled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.Invalid:
|
||||||
|
// Do nothing. We should never get here since invalid has already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
printBool(d.w, v.Bool())
|
||||||
|
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
printInt(d.w, v.Int(), 10)
|
||||||
|
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
printUint(d.w, v.Uint(), 10)
|
||||||
|
|
||||||
|
case reflect.Float32:
|
||||||
|
printFloat(d.w, v.Float(), 32)
|
||||||
|
|
||||||
|
case reflect.Float64:
|
||||||
|
printFloat(d.w, v.Float(), 64)
|
||||||
|
|
||||||
|
case reflect.Complex64:
|
||||||
|
printComplex(d.w, v.Complex(), 32)
|
||||||
|
|
||||||
|
case reflect.Complex128:
|
||||||
|
printComplex(d.w, v.Complex(), 64)
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.IsNil() {
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case reflect.Array:
|
||||||
|
d.w.Write(openBraceNewlineBytes)
|
||||||
|
d.depth++
|
||||||
|
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(maxNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.dumpSlice(v)
|
||||||
|
}
|
||||||
|
d.depth--
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.String:
|
||||||
|
d.w.Write([]byte(strconv.Quote(v.String())))
|
||||||
|
|
||||||
|
case reflect.Interface:
|
||||||
|
// The only time we should get here is for nil interfaces due to
|
||||||
|
// unpackValue calls.
|
||||||
|
if v.IsNil() {
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
// Do nothing. We should never get here since pointers have already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
// nil maps should be indicated as different than empty maps
|
||||||
|
if v.IsNil() {
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
d.w.Write(openBraceNewlineBytes)
|
||||||
|
d.depth++
|
||||||
|
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(maxNewlineBytes)
|
||||||
|
} else {
|
||||||
|
numEntries := v.Len()
|
||||||
|
keys := v.MapKeys()
|
||||||
|
if d.cs.SortKeys {
|
||||||
|
sortValues(keys, d.cs)
|
||||||
|
}
|
||||||
|
for i, key := range keys {
|
||||||
|
d.dump(d.unpackValue(key))
|
||||||
|
d.w.Write(colonSpaceBytes)
|
||||||
|
d.ignoreNextIndent = true
|
||||||
|
d.dump(d.unpackValue(v.MapIndex(key)))
|
||||||
|
if i < (numEntries - 1) {
|
||||||
|
d.w.Write(commaNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.depth--
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
d.w.Write(openBraceNewlineBytes)
|
||||||
|
d.depth++
|
||||||
|
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(maxNewlineBytes)
|
||||||
|
} else {
|
||||||
|
vt := v.Type()
|
||||||
|
numFields := v.NumField()
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
d.indent()
|
||||||
|
vtf := vt.Field(i)
|
||||||
|
d.w.Write([]byte(vtf.Name))
|
||||||
|
d.w.Write(colonSpaceBytes)
|
||||||
|
d.ignoreNextIndent = true
|
||||||
|
d.dump(d.unpackValue(v.Field(i)))
|
||||||
|
if i < (numFields - 1) {
|
||||||
|
d.w.Write(commaNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.depth--
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.Uintptr:
|
||||||
|
printHexPtr(d.w, uintptr(v.Uint()))
|
||||||
|
|
||||||
|
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||||
|
printHexPtr(d.w, v.Pointer())
|
||||||
|
|
||||||
|
// There were not any other types at the time this code was written, but
|
||||||
|
// fall back to letting the default fmt package handle it in case any new
|
||||||
|
// types are added.
|
||||||
|
default:
|
||||||
|
if v.CanInterface() {
|
||||||
|
fmt.Fprintf(d.w, "%v", v.Interface())
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(d.w, "%v", v.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fdump is a helper function to consolidate the logic from the various public
|
||||||
|
// methods which take varying writers and config states.
|
||||||
|
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
|
||||||
|
for _, arg := range a {
|
||||||
|
if arg == nil {
|
||||||
|
w.Write(interfaceBytes)
|
||||||
|
w.Write(spaceBytes)
|
||||||
|
w.Write(nilAngleBytes)
|
||||||
|
w.Write(newlineBytes)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
d := dumpState{w: w, cs: cs}
|
||||||
|
d.pointers = make(map[uintptr]int)
|
||||||
|
d.dump(reflect.ValueOf(arg))
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||||
|
// exactly the same as Dump.
|
||||||
|
func Fdump(w io.Writer, a ...interface{}) {
|
||||||
|
fdump(&Config, w, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||||
|
// as Dump.
|
||||||
|
func Sdump(a ...interface{}) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fdump(&Config, &buf, a...)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Dump displays the passed parameters to standard out with newlines, customizable
|
||||||
|
indentation, and additional debug information such as complete types and all
|
||||||
|
pointer addresses used to indirect to the final value. It provides the
|
||||||
|
following features over the built-in printing facilities provided by the fmt
|
||||||
|
package:
|
||||||
|
|
||||||
|
* Pointers are dereferenced and followed
|
||||||
|
* Circular data structures are detected and handled properly
|
||||||
|
* Custom Stringer/error interfaces are optionally invoked, including
|
||||||
|
on unexported types
|
||||||
|
* Custom types which only implement the Stringer/error interfaces via
|
||||||
|
a pointer receiver are optionally invoked when passing non-pointer
|
||||||
|
variables
|
||||||
|
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||||
|
includes offsets, byte values in hex, and ASCII output
|
||||||
|
|
||||||
|
The configuration options are controlled by an exported package global,
|
||||||
|
spew.Config. See ConfigState for options documentation.
|
||||||
|
|
||||||
|
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||||
|
get the formatted result as a string.
|
||||||
|
*/
|
||||||
|
func Dump(a ...interface{}) {
|
||||||
|
fdump(&Config, os.Stdout, a...)
|
||||||
|
}
|
1042
vendor/github.com/davecgh/go-spew/spew/dump_test.go
generated
vendored
Normal file
1042
vendor/github.com/davecgh/go-spew/spew/dump_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
99
vendor/github.com/davecgh/go-spew/spew/dumpcgo_test.go
generated
vendored
Normal file
99
vendor/github.com/davecgh/go-spew/spew/dumpcgo_test.go
generated
vendored
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
// Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when both cgo is supported and "-tags testcgo" is added to the go test
|
||||||
|
// command line. This means the cgo tests are only added (and hence run) when
|
||||||
|
// specifially requested. This configuration is used because spew itself
|
||||||
|
// does not require cgo to run even though it does handle certain cgo types
|
||||||
|
// specially. Rather than forcing all clients to require cgo and an external
|
||||||
|
// C compiler just to run the tests, this scheme makes them optional.
|
||||||
|
// +build cgo,testcgo
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew/testdata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addCgoDumpTests() {
|
||||||
|
// C char pointer.
|
||||||
|
v := testdata.GetCgoCharPointer()
|
||||||
|
nv := testdata.GetCgoNullCharPointer()
|
||||||
|
pv := &v
|
||||||
|
vcAddr := fmt.Sprintf("%p", v)
|
||||||
|
vAddr := fmt.Sprintf("%p", pv)
|
||||||
|
pvAddr := fmt.Sprintf("%p", &pv)
|
||||||
|
vt := "*testdata._Ctype_char"
|
||||||
|
vs := "116"
|
||||||
|
addDumpTest(v, "("+vt+")("+vcAddr+")("+vs+")\n")
|
||||||
|
addDumpTest(pv, "(*"+vt+")("+vAddr+"->"+vcAddr+")("+vs+")\n")
|
||||||
|
addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+"->"+vcAddr+")("+vs+")\n")
|
||||||
|
addDumpTest(nv, "("+vt+")(<nil>)\n")
|
||||||
|
|
||||||
|
// C char array.
|
||||||
|
v2, v2l, v2c := testdata.GetCgoCharArray()
|
||||||
|
v2Len := fmt.Sprintf("%d", v2l)
|
||||||
|
v2Cap := fmt.Sprintf("%d", v2c)
|
||||||
|
v2t := "[6]testdata._Ctype_char"
|
||||||
|
v2s := "(len=" + v2Len + " cap=" + v2Cap + ") " +
|
||||||
|
"{\n 00000000 74 65 73 74 32 00 " +
|
||||||
|
" |test2.|\n}"
|
||||||
|
addDumpTest(v2, "("+v2t+") "+v2s+"\n")
|
||||||
|
|
||||||
|
// C unsigned char array.
|
||||||
|
v3, v3l, v3c := testdata.GetCgoUnsignedCharArray()
|
||||||
|
v3Len := fmt.Sprintf("%d", v3l)
|
||||||
|
v3Cap := fmt.Sprintf("%d", v3c)
|
||||||
|
v3t := "[6]testdata._Ctype_unsignedchar"
|
||||||
|
v3t2 := "[6]testdata._Ctype_uchar"
|
||||||
|
v3s := "(len=" + v3Len + " cap=" + v3Cap + ") " +
|
||||||
|
"{\n 00000000 74 65 73 74 33 00 " +
|
||||||
|
" |test3.|\n}"
|
||||||
|
addDumpTest(v3, "("+v3t+") "+v3s+"\n", "("+v3t2+") "+v3s+"\n")
|
||||||
|
|
||||||
|
// C signed char array.
|
||||||
|
v4, v4l, v4c := testdata.GetCgoSignedCharArray()
|
||||||
|
v4Len := fmt.Sprintf("%d", v4l)
|
||||||
|
v4Cap := fmt.Sprintf("%d", v4c)
|
||||||
|
v4t := "[6]testdata._Ctype_schar"
|
||||||
|
v4t2 := "testdata._Ctype_schar"
|
||||||
|
v4s := "(len=" + v4Len + " cap=" + v4Cap + ") " +
|
||||||
|
"{\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 101,\n (" + v4t2 +
|
||||||
|
") 115,\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 52,\n (" + v4t2 +
|
||||||
|
") 0\n}"
|
||||||
|
addDumpTest(v4, "("+v4t+") "+v4s+"\n")
|
||||||
|
|
||||||
|
// C uint8_t array.
|
||||||
|
v5, v5l, v5c := testdata.GetCgoUint8tArray()
|
||||||
|
v5Len := fmt.Sprintf("%d", v5l)
|
||||||
|
v5Cap := fmt.Sprintf("%d", v5c)
|
||||||
|
v5t := "[6]testdata._Ctype_uint8_t"
|
||||||
|
v5s := "(len=" + v5Len + " cap=" + v5Cap + ") " +
|
||||||
|
"{\n 00000000 74 65 73 74 35 00 " +
|
||||||
|
" |test5.|\n}"
|
||||||
|
addDumpTest(v5, "("+v5t+") "+v5s+"\n")
|
||||||
|
|
||||||
|
// C typedefed unsigned char array.
|
||||||
|
v6, v6l, v6c := testdata.GetCgoTypdefedUnsignedCharArray()
|
||||||
|
v6Len := fmt.Sprintf("%d", v6l)
|
||||||
|
v6Cap := fmt.Sprintf("%d", v6c)
|
||||||
|
v6t := "[6]testdata._Ctype_custom_uchar_t"
|
||||||
|
v6s := "(len=" + v6Len + " cap=" + v6Cap + ") " +
|
||||||
|
"{\n 00000000 74 65 73 74 36 00 " +
|
||||||
|
" |test6.|\n}"
|
||||||
|
addDumpTest(v6, "("+v6t+") "+v6s+"\n")
|
||||||
|
}
|
26
vendor/github.com/davecgh/go-spew/spew/dumpnocgo_test.go
generated
vendored
Normal file
26
vendor/github.com/davecgh/go-spew/spew/dumpnocgo_test.go
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when either cgo is not supported or "-tags testcgo" is not added to the go
|
||||||
|
// test command line. This file intentionally does not setup any cgo tests in
|
||||||
|
// this scenario.
|
||||||
|
// +build !cgo !testcgo
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
func addCgoDumpTests() {
|
||||||
|
// Don't add any tests for cgo since this file is only compiled when
|
||||||
|
// there should not be any cgo tests.
|
||||||
|
}
|
226
vendor/github.com/davecgh/go-spew/spew/example_test.go
generated
vendored
Normal file
226
vendor/github.com/davecgh/go-spew/spew/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Flag int
|
||||||
|
|
||||||
|
const (
|
||||||
|
flagOne Flag = iota
|
||||||
|
flagTwo
|
||||||
|
)
|
||||||
|
|
||||||
|
var flagStrings = map[Flag]string{
|
||||||
|
flagOne: "flagOne",
|
||||||
|
flagTwo: "flagTwo",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Flag) String() string {
|
||||||
|
if s, ok := flagStrings[f]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Unknown flag (%d)", int(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bar struct {
|
||||||
|
data uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type Foo struct {
|
||||||
|
unexportedField Bar
|
||||||
|
ExportedField map[interface{}]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use Dump to dump variables to stdout.
|
||||||
|
func ExampleDump() {
|
||||||
|
// The following package level declarations are assumed for this example:
|
||||||
|
/*
|
||||||
|
type Flag int
|
||||||
|
|
||||||
|
const (
|
||||||
|
flagOne Flag = iota
|
||||||
|
flagTwo
|
||||||
|
)
|
||||||
|
|
||||||
|
var flagStrings = map[Flag]string{
|
||||||
|
flagOne: "flagOne",
|
||||||
|
flagTwo: "flagTwo",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Flag) String() string {
|
||||||
|
if s, ok := flagStrings[f]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Unknown flag (%d)", int(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bar struct {
|
||||||
|
data uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type Foo struct {
|
||||||
|
unexportedField Bar
|
||||||
|
ExportedField map[interface{}]interface{}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Setup some sample data structures for the example.
|
||||||
|
bar := Bar{uintptr(0)}
|
||||||
|
s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
|
||||||
|
f := Flag(5)
|
||||||
|
b := []byte{
|
||||||
|
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
||||||
|
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
|
||||||
|
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
|
||||||
|
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
|
||||||
|
0x31, 0x32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump!
|
||||||
|
spew.Dump(s1, f, b)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// (spew_test.Foo) {
|
||||||
|
// unexportedField: (spew_test.Bar) {
|
||||||
|
// data: (uintptr) <nil>
|
||||||
|
// },
|
||||||
|
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||||
|
// (string) (len=3) "one": (bool) true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// (spew_test.Flag) Unknown flag (5)
|
||||||
|
// ([]uint8) (len=34 cap=34) {
|
||||||
|
// 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||||
|
// 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||||
|
// 00000020 31 32 |12|
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use Printf to display a variable with a
|
||||||
|
// format string and inline formatting.
|
||||||
|
func ExamplePrintf() {
|
||||||
|
// Create a double pointer to a uint 8.
|
||||||
|
ui8 := uint8(5)
|
||||||
|
pui8 := &ui8
|
||||||
|
ppui8 := &pui8
|
||||||
|
|
||||||
|
// Create a circular data type.
|
||||||
|
type circular struct {
|
||||||
|
ui8 uint8
|
||||||
|
c *circular
|
||||||
|
}
|
||||||
|
c := circular{ui8: 1}
|
||||||
|
c.c = &c
|
||||||
|
|
||||||
|
// Print!
|
||||||
|
spew.Printf("ppui8: %v\n", ppui8)
|
||||||
|
spew.Printf("circular: %v\n", c)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// ppui8: <**>5
|
||||||
|
// circular: {1 <*>{1 <*><shown>}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use a ConfigState.
|
||||||
|
func ExampleConfigState() {
|
||||||
|
// Modify the indent level of the ConfigState only. The global
|
||||||
|
// configuration is not modified.
|
||||||
|
scs := spew.ConfigState{Indent: "\t"}
|
||||||
|
|
||||||
|
// Output using the ConfigState instance.
|
||||||
|
v := map[string]int{"one": 1}
|
||||||
|
scs.Printf("v: %v\n", v)
|
||||||
|
scs.Dump(v)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// v: map[one:1]
|
||||||
|
// (map[string]int) (len=1) {
|
||||||
|
// (string) (len=3) "one": (int) 1
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use ConfigState.Dump to dump variables to
|
||||||
|
// stdout
|
||||||
|
func ExampleConfigState_Dump() {
|
||||||
|
// See the top-level Dump example for details on the types used in this
|
||||||
|
// example.
|
||||||
|
|
||||||
|
// Create two ConfigState instances with different indentation.
|
||||||
|
scs := spew.ConfigState{Indent: "\t"}
|
||||||
|
scs2 := spew.ConfigState{Indent: " "}
|
||||||
|
|
||||||
|
// Setup some sample data structures for the example.
|
||||||
|
bar := Bar{uintptr(0)}
|
||||||
|
s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
|
||||||
|
|
||||||
|
// Dump using the ConfigState instances.
|
||||||
|
scs.Dump(s1)
|
||||||
|
scs2.Dump(s1)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// (spew_test.Foo) {
|
||||||
|
// unexportedField: (spew_test.Bar) {
|
||||||
|
// data: (uintptr) <nil>
|
||||||
|
// },
|
||||||
|
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||||
|
// (string) (len=3) "one": (bool) true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// (spew_test.Foo) {
|
||||||
|
// unexportedField: (spew_test.Bar) {
|
||||||
|
// data: (uintptr) <nil>
|
||||||
|
// },
|
||||||
|
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||||
|
// (string) (len=3) "one": (bool) true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use ConfigState.Printf to display a variable
|
||||||
|
// with a format string and inline formatting.
|
||||||
|
func ExampleConfigState_Printf() {
|
||||||
|
// See the top-level Dump example for details on the types used in this
|
||||||
|
// example.
|
||||||
|
|
||||||
|
// Create two ConfigState instances and modify the method handling of the
|
||||||
|
// first ConfigState only.
|
||||||
|
scs := spew.NewDefaultConfig()
|
||||||
|
scs2 := spew.NewDefaultConfig()
|
||||||
|
scs.DisableMethods = true
|
||||||
|
|
||||||
|
// Alternatively
|
||||||
|
// scs := spew.ConfigState{Indent: " ", DisableMethods: true}
|
||||||
|
// scs2 := spew.ConfigState{Indent: " "}
|
||||||
|
|
||||||
|
// This is of type Flag which implements a Stringer and has raw value 1.
|
||||||
|
f := flagTwo
|
||||||
|
|
||||||
|
// Dump using the ConfigState instances.
|
||||||
|
scs.Printf("f: %v\n", f)
|
||||||
|
scs2.Printf("f: %v\n", f)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// f: 1
|
||||||
|
// f: flagTwo
|
||||||
|
}
|
419
vendor/github.com/davecgh/go-spew/spew/format.go
generated
vendored
Normal file
419
vendor/github.com/davecgh/go-spew/spew/format.go
generated
vendored
Normal file
|
@ -0,0 +1,419 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// supportedFlags is a list of all the character flags supported by fmt package.
|
||||||
|
const supportedFlags = "0-+# "
|
||||||
|
|
||||||
|
// formatState implements the fmt.Formatter interface and contains information
|
||||||
|
// about the state of a formatting operation. The NewFormatter function can
|
||||||
|
// be used to get a new Formatter which can be used directly as arguments
|
||||||
|
// in standard fmt package printing calls.
|
||||||
|
type formatState struct {
|
||||||
|
value interface{}
|
||||||
|
fs fmt.State
|
||||||
|
depth int
|
||||||
|
pointers map[uintptr]int
|
||||||
|
ignoreNextType bool
|
||||||
|
cs *ConfigState
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildDefaultFormat recreates the original format string without precision
|
||||||
|
// and width information to pass in to fmt.Sprintf in the case of an
|
||||||
|
// unrecognized type. Unless new types are added to the language, this
|
||||||
|
// function won't ever be called.
|
||||||
|
func (f *formatState) buildDefaultFormat() (format string) {
|
||||||
|
buf := bytes.NewBuffer(percentBytes)
|
||||||
|
|
||||||
|
for _, flag := range supportedFlags {
|
||||||
|
if f.fs.Flag(int(flag)) {
|
||||||
|
buf.WriteRune(flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteRune('v')
|
||||||
|
|
||||||
|
format = buf.String()
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
// constructOrigFormat recreates the original format string including precision
|
||||||
|
// and width information to pass along to the standard fmt package. This allows
|
||||||
|
// automatic deferral of all format strings this package doesn't support.
|
||||||
|
func (f *formatState) constructOrigFormat(verb rune) (format string) {
|
||||||
|
buf := bytes.NewBuffer(percentBytes)
|
||||||
|
|
||||||
|
for _, flag := range supportedFlags {
|
||||||
|
if f.fs.Flag(int(flag)) {
|
||||||
|
buf.WriteRune(flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if width, ok := f.fs.Width(); ok {
|
||||||
|
buf.WriteString(strconv.Itoa(width))
|
||||||
|
}
|
||||||
|
|
||||||
|
if precision, ok := f.fs.Precision(); ok {
|
||||||
|
buf.Write(precisionBytes)
|
||||||
|
buf.WriteString(strconv.Itoa(precision))
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteRune(verb)
|
||||||
|
|
||||||
|
format = buf.String()
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackValue returns values inside of non-nil interfaces when possible and
|
||||||
|
// ensures that types for values which have been unpacked from an interface
|
||||||
|
// are displayed when the show types flag is also set.
|
||||||
|
// This is useful for data types like structs, arrays, slices, and maps which
|
||||||
|
// can contain varying types packed inside an interface.
|
||||||
|
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
|
||||||
|
if v.Kind() == reflect.Interface {
|
||||||
|
f.ignoreNextType = false
|
||||||
|
if !v.IsNil() {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatPtr handles formatting of pointers by indirecting them as necessary.
|
||||||
|
func (f *formatState) formatPtr(v reflect.Value) {
|
||||||
|
// Display nil if top level pointer is nil.
|
||||||
|
showTypes := f.fs.Flag('#')
|
||||||
|
if v.IsNil() && (!showTypes || f.ignoreNextType) {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove pointers at or below the current depth from map used to detect
|
||||||
|
// circular refs.
|
||||||
|
for k, depth := range f.pointers {
|
||||||
|
if depth >= f.depth {
|
||||||
|
delete(f.pointers, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep list of all dereferenced pointers to possibly show later.
|
||||||
|
pointerChain := make([]uintptr, 0)
|
||||||
|
|
||||||
|
// Figure out how many levels of indirection there are by derferencing
|
||||||
|
// pointers and unpacking interfaces down the chain while detecting circular
|
||||||
|
// references.
|
||||||
|
nilFound := false
|
||||||
|
cycleFound := false
|
||||||
|
indirects := 0
|
||||||
|
ve := v
|
||||||
|
for ve.Kind() == reflect.Ptr {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
indirects++
|
||||||
|
addr := ve.Pointer()
|
||||||
|
pointerChain = append(pointerChain, addr)
|
||||||
|
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
|
||||||
|
cycleFound = true
|
||||||
|
indirects--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
f.pointers[addr] = f.depth
|
||||||
|
|
||||||
|
ve = ve.Elem()
|
||||||
|
if ve.Kind() == reflect.Interface {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ve = ve.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display type or indirection level depending on flags.
|
||||||
|
if showTypes && !f.ignoreNextType {
|
||||||
|
f.fs.Write(openParenBytes)
|
||||||
|
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||||
|
f.fs.Write([]byte(ve.Type().String()))
|
||||||
|
f.fs.Write(closeParenBytes)
|
||||||
|
} else {
|
||||||
|
if nilFound || cycleFound {
|
||||||
|
indirects += strings.Count(ve.Type().String(), "*")
|
||||||
|
}
|
||||||
|
f.fs.Write(openAngleBytes)
|
||||||
|
f.fs.Write([]byte(strings.Repeat("*", indirects)))
|
||||||
|
f.fs.Write(closeAngleBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display pointer information depending on flags.
|
||||||
|
if f.fs.Flag('+') && (len(pointerChain) > 0) {
|
||||||
|
f.fs.Write(openParenBytes)
|
||||||
|
for i, addr := range pointerChain {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(pointerChainBytes)
|
||||||
|
}
|
||||||
|
printHexPtr(f.fs, addr)
|
||||||
|
}
|
||||||
|
f.fs.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display dereferenced value.
|
||||||
|
switch {
|
||||||
|
case nilFound == true:
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
|
||||||
|
case cycleFound == true:
|
||||||
|
f.fs.Write(circularShortBytes)
|
||||||
|
|
||||||
|
default:
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(ve)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// format is the main workhorse for providing the Formatter interface. It
|
||||||
|
// uses the passed reflect value to figure out what kind of object we are
|
||||||
|
// dealing with and formats it appropriately. It is a recursive function,
|
||||||
|
// however circular data structures are detected and handled properly.
|
||||||
|
func (f *formatState) format(v reflect.Value) {
|
||||||
|
// Handle invalid reflect values immediately.
|
||||||
|
kind := v.Kind()
|
||||||
|
if kind == reflect.Invalid {
|
||||||
|
f.fs.Write(invalidAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle pointers specially.
|
||||||
|
if kind == reflect.Ptr {
|
||||||
|
f.formatPtr(v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print type information unless already handled elsewhere.
|
||||||
|
if !f.ignoreNextType && f.fs.Flag('#') {
|
||||||
|
f.fs.Write(openParenBytes)
|
||||||
|
f.fs.Write([]byte(v.Type().String()))
|
||||||
|
f.fs.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
f.ignoreNextType = false
|
||||||
|
|
||||||
|
// Call Stringer/error interfaces if they exist and the handle methods
|
||||||
|
// flag is enabled.
|
||||||
|
if !f.cs.DisableMethods {
|
||||||
|
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||||
|
if handled := handleMethods(f.cs, f.fs, v); handled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.Invalid:
|
||||||
|
// Do nothing. We should never get here since invalid has already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
printBool(f.fs, v.Bool())
|
||||||
|
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
printInt(f.fs, v.Int(), 10)
|
||||||
|
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
printUint(f.fs, v.Uint(), 10)
|
||||||
|
|
||||||
|
case reflect.Float32:
|
||||||
|
printFloat(f.fs, v.Float(), 32)
|
||||||
|
|
||||||
|
case reflect.Float64:
|
||||||
|
printFloat(f.fs, v.Float(), 64)
|
||||||
|
|
||||||
|
case reflect.Complex64:
|
||||||
|
printComplex(f.fs, v.Complex(), 32)
|
||||||
|
|
||||||
|
case reflect.Complex128:
|
||||||
|
printComplex(f.fs, v.Complex(), 64)
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.IsNil() {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case reflect.Array:
|
||||||
|
f.fs.Write(openBracketBytes)
|
||||||
|
f.depth++
|
||||||
|
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||||
|
f.fs.Write(maxShortBytes)
|
||||||
|
} else {
|
||||||
|
numEntries := v.Len()
|
||||||
|
for i := 0; i < numEntries; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(f.unpackValue(v.Index(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.depth--
|
||||||
|
f.fs.Write(closeBracketBytes)
|
||||||
|
|
||||||
|
case reflect.String:
|
||||||
|
f.fs.Write([]byte(v.String()))
|
||||||
|
|
||||||
|
case reflect.Interface:
|
||||||
|
// The only time we should get here is for nil interfaces due to
|
||||||
|
// unpackValue calls.
|
||||||
|
if v.IsNil() {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
// Do nothing. We should never get here since pointers have already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
// nil maps should be indicated as different than empty maps
|
||||||
|
if v.IsNil() {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
f.fs.Write(openMapBytes)
|
||||||
|
f.depth++
|
||||||
|
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||||
|
f.fs.Write(maxShortBytes)
|
||||||
|
} else {
|
||||||
|
keys := v.MapKeys()
|
||||||
|
if f.cs.SortKeys {
|
||||||
|
sortValues(keys, f.cs)
|
||||||
|
}
|
||||||
|
for i, key := range keys {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(f.unpackValue(key))
|
||||||
|
f.fs.Write(colonBytes)
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(f.unpackValue(v.MapIndex(key)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.depth--
|
||||||
|
f.fs.Write(closeMapBytes)
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
numFields := v.NumField()
|
||||||
|
f.fs.Write(openBraceBytes)
|
||||||
|
f.depth++
|
||||||
|
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||||
|
f.fs.Write(maxShortBytes)
|
||||||
|
} else {
|
||||||
|
vt := v.Type()
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
vtf := vt.Field(i)
|
||||||
|
if f.fs.Flag('+') || f.fs.Flag('#') {
|
||||||
|
f.fs.Write([]byte(vtf.Name))
|
||||||
|
f.fs.Write(colonBytes)
|
||||||
|
}
|
||||||
|
f.format(f.unpackValue(v.Field(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.depth--
|
||||||
|
f.fs.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.Uintptr:
|
||||||
|
printHexPtr(f.fs, uintptr(v.Uint()))
|
||||||
|
|
||||||
|
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||||
|
printHexPtr(f.fs, v.Pointer())
|
||||||
|
|
||||||
|
// There were not any other types at the time this code was written, but
|
||||||
|
// fall back to letting the default fmt package handle it if any get added.
|
||||||
|
default:
|
||||||
|
format := f.buildDefaultFormat()
|
||||||
|
if v.CanInterface() {
|
||||||
|
fmt.Fprintf(f.fs, format, v.Interface())
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(f.fs, format, v.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
|
||||||
|
// details.
|
||||||
|
func (f *formatState) Format(fs fmt.State, verb rune) {
|
||||||
|
f.fs = fs
|
||||||
|
|
||||||
|
// Use standard formatting for verbs that are not v.
|
||||||
|
if verb != 'v' {
|
||||||
|
format := f.constructOrigFormat(verb)
|
||||||
|
fmt.Fprintf(fs, format, f.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.value == nil {
|
||||||
|
if fs.Flag('#') {
|
||||||
|
fs.Write(interfaceBytes)
|
||||||
|
}
|
||||||
|
fs.Write(nilAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.format(reflect.ValueOf(f.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFormatter is a helper function to consolidate the logic from the various
|
||||||
|
// public methods which take varying config states.
|
||||||
|
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
|
||||||
|
fs := &formatState{value: v, cs: cs}
|
||||||
|
fs.pointers = make(map[uintptr]int)
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||||
|
interface. As a result, it integrates cleanly with standard fmt package
|
||||||
|
printing functions. The formatter is useful for inline printing of smaller data
|
||||||
|
types similar to the standard %v format specifier.
|
||||||
|
|
||||||
|
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||||
|
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||||
|
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||||
|
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||||
|
the width and precision arguments (however they will still work on the format
|
||||||
|
specifiers not handled by the custom formatter).
|
||||||
|
|
||||||
|
Typically this function shouldn't be called directly. It is much easier to make
|
||||||
|
use of the custom formatter by calling one of the convenience functions such as
|
||||||
|
Printf, Println, or Fprintf.
|
||||||
|
*/
|
||||||
|
func NewFormatter(v interface{}) fmt.Formatter {
|
||||||
|
return newFormatter(&Config, v)
|
||||||
|
}
|
1558
vendor/github.com/davecgh/go-spew/spew/format_test.go
generated
vendored
Normal file
1558
vendor/github.com/davecgh/go-spew/spew/format_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
87
vendor/github.com/davecgh/go-spew/spew/internal_test.go
generated
vendored
Normal file
87
vendor/github.com/davecgh/go-spew/spew/internal_test.go
generated
vendored
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test file is part of the spew package rather than than the spew_test
|
||||||
|
package because it needs access to internals to properly test certain cases
|
||||||
|
which are not possible via the public interface since they should never happen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// dummyFmtState implements a fake fmt.State to use for testing invalid
|
||||||
|
// reflect.Value handling. This is necessary because the fmt package catches
|
||||||
|
// invalid values before invoking the formatter on them.
|
||||||
|
type dummyFmtState struct {
|
||||||
|
bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dfs *dummyFmtState) Flag(f int) bool {
|
||||||
|
if f == int('+') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dfs *dummyFmtState) Precision() (int, bool) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dfs *dummyFmtState) Width() (int, bool) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestInvalidReflectValue ensures the dump and formatter code handles an
|
||||||
|
// invalid reflect value properly. This needs access to internal state since it
|
||||||
|
// should never happen in real code and therefore can't be tested via the public
|
||||||
|
// API.
|
||||||
|
func TestInvalidReflectValue(t *testing.T) {
|
||||||
|
i := 1
|
||||||
|
|
||||||
|
// Dump invalid reflect value.
|
||||||
|
v := new(reflect.Value)
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
d := dumpState{w: buf, cs: &Config}
|
||||||
|
d.dump(*v)
|
||||||
|
s := buf.String()
|
||||||
|
want := "<invalid>"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("InvalidReflectValue #%d\n got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
// Formatter invalid reflect value.
|
||||||
|
buf2 := new(dummyFmtState)
|
||||||
|
f := formatState{value: *v, cs: &Config, fs: buf2}
|
||||||
|
f.format(*v)
|
||||||
|
s = buf2.String()
|
||||||
|
want = "<invalid>"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("InvalidReflectValue #%d got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortValues makes the internal sortValues function available to the test
|
||||||
|
// package.
|
||||||
|
func SortValues(values []reflect.Value, cs *ConfigState) {
|
||||||
|
sortValues(values, cs)
|
||||||
|
}
|
102
vendor/github.com/davecgh/go-spew/spew/internalunsafe_test.go
generated
vendored
Normal file
102
vendor/github.com/davecgh/go-spew/spew/internalunsafe_test.go
generated
vendored
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
// Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
||||||
|
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
||||||
|
// tag is deprecated and thus should not be used.
|
||||||
|
// +build !js,!appengine,!safe,!disableunsafe
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test file is part of the spew package rather than than the spew_test
|
||||||
|
package because it needs access to internals to properly test certain cases
|
||||||
|
which are not possible via the public interface since they should never happen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// changeKind uses unsafe to intentionally change the kind of a reflect.Value to
|
||||||
|
// the maximum kind value which does not exist. This is needed to test the
|
||||||
|
// fallback code which punts to the standard fmt library for new types that
|
||||||
|
// might get added to the language.
|
||||||
|
func changeKind(v *reflect.Value, readOnly bool) {
|
||||||
|
rvf := (*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + offsetFlag))
|
||||||
|
*rvf = *rvf | ((1<<flagKindWidth - 1) << flagKindShift)
|
||||||
|
if readOnly {
|
||||||
|
*rvf |= flagRO
|
||||||
|
} else {
|
||||||
|
*rvf &= ^uintptr(flagRO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAddedReflectValue tests functionaly of the dump and formatter code which
|
||||||
|
// falls back to the standard fmt library for new types that might get added to
|
||||||
|
// the language.
|
||||||
|
func TestAddedReflectValue(t *testing.T) {
|
||||||
|
i := 1
|
||||||
|
|
||||||
|
// Dump using a reflect.Value that is exported.
|
||||||
|
v := reflect.ValueOf(int8(5))
|
||||||
|
changeKind(&v, false)
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
d := dumpState{w: buf, cs: &Config}
|
||||||
|
d.dump(v)
|
||||||
|
s := buf.String()
|
||||||
|
want := "(int8) 5"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
// Dump using a reflect.Value that is not exported.
|
||||||
|
changeKind(&v, true)
|
||||||
|
buf.Reset()
|
||||||
|
d.dump(v)
|
||||||
|
s = buf.String()
|
||||||
|
want = "(int8) <int8 Value>"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
// Formatter using a reflect.Value that is exported.
|
||||||
|
changeKind(&v, false)
|
||||||
|
buf2 := new(dummyFmtState)
|
||||||
|
f := formatState{value: v, cs: &Config, fs: buf2}
|
||||||
|
f.format(v)
|
||||||
|
s = buf2.String()
|
||||||
|
want = "5"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
// Formatter using a reflect.Value that is not exported.
|
||||||
|
changeKind(&v, true)
|
||||||
|
buf2.Reset()
|
||||||
|
f = formatState{value: v, cs: &Config, fs: buf2}
|
||||||
|
f.format(v)
|
||||||
|
s = buf2.String()
|
||||||
|
want = "<int8 Value>"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
}
|
148
vendor/github.com/davecgh/go-spew/spew/spew.go
generated
vendored
Normal file
148
vendor/github.com/davecgh/go-spew/spew/spew.go
generated
vendored
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the formatted string as a value that satisfies error. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Errorf(format string, a ...interface{}) (err error) {
|
||||||
|
return fmt.Errorf(format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprint(w, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintf(w, format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintln(w, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Print(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Print(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Printf(format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Printf(format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Println(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Println(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Sprint(a ...interface{}) string {
|
||||||
|
return fmt.Sprint(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Sprintf(format string, a ...interface{}) string {
|
||||||
|
return fmt.Sprintf(format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||||
|
// were passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Sprintln(a ...interface{}) string {
|
||||||
|
return fmt.Sprintln(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||||
|
// length with each argument converted to a default spew Formatter interface.
|
||||||
|
func convertArgs(args []interface{}) (formatters []interface{}) {
|
||||||
|
formatters = make([]interface{}, len(args))
|
||||||
|
for index, arg := range args {
|
||||||
|
formatters[index] = NewFormatter(arg)
|
||||||
|
}
|
||||||
|
return formatters
|
||||||
|
}
|
320
vendor/github.com/davecgh/go-spew/spew/spew_test.go
generated
vendored
Normal file
320
vendor/github.com/davecgh/go-spew/spew/spew_test.go
generated
vendored
Normal file
|
@ -0,0 +1,320 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// spewFunc is used to identify which public function of the spew package or
|
||||||
|
// ConfigState a test applies to.
|
||||||
|
type spewFunc int
|
||||||
|
|
||||||
|
const (
|
||||||
|
fCSFdump spewFunc = iota
|
||||||
|
fCSFprint
|
||||||
|
fCSFprintf
|
||||||
|
fCSFprintln
|
||||||
|
fCSPrint
|
||||||
|
fCSPrintln
|
||||||
|
fCSSdump
|
||||||
|
fCSSprint
|
||||||
|
fCSSprintf
|
||||||
|
fCSSprintln
|
||||||
|
fCSErrorf
|
||||||
|
fCSNewFormatter
|
||||||
|
fErrorf
|
||||||
|
fFprint
|
||||||
|
fFprintln
|
||||||
|
fPrint
|
||||||
|
fPrintln
|
||||||
|
fSdump
|
||||||
|
fSprint
|
||||||
|
fSprintf
|
||||||
|
fSprintln
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map of spewFunc values to names for pretty printing.
|
||||||
|
var spewFuncStrings = map[spewFunc]string{
|
||||||
|
fCSFdump: "ConfigState.Fdump",
|
||||||
|
fCSFprint: "ConfigState.Fprint",
|
||||||
|
fCSFprintf: "ConfigState.Fprintf",
|
||||||
|
fCSFprintln: "ConfigState.Fprintln",
|
||||||
|
fCSSdump: "ConfigState.Sdump",
|
||||||
|
fCSPrint: "ConfigState.Print",
|
||||||
|
fCSPrintln: "ConfigState.Println",
|
||||||
|
fCSSprint: "ConfigState.Sprint",
|
||||||
|
fCSSprintf: "ConfigState.Sprintf",
|
||||||
|
fCSSprintln: "ConfigState.Sprintln",
|
||||||
|
fCSErrorf: "ConfigState.Errorf",
|
||||||
|
fCSNewFormatter: "ConfigState.NewFormatter",
|
||||||
|
fErrorf: "spew.Errorf",
|
||||||
|
fFprint: "spew.Fprint",
|
||||||
|
fFprintln: "spew.Fprintln",
|
||||||
|
fPrint: "spew.Print",
|
||||||
|
fPrintln: "spew.Println",
|
||||||
|
fSdump: "spew.Sdump",
|
||||||
|
fSprint: "spew.Sprint",
|
||||||
|
fSprintf: "spew.Sprintf",
|
||||||
|
fSprintln: "spew.Sprintln",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f spewFunc) String() string {
|
||||||
|
if s, ok := spewFuncStrings[f]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Unknown spewFunc (%d)", int(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// spewTest is used to describe a test to be performed against the public
|
||||||
|
// functions of the spew package or ConfigState.
|
||||||
|
type spewTest struct {
|
||||||
|
cs *spew.ConfigState
|
||||||
|
f spewFunc
|
||||||
|
format string
|
||||||
|
in interface{}
|
||||||
|
want string
|
||||||
|
}
|
||||||
|
|
||||||
|
// spewTests houses the tests to be performed against the public functions of
|
||||||
|
// the spew package and ConfigState.
|
||||||
|
//
|
||||||
|
// These tests are only intended to ensure the public functions are exercised
|
||||||
|
// and are intentionally not exhaustive of types. The exhaustive type
|
||||||
|
// tests are handled in the dump and format tests.
|
||||||
|
var spewTests []spewTest
|
||||||
|
|
||||||
|
// redirStdout is a helper function to return the standard output from f as a
|
||||||
|
// byte slice.
|
||||||
|
func redirStdout(f func()) ([]byte, error) {
|
||||||
|
tempFile, err := ioutil.TempFile("", "ss-test")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fileName := tempFile.Name()
|
||||||
|
defer os.Remove(fileName) // Ignore error
|
||||||
|
|
||||||
|
origStdout := os.Stdout
|
||||||
|
os.Stdout = tempFile
|
||||||
|
f()
|
||||||
|
os.Stdout = origStdout
|
||||||
|
tempFile.Close()
|
||||||
|
|
||||||
|
return ioutil.ReadFile(fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initSpewTests() {
|
||||||
|
// Config states with various settings.
|
||||||
|
scsDefault := spew.NewDefaultConfig()
|
||||||
|
scsNoMethods := &spew.ConfigState{Indent: " ", DisableMethods: true}
|
||||||
|
scsNoPmethods := &spew.ConfigState{Indent: " ", DisablePointerMethods: true}
|
||||||
|
scsMaxDepth := &spew.ConfigState{Indent: " ", MaxDepth: 1}
|
||||||
|
scsContinue := &spew.ConfigState{Indent: " ", ContinueOnMethod: true}
|
||||||
|
scsNoPtrAddr := &spew.ConfigState{DisablePointerAddresses: true}
|
||||||
|
scsNoCap := &spew.ConfigState{DisableCapacities: true}
|
||||||
|
|
||||||
|
// Variables for tests on types which implement Stringer interface with and
|
||||||
|
// without a pointer receiver.
|
||||||
|
ts := stringer("test")
|
||||||
|
tps := pstringer("test")
|
||||||
|
|
||||||
|
type ptrTester struct {
|
||||||
|
s *struct{}
|
||||||
|
}
|
||||||
|
tptr := &ptrTester{s: &struct{}{}}
|
||||||
|
|
||||||
|
// depthTester is used to test max depth handling for structs, array, slices
|
||||||
|
// and maps.
|
||||||
|
type depthTester struct {
|
||||||
|
ic indirCir1
|
||||||
|
arr [1]string
|
||||||
|
slice []string
|
||||||
|
m map[string]int
|
||||||
|
}
|
||||||
|
dt := depthTester{indirCir1{nil}, [1]string{"arr"}, []string{"slice"},
|
||||||
|
map[string]int{"one": 1}}
|
||||||
|
|
||||||
|
// Variable for tests on types which implement error interface.
|
||||||
|
te := customError(10)
|
||||||
|
|
||||||
|
spewTests = []spewTest{
|
||||||
|
{scsDefault, fCSFdump, "", int8(127), "(int8) 127\n"},
|
||||||
|
{scsDefault, fCSFprint, "", int16(32767), "32767"},
|
||||||
|
{scsDefault, fCSFprintf, "%v", int32(2147483647), "2147483647"},
|
||||||
|
{scsDefault, fCSFprintln, "", int(2147483647), "2147483647\n"},
|
||||||
|
{scsDefault, fCSPrint, "", int64(9223372036854775807), "9223372036854775807"},
|
||||||
|
{scsDefault, fCSPrintln, "", uint8(255), "255\n"},
|
||||||
|
{scsDefault, fCSSdump, "", uint8(64), "(uint8) 64\n"},
|
||||||
|
{scsDefault, fCSSprint, "", complex(1, 2), "(1+2i)"},
|
||||||
|
{scsDefault, fCSSprintf, "%v", complex(float32(3), 4), "(3+4i)"},
|
||||||
|
{scsDefault, fCSSprintln, "", complex(float64(5), 6), "(5+6i)\n"},
|
||||||
|
{scsDefault, fCSErrorf, "%#v", uint16(65535), "(uint16)65535"},
|
||||||
|
{scsDefault, fCSNewFormatter, "%v", uint32(4294967295), "4294967295"},
|
||||||
|
{scsDefault, fErrorf, "%v", uint64(18446744073709551615), "18446744073709551615"},
|
||||||
|
{scsDefault, fFprint, "", float32(3.14), "3.14"},
|
||||||
|
{scsDefault, fFprintln, "", float64(6.28), "6.28\n"},
|
||||||
|
{scsDefault, fPrint, "", true, "true"},
|
||||||
|
{scsDefault, fPrintln, "", false, "false\n"},
|
||||||
|
{scsDefault, fSdump, "", complex(-10, -20), "(complex128) (-10-20i)\n"},
|
||||||
|
{scsDefault, fSprint, "", complex(-1, -2), "(-1-2i)"},
|
||||||
|
{scsDefault, fSprintf, "%v", complex(float32(-3), -4), "(-3-4i)"},
|
||||||
|
{scsDefault, fSprintln, "", complex(float64(-5), -6), "(-5-6i)\n"},
|
||||||
|
{scsNoMethods, fCSFprint, "", ts, "test"},
|
||||||
|
{scsNoMethods, fCSFprint, "", &ts, "<*>test"},
|
||||||
|
{scsNoMethods, fCSFprint, "", tps, "test"},
|
||||||
|
{scsNoMethods, fCSFprint, "", &tps, "<*>test"},
|
||||||
|
{scsNoPmethods, fCSFprint, "", ts, "stringer test"},
|
||||||
|
{scsNoPmethods, fCSFprint, "", &ts, "<*>stringer test"},
|
||||||
|
{scsNoPmethods, fCSFprint, "", tps, "test"},
|
||||||
|
{scsNoPmethods, fCSFprint, "", &tps, "<*>stringer test"},
|
||||||
|
{scsMaxDepth, fCSFprint, "", dt, "{{<max>} [<max>] [<max>] map[<max>]}"},
|
||||||
|
{scsMaxDepth, fCSFdump, "", dt, "(spew_test.depthTester) {\n" +
|
||||||
|
" ic: (spew_test.indirCir1) {\n <max depth reached>\n },\n" +
|
||||||
|
" arr: ([1]string) (len=1 cap=1) {\n <max depth reached>\n },\n" +
|
||||||
|
" slice: ([]string) (len=1 cap=1) {\n <max depth reached>\n },\n" +
|
||||||
|
" m: (map[string]int) (len=1) {\n <max depth reached>\n }\n}\n"},
|
||||||
|
{scsContinue, fCSFprint, "", ts, "(stringer test) test"},
|
||||||
|
{scsContinue, fCSFdump, "", ts, "(spew_test.stringer) " +
|
||||||
|
"(len=4) (stringer test) \"test\"\n"},
|
||||||
|
{scsContinue, fCSFprint, "", te, "(error: 10) 10"},
|
||||||
|
{scsContinue, fCSFdump, "", te, "(spew_test.customError) " +
|
||||||
|
"(error: 10) 10\n"},
|
||||||
|
{scsNoPtrAddr, fCSFprint, "", tptr, "<*>{<*>{}}"},
|
||||||
|
{scsNoPtrAddr, fCSSdump, "", tptr, "(*spew_test.ptrTester)({\ns: (*struct {})({\n})\n})\n"},
|
||||||
|
{scsNoCap, fCSSdump, "", make([]string, 0, 10), "([]string) {\n}\n"},
|
||||||
|
{scsNoCap, fCSSdump, "", make([]string, 1, 10), "([]string) (len=1) {\n(string) \"\"\n}\n"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSpew executes all of the tests described by spewTests.
|
||||||
|
func TestSpew(t *testing.T) {
|
||||||
|
initSpewTests()
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(spewTests))
|
||||||
|
for i, test := range spewTests {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
switch test.f {
|
||||||
|
case fCSFdump:
|
||||||
|
test.cs.Fdump(buf, test.in)
|
||||||
|
|
||||||
|
case fCSFprint:
|
||||||
|
test.cs.Fprint(buf, test.in)
|
||||||
|
|
||||||
|
case fCSFprintf:
|
||||||
|
test.cs.Fprintf(buf, test.format, test.in)
|
||||||
|
|
||||||
|
case fCSFprintln:
|
||||||
|
test.cs.Fprintln(buf, test.in)
|
||||||
|
|
||||||
|
case fCSPrint:
|
||||||
|
b, err := redirStdout(func() { test.cs.Print(test.in) })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v #%d %v", test.f, i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.Write(b)
|
||||||
|
|
||||||
|
case fCSPrintln:
|
||||||
|
b, err := redirStdout(func() { test.cs.Println(test.in) })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v #%d %v", test.f, i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.Write(b)
|
||||||
|
|
||||||
|
case fCSSdump:
|
||||||
|
str := test.cs.Sdump(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fCSSprint:
|
||||||
|
str := test.cs.Sprint(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fCSSprintf:
|
||||||
|
str := test.cs.Sprintf(test.format, test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fCSSprintln:
|
||||||
|
str := test.cs.Sprintln(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fCSErrorf:
|
||||||
|
err := test.cs.Errorf(test.format, test.in)
|
||||||
|
buf.WriteString(err.Error())
|
||||||
|
|
||||||
|
case fCSNewFormatter:
|
||||||
|
fmt.Fprintf(buf, test.format, test.cs.NewFormatter(test.in))
|
||||||
|
|
||||||
|
case fErrorf:
|
||||||
|
err := spew.Errorf(test.format, test.in)
|
||||||
|
buf.WriteString(err.Error())
|
||||||
|
|
||||||
|
case fFprint:
|
||||||
|
spew.Fprint(buf, test.in)
|
||||||
|
|
||||||
|
case fFprintln:
|
||||||
|
spew.Fprintln(buf, test.in)
|
||||||
|
|
||||||
|
case fPrint:
|
||||||
|
b, err := redirStdout(func() { spew.Print(test.in) })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v #%d %v", test.f, i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.Write(b)
|
||||||
|
|
||||||
|
case fPrintln:
|
||||||
|
b, err := redirStdout(func() { spew.Println(test.in) })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v #%d %v", test.f, i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.Write(b)
|
||||||
|
|
||||||
|
case fSdump:
|
||||||
|
str := spew.Sdump(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fSprint:
|
||||||
|
str := spew.Sprint(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fSprintf:
|
||||||
|
str := spew.Sprintf(test.format, test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fSprintln:
|
||||||
|
str := spew.Sprintln(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
default:
|
||||||
|
t.Errorf("%v #%d unrecognized function", test.f, i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s := buf.String()
|
||||||
|
if test.want != s {
|
||||||
|
t.Errorf("ConfigState #%d\n got: %s want: %s", i, s, test.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
82
vendor/github.com/davecgh/go-spew/spew/testdata/dumpcgo.go
generated
vendored
Normal file
82
vendor/github.com/davecgh/go-spew/spew/testdata/dumpcgo.go
generated
vendored
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when both cgo is supported and "-tags testcgo" is added to the go test
|
||||||
|
// command line. This code should really only be in the dumpcgo_test.go file,
|
||||||
|
// but unfortunately Go will not allow cgo in test files, so this is a
|
||||||
|
// workaround to allow cgo types to be tested. This configuration is used
|
||||||
|
// because spew itself does not require cgo to run even though it does handle
|
||||||
|
// certain cgo types specially. Rather than forcing all clients to require cgo
|
||||||
|
// and an external C compiler just to run the tests, this scheme makes them
|
||||||
|
// optional.
|
||||||
|
// +build cgo,testcgo
|
||||||
|
|
||||||
|
package testdata
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <stdint.h>
|
||||||
|
typedef unsigned char custom_uchar_t;
|
||||||
|
|
||||||
|
char *ncp = 0;
|
||||||
|
char *cp = "test";
|
||||||
|
char ca[6] = {'t', 'e', 's', 't', '2', '\0'};
|
||||||
|
unsigned char uca[6] = {'t', 'e', 's', 't', '3', '\0'};
|
||||||
|
signed char sca[6] = {'t', 'e', 's', 't', '4', '\0'};
|
||||||
|
uint8_t ui8ta[6] = {'t', 'e', 's', 't', '5', '\0'};
|
||||||
|
custom_uchar_t tuca[6] = {'t', 'e', 's', 't', '6', '\0'};
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
// GetCgoNullCharPointer returns a null char pointer via cgo. This is only
|
||||||
|
// used for tests.
|
||||||
|
func GetCgoNullCharPointer() interface{} {
|
||||||
|
return C.ncp
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoCharPointer returns a char pointer via cgo. This is only used for
|
||||||
|
// tests.
|
||||||
|
func GetCgoCharPointer() interface{} {
|
||||||
|
return C.cp
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoCharArray returns a char array via cgo and the array's len and cap.
|
||||||
|
// This is only used for tests.
|
||||||
|
func GetCgoCharArray() (interface{}, int, int) {
|
||||||
|
return C.ca, len(C.ca), cap(C.ca)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoUnsignedCharArray returns an unsigned char array via cgo and the
|
||||||
|
// array's len and cap. This is only used for tests.
|
||||||
|
func GetCgoUnsignedCharArray() (interface{}, int, int) {
|
||||||
|
return C.uca, len(C.uca), cap(C.uca)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoSignedCharArray returns a signed char array via cgo and the array's len
|
||||||
|
// and cap. This is only used for tests.
|
||||||
|
func GetCgoSignedCharArray() (interface{}, int, int) {
|
||||||
|
return C.sca, len(C.sca), cap(C.sca)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoUint8tArray returns a uint8_t array via cgo and the array's len and
|
||||||
|
// cap. This is only used for tests.
|
||||||
|
func GetCgoUint8tArray() (interface{}, int, int) {
|
||||||
|
return C.ui8ta, len(C.ui8ta), cap(C.ui8ta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoTypdefedUnsignedCharArray returns a typedefed unsigned char array via
|
||||||
|
// cgo and the array's len and cap. This is only used for tests.
|
||||||
|
func GetCgoTypdefedUnsignedCharArray() (interface{}, int, int) {
|
||||||
|
return C.tuca, len(C.tuca), cap(C.tuca)
|
||||||
|
}
|
61
vendor/github.com/davecgh/go-spew/test_coverage.txt
generated
vendored
Normal file
61
vendor/github.com/davecgh/go-spew/test_coverage.txt
generated
vendored
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go dumpState.dump 100.00% (88/88)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.format 100.00% (82/82)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.formatPtr 100.00% (52/52)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go dumpState.dumpPtr 100.00% (44/44)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go dumpState.dumpSlice 100.00% (39/39)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go handleMethods 100.00% (30/30)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printHexPtr 100.00% (18/18)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go unsafeReflectValue 100.00% (13/13)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.constructOrigFormat 100.00% (12/12)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go fdump 100.00% (11/11)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.Format 100.00% (11/11)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go init 100.00% (10/10)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printComplex 100.00% (9/9)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go valuesSorter.Less 100.00% (8/8)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.buildDefaultFormat 100.00% (7/7)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.unpackValue 100.00% (5/5)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go dumpState.indent 100.00% (4/4)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go catchPanic 100.00% (4/4)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.convertArgs 100.00% (4/4)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go convertArgs 100.00% (4/4)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go newFormatter 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go Sdump 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printBool 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go sortValues 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Sdump 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go dumpState.unpackValue 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Printf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Println 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Sprint 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Sprintf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Sprintln 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printFloat 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go NewDefaultConfig 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printInt 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printUint 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go valuesSorter.Len 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go valuesSorter.Swap 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Errorf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprint 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprintf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprintln 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Print 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Printf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Println 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprint 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprintf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprintln 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.NewFormatter 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Fdump 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Dump 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go Fdump 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go Dump 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Fprintln 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go NewFormatter 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Errorf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Fprint 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Fprintf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Print 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew ------------------------------- 100.00% (505/505)
|
||||||
|
|
19
vendor/github.com/gorilla/context/.travis.yml
generated
vendored
Normal file
19
vendor/github.com/gorilla/context/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- go: 1.3
|
||||||
|
- go: 1.4
|
||||||
|
- go: 1.5
|
||||||
|
- go: 1.6
|
||||||
|
- go: tip
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get golang.org/x/tools/cmd/vet
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go get -t -v ./...
|
||||||
|
- diff -u <(echo -n) <(gofmt -d .)
|
||||||
|
- go tool vet .
|
||||||
|
- go test -v -race ./...
|
27
vendor/github.com/gorilla/context/LICENSE
generated
vendored
Normal file
27
vendor/github.com/gorilla/context/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
7
vendor/github.com/gorilla/context/README.md
generated
vendored
Normal file
7
vendor/github.com/gorilla/context/README.md
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
context
|
||||||
|
=======
|
||||||
|
[![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context)
|
||||||
|
|
||||||
|
gorilla/context is a general purpose registry for global request variables.
|
||||||
|
|
||||||
|
Read the full documentation here: http://www.gorillatoolkit.org/pkg/context
|
143
vendor/github.com/gorilla/context/context.go
generated
vendored
Normal file
143
vendor/github.com/gorilla/context/context.go
generated
vendored
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mutex sync.RWMutex
|
||||||
|
data = make(map[*http.Request]map[interface{}]interface{})
|
||||||
|
datat = make(map[*http.Request]int64)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set stores a value for a given key in a given request.
|
||||||
|
func Set(r *http.Request, key, val interface{}) {
|
||||||
|
mutex.Lock()
|
||||||
|
if data[r] == nil {
|
||||||
|
data[r] = make(map[interface{}]interface{})
|
||||||
|
datat[r] = time.Now().Unix()
|
||||||
|
}
|
||||||
|
data[r][key] = val
|
||||||
|
mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a value stored for a given key in a given request.
|
||||||
|
func Get(r *http.Request, key interface{}) interface{} {
|
||||||
|
mutex.RLock()
|
||||||
|
if ctx := data[r]; ctx != nil {
|
||||||
|
value := ctx[key]
|
||||||
|
mutex.RUnlock()
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
mutex.RUnlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOk returns stored value and presence state like multi-value return of map access.
|
||||||
|
func GetOk(r *http.Request, key interface{}) (interface{}, bool) {
|
||||||
|
mutex.RLock()
|
||||||
|
if _, ok := data[r]; ok {
|
||||||
|
value, ok := data[r][key]
|
||||||
|
mutex.RUnlock()
|
||||||
|
return value, ok
|
||||||
|
}
|
||||||
|
mutex.RUnlock()
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll returns all stored values for the request as a map. Nil is returned for invalid requests.
|
||||||
|
func GetAll(r *http.Request) map[interface{}]interface{} {
|
||||||
|
mutex.RLock()
|
||||||
|
if context, ok := data[r]; ok {
|
||||||
|
result := make(map[interface{}]interface{}, len(context))
|
||||||
|
for k, v := range context {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
mutex.RUnlock()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
mutex.RUnlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllOk returns all stored values for the request as a map and a boolean value that indicates if
|
||||||
|
// the request was registered.
|
||||||
|
func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) {
|
||||||
|
mutex.RLock()
|
||||||
|
context, ok := data[r]
|
||||||
|
result := make(map[interface{}]interface{}, len(context))
|
||||||
|
for k, v := range context {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
mutex.RUnlock()
|
||||||
|
return result, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes a value stored for a given key in a given request.
|
||||||
|
func Delete(r *http.Request, key interface{}) {
|
||||||
|
mutex.Lock()
|
||||||
|
if data[r] != nil {
|
||||||
|
delete(data[r], key)
|
||||||
|
}
|
||||||
|
mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear removes all values stored for a given request.
|
||||||
|
//
|
||||||
|
// This is usually called by a handler wrapper to clean up request
|
||||||
|
// variables at the end of a request lifetime. See ClearHandler().
|
||||||
|
func Clear(r *http.Request) {
|
||||||
|
mutex.Lock()
|
||||||
|
clear(r)
|
||||||
|
mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear is Clear without the lock.
|
||||||
|
func clear(r *http.Request) {
|
||||||
|
delete(data, r)
|
||||||
|
delete(datat, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purge removes request data stored for longer than maxAge, in seconds.
|
||||||
|
// It returns the amount of requests removed.
|
||||||
|
//
|
||||||
|
// If maxAge <= 0, all request data is removed.
|
||||||
|
//
|
||||||
|
// This is only used for sanity check: in case context cleaning was not
|
||||||
|
// properly set some request data can be kept forever, consuming an increasing
|
||||||
|
// amount of memory. In case this is detected, Purge() must be called
|
||||||
|
// periodically until the problem is fixed.
|
||||||
|
func Purge(maxAge int) int {
|
||||||
|
mutex.Lock()
|
||||||
|
count := 0
|
||||||
|
if maxAge <= 0 {
|
||||||
|
count = len(data)
|
||||||
|
data = make(map[*http.Request]map[interface{}]interface{})
|
||||||
|
datat = make(map[*http.Request]int64)
|
||||||
|
} else {
|
||||||
|
min := time.Now().Unix() - int64(maxAge)
|
||||||
|
for r := range data {
|
||||||
|
if datat[r] < min {
|
||||||
|
clear(r)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutex.Unlock()
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearHandler wraps an http.Handler and clears request values at the end
|
||||||
|
// of a request lifetime.
|
||||||
|
func ClearHandler(h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer Clear(r)
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
161
vendor/github.com/gorilla/context/context_test.go
generated
vendored
Normal file
161
vendor/github.com/gorilla/context/context_test.go
generated
vendored
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type keyType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
key1 keyType = iota
|
||||||
|
key2
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestContext(t *testing.T) {
|
||||||
|
assertEqual := func(val interface{}, exp interface{}) {
|
||||||
|
if val != exp {
|
||||||
|
t.Errorf("Expected %v, got %v.", exp, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||||
|
emptyR, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||||
|
|
||||||
|
// Get()
|
||||||
|
assertEqual(Get(r, key1), nil)
|
||||||
|
|
||||||
|
// Set()
|
||||||
|
Set(r, key1, "1")
|
||||||
|
assertEqual(Get(r, key1), "1")
|
||||||
|
assertEqual(len(data[r]), 1)
|
||||||
|
|
||||||
|
Set(r, key2, "2")
|
||||||
|
assertEqual(Get(r, key2), "2")
|
||||||
|
assertEqual(len(data[r]), 2)
|
||||||
|
|
||||||
|
//GetOk
|
||||||
|
value, ok := GetOk(r, key1)
|
||||||
|
assertEqual(value, "1")
|
||||||
|
assertEqual(ok, true)
|
||||||
|
|
||||||
|
value, ok = GetOk(r, "not exists")
|
||||||
|
assertEqual(value, nil)
|
||||||
|
assertEqual(ok, false)
|
||||||
|
|
||||||
|
Set(r, "nil value", nil)
|
||||||
|
value, ok = GetOk(r, "nil value")
|
||||||
|
assertEqual(value, nil)
|
||||||
|
assertEqual(ok, true)
|
||||||
|
|
||||||
|
// GetAll()
|
||||||
|
values := GetAll(r)
|
||||||
|
assertEqual(len(values), 3)
|
||||||
|
|
||||||
|
// GetAll() for empty request
|
||||||
|
values = GetAll(emptyR)
|
||||||
|
if values != nil {
|
||||||
|
t.Error("GetAll didn't return nil value for invalid request")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllOk()
|
||||||
|
values, ok = GetAllOk(r)
|
||||||
|
assertEqual(len(values), 3)
|
||||||
|
assertEqual(ok, true)
|
||||||
|
|
||||||
|
// GetAllOk() for empty request
|
||||||
|
values, ok = GetAllOk(emptyR)
|
||||||
|
assertEqual(value, nil)
|
||||||
|
assertEqual(ok, false)
|
||||||
|
|
||||||
|
// Delete()
|
||||||
|
Delete(r, key1)
|
||||||
|
assertEqual(Get(r, key1), nil)
|
||||||
|
assertEqual(len(data[r]), 2)
|
||||||
|
|
||||||
|
Delete(r, key2)
|
||||||
|
assertEqual(Get(r, key2), nil)
|
||||||
|
assertEqual(len(data[r]), 1)
|
||||||
|
|
||||||
|
// Clear()
|
||||||
|
Clear(r)
|
||||||
|
assertEqual(len(data), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parallelReader(r *http.Request, key string, iterations int, wait, done chan struct{}) {
|
||||||
|
<-wait
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
Get(r, key)
|
||||||
|
}
|
||||||
|
done <- struct{}{}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func parallelWriter(r *http.Request, key, value string, iterations int, wait, done chan struct{}) {
|
||||||
|
<-wait
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
Set(r, key, value)
|
||||||
|
}
|
||||||
|
done <- struct{}{}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchmarkMutex(b *testing.B, numReaders, numWriters, iterations int) {
|
||||||
|
|
||||||
|
b.StopTimer()
|
||||||
|
r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||||
|
done := make(chan struct{})
|
||||||
|
b.StartTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
wait := make(chan struct{})
|
||||||
|
|
||||||
|
for i := 0; i < numReaders; i++ {
|
||||||
|
go parallelReader(r, "test", iterations, wait, done)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < numWriters; i++ {
|
||||||
|
go parallelWriter(r, "test", "123", iterations, wait, done)
|
||||||
|
}
|
||||||
|
|
||||||
|
close(wait)
|
||||||
|
|
||||||
|
for i := 0; i < numReaders+numWriters; i++ {
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMutexSameReadWrite1(b *testing.B) {
|
||||||
|
benchmarkMutex(b, 1, 1, 32)
|
||||||
|
}
|
||||||
|
func BenchmarkMutexSameReadWrite2(b *testing.B) {
|
||||||
|
benchmarkMutex(b, 2, 2, 32)
|
||||||
|
}
|
||||||
|
func BenchmarkMutexSameReadWrite4(b *testing.B) {
|
||||||
|
benchmarkMutex(b, 4, 4, 32)
|
||||||
|
}
|
||||||
|
func BenchmarkMutex1(b *testing.B) {
|
||||||
|
benchmarkMutex(b, 2, 8, 32)
|
||||||
|
}
|
||||||
|
func BenchmarkMutex2(b *testing.B) {
|
||||||
|
benchmarkMutex(b, 16, 4, 64)
|
||||||
|
}
|
||||||
|
func BenchmarkMutex3(b *testing.B) {
|
||||||
|
benchmarkMutex(b, 1, 2, 128)
|
||||||
|
}
|
||||||
|
func BenchmarkMutex4(b *testing.B) {
|
||||||
|
benchmarkMutex(b, 128, 32, 256)
|
||||||
|
}
|
||||||
|
func BenchmarkMutex5(b *testing.B) {
|
||||||
|
benchmarkMutex(b, 1024, 2048, 64)
|
||||||
|
}
|
||||||
|
func BenchmarkMutex6(b *testing.B) {
|
||||||
|
benchmarkMutex(b, 2048, 1024, 512)
|
||||||
|
}
|
82
vendor/github.com/gorilla/context/doc.go
generated
vendored
Normal file
82
vendor/github.com/gorilla/context/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package context stores values shared during a request lifetime.
|
||||||
|
|
||||||
|
For example, a router can set variables extracted from the URL and later
|
||||||
|
application handlers can access those values, or it can be used to store
|
||||||
|
sessions values to be saved at the end of a request. There are several
|
||||||
|
others common uses.
|
||||||
|
|
||||||
|
The idea was posted by Brad Fitzpatrick to the go-nuts mailing list:
|
||||||
|
|
||||||
|
http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53
|
||||||
|
|
||||||
|
Here's the basic usage: first define the keys that you will need. The key
|
||||||
|
type is interface{} so a key can be of any type that supports equality.
|
||||||
|
Here we define a key using a custom int type to avoid name collisions:
|
||||||
|
|
||||||
|
package foo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gorilla/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type key int
|
||||||
|
|
||||||
|
const MyKey key = 0
|
||||||
|
|
||||||
|
Then set a variable. Variables are bound to an http.Request object, so you
|
||||||
|
need a request instance to set a value:
|
||||||
|
|
||||||
|
context.Set(r, MyKey, "bar")
|
||||||
|
|
||||||
|
The application can later access the variable using the same key you provided:
|
||||||
|
|
||||||
|
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// val is "bar".
|
||||||
|
val := context.Get(r, foo.MyKey)
|
||||||
|
|
||||||
|
// returns ("bar", true)
|
||||||
|
val, ok := context.GetOk(r, foo.MyKey)
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
And that's all about the basic usage. We discuss some other ideas below.
|
||||||
|
|
||||||
|
Any type can be stored in the context. To enforce a given type, make the key
|
||||||
|
private and wrap Get() and Set() to accept and return values of a specific
|
||||||
|
type:
|
||||||
|
|
||||||
|
type key int
|
||||||
|
|
||||||
|
const mykey key = 0
|
||||||
|
|
||||||
|
// GetMyKey returns a value for this package from the request values.
|
||||||
|
func GetMyKey(r *http.Request) SomeType {
|
||||||
|
if rv := context.Get(r, mykey); rv != nil {
|
||||||
|
return rv.(SomeType)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMyKey sets a value for this package in the request values.
|
||||||
|
func SetMyKey(r *http.Request, val SomeType) {
|
||||||
|
context.Set(r, mykey, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
Variables must be cleared at the end of a request, to remove all values
|
||||||
|
that were stored. This can be done in an http.Handler, after a request was
|
||||||
|
served. Just call Clear() passing the request:
|
||||||
|
|
||||||
|
context.Clear(r)
|
||||||
|
|
||||||
|
...or use ClearHandler(), which conveniently wraps an http.Handler to clear
|
||||||
|
variables at the end of a request lifetime.
|
||||||
|
|
||||||
|
The Routers from the packages gorilla/mux and gorilla/pat call Clear()
|
||||||
|
so if you are using either of them you don't need to clear the context manually.
|
||||||
|
*/
|
||||||
|
package context
|
21
vendor/github.com/gorilla/mux/.travis.yml
generated
vendored
Normal file
21
vendor/github.com/gorilla/mux/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- go: 1.2
|
||||||
|
- go: 1.3
|
||||||
|
- go: 1.4
|
||||||
|
- go: 1.5
|
||||||
|
- go: 1.6
|
||||||
|
- go: 1.7
|
||||||
|
- go: tip
|
||||||
|
|
||||||
|
install:
|
||||||
|
- # Skip
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go get -t -v ./...
|
||||||
|
- diff -u <(echo -n) <(gofmt -d .)
|
||||||
|
- go tool vet .
|
||||||
|
- go test -v -race ./...
|
27
vendor/github.com/gorilla/mux/LICENSE
generated
vendored
Normal file
27
vendor/github.com/gorilla/mux/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
339
vendor/github.com/gorilla/mux/README.md
generated
vendored
Normal file
339
vendor/github.com/gorilla/mux/README.md
generated
vendored
Normal file
|
@ -0,0 +1,339 @@
|
||||||
|
gorilla/mux
|
||||||
|
===
|
||||||
|
[![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux)
|
||||||
|
[![Build Status](https://travis-ci.org/gorilla/mux.svg?branch=master)](https://travis-ci.org/gorilla/mux)
|
||||||
|
|
||||||
|
![Gorilla Logo](http://www.gorillatoolkit.org/static/images/gorilla-icon-64.png)
|
||||||
|
|
||||||
|
http://www.gorillatoolkit.org/pkg/mux
|
||||||
|
|
||||||
|
Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to
|
||||||
|
their respective handler.
|
||||||
|
|
||||||
|
The name mux stands for "HTTP request multiplexer". Like the standard `http.ServeMux`, `mux.Router` matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are:
|
||||||
|
|
||||||
|
* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
|
||||||
|
* Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.
|
||||||
|
* URL hosts and paths can have variables with an optional regular expression.
|
||||||
|
* Registered URLs can be built, or "reversed", which helps maintaining references to resources.
|
||||||
|
* Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
* [Install](#install)
|
||||||
|
* [Examples](#examples)
|
||||||
|
* [Matching Routes](#matching-routes)
|
||||||
|
* [Listing Routes](#listing-routes)
|
||||||
|
* [Static Files](#static-files)
|
||||||
|
* [Registered URLs](#registered-urls)
|
||||||
|
* [Full Example](#full-example)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
With a [correctly configured](https://golang.org/doc/install#testing) Go toolchain:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get -u github.com/gorilla/mux
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Let's start registering a couple of URL paths and handlers:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/", HomeHandler)
|
||||||
|
r.HandleFunc("/products", ProductsHandler)
|
||||||
|
r.HandleFunc("/articles", ArticlesHandler)
|
||||||
|
http.Handle("/", r)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here we register three routes mapping URL paths to handlers. This is equivalent to how `http.HandleFunc()` works: if an incoming request URL matches one of the paths, the corresponding handler is called passing (`http.ResponseWriter`, `*http.Request`) as parameters.
|
||||||
|
|
||||||
|
Paths can have variables. They are defined using the format `{name}` or `{name:pattern}`. If a regular expression pattern is not defined, the matched variable will be anything until the next slash. For example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/products/{key}", ProductHandler)
|
||||||
|
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
|
||||||
|
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
|
||||||
|
```
|
||||||
|
|
||||||
|
The names are used to create a map of route variables which can be retrieved calling `mux.Vars()`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, "Category: %v\n", vars["category"])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And this is all you need to know about the basic usage. More advanced options are explained below.
|
||||||
|
|
||||||
|
### Matching Routes
|
||||||
|
|
||||||
|
Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
// Only matches if domain is "www.example.com".
|
||||||
|
r.Host("www.example.com")
|
||||||
|
// Matches a dynamic subdomain.
|
||||||
|
r.Host("{subdomain:[a-z]+}.domain.com")
|
||||||
|
```
|
||||||
|
|
||||||
|
There are several other matchers that can be added. To match path prefixes:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.PathPrefix("/products/")
|
||||||
|
```
|
||||||
|
|
||||||
|
...or HTTP methods:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.Methods("GET", "POST")
|
||||||
|
```
|
||||||
|
|
||||||
|
...or URL schemes:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.Schemes("https")
|
||||||
|
```
|
||||||
|
|
||||||
|
...or header values:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.Headers("X-Requested-With", "XMLHttpRequest")
|
||||||
|
```
|
||||||
|
|
||||||
|
...or query values:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.Queries("key", "value")
|
||||||
|
```
|
||||||
|
|
||||||
|
...or to use a custom matcher function:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
|
||||||
|
return r.ProtoMajor == 0
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
...and finally, it is possible to combine several matchers in a single route:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.HandleFunc("/products", ProductsHandler).
|
||||||
|
Host("www.example.com").
|
||||||
|
Methods("GET").
|
||||||
|
Schemes("http")
|
||||||
|
```
|
||||||
|
|
||||||
|
Setting the same matching conditions again and again can be boring, so we have a way to group several routes that share the same requirements. We call it "subrouting".
|
||||||
|
|
||||||
|
For example, let's say we have several URLs that should only match when the host is `www.example.com`. Create a route for that host and get a "subrouter" from it:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
s := r.Host("www.example.com").Subrouter()
|
||||||
|
```
|
||||||
|
|
||||||
|
Then register routes in the subrouter:
|
||||||
|
|
||||||
|
```go
|
||||||
|
s.HandleFunc("/products/", ProductsHandler)
|
||||||
|
s.HandleFunc("/products/{key}", ProductHandler)
|
||||||
|
s.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
|
||||||
|
```
|
||||||
|
|
||||||
|
The three URL paths we registered above will only be tested if the domain is `www.example.com`, because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route.
|
||||||
|
|
||||||
|
Subrouters can be used to create domain or path "namespaces": you define subrouters in a central place and then parts of the app can register its paths relatively to a given subrouter.
|
||||||
|
|
||||||
|
There's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
s := r.PathPrefix("/products").Subrouter()
|
||||||
|
// "/products/"
|
||||||
|
s.HandleFunc("/", ProductsHandler)
|
||||||
|
// "/products/{key}/"
|
||||||
|
s.HandleFunc("/{key}/", ProductHandler)
|
||||||
|
// "/products/{key}/details"
|
||||||
|
s.HandleFunc("/{key}/details", ProductDetailsHandler)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Listing Routes
|
||||||
|
|
||||||
|
Routes on a mux can be listed using the Router.Walk method—useful for generating documentation:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/", handler)
|
||||||
|
r.HandleFunc("/products", handler)
|
||||||
|
r.HandleFunc("/articles", handler)
|
||||||
|
r.HandleFunc("/articles/{id}", handler)
|
||||||
|
r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
|
||||||
|
t, err := route.GetPathTemplate()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println(t)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
http.Handle("/", r)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Static Files
|
||||||
|
|
||||||
|
Note that the path provided to `PathPrefix()` represents a "wildcard": calling
|
||||||
|
`PathPrefix("/static/").Handler(...)` means that the handler will be passed any
|
||||||
|
request that matches "/static/*". This makes it easy to serve static files with mux:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
var dir string
|
||||||
|
|
||||||
|
flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
|
||||||
|
flag.Parse()
|
||||||
|
r := mux.NewRouter()
|
||||||
|
|
||||||
|
// This will serve files under http://localhost:8000/static/<filename>
|
||||||
|
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
|
||||||
|
|
||||||
|
srv := &http.Server{
|
||||||
|
Handler: r,
|
||||||
|
Addr: "127.0.0.1:8000",
|
||||||
|
// Good practice: enforce timeouts for servers you create!
|
||||||
|
WriteTimeout: 15 * time.Second,
|
||||||
|
ReadTimeout: 15 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Fatal(srv.ListenAndServe())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Registered URLs
|
||||||
|
|
||||||
|
Now let's see how to build registered URLs.
|
||||||
|
|
||||||
|
Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling `Name()` on a route. For example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
||||||
|
Name("article")
|
||||||
|
```
|
||||||
|
|
||||||
|
To build a URL, get the route and call the `URL()` method, passing a sequence of key/value pairs for the route variables. For the previous route, we would do:
|
||||||
|
|
||||||
|
```go
|
||||||
|
url, err := r.Get("article").URL("category", "technology", "id", "42")
|
||||||
|
```
|
||||||
|
|
||||||
|
...and the result will be a `url.URL` with the following path:
|
||||||
|
|
||||||
|
```
|
||||||
|
"/articles/technology/42"
|
||||||
|
```
|
||||||
|
|
||||||
|
This also works for host variables:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.Host("{subdomain}.domain.com").
|
||||||
|
Path("/articles/{category}/{id:[0-9]+}").
|
||||||
|
HandlerFunc(ArticleHandler).
|
||||||
|
Name("article")
|
||||||
|
|
||||||
|
// url.String() will be "http://news.domain.com/articles/technology/42"
|
||||||
|
url, err := r.Get("article").URL("subdomain", "news",
|
||||||
|
"category", "technology",
|
||||||
|
"id", "42")
|
||||||
|
```
|
||||||
|
|
||||||
|
All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined "build-only" routes which never match.
|
||||||
|
|
||||||
|
Regex support also exists for matching Headers within a route. For example, we could do:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.HeadersRegexp("Content-Type", "application/(text|json)")
|
||||||
|
```
|
||||||
|
|
||||||
|
...and the route will match both requests with a Content-Type of `application/json` as well as `application/text`
|
||||||
|
|
||||||
|
There's also a way to build only the URL host or path for a route: use the methods `URLHost()` or `URLPath()` instead. For the previous route, we would do:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// "http://news.domain.com/"
|
||||||
|
host, err := r.Get("article").URLHost("subdomain", "news")
|
||||||
|
|
||||||
|
// "/articles/technology/42"
|
||||||
|
path, err := r.Get("article").URLPath("category", "technology", "id", "42")
|
||||||
|
```
|
||||||
|
|
||||||
|
And if you use subrouters, host and path defined separately can be built as well:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
s := r.Host("{subdomain}.domain.com").Subrouter()
|
||||||
|
s.Path("/articles/{category}/{id:[0-9]+}").
|
||||||
|
HandlerFunc(ArticleHandler).
|
||||||
|
Name("article")
|
||||||
|
|
||||||
|
// "http://news.domain.com/articles/technology/42"
|
||||||
|
url, err := r.Get("article").URL("subdomain", "news",
|
||||||
|
"category", "technology",
|
||||||
|
"id", "42")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Full Example
|
||||||
|
|
||||||
|
Here's a complete, runnable example of a small `mux` based server:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"log"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func YourHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("Gorilla!\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
// Routes consist of a path and a handler function.
|
||||||
|
r.HandleFunc("/", YourHandler)
|
||||||
|
|
||||||
|
// Bind to a port and pass our router in
|
||||||
|
log.Fatal(http.ListenAndServe(":8000", r))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
BSD licensed. See the LICENSE file for details.
|
49
vendor/github.com/gorilla/mux/bench_test.go
generated
vendored
Normal file
49
vendor/github.com/gorilla/mux/bench_test.go
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkMux(b *testing.B) {
|
||||||
|
router := new(Router)
|
||||||
|
handler := func(w http.ResponseWriter, r *http.Request) {}
|
||||||
|
router.HandleFunc("/v1/{v1}", handler)
|
||||||
|
|
||||||
|
request, _ := http.NewRequest("GET", "/v1/anything", nil)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
router.ServeHTTP(nil, request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMuxAlternativeInRegexp(b *testing.B) {
|
||||||
|
router := new(Router)
|
||||||
|
handler := func(w http.ResponseWriter, r *http.Request) {}
|
||||||
|
router.HandleFunc("/v1/{v1:(a|b)}", handler)
|
||||||
|
|
||||||
|
requestA, _ := http.NewRequest("GET", "/v1/a", nil)
|
||||||
|
requestB, _ := http.NewRequest("GET", "/v1/b", nil)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
router.ServeHTTP(nil, requestA)
|
||||||
|
router.ServeHTTP(nil, requestB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkManyPathVariables(b *testing.B) {
|
||||||
|
router := new(Router)
|
||||||
|
handler := func(w http.ResponseWriter, r *http.Request) {}
|
||||||
|
router.HandleFunc("/v1/{v1}/{v2}/{v3}/{v4}/{v5}", handler)
|
||||||
|
|
||||||
|
matchingRequest, _ := http.NewRequest("GET", "/v1/1/2/3/4/5", nil)
|
||||||
|
notMatchingRequest, _ := http.NewRequest("GET", "/v1/1/2/3/4", nil)
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
router.ServeHTTP(nil, matchingRequest)
|
||||||
|
router.ServeHTTP(recorder, notMatchingRequest)
|
||||||
|
}
|
||||||
|
}
|
26
vendor/github.com/gorilla/mux/context_gorilla.go
generated
vendored
Normal file
26
vendor/github.com/gorilla/mux/context_gorilla.go
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// +build !go1.7
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func contextGet(r *http.Request, key interface{}) interface{} {
|
||||||
|
return context.Get(r, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func contextSet(r *http.Request, key, val interface{}) *http.Request {
|
||||||
|
if val == nil {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Set(r, key, val)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func contextClear(r *http.Request) {
|
||||||
|
context.Clear(r)
|
||||||
|
}
|
40
vendor/github.com/gorilla/mux/context_gorilla_test.go
generated
vendored
Normal file
40
vendor/github.com/gorilla/mux/context_gorilla_test.go
generated
vendored
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// +build !go1.7
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tests that the context is cleared or not cleared properly depending on
|
||||||
|
// the configuration of the router
|
||||||
|
func TestKeepContext(t *testing.T) {
|
||||||
|
func1 := func(w http.ResponseWriter, r *http.Request) {}
|
||||||
|
|
||||||
|
r := NewRouter()
|
||||||
|
r.HandleFunc("/", func1).Name("func1")
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("GET", "http://localhost/", nil)
|
||||||
|
context.Set(req, "t", 1)
|
||||||
|
|
||||||
|
res := new(http.ResponseWriter)
|
||||||
|
r.ServeHTTP(*res, req)
|
||||||
|
|
||||||
|
if _, ok := context.GetOk(req, "t"); ok {
|
||||||
|
t.Error("Context should have been cleared at end of request")
|
||||||
|
}
|
||||||
|
|
||||||
|
r.KeepContext = true
|
||||||
|
|
||||||
|
req, _ = http.NewRequest("GET", "http://localhost/", nil)
|
||||||
|
context.Set(req, "t", 1)
|
||||||
|
|
||||||
|
r.ServeHTTP(*res, req)
|
||||||
|
if _, ok := context.GetOk(req, "t"); !ok {
|
||||||
|
t.Error("Context should NOT have been cleared at end of request")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
24
vendor/github.com/gorilla/mux/context_native.go
generated
vendored
Normal file
24
vendor/github.com/gorilla/mux/context_native.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// +build go1.7
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func contextGet(r *http.Request, key interface{}) interface{} {
|
||||||
|
return r.Context().Value(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func contextSet(r *http.Request, key, val interface{}) *http.Request {
|
||||||
|
if val == nil {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.WithContext(context.WithValue(r.Context(), key, val))
|
||||||
|
}
|
||||||
|
|
||||||
|
func contextClear(r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
32
vendor/github.com/gorilla/mux/context_native_test.go
generated
vendored
Normal file
32
vendor/github.com/gorilla/mux/context_native_test.go
generated
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// +build go1.7
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNativeContextMiddleware(t *testing.T) {
|
||||||
|
withTimeout := func(h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
h.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
r := NewRouter()
|
||||||
|
r.Handle("/path/{foo}", withTimeout(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := Vars(r)
|
||||||
|
if vars["foo"] != "bar" {
|
||||||
|
t.Fatal("Expected foo var to be set")
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
|
||||||
|
rec := NewRecorder()
|
||||||
|
req := newRequest("GET", "/path/bar")
|
||||||
|
r.ServeHTTP(rec, req)
|
||||||
|
}
|
240
vendor/github.com/gorilla/mux/doc.go
generated
vendored
Normal file
240
vendor/github.com/gorilla/mux/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package mux implements a request router and dispatcher.
|
||||||
|
|
||||||
|
The name mux stands for "HTTP request multiplexer". Like the standard
|
||||||
|
http.ServeMux, mux.Router matches incoming requests against a list of
|
||||||
|
registered routes and calls a handler for the route that matches the URL
|
||||||
|
or other conditions. The main features are:
|
||||||
|
|
||||||
|
* Requests can be matched based on URL host, path, path prefix, schemes,
|
||||||
|
header and query values, HTTP methods or using custom matchers.
|
||||||
|
* URL hosts and paths can have variables with an optional regular
|
||||||
|
expression.
|
||||||
|
* Registered URLs can be built, or "reversed", which helps maintaining
|
||||||
|
references to resources.
|
||||||
|
* Routes can be used as subrouters: nested routes are only tested if the
|
||||||
|
parent route matches. This is useful to define groups of routes that
|
||||||
|
share common conditions like a host, a path prefix or other repeated
|
||||||
|
attributes. As a bonus, this optimizes request matching.
|
||||||
|
* It implements the http.Handler interface so it is compatible with the
|
||||||
|
standard http.ServeMux.
|
||||||
|
|
||||||
|
Let's start registering a couple of URL paths and handlers:
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/", HomeHandler)
|
||||||
|
r.HandleFunc("/products", ProductsHandler)
|
||||||
|
r.HandleFunc("/articles", ArticlesHandler)
|
||||||
|
http.Handle("/", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
Here we register three routes mapping URL paths to handlers. This is
|
||||||
|
equivalent to how http.HandleFunc() works: if an incoming request URL matches
|
||||||
|
one of the paths, the corresponding handler is called passing
|
||||||
|
(http.ResponseWriter, *http.Request) as parameters.
|
||||||
|
|
||||||
|
Paths can have variables. They are defined using the format {name} or
|
||||||
|
{name:pattern}. If a regular expression pattern is not defined, the matched
|
||||||
|
variable will be anything until the next slash. For example:
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/products/{key}", ProductHandler)
|
||||||
|
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
|
||||||
|
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
|
||||||
|
|
||||||
|
Groups can be used inside patterns, as long as they are non-capturing (?:re). For example:
|
||||||
|
|
||||||
|
r.HandleFunc("/articles/{category}/{sort:(?:asc|desc|new)}", ArticlesCategoryHandler)
|
||||||
|
|
||||||
|
The names are used to create a map of route variables which can be retrieved
|
||||||
|
calling mux.Vars():
|
||||||
|
|
||||||
|
vars := mux.Vars(request)
|
||||||
|
category := vars["category"]
|
||||||
|
|
||||||
|
Note that if any capturing groups are present, mux will panic() during parsing. To prevent
|
||||||
|
this, convert any capturing groups to non-capturing, e.g. change "/{sort:(asc|desc)}" to
|
||||||
|
"/{sort:(?:asc|desc)}". This is a change from prior versions which behaved unpredictably
|
||||||
|
when capturing groups were present.
|
||||||
|
|
||||||
|
And this is all you need to know about the basic usage. More advanced options
|
||||||
|
are explained below.
|
||||||
|
|
||||||
|
Routes can also be restricted to a domain or subdomain. Just define a host
|
||||||
|
pattern to be matched. They can also have variables:
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
// Only matches if domain is "www.example.com".
|
||||||
|
r.Host("www.example.com")
|
||||||
|
// Matches a dynamic subdomain.
|
||||||
|
r.Host("{subdomain:[a-z]+}.domain.com")
|
||||||
|
|
||||||
|
There are several other matchers that can be added. To match path prefixes:
|
||||||
|
|
||||||
|
r.PathPrefix("/products/")
|
||||||
|
|
||||||
|
...or HTTP methods:
|
||||||
|
|
||||||
|
r.Methods("GET", "POST")
|
||||||
|
|
||||||
|
...or URL schemes:
|
||||||
|
|
||||||
|
r.Schemes("https")
|
||||||
|
|
||||||
|
...or header values:
|
||||||
|
|
||||||
|
r.Headers("X-Requested-With", "XMLHttpRequest")
|
||||||
|
|
||||||
|
...or query values:
|
||||||
|
|
||||||
|
r.Queries("key", "value")
|
||||||
|
|
||||||
|
...or to use a custom matcher function:
|
||||||
|
|
||||||
|
r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
|
||||||
|
return r.ProtoMajor == 0
|
||||||
|
})
|
||||||
|
|
||||||
|
...and finally, it is possible to combine several matchers in a single route:
|
||||||
|
|
||||||
|
r.HandleFunc("/products", ProductsHandler).
|
||||||
|
Host("www.example.com").
|
||||||
|
Methods("GET").
|
||||||
|
Schemes("http")
|
||||||
|
|
||||||
|
Setting the same matching conditions again and again can be boring, so we have
|
||||||
|
a way to group several routes that share the same requirements.
|
||||||
|
We call it "subrouting".
|
||||||
|
|
||||||
|
For example, let's say we have several URLs that should only match when the
|
||||||
|
host is "www.example.com". Create a route for that host and get a "subrouter"
|
||||||
|
from it:
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
s := r.Host("www.example.com").Subrouter()
|
||||||
|
|
||||||
|
Then register routes in the subrouter:
|
||||||
|
|
||||||
|
s.HandleFunc("/products/", ProductsHandler)
|
||||||
|
s.HandleFunc("/products/{key}", ProductHandler)
|
||||||
|
s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
|
||||||
|
|
||||||
|
The three URL paths we registered above will only be tested if the domain is
|
||||||
|
"www.example.com", because the subrouter is tested first. This is not
|
||||||
|
only convenient, but also optimizes request matching. You can create
|
||||||
|
subrouters combining any attribute matchers accepted by a route.
|
||||||
|
|
||||||
|
Subrouters can be used to create domain or path "namespaces": you define
|
||||||
|
subrouters in a central place and then parts of the app can register its
|
||||||
|
paths relatively to a given subrouter.
|
||||||
|
|
||||||
|
There's one more thing about subroutes. When a subrouter has a path prefix,
|
||||||
|
the inner routes use it as base for their paths:
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
s := r.PathPrefix("/products").Subrouter()
|
||||||
|
// "/products/"
|
||||||
|
s.HandleFunc("/", ProductsHandler)
|
||||||
|
// "/products/{key}/"
|
||||||
|
s.HandleFunc("/{key}/", ProductHandler)
|
||||||
|
// "/products/{key}/details"
|
||||||
|
s.HandleFunc("/{key}/details", ProductDetailsHandler)
|
||||||
|
|
||||||
|
Note that the path provided to PathPrefix() represents a "wildcard": calling
|
||||||
|
PathPrefix("/static/").Handler(...) means that the handler will be passed any
|
||||||
|
request that matches "/static/*". This makes it easy to serve static files with mux:
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var dir string
|
||||||
|
|
||||||
|
flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
|
||||||
|
flag.Parse()
|
||||||
|
r := mux.NewRouter()
|
||||||
|
|
||||||
|
// This will serve files under http://localhost:8000/static/<filename>
|
||||||
|
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
|
||||||
|
|
||||||
|
srv := &http.Server{
|
||||||
|
Handler: r,
|
||||||
|
Addr: "127.0.0.1:8000",
|
||||||
|
// Good practice: enforce timeouts for servers you create!
|
||||||
|
WriteTimeout: 15 * time.Second,
|
||||||
|
ReadTimeout: 15 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Fatal(srv.ListenAndServe())
|
||||||
|
}
|
||||||
|
|
||||||
|
Now let's see how to build registered URLs.
|
||||||
|
|
||||||
|
Routes can be named. All routes that define a name can have their URLs built,
|
||||||
|
or "reversed". We define a name calling Name() on a route. For example:
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
||||||
|
Name("article")
|
||||||
|
|
||||||
|
To build a URL, get the route and call the URL() method, passing a sequence of
|
||||||
|
key/value pairs for the route variables. For the previous route, we would do:
|
||||||
|
|
||||||
|
url, err := r.Get("article").URL("category", "technology", "id", "42")
|
||||||
|
|
||||||
|
...and the result will be a url.URL with the following path:
|
||||||
|
|
||||||
|
"/articles/technology/42"
|
||||||
|
|
||||||
|
This also works for host variables:
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.Host("{subdomain}.domain.com").
|
||||||
|
Path("/articles/{category}/{id:[0-9]+}").
|
||||||
|
HandlerFunc(ArticleHandler).
|
||||||
|
Name("article")
|
||||||
|
|
||||||
|
// url.String() will be "http://news.domain.com/articles/technology/42"
|
||||||
|
url, err := r.Get("article").URL("subdomain", "news",
|
||||||
|
"category", "technology",
|
||||||
|
"id", "42")
|
||||||
|
|
||||||
|
All variables defined in the route are required, and their values must
|
||||||
|
conform to the corresponding patterns. These requirements guarantee that a
|
||||||
|
generated URL will always match a registered route -- the only exception is
|
||||||
|
for explicitly defined "build-only" routes which never match.
|
||||||
|
|
||||||
|
Regex support also exists for matching Headers within a route. For example, we could do:
|
||||||
|
|
||||||
|
r.HeadersRegexp("Content-Type", "application/(text|json)")
|
||||||
|
|
||||||
|
...and the route will match both requests with a Content-Type of `application/json` as well as
|
||||||
|
`application/text`
|
||||||
|
|
||||||
|
There's also a way to build only the URL host or path for a route:
|
||||||
|
use the methods URLHost() or URLPath() instead. For the previous route,
|
||||||
|
we would do:
|
||||||
|
|
||||||
|
// "http://news.domain.com/"
|
||||||
|
host, err := r.Get("article").URLHost("subdomain", "news")
|
||||||
|
|
||||||
|
// "/articles/technology/42"
|
||||||
|
path, err := r.Get("article").URLPath("category", "technology", "id", "42")
|
||||||
|
|
||||||
|
And if you use subrouters, host and path defined separately can be built
|
||||||
|
as well:
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
s := r.Host("{subdomain}.domain.com").Subrouter()
|
||||||
|
s.Path("/articles/{category}/{id:[0-9]+}").
|
||||||
|
HandlerFunc(ArticleHandler).
|
||||||
|
Name("article")
|
||||||
|
|
||||||
|
// "http://news.domain.com/articles/technology/42"
|
||||||
|
url, err := r.Get("article").URL("subdomain", "news",
|
||||||
|
"category", "technology",
|
||||||
|
"id", "42")
|
||||||
|
*/
|
||||||
|
package mux
|
542
vendor/github.com/gorilla/mux/mux.go
generated
vendored
Normal file
542
vendor/github.com/gorilla/mux/mux.go
generated
vendored
Normal file
|
@ -0,0 +1,542 @@
|
||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewRouter returns a new router instance.
|
||||||
|
func NewRouter() *Router {
|
||||||
|
return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Router registers routes to be matched and dispatches a handler.
|
||||||
|
//
|
||||||
|
// It implements the http.Handler interface, so it can be registered to serve
|
||||||
|
// requests:
|
||||||
|
//
|
||||||
|
// var router = mux.NewRouter()
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// http.Handle("/", router)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Or, for Google App Engine, register it in a init() function:
|
||||||
|
//
|
||||||
|
// func init() {
|
||||||
|
// http.Handle("/", router)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// This will send all incoming requests to the router.
|
||||||
|
type Router struct {
|
||||||
|
// Configurable Handler to be used when no route matches.
|
||||||
|
NotFoundHandler http.Handler
|
||||||
|
// Parent route, if this is a subrouter.
|
||||||
|
parent parentRoute
|
||||||
|
// Routes to be matched, in order.
|
||||||
|
routes []*Route
|
||||||
|
// Routes by name for URL building.
|
||||||
|
namedRoutes map[string]*Route
|
||||||
|
// See Router.StrictSlash(). This defines the flag for new routes.
|
||||||
|
strictSlash bool
|
||||||
|
// See Router.SkipClean(). This defines the flag for new routes.
|
||||||
|
skipClean bool
|
||||||
|
// If true, do not clear the request context after handling the request.
|
||||||
|
// This has no effect when go1.7+ is used, since the context is stored
|
||||||
|
// on the request itself.
|
||||||
|
KeepContext bool
|
||||||
|
// see Router.UseEncodedPath(). This defines a flag for all routes.
|
||||||
|
useEncodedPath bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match matches registered routes against the request.
|
||||||
|
func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
|
||||||
|
for _, route := range r.routes {
|
||||||
|
if route.Match(req, match) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closest match for a router (includes sub-routers)
|
||||||
|
if r.NotFoundHandler != nil {
|
||||||
|
match.Handler = r.NotFoundHandler
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP dispatches the handler registered in the matched route.
|
||||||
|
//
|
||||||
|
// When there is a match, the route variables can be retrieved calling
|
||||||
|
// mux.Vars(request).
|
||||||
|
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if !r.skipClean {
|
||||||
|
path := req.URL.Path
|
||||||
|
if r.useEncodedPath {
|
||||||
|
path = getPath(req)
|
||||||
|
}
|
||||||
|
// Clean path to canonical form and redirect.
|
||||||
|
if p := cleanPath(path); p != path {
|
||||||
|
|
||||||
|
// Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
|
||||||
|
// This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue:
|
||||||
|
// http://code.google.com/p/go/issues/detail?id=5252
|
||||||
|
url := *req.URL
|
||||||
|
url.Path = p
|
||||||
|
p = url.String()
|
||||||
|
|
||||||
|
w.Header().Set("Location", p)
|
||||||
|
w.WriteHeader(http.StatusMovedPermanently)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var match RouteMatch
|
||||||
|
var handler http.Handler
|
||||||
|
if r.Match(req, &match) {
|
||||||
|
handler = match.Handler
|
||||||
|
req = setVars(req, match.Vars)
|
||||||
|
req = setCurrentRoute(req, match.Route)
|
||||||
|
}
|
||||||
|
if handler == nil {
|
||||||
|
handler = http.NotFoundHandler()
|
||||||
|
}
|
||||||
|
if !r.KeepContext {
|
||||||
|
defer contextClear(req)
|
||||||
|
}
|
||||||
|
handler.ServeHTTP(w, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a route registered with the given name.
|
||||||
|
func (r *Router) Get(name string) *Route {
|
||||||
|
return r.getNamedRoutes()[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoute returns a route registered with the given name. This method
|
||||||
|
// was renamed to Get() and remains here for backwards compatibility.
|
||||||
|
func (r *Router) GetRoute(name string) *Route {
|
||||||
|
return r.getNamedRoutes()[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrictSlash defines the trailing slash behavior for new routes. The initial
|
||||||
|
// value is false.
|
||||||
|
//
|
||||||
|
// When true, if the route path is "/path/", accessing "/path" will redirect
|
||||||
|
// to the former and vice versa. In other words, your application will always
|
||||||
|
// see the path as specified in the route.
|
||||||
|
//
|
||||||
|
// When false, if the route path is "/path", accessing "/path/" will not match
|
||||||
|
// this route and vice versa.
|
||||||
|
//
|
||||||
|
// Special case: when a route sets a path prefix using the PathPrefix() method,
|
||||||
|
// strict slash is ignored for that route because the redirect behavior can't
|
||||||
|
// be determined from a prefix alone. However, any subrouters created from that
|
||||||
|
// route inherit the original StrictSlash setting.
|
||||||
|
func (r *Router) StrictSlash(value bool) *Router {
|
||||||
|
r.strictSlash = value
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkipClean defines the path cleaning behaviour for new routes. The initial
|
||||||
|
// value is false. Users should be careful about which routes are not cleaned
|
||||||
|
//
|
||||||
|
// When true, if the route path is "/path//to", it will remain with the double
|
||||||
|
// slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/
|
||||||
|
//
|
||||||
|
// When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will
|
||||||
|
// become /fetch/http/xkcd.com/534
|
||||||
|
func (r *Router) SkipClean(value bool) *Router {
|
||||||
|
r.skipClean = value
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseEncodedPath tells the router to match the encoded original path
|
||||||
|
// to the routes.
|
||||||
|
// For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to".
|
||||||
|
// This behavior has the drawback of needing to match routes against
|
||||||
|
// r.RequestURI instead of r.URL.Path. Any modifications (such as http.StripPrefix)
|
||||||
|
// to r.URL.Path will not affect routing when this flag is on and thus may
|
||||||
|
// induce unintended behavior.
|
||||||
|
//
|
||||||
|
// If not called, the router will match the unencoded path to the routes.
|
||||||
|
// For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to"
|
||||||
|
func (r *Router) UseEncodedPath() *Router {
|
||||||
|
r.useEncodedPath = true
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// parentRoute
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// getNamedRoutes returns the map where named routes are registered.
|
||||||
|
func (r *Router) getNamedRoutes() map[string]*Route {
|
||||||
|
if r.namedRoutes == nil {
|
||||||
|
if r.parent != nil {
|
||||||
|
r.namedRoutes = r.parent.getNamedRoutes()
|
||||||
|
} else {
|
||||||
|
r.namedRoutes = make(map[string]*Route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.namedRoutes
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRegexpGroup returns regexp definitions from the parent route, if any.
|
||||||
|
func (r *Router) getRegexpGroup() *routeRegexpGroup {
|
||||||
|
if r.parent != nil {
|
||||||
|
return r.parent.getRegexpGroup()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) buildVars(m map[string]string) map[string]string {
|
||||||
|
if r.parent != nil {
|
||||||
|
m = r.parent.buildVars(m)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Route factories
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// NewRoute registers an empty route.
|
||||||
|
func (r *Router) NewRoute() *Route {
|
||||||
|
route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath}
|
||||||
|
r.routes = append(r.routes, route)
|
||||||
|
return route
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle registers a new route with a matcher for the URL path.
|
||||||
|
// See Route.Path() and Route.Handler().
|
||||||
|
func (r *Router) Handle(path string, handler http.Handler) *Route {
|
||||||
|
return r.NewRoute().Path(path).Handler(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleFunc registers a new route with a matcher for the URL path.
|
||||||
|
// See Route.Path() and Route.HandlerFunc().
|
||||||
|
func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
|
||||||
|
*http.Request)) *Route {
|
||||||
|
return r.NewRoute().Path(path).HandlerFunc(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Headers registers a new route with a matcher for request header values.
|
||||||
|
// See Route.Headers().
|
||||||
|
func (r *Router) Headers(pairs ...string) *Route {
|
||||||
|
return r.NewRoute().Headers(pairs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host registers a new route with a matcher for the URL host.
|
||||||
|
// See Route.Host().
|
||||||
|
func (r *Router) Host(tpl string) *Route {
|
||||||
|
return r.NewRoute().Host(tpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatcherFunc registers a new route with a custom matcher function.
|
||||||
|
// See Route.MatcherFunc().
|
||||||
|
func (r *Router) MatcherFunc(f MatcherFunc) *Route {
|
||||||
|
return r.NewRoute().MatcherFunc(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods registers a new route with a matcher for HTTP methods.
|
||||||
|
// See Route.Methods().
|
||||||
|
func (r *Router) Methods(methods ...string) *Route {
|
||||||
|
return r.NewRoute().Methods(methods...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path registers a new route with a matcher for the URL path.
|
||||||
|
// See Route.Path().
|
||||||
|
func (r *Router) Path(tpl string) *Route {
|
||||||
|
return r.NewRoute().Path(tpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathPrefix registers a new route with a matcher for the URL path prefix.
|
||||||
|
// See Route.PathPrefix().
|
||||||
|
func (r *Router) PathPrefix(tpl string) *Route {
|
||||||
|
return r.NewRoute().PathPrefix(tpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queries registers a new route with a matcher for URL query values.
|
||||||
|
// See Route.Queries().
|
||||||
|
func (r *Router) Queries(pairs ...string) *Route {
|
||||||
|
return r.NewRoute().Queries(pairs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schemes registers a new route with a matcher for URL schemes.
|
||||||
|
// See Route.Schemes().
|
||||||
|
func (r *Router) Schemes(schemes ...string) *Route {
|
||||||
|
return r.NewRoute().Schemes(schemes...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildVarsFunc registers a new route with a custom function for modifying
|
||||||
|
// route variables before building a URL.
|
||||||
|
func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route {
|
||||||
|
return r.NewRoute().BuildVarsFunc(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk walks the router and all its sub-routers, calling walkFn for each route
|
||||||
|
// in the tree. The routes are walked in the order they were added. Sub-routers
|
||||||
|
// are explored depth-first.
|
||||||
|
func (r *Router) Walk(walkFn WalkFunc) error {
|
||||||
|
return r.walk(walkFn, []*Route{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkipRouter is used as a return value from WalkFuncs to indicate that the
|
||||||
|
// router that walk is about to descend down to should be skipped.
|
||||||
|
var SkipRouter = errors.New("skip this router")
|
||||||
|
|
||||||
|
// WalkFunc is the type of the function called for each route visited by Walk.
|
||||||
|
// At every invocation, it is given the current route, and the current router,
|
||||||
|
// and a list of ancestor routes that lead to the current route.
|
||||||
|
type WalkFunc func(route *Route, router *Router, ancestors []*Route) error
|
||||||
|
|
||||||
|
func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
|
||||||
|
for _, t := range r.routes {
|
||||||
|
if t.regexp == nil || t.regexp.path == nil || t.regexp.path.template == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err := walkFn(t, r, ancestors)
|
||||||
|
if err == SkipRouter {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, sr := range t.matchers {
|
||||||
|
if h, ok := sr.(*Router); ok {
|
||||||
|
err := h.walk(walkFn, ancestors)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if h, ok := t.handler.(*Router); ok {
|
||||||
|
ancestors = append(ancestors, t)
|
||||||
|
err := h.walk(walkFn, ancestors)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ancestors = ancestors[:len(ancestors)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Context
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// RouteMatch stores information about a matched route.
|
||||||
|
type RouteMatch struct {
|
||||||
|
Route *Route
|
||||||
|
Handler http.Handler
|
||||||
|
Vars map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
type contextKey int
|
||||||
|
|
||||||
|
const (
|
||||||
|
varsKey contextKey = iota
|
||||||
|
routeKey
|
||||||
|
)
|
||||||
|
|
||||||
|
// Vars returns the route variables for the current request, if any.
|
||||||
|
func Vars(r *http.Request) map[string]string {
|
||||||
|
if rv := contextGet(r, varsKey); rv != nil {
|
||||||
|
return rv.(map[string]string)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentRoute returns the matched route for the current request, if any.
|
||||||
|
// This only works when called inside the handler of the matched route
|
||||||
|
// because the matched route is stored in the request context which is cleared
|
||||||
|
// after the handler returns, unless the KeepContext option is set on the
|
||||||
|
// Router.
|
||||||
|
func CurrentRoute(r *http.Request) *Route {
|
||||||
|
if rv := contextGet(r, routeKey); rv != nil {
|
||||||
|
return rv.(*Route)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setVars(r *http.Request, val interface{}) *http.Request {
|
||||||
|
return contextSet(r, varsKey, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setCurrentRoute(r *http.Request, val interface{}) *http.Request {
|
||||||
|
return contextSet(r, routeKey, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Helpers
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// getPath returns the escaped path if possible; doing what URL.EscapedPath()
|
||||||
|
// which was added in go1.5 does
|
||||||
|
func getPath(req *http.Request) string {
|
||||||
|
if req.RequestURI != "" {
|
||||||
|
// Extract the path from RequestURI (which is escaped unlike URL.Path)
|
||||||
|
// as detailed here as detailed in https://golang.org/pkg/net/url/#URL
|
||||||
|
// for < 1.5 server side workaround
|
||||||
|
// http://localhost/path/here?v=1 -> /path/here
|
||||||
|
path := req.RequestURI
|
||||||
|
path = strings.TrimPrefix(path, req.URL.Scheme+`://`)
|
||||||
|
path = strings.TrimPrefix(path, req.URL.Host)
|
||||||
|
if i := strings.LastIndex(path, "?"); i > -1 {
|
||||||
|
path = path[:i]
|
||||||
|
}
|
||||||
|
if i := strings.LastIndex(path, "#"); i > -1 {
|
||||||
|
path = path[:i]
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
return req.URL.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanPath returns the canonical path for p, eliminating . and .. elements.
|
||||||
|
// Borrowed from the net/http package.
|
||||||
|
func cleanPath(p string) string {
|
||||||
|
if p == "" {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
if p[0] != '/' {
|
||||||
|
p = "/" + p
|
||||||
|
}
|
||||||
|
np := path.Clean(p)
|
||||||
|
// path.Clean removes trailing slash except for root;
|
||||||
|
// put the trailing slash back if necessary.
|
||||||
|
if p[len(p)-1] == '/' && np != "/" {
|
||||||
|
np += "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
return np
|
||||||
|
}
|
||||||
|
|
||||||
|
// uniqueVars returns an error if two slices contain duplicated strings.
|
||||||
|
func uniqueVars(s1, s2 []string) error {
|
||||||
|
for _, v1 := range s1 {
|
||||||
|
for _, v2 := range s2 {
|
||||||
|
if v1 == v2 {
|
||||||
|
return fmt.Errorf("mux: duplicated route variable %q", v2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkPairs returns the count of strings passed in, and an error if
|
||||||
|
// the count is not an even number.
|
||||||
|
func checkPairs(pairs ...string) (int, error) {
|
||||||
|
length := len(pairs)
|
||||||
|
if length%2 != 0 {
|
||||||
|
return length, fmt.Errorf(
|
||||||
|
"mux: number of parameters must be multiple of 2, got %v", pairs)
|
||||||
|
}
|
||||||
|
return length, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mapFromPairsToString converts variadic string parameters to a
|
||||||
|
// string to string map.
|
||||||
|
func mapFromPairsToString(pairs ...string) (map[string]string, error) {
|
||||||
|
length, err := checkPairs(pairs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m := make(map[string]string, length/2)
|
||||||
|
for i := 0; i < length; i += 2 {
|
||||||
|
m[pairs[i]] = pairs[i+1]
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mapFromPairsToRegex converts variadic string paramers to a
|
||||||
|
// string to regex map.
|
||||||
|
func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) {
|
||||||
|
length, err := checkPairs(pairs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m := make(map[string]*regexp.Regexp, length/2)
|
||||||
|
for i := 0; i < length; i += 2 {
|
||||||
|
regex, err := regexp.Compile(pairs[i+1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m[pairs[i]] = regex
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchInArray returns true if the given string value is in the array.
|
||||||
|
func matchInArray(arr []string, value string) bool {
|
||||||
|
for _, v := range arr {
|
||||||
|
if v == value {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchMapWithString returns true if the given key/value pairs exist in a given map.
|
||||||
|
func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool {
|
||||||
|
for k, v := range toCheck {
|
||||||
|
// Check if key exists.
|
||||||
|
if canonicalKey {
|
||||||
|
k = http.CanonicalHeaderKey(k)
|
||||||
|
}
|
||||||
|
if values := toMatch[k]; values == nil {
|
||||||
|
return false
|
||||||
|
} else if v != "" {
|
||||||
|
// If value was defined as an empty string we only check that the
|
||||||
|
// key exists. Otherwise we also check for equality.
|
||||||
|
valueExists := false
|
||||||
|
for _, value := range values {
|
||||||
|
if v == value {
|
||||||
|
valueExists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !valueExists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against
|
||||||
|
// the given regex
|
||||||
|
func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool {
|
||||||
|
for k, v := range toCheck {
|
||||||
|
// Check if key exists.
|
||||||
|
if canonicalKey {
|
||||||
|
k = http.CanonicalHeaderKey(k)
|
||||||
|
}
|
||||||
|
if values := toMatch[k]; values == nil {
|
||||||
|
return false
|
||||||
|
} else if v != nil {
|
||||||
|
// If value was defined as an empty string we only check that the
|
||||||
|
// key exists. Otherwise we also check for equality.
|
||||||
|
valueExists := false
|
||||||
|
for _, value := range values {
|
||||||
|
if v.MatchString(value) {
|
||||||
|
valueExists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !valueExists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
1655
vendor/github.com/gorilla/mux/mux_test.go
generated
vendored
Normal file
1655
vendor/github.com/gorilla/mux/mux_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
710
vendor/github.com/gorilla/mux/old_test.go
generated
vendored
Normal file
710
vendor/github.com/gorilla/mux/old_test.go
generated
vendored
Normal file
|
@ -0,0 +1,710 @@
|
||||||
|
// Old tests ported to Go1. This is a mess. Want to drop it one day.
|
||||||
|
|
||||||
|
// Copyright 2011 Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// ResponseRecorder
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// ResponseRecorder is an implementation of http.ResponseWriter that
|
||||||
|
// records its mutations for later inspection in tests.
|
||||||
|
type ResponseRecorder struct {
|
||||||
|
Code int // the HTTP response code from WriteHeader
|
||||||
|
HeaderMap http.Header // the HTTP response headers
|
||||||
|
Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
|
||||||
|
Flushed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRecorder returns an initialized ResponseRecorder.
|
||||||
|
func NewRecorder() *ResponseRecorder {
|
||||||
|
return &ResponseRecorder{
|
||||||
|
HeaderMap: make(http.Header),
|
||||||
|
Body: new(bytes.Buffer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header returns the response headers.
|
||||||
|
func (rw *ResponseRecorder) Header() http.Header {
|
||||||
|
return rw.HeaderMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write always succeeds and writes to rw.Body, if not nil.
|
||||||
|
func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
|
||||||
|
if rw.Body != nil {
|
||||||
|
rw.Body.Write(buf)
|
||||||
|
}
|
||||||
|
if rw.Code == 0 {
|
||||||
|
rw.Code = http.StatusOK
|
||||||
|
}
|
||||||
|
return len(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHeader sets rw.Code.
|
||||||
|
func (rw *ResponseRecorder) WriteHeader(code int) {
|
||||||
|
rw.Code = code
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush sets rw.Flushed to true.
|
||||||
|
func (rw *ResponseRecorder) Flush() {
|
||||||
|
rw.Flushed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func TestRouteMatchers(t *testing.T) {
|
||||||
|
var scheme, host, path, query, method string
|
||||||
|
var headers map[string]string
|
||||||
|
var resultVars map[bool]map[string]string
|
||||||
|
|
||||||
|
router := NewRouter()
|
||||||
|
router.NewRoute().Host("{var1}.google.com").
|
||||||
|
Path("/{var2:[a-z]+}/{var3:[0-9]+}").
|
||||||
|
Queries("foo", "bar").
|
||||||
|
Methods("GET").
|
||||||
|
Schemes("https").
|
||||||
|
Headers("x-requested-with", "XMLHttpRequest")
|
||||||
|
router.NewRoute().Host("www.{var4}.com").
|
||||||
|
PathPrefix("/foo/{var5:[a-z]+}/{var6:[0-9]+}").
|
||||||
|
Queries("baz", "ding").
|
||||||
|
Methods("POST").
|
||||||
|
Schemes("http").
|
||||||
|
Headers("Content-Type", "application/json")
|
||||||
|
|
||||||
|
reset := func() {
|
||||||
|
// Everything match.
|
||||||
|
scheme = "https"
|
||||||
|
host = "www.google.com"
|
||||||
|
path = "/product/42"
|
||||||
|
query = "?foo=bar"
|
||||||
|
method = "GET"
|
||||||
|
headers = map[string]string{"X-Requested-With": "XMLHttpRequest"}
|
||||||
|
resultVars = map[bool]map[string]string{
|
||||||
|
true: {"var1": "www", "var2": "product", "var3": "42"},
|
||||||
|
false: {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reset2 := func() {
|
||||||
|
// Everything match.
|
||||||
|
scheme = "http"
|
||||||
|
host = "www.google.com"
|
||||||
|
path = "/foo/product/42/path/that/is/ignored"
|
||||||
|
query = "?baz=ding"
|
||||||
|
method = "POST"
|
||||||
|
headers = map[string]string{"Content-Type": "application/json"}
|
||||||
|
resultVars = map[bool]map[string]string{
|
||||||
|
true: {"var4": "google", "var5": "product", "var6": "42"},
|
||||||
|
false: {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match := func(shouldMatch bool) {
|
||||||
|
url := scheme + "://" + host + path + query
|
||||||
|
request, _ := http.NewRequest(method, url, nil)
|
||||||
|
for key, value := range headers {
|
||||||
|
request.Header.Add(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
var routeMatch RouteMatch
|
||||||
|
matched := router.Match(request, &routeMatch)
|
||||||
|
if matched != shouldMatch {
|
||||||
|
// Need better messages. :)
|
||||||
|
if matched {
|
||||||
|
t.Errorf("Should match.")
|
||||||
|
} else {
|
||||||
|
t.Errorf("Should not match.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if matched {
|
||||||
|
currentRoute := routeMatch.Route
|
||||||
|
if currentRoute == nil {
|
||||||
|
t.Errorf("Expected a current route.")
|
||||||
|
}
|
||||||
|
vars := routeMatch.Vars
|
||||||
|
expectedVars := resultVars[shouldMatch]
|
||||||
|
if len(vars) != len(expectedVars) {
|
||||||
|
t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars)
|
||||||
|
}
|
||||||
|
for name, value := range vars {
|
||||||
|
if expectedVars[name] != value {
|
||||||
|
t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1st route --------------------------------------------------------------
|
||||||
|
|
||||||
|
// Everything match.
|
||||||
|
reset()
|
||||||
|
match(true)
|
||||||
|
|
||||||
|
// Scheme doesn't match.
|
||||||
|
reset()
|
||||||
|
scheme = "http"
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Host doesn't match.
|
||||||
|
reset()
|
||||||
|
host = "www.mygoogle.com"
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Path doesn't match.
|
||||||
|
reset()
|
||||||
|
path = "/product/notdigits"
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Query doesn't match.
|
||||||
|
reset()
|
||||||
|
query = "?foo=baz"
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Method doesn't match.
|
||||||
|
reset()
|
||||||
|
method = "POST"
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Header doesn't match.
|
||||||
|
reset()
|
||||||
|
headers = map[string]string{}
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Everything match, again.
|
||||||
|
reset()
|
||||||
|
match(true)
|
||||||
|
|
||||||
|
// 2nd route --------------------------------------------------------------
|
||||||
|
|
||||||
|
// Everything match.
|
||||||
|
reset2()
|
||||||
|
match(true)
|
||||||
|
|
||||||
|
// Scheme doesn't match.
|
||||||
|
reset2()
|
||||||
|
scheme = "https"
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Host doesn't match.
|
||||||
|
reset2()
|
||||||
|
host = "sub.google.com"
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Path doesn't match.
|
||||||
|
reset2()
|
||||||
|
path = "/bar/product/42"
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Query doesn't match.
|
||||||
|
reset2()
|
||||||
|
query = "?foo=baz"
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Method doesn't match.
|
||||||
|
reset2()
|
||||||
|
method = "GET"
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Header doesn't match.
|
||||||
|
reset2()
|
||||||
|
headers = map[string]string{}
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Everything match, again.
|
||||||
|
reset2()
|
||||||
|
match(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
type headerMatcherTest struct {
|
||||||
|
matcher headerMatcher
|
||||||
|
headers map[string]string
|
||||||
|
result bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var headerMatcherTests = []headerMatcherTest{
|
||||||
|
{
|
||||||
|
matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}),
|
||||||
|
headers: map[string]string{"X-Requested-With": "XMLHttpRequest"},
|
||||||
|
result: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: headerMatcher(map[string]string{"x-requested-with": ""}),
|
||||||
|
headers: map[string]string{"X-Requested-With": "anything"},
|
||||||
|
result: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}),
|
||||||
|
headers: map[string]string{},
|
||||||
|
result: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type hostMatcherTest struct {
|
||||||
|
matcher *Route
|
||||||
|
url string
|
||||||
|
vars map[string]string
|
||||||
|
result bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var hostMatcherTests = []hostMatcherTest{
|
||||||
|
{
|
||||||
|
matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
|
||||||
|
url: "http://abc.def.ghi/",
|
||||||
|
vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
|
||||||
|
result: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
|
||||||
|
url: "http://a.b.c/",
|
||||||
|
vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
|
||||||
|
result: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type methodMatcherTest struct {
|
||||||
|
matcher methodMatcher
|
||||||
|
method string
|
||||||
|
result bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var methodMatcherTests = []methodMatcherTest{
|
||||||
|
{
|
||||||
|
matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
|
||||||
|
method: "GET",
|
||||||
|
result: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
|
||||||
|
method: "POST",
|
||||||
|
result: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
|
||||||
|
method: "PUT",
|
||||||
|
result: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
|
||||||
|
method: "DELETE",
|
||||||
|
result: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type pathMatcherTest struct {
|
||||||
|
matcher *Route
|
||||||
|
url string
|
||||||
|
vars map[string]string
|
||||||
|
result bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var pathMatcherTests = []pathMatcherTest{
|
||||||
|
{
|
||||||
|
matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"),
|
||||||
|
url: "http://localhost:8080/123/456/789",
|
||||||
|
vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"},
|
||||||
|
result: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"),
|
||||||
|
url: "http://localhost:8080/1/2/3",
|
||||||
|
vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"},
|
||||||
|
result: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type schemeMatcherTest struct {
|
||||||
|
matcher schemeMatcher
|
||||||
|
url string
|
||||||
|
result bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var schemeMatcherTests = []schemeMatcherTest{
|
||||||
|
{
|
||||||
|
matcher: schemeMatcher([]string{"http", "https"}),
|
||||||
|
url: "http://localhost:8080/",
|
||||||
|
result: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: schemeMatcher([]string{"http", "https"}),
|
||||||
|
url: "https://localhost:8080/",
|
||||||
|
result: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: schemeMatcher([]string{"https"}),
|
||||||
|
url: "http://localhost:8080/",
|
||||||
|
result: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: schemeMatcher([]string{"http"}),
|
||||||
|
url: "https://localhost:8080/",
|
||||||
|
result: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type urlBuildingTest struct {
|
||||||
|
route *Route
|
||||||
|
vars []string
|
||||||
|
url string
|
||||||
|
}
|
||||||
|
|
||||||
|
var urlBuildingTests = []urlBuildingTest{
|
||||||
|
{
|
||||||
|
route: new(Route).Host("foo.domain.com"),
|
||||||
|
vars: []string{},
|
||||||
|
url: "http://foo.domain.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: new(Route).Host("{subdomain}.domain.com"),
|
||||||
|
vars: []string{"subdomain", "bar"},
|
||||||
|
url: "http://bar.domain.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: new(Route).Host("foo.domain.com").Path("/articles"),
|
||||||
|
vars: []string{},
|
||||||
|
url: "http://foo.domain.com/articles",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: new(Route).Path("/articles"),
|
||||||
|
vars: []string{},
|
||||||
|
url: "/articles",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: new(Route).Path("/articles/{category}/{id:[0-9]+}"),
|
||||||
|
vars: []string{"category", "technology", "id", "42"},
|
||||||
|
url: "/articles/technology/42",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: new(Route).Host("{subdomain}.domain.com").Path("/articles/{category}/{id:[0-9]+}"),
|
||||||
|
vars: []string{"subdomain", "foo", "category", "technology", "id", "42"},
|
||||||
|
url: "http://foo.domain.com/articles/technology/42",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHeaderMatcher(t *testing.T) {
|
||||||
|
for _, v := range headerMatcherTests {
|
||||||
|
request, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||||
|
for key, value := range v.headers {
|
||||||
|
request.Header.Add(key, value)
|
||||||
|
}
|
||||||
|
var routeMatch RouteMatch
|
||||||
|
result := v.matcher.Match(request, &routeMatch)
|
||||||
|
if result != v.result {
|
||||||
|
if v.result {
|
||||||
|
t.Errorf("%#v: should match %v.", v.matcher, request.Header)
|
||||||
|
} else {
|
||||||
|
t.Errorf("%#v: should not match %v.", v.matcher, request.Header)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHostMatcher(t *testing.T) {
|
||||||
|
for _, v := range hostMatcherTests {
|
||||||
|
request, _ := http.NewRequest("GET", v.url, nil)
|
||||||
|
var routeMatch RouteMatch
|
||||||
|
result := v.matcher.Match(request, &routeMatch)
|
||||||
|
vars := routeMatch.Vars
|
||||||
|
if result != v.result {
|
||||||
|
if v.result {
|
||||||
|
t.Errorf("%#v: should match %v.", v.matcher, v.url)
|
||||||
|
} else {
|
||||||
|
t.Errorf("%#v: should not match %v.", v.matcher, v.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if result {
|
||||||
|
if len(vars) != len(v.vars) {
|
||||||
|
t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars))
|
||||||
|
}
|
||||||
|
for name, value := range vars {
|
||||||
|
if v.vars[name] != value {
|
||||||
|
t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(vars) != 0 {
|
||||||
|
t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMethodMatcher(t *testing.T) {
|
||||||
|
for _, v := range methodMatcherTests {
|
||||||
|
request, _ := http.NewRequest(v.method, "http://localhost:8080/", nil)
|
||||||
|
var routeMatch RouteMatch
|
||||||
|
result := v.matcher.Match(request, &routeMatch)
|
||||||
|
if result != v.result {
|
||||||
|
if v.result {
|
||||||
|
t.Errorf("%#v: should match %v.", v.matcher, v.method)
|
||||||
|
} else {
|
||||||
|
t.Errorf("%#v: should not match %v.", v.matcher, v.method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathMatcher(t *testing.T) {
|
||||||
|
for _, v := range pathMatcherTests {
|
||||||
|
request, _ := http.NewRequest("GET", v.url, nil)
|
||||||
|
var routeMatch RouteMatch
|
||||||
|
result := v.matcher.Match(request, &routeMatch)
|
||||||
|
vars := routeMatch.Vars
|
||||||
|
if result != v.result {
|
||||||
|
if v.result {
|
||||||
|
t.Errorf("%#v: should match %v.", v.matcher, v.url)
|
||||||
|
} else {
|
||||||
|
t.Errorf("%#v: should not match %v.", v.matcher, v.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if result {
|
||||||
|
if len(vars) != len(v.vars) {
|
||||||
|
t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars))
|
||||||
|
}
|
||||||
|
for name, value := range vars {
|
||||||
|
if v.vars[name] != value {
|
||||||
|
t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(vars) != 0 {
|
||||||
|
t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSchemeMatcher(t *testing.T) {
|
||||||
|
for _, v := range schemeMatcherTests {
|
||||||
|
request, _ := http.NewRequest("GET", v.url, nil)
|
||||||
|
var routeMatch RouteMatch
|
||||||
|
result := v.matcher.Match(request, &routeMatch)
|
||||||
|
if result != v.result {
|
||||||
|
if v.result {
|
||||||
|
t.Errorf("%#v: should match %v.", v.matcher, v.url)
|
||||||
|
} else {
|
||||||
|
t.Errorf("%#v: should not match %v.", v.matcher, v.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUrlBuilding(t *testing.T) {
|
||||||
|
|
||||||
|
for _, v := range urlBuildingTests {
|
||||||
|
u, _ := v.route.URL(v.vars...)
|
||||||
|
url := u.String()
|
||||||
|
if url != v.url {
|
||||||
|
t.Errorf("expected %v, got %v", v.url, url)
|
||||||
|
/*
|
||||||
|
reversePath := ""
|
||||||
|
reverseHost := ""
|
||||||
|
if v.route.pathTemplate != nil {
|
||||||
|
reversePath = v.route.pathTemplate.Reverse
|
||||||
|
}
|
||||||
|
if v.route.hostTemplate != nil {
|
||||||
|
reverseHost = v.route.hostTemplate.Reverse
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Errorf("%#v:\nexpected: %q\ngot: %q\nreverse path: %q\nreverse host: %q", v.route, v.url, url, reversePath, reverseHost)
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ArticleHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
}
|
||||||
|
|
||||||
|
router := NewRouter()
|
||||||
|
router.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).Name("article")
|
||||||
|
|
||||||
|
url, _ := router.Get("article").URL("category", "technology", "id", "42")
|
||||||
|
expected := "/articles/technology/42"
|
||||||
|
if url.String() != expected {
|
||||||
|
t.Errorf("Expected %v, got %v", expected, url.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatchedRouteName(t *testing.T) {
|
||||||
|
routeName := "stock"
|
||||||
|
router := NewRouter()
|
||||||
|
route := router.NewRoute().Path("/products/").Name(routeName)
|
||||||
|
|
||||||
|
url := "http://www.example.com/products/"
|
||||||
|
request, _ := http.NewRequest("GET", url, nil)
|
||||||
|
var rv RouteMatch
|
||||||
|
ok := router.Match(request, &rv)
|
||||||
|
|
||||||
|
if !ok || rv.Route != route {
|
||||||
|
t.Errorf("Expected same route, got %+v.", rv.Route)
|
||||||
|
}
|
||||||
|
|
||||||
|
retName := rv.Route.GetName()
|
||||||
|
if retName != routeName {
|
||||||
|
t.Errorf("Expected %q, got %q.", routeName, retName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSubRouting(t *testing.T) {
|
||||||
|
// Example from docs.
|
||||||
|
router := NewRouter()
|
||||||
|
subrouter := router.NewRoute().Host("www.example.com").Subrouter()
|
||||||
|
route := subrouter.NewRoute().Path("/products/").Name("products")
|
||||||
|
|
||||||
|
url := "http://www.example.com/products/"
|
||||||
|
request, _ := http.NewRequest("GET", url, nil)
|
||||||
|
var rv RouteMatch
|
||||||
|
ok := router.Match(request, &rv)
|
||||||
|
|
||||||
|
if !ok || rv.Route != route {
|
||||||
|
t.Errorf("Expected same route, got %+v.", rv.Route)
|
||||||
|
}
|
||||||
|
|
||||||
|
u, _ := router.Get("products").URL()
|
||||||
|
builtURL := u.String()
|
||||||
|
// Yay, subroute aware of the domain when building!
|
||||||
|
if builtURL != url {
|
||||||
|
t.Errorf("Expected %q, got %q.", url, builtURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVariableNames(t *testing.T) {
|
||||||
|
route := new(Route).Host("{arg1}.domain.com").Path("/{arg1}/{arg2:[0-9]+}")
|
||||||
|
if route.err == nil {
|
||||||
|
t.Errorf("Expected error for duplicated variable names")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedirectSlash(t *testing.T) {
|
||||||
|
var route *Route
|
||||||
|
var routeMatch RouteMatch
|
||||||
|
r := NewRouter()
|
||||||
|
|
||||||
|
r.StrictSlash(false)
|
||||||
|
route = r.NewRoute()
|
||||||
|
if route.strictSlash != false {
|
||||||
|
t.Errorf("Expected false redirectSlash.")
|
||||||
|
}
|
||||||
|
|
||||||
|
r.StrictSlash(true)
|
||||||
|
route = r.NewRoute()
|
||||||
|
if route.strictSlash != true {
|
||||||
|
t.Errorf("Expected true redirectSlash.")
|
||||||
|
}
|
||||||
|
|
||||||
|
route = new(Route)
|
||||||
|
route.strictSlash = true
|
||||||
|
route.Path("/{arg1}/{arg2:[0-9]+}/")
|
||||||
|
request, _ := http.NewRequest("GET", "http://localhost/foo/123", nil)
|
||||||
|
routeMatch = RouteMatch{}
|
||||||
|
_ = route.Match(request, &routeMatch)
|
||||||
|
vars := routeMatch.Vars
|
||||||
|
if vars["arg1"] != "foo" {
|
||||||
|
t.Errorf("Expected foo.")
|
||||||
|
}
|
||||||
|
if vars["arg2"] != "123" {
|
||||||
|
t.Errorf("Expected 123.")
|
||||||
|
}
|
||||||
|
rsp := NewRecorder()
|
||||||
|
routeMatch.Handler.ServeHTTP(rsp, request)
|
||||||
|
if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123/" {
|
||||||
|
t.Errorf("Expected redirect header.")
|
||||||
|
}
|
||||||
|
|
||||||
|
route = new(Route)
|
||||||
|
route.strictSlash = true
|
||||||
|
route.Path("/{arg1}/{arg2:[0-9]+}")
|
||||||
|
request, _ = http.NewRequest("GET", "http://localhost/foo/123/", nil)
|
||||||
|
routeMatch = RouteMatch{}
|
||||||
|
_ = route.Match(request, &routeMatch)
|
||||||
|
vars = routeMatch.Vars
|
||||||
|
if vars["arg1"] != "foo" {
|
||||||
|
t.Errorf("Expected foo.")
|
||||||
|
}
|
||||||
|
if vars["arg2"] != "123" {
|
||||||
|
t.Errorf("Expected 123.")
|
||||||
|
}
|
||||||
|
rsp = NewRecorder()
|
||||||
|
routeMatch.Handler.ServeHTTP(rsp, request)
|
||||||
|
if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123" {
|
||||||
|
t.Errorf("Expected redirect header.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for the new regexp library, still not available in stable Go.
|
||||||
|
func TestNewRegexp(t *testing.T) {
|
||||||
|
var p *routeRegexp
|
||||||
|
var matches []string
|
||||||
|
|
||||||
|
tests := map[string]map[string][]string{
|
||||||
|
"/{foo:a{2}}": {
|
||||||
|
"/a": nil,
|
||||||
|
"/aa": {"aa"},
|
||||||
|
"/aaa": nil,
|
||||||
|
"/aaaa": nil,
|
||||||
|
},
|
||||||
|
"/{foo:a{2,}}": {
|
||||||
|
"/a": nil,
|
||||||
|
"/aa": {"aa"},
|
||||||
|
"/aaa": {"aaa"},
|
||||||
|
"/aaaa": {"aaaa"},
|
||||||
|
},
|
||||||
|
"/{foo:a{2,3}}": {
|
||||||
|
"/a": nil,
|
||||||
|
"/aa": {"aa"},
|
||||||
|
"/aaa": {"aaa"},
|
||||||
|
"/aaaa": nil,
|
||||||
|
},
|
||||||
|
"/{foo:[a-z]{3}}/{bar:[a-z]{2}}": {
|
||||||
|
"/a": nil,
|
||||||
|
"/ab": nil,
|
||||||
|
"/abc": nil,
|
||||||
|
"/abcd": nil,
|
||||||
|
"/abc/ab": {"abc", "ab"},
|
||||||
|
"/abc/abc": nil,
|
||||||
|
"/abcd/ab": nil,
|
||||||
|
},
|
||||||
|
`/{foo:\w{3,}}/{bar:\d{2,}}`: {
|
||||||
|
"/a": nil,
|
||||||
|
"/ab": nil,
|
||||||
|
"/abc": nil,
|
||||||
|
"/abc/1": nil,
|
||||||
|
"/abc/12": {"abc", "12"},
|
||||||
|
"/abcd/12": {"abcd", "12"},
|
||||||
|
"/abcd/123": {"abcd", "123"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for pattern, paths := range tests {
|
||||||
|
p, _ = newRouteRegexp(pattern, false, false, false, false, false)
|
||||||
|
for path, result := range paths {
|
||||||
|
matches = p.regexp.FindStringSubmatch(path)
|
||||||
|
if result == nil {
|
||||||
|
if matches != nil {
|
||||||
|
t.Errorf("%v should not match %v.", pattern, path)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(matches) != len(result)+1 {
|
||||||
|
t.Errorf("Expected %v matches, got %v.", len(result)+1, len(matches))
|
||||||
|
} else {
|
||||||
|
for k, v := range result {
|
||||||
|
if matches[k+1] != v {
|
||||||
|
t.Errorf("Expected %v, got %v.", v, matches[k+1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
323
vendor/github.com/gorilla/mux/regexp.go
generated
vendored
Normal file
323
vendor/github.com/gorilla/mux/regexp.go
generated
vendored
Normal file
|
@ -0,0 +1,323 @@
|
||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newRouteRegexp parses a route template and returns a routeRegexp,
|
||||||
|
// used to match a host, a path or a query string.
|
||||||
|
//
|
||||||
|
// It will extract named variables, assemble a regexp to be matched, create
|
||||||
|
// a "reverse" template to build URLs and compile regexps to validate variable
|
||||||
|
// values used in URL building.
|
||||||
|
//
|
||||||
|
// Previously we accepted only Python-like identifiers for variable
|
||||||
|
// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
|
||||||
|
// name and pattern can't be empty, and names can't contain a colon.
|
||||||
|
func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash, useEncodedPath bool) (*routeRegexp, error) {
|
||||||
|
// Check if it is well-formed.
|
||||||
|
idxs, errBraces := braceIndices(tpl)
|
||||||
|
if errBraces != nil {
|
||||||
|
return nil, errBraces
|
||||||
|
}
|
||||||
|
// Backup the original.
|
||||||
|
template := tpl
|
||||||
|
// Now let's parse it.
|
||||||
|
defaultPattern := "[^/]+"
|
||||||
|
if matchQuery {
|
||||||
|
defaultPattern = "[^?&]*"
|
||||||
|
} else if matchHost {
|
||||||
|
defaultPattern = "[^.]+"
|
||||||
|
matchPrefix = false
|
||||||
|
}
|
||||||
|
// Only match strict slash if not matching
|
||||||
|
if matchPrefix || matchHost || matchQuery {
|
||||||
|
strictSlash = false
|
||||||
|
}
|
||||||
|
// Set a flag for strictSlash.
|
||||||
|
endSlash := false
|
||||||
|
if strictSlash && strings.HasSuffix(tpl, "/") {
|
||||||
|
tpl = tpl[:len(tpl)-1]
|
||||||
|
endSlash = true
|
||||||
|
}
|
||||||
|
varsN := make([]string, len(idxs)/2)
|
||||||
|
varsR := make([]*regexp.Regexp, len(idxs)/2)
|
||||||
|
pattern := bytes.NewBufferString("")
|
||||||
|
pattern.WriteByte('^')
|
||||||
|
reverse := bytes.NewBufferString("")
|
||||||
|
var end int
|
||||||
|
var err error
|
||||||
|
for i := 0; i < len(idxs); i += 2 {
|
||||||
|
// Set all values we are interested in.
|
||||||
|
raw := tpl[end:idxs[i]]
|
||||||
|
end = idxs[i+1]
|
||||||
|
parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2)
|
||||||
|
name := parts[0]
|
||||||
|
patt := defaultPattern
|
||||||
|
if len(parts) == 2 {
|
||||||
|
patt = parts[1]
|
||||||
|
}
|
||||||
|
// Name or pattern can't be empty.
|
||||||
|
if name == "" || patt == "" {
|
||||||
|
return nil, fmt.Errorf("mux: missing name or pattern in %q",
|
||||||
|
tpl[idxs[i]:end])
|
||||||
|
}
|
||||||
|
// Build the regexp pattern.
|
||||||
|
fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt)
|
||||||
|
|
||||||
|
// Build the reverse template.
|
||||||
|
fmt.Fprintf(reverse, "%s%%s", raw)
|
||||||
|
|
||||||
|
// Append variable name and compiled pattern.
|
||||||
|
varsN[i/2] = name
|
||||||
|
varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add the remaining.
|
||||||
|
raw := tpl[end:]
|
||||||
|
pattern.WriteString(regexp.QuoteMeta(raw))
|
||||||
|
if strictSlash {
|
||||||
|
pattern.WriteString("[/]?")
|
||||||
|
}
|
||||||
|
if matchQuery {
|
||||||
|
// Add the default pattern if the query value is empty
|
||||||
|
if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" {
|
||||||
|
pattern.WriteString(defaultPattern)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !matchPrefix {
|
||||||
|
pattern.WriteByte('$')
|
||||||
|
}
|
||||||
|
reverse.WriteString(raw)
|
||||||
|
if endSlash {
|
||||||
|
reverse.WriteByte('/')
|
||||||
|
}
|
||||||
|
// Compile full regexp.
|
||||||
|
reg, errCompile := regexp.Compile(pattern.String())
|
||||||
|
if errCompile != nil {
|
||||||
|
return nil, errCompile
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for capturing groups which used to work in older versions
|
||||||
|
if reg.NumSubexp() != len(idxs)/2 {
|
||||||
|
panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) +
|
||||||
|
"Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done!
|
||||||
|
return &routeRegexp{
|
||||||
|
template: template,
|
||||||
|
matchHost: matchHost,
|
||||||
|
matchQuery: matchQuery,
|
||||||
|
strictSlash: strictSlash,
|
||||||
|
useEncodedPath: useEncodedPath,
|
||||||
|
regexp: reg,
|
||||||
|
reverse: reverse.String(),
|
||||||
|
varsN: varsN,
|
||||||
|
varsR: varsR,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// routeRegexp stores a regexp to match a host or path and information to
|
||||||
|
// collect and validate route variables.
|
||||||
|
type routeRegexp struct {
|
||||||
|
// The unmodified template.
|
||||||
|
template string
|
||||||
|
// True for host match, false for path or query string match.
|
||||||
|
matchHost bool
|
||||||
|
// True for query string match, false for path and host match.
|
||||||
|
matchQuery bool
|
||||||
|
// The strictSlash value defined on the route, but disabled if PathPrefix was used.
|
||||||
|
strictSlash bool
|
||||||
|
// Determines whether to use encoded path from getPath function or unencoded
|
||||||
|
// req.URL.Path for path matching
|
||||||
|
useEncodedPath bool
|
||||||
|
// Expanded regexp.
|
||||||
|
regexp *regexp.Regexp
|
||||||
|
// Reverse template.
|
||||||
|
reverse string
|
||||||
|
// Variable names.
|
||||||
|
varsN []string
|
||||||
|
// Variable regexps (validators).
|
||||||
|
varsR []*regexp.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match matches the regexp against the URL host or path.
|
||||||
|
func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
|
||||||
|
if !r.matchHost {
|
||||||
|
if r.matchQuery {
|
||||||
|
return r.matchQueryString(req)
|
||||||
|
}
|
||||||
|
path := req.URL.Path
|
||||||
|
if r.useEncodedPath {
|
||||||
|
path = getPath(req)
|
||||||
|
}
|
||||||
|
return r.regexp.MatchString(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.regexp.MatchString(getHost(req))
|
||||||
|
}
|
||||||
|
|
||||||
|
// url builds a URL part using the given values.
|
||||||
|
func (r *routeRegexp) url(values map[string]string) (string, error) {
|
||||||
|
urlValues := make([]interface{}, len(r.varsN))
|
||||||
|
for k, v := range r.varsN {
|
||||||
|
value, ok := values[v]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("mux: missing route variable %q", v)
|
||||||
|
}
|
||||||
|
urlValues[k] = value
|
||||||
|
}
|
||||||
|
rv := fmt.Sprintf(r.reverse, urlValues...)
|
||||||
|
if !r.regexp.MatchString(rv) {
|
||||||
|
// The URL is checked against the full regexp, instead of checking
|
||||||
|
// individual variables. This is faster but to provide a good error
|
||||||
|
// message, we check individual regexps if the URL doesn't match.
|
||||||
|
for k, v := range r.varsN {
|
||||||
|
if !r.varsR[k].MatchString(values[v]) {
|
||||||
|
return "", fmt.Errorf(
|
||||||
|
"mux: variable %q doesn't match, expected %q", values[v],
|
||||||
|
r.varsR[k].String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getURLQuery returns a single query parameter from a request URL.
|
||||||
|
// For a URL with foo=bar&baz=ding, we return only the relevant key
|
||||||
|
// value pair for the routeRegexp.
|
||||||
|
func (r *routeRegexp) getURLQuery(req *http.Request) string {
|
||||||
|
if !r.matchQuery {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
templateKey := strings.SplitN(r.template, "=", 2)[0]
|
||||||
|
for key, vals := range req.URL.Query() {
|
||||||
|
if key == templateKey && len(vals) > 0 {
|
||||||
|
return key + "=" + vals[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routeRegexp) matchQueryString(req *http.Request) bool {
|
||||||
|
return r.regexp.MatchString(r.getURLQuery(req))
|
||||||
|
}
|
||||||
|
|
||||||
|
// braceIndices returns the first level curly brace indices from a string.
|
||||||
|
// It returns an error in case of unbalanced braces.
|
||||||
|
func braceIndices(s string) ([]int, error) {
|
||||||
|
var level, idx int
|
||||||
|
var idxs []int
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
switch s[i] {
|
||||||
|
case '{':
|
||||||
|
if level++; level == 1 {
|
||||||
|
idx = i
|
||||||
|
}
|
||||||
|
case '}':
|
||||||
|
if level--; level == 0 {
|
||||||
|
idxs = append(idxs, idx, i+1)
|
||||||
|
} else if level < 0 {
|
||||||
|
return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if level != 0 {
|
||||||
|
return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
|
||||||
|
}
|
||||||
|
return idxs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// varGroupName builds a capturing group name for the indexed variable.
|
||||||
|
func varGroupName(idx int) string {
|
||||||
|
return "v" + strconv.Itoa(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// routeRegexpGroup
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// routeRegexpGroup groups the route matchers that carry variables.
|
||||||
|
type routeRegexpGroup struct {
|
||||||
|
host *routeRegexp
|
||||||
|
path *routeRegexp
|
||||||
|
queries []*routeRegexp
|
||||||
|
}
|
||||||
|
|
||||||
|
// setMatch extracts the variables from the URL once a route matches.
|
||||||
|
func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
|
||||||
|
// Store host variables.
|
||||||
|
if v.host != nil {
|
||||||
|
host := getHost(req)
|
||||||
|
matches := v.host.regexp.FindStringSubmatchIndex(host)
|
||||||
|
if len(matches) > 0 {
|
||||||
|
extractVars(host, matches, v.host.varsN, m.Vars)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path := req.URL.Path
|
||||||
|
if r.useEncodedPath {
|
||||||
|
path = getPath(req)
|
||||||
|
}
|
||||||
|
// Store path variables.
|
||||||
|
if v.path != nil {
|
||||||
|
matches := v.path.regexp.FindStringSubmatchIndex(path)
|
||||||
|
if len(matches) > 0 {
|
||||||
|
extractVars(path, matches, v.path.varsN, m.Vars)
|
||||||
|
// Check if we should redirect.
|
||||||
|
if v.path.strictSlash {
|
||||||
|
p1 := strings.HasSuffix(path, "/")
|
||||||
|
p2 := strings.HasSuffix(v.path.template, "/")
|
||||||
|
if p1 != p2 {
|
||||||
|
u, _ := url.Parse(req.URL.String())
|
||||||
|
if p1 {
|
||||||
|
u.Path = u.Path[:len(u.Path)-1]
|
||||||
|
} else {
|
||||||
|
u.Path += "/"
|
||||||
|
}
|
||||||
|
m.Handler = http.RedirectHandler(u.String(), 301)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Store query string variables.
|
||||||
|
for _, q := range v.queries {
|
||||||
|
queryURL := q.getURLQuery(req)
|
||||||
|
matches := q.regexp.FindStringSubmatchIndex(queryURL)
|
||||||
|
if len(matches) > 0 {
|
||||||
|
extractVars(queryURL, matches, q.varsN, m.Vars)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getHost tries its best to return the request host.
|
||||||
|
func getHost(r *http.Request) string {
|
||||||
|
if r.URL.IsAbs() {
|
||||||
|
return r.URL.Host
|
||||||
|
}
|
||||||
|
host := r.Host
|
||||||
|
// Slice off any port information.
|
||||||
|
if i := strings.Index(host, ":"); i != -1 {
|
||||||
|
host = host[:i]
|
||||||
|
}
|
||||||
|
return host
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractVars(input string, matches []int, names []string, output map[string]string) {
|
||||||
|
for i, name := range names {
|
||||||
|
output[name] = input[matches[2*i+2]:matches[2*i+3]]
|
||||||
|
}
|
||||||
|
}
|
636
vendor/github.com/gorilla/mux/route.go
generated
vendored
Normal file
636
vendor/github.com/gorilla/mux/route.go
generated
vendored
Normal file
|
@ -0,0 +1,636 @@
|
||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Route stores information to match a request and build URLs.
|
||||||
|
type Route struct {
|
||||||
|
// Parent where the route was registered (a Router).
|
||||||
|
parent parentRoute
|
||||||
|
// Request handler for the route.
|
||||||
|
handler http.Handler
|
||||||
|
// List of matchers.
|
||||||
|
matchers []matcher
|
||||||
|
// Manager for the variables from host and path.
|
||||||
|
regexp *routeRegexpGroup
|
||||||
|
// If true, when the path pattern is "/path/", accessing "/path" will
|
||||||
|
// redirect to the former and vice versa.
|
||||||
|
strictSlash bool
|
||||||
|
// If true, when the path pattern is "/path//to", accessing "/path//to"
|
||||||
|
// will not redirect
|
||||||
|
skipClean bool
|
||||||
|
// If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
|
||||||
|
useEncodedPath bool
|
||||||
|
// If true, this route never matches: it is only used to build URLs.
|
||||||
|
buildOnly bool
|
||||||
|
// The name used to build URLs.
|
||||||
|
name string
|
||||||
|
// Error resulted from building a route.
|
||||||
|
err error
|
||||||
|
|
||||||
|
buildVarsFunc BuildVarsFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Route) SkipClean() bool {
|
||||||
|
return r.skipClean
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match matches the route against the request.
|
||||||
|
func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
|
||||||
|
if r.buildOnly || r.err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Match everything.
|
||||||
|
for _, m := range r.matchers {
|
||||||
|
if matched := m.Match(req, match); !matched {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Yay, we have a match. Let's collect some info about it.
|
||||||
|
if match.Route == nil {
|
||||||
|
match.Route = r
|
||||||
|
}
|
||||||
|
if match.Handler == nil {
|
||||||
|
match.Handler = r.handler
|
||||||
|
}
|
||||||
|
if match.Vars == nil {
|
||||||
|
match.Vars = make(map[string]string)
|
||||||
|
}
|
||||||
|
// Set variables.
|
||||||
|
if r.regexp != nil {
|
||||||
|
r.regexp.setMatch(req, match, r)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Route attributes
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// GetError returns an error resulted from building the route, if any.
|
||||||
|
func (r *Route) GetError() error {
|
||||||
|
return r.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildOnly sets the route to never match: it is only used to build URLs.
|
||||||
|
func (r *Route) BuildOnly() *Route {
|
||||||
|
r.buildOnly = true
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler --------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Handler sets a handler for the route.
|
||||||
|
func (r *Route) Handler(handler http.Handler) *Route {
|
||||||
|
if r.err == nil {
|
||||||
|
r.handler = handler
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlerFunc sets a handler function for the route.
|
||||||
|
func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route {
|
||||||
|
return r.Handler(http.HandlerFunc(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHandler returns the handler for the route, if any.
|
||||||
|
func (r *Route) GetHandler() http.Handler {
|
||||||
|
return r.handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Name sets the name for the route, used to build URLs.
|
||||||
|
// If the name was registered already it will be overwritten.
|
||||||
|
func (r *Route) Name(name string) *Route {
|
||||||
|
if r.name != "" {
|
||||||
|
r.err = fmt.Errorf("mux: route already has name %q, can't set %q",
|
||||||
|
r.name, name)
|
||||||
|
}
|
||||||
|
if r.err == nil {
|
||||||
|
r.name = name
|
||||||
|
r.getNamedRoutes()[name] = r
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetName returns the name for the route, if any.
|
||||||
|
func (r *Route) GetName() string {
|
||||||
|
return r.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Matchers
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// matcher types try to match a request.
|
||||||
|
type matcher interface {
|
||||||
|
Match(*http.Request, *RouteMatch) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// addMatcher adds a matcher to the route.
|
||||||
|
func (r *Route) addMatcher(m matcher) *Route {
|
||||||
|
if r.err == nil {
|
||||||
|
r.matchers = append(r.matchers, m)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// addRegexpMatcher adds a host or path matcher and builder to a route.
|
||||||
|
func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery bool) error {
|
||||||
|
if r.err != nil {
|
||||||
|
return r.err
|
||||||
|
}
|
||||||
|
r.regexp = r.getRegexpGroup()
|
||||||
|
if !matchHost && !matchQuery {
|
||||||
|
if tpl == "/" && (len(tpl) == 0 || tpl[0] != '/') {
|
||||||
|
return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
|
||||||
|
}
|
||||||
|
if r.regexp.path != nil {
|
||||||
|
tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash, r.useEncodedPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, q := range r.regexp.queries {
|
||||||
|
if err = uniqueVars(rr.varsN, q.varsN); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if matchHost {
|
||||||
|
if r.regexp.path != nil {
|
||||||
|
if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.regexp.host = rr
|
||||||
|
} else {
|
||||||
|
if r.regexp.host != nil {
|
||||||
|
if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if matchQuery {
|
||||||
|
r.regexp.queries = append(r.regexp.queries, rr)
|
||||||
|
} else {
|
||||||
|
r.regexp.path = rr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.addMatcher(rr)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Headers --------------------------------------------------------------------
|
||||||
|
|
||||||
|
// headerMatcher matches the request against header values.
|
||||||
|
type headerMatcher map[string]string
|
||||||
|
|
||||||
|
func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
||||||
|
return matchMapWithString(m, r.Header, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Headers adds a matcher for request header values.
|
||||||
|
// It accepts a sequence of key/value pairs to be matched. For example:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.Headers("Content-Type", "application/json",
|
||||||
|
// "X-Requested-With", "XMLHttpRequest")
|
||||||
|
//
|
||||||
|
// The above route will only match if both request header values match.
|
||||||
|
// If the value is an empty string, it will match any value if the key is set.
|
||||||
|
func (r *Route) Headers(pairs ...string) *Route {
|
||||||
|
if r.err == nil {
|
||||||
|
var headers map[string]string
|
||||||
|
headers, r.err = mapFromPairsToString(pairs...)
|
||||||
|
return r.addMatcher(headerMatcher(headers))
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// headerRegexMatcher matches the request against the route given a regex for the header
|
||||||
|
type headerRegexMatcher map[string]*regexp.Regexp
|
||||||
|
|
||||||
|
func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
||||||
|
return matchMapWithRegex(m, r.Header, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeadersRegexp accepts a sequence of key/value pairs, where the value has regex
|
||||||
|
// support. For example:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.HeadersRegexp("Content-Type", "application/(text|json)",
|
||||||
|
// "X-Requested-With", "XMLHttpRequest")
|
||||||
|
//
|
||||||
|
// The above route will only match if both the request header matches both regular expressions.
|
||||||
|
// It the value is an empty string, it will match any value if the key is set.
|
||||||
|
func (r *Route) HeadersRegexp(pairs ...string) *Route {
|
||||||
|
if r.err == nil {
|
||||||
|
var headers map[string]*regexp.Regexp
|
||||||
|
headers, r.err = mapFromPairsToRegex(pairs...)
|
||||||
|
return r.addMatcher(headerRegexMatcher(headers))
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Host adds a matcher for the URL host.
|
||||||
|
// It accepts a template with zero or more URL variables enclosed by {}.
|
||||||
|
// Variables can define an optional regexp pattern to be matched:
|
||||||
|
//
|
||||||
|
// - {name} matches anything until the next dot.
|
||||||
|
//
|
||||||
|
// - {name:pattern} matches the given regexp pattern.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.Host("www.example.com")
|
||||||
|
// r.Host("{subdomain}.domain.com")
|
||||||
|
// r.Host("{subdomain:[a-z]+}.domain.com")
|
||||||
|
//
|
||||||
|
// Variable names must be unique in a given route. They can be retrieved
|
||||||
|
// calling mux.Vars(request).
|
||||||
|
func (r *Route) Host(tpl string) *Route {
|
||||||
|
r.err = r.addRegexpMatcher(tpl, true, false, false)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatcherFunc ----------------------------------------------------------------
|
||||||
|
|
||||||
|
// MatcherFunc is the function signature used by custom matchers.
|
||||||
|
type MatcherFunc func(*http.Request, *RouteMatch) bool
|
||||||
|
|
||||||
|
// Match returns the match for a given request.
|
||||||
|
func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool {
|
||||||
|
return m(r, match)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatcherFunc adds a custom function to be used as request matcher.
|
||||||
|
func (r *Route) MatcherFunc(f MatcherFunc) *Route {
|
||||||
|
return r.addMatcher(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods --------------------------------------------------------------------
|
||||||
|
|
||||||
|
// methodMatcher matches the request against HTTP methods.
|
||||||
|
type methodMatcher []string
|
||||||
|
|
||||||
|
func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
||||||
|
return matchInArray(m, r.Method)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods adds a matcher for HTTP methods.
|
||||||
|
// It accepts a sequence of one or more methods to be matched, e.g.:
|
||||||
|
// "GET", "POST", "PUT".
|
||||||
|
func (r *Route) Methods(methods ...string) *Route {
|
||||||
|
for k, v := range methods {
|
||||||
|
methods[k] = strings.ToUpper(v)
|
||||||
|
}
|
||||||
|
return r.addMatcher(methodMatcher(methods))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Path adds a matcher for the URL path.
|
||||||
|
// It accepts a template with zero or more URL variables enclosed by {}. The
|
||||||
|
// template must start with a "/".
|
||||||
|
// Variables can define an optional regexp pattern to be matched:
|
||||||
|
//
|
||||||
|
// - {name} matches anything until the next slash.
|
||||||
|
//
|
||||||
|
// - {name:pattern} matches the given regexp pattern.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.Path("/products/").Handler(ProductsHandler)
|
||||||
|
// r.Path("/products/{key}").Handler(ProductsHandler)
|
||||||
|
// r.Path("/articles/{category}/{id:[0-9]+}").
|
||||||
|
// Handler(ArticleHandler)
|
||||||
|
//
|
||||||
|
// Variable names must be unique in a given route. They can be retrieved
|
||||||
|
// calling mux.Vars(request).
|
||||||
|
func (r *Route) Path(tpl string) *Route {
|
||||||
|
r.err = r.addRegexpMatcher(tpl, false, false, false)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathPrefix -----------------------------------------------------------------
|
||||||
|
|
||||||
|
// PathPrefix adds a matcher for the URL path prefix. This matches if the given
|
||||||
|
// template is a prefix of the full URL path. See Route.Path() for details on
|
||||||
|
// the tpl argument.
|
||||||
|
//
|
||||||
|
// Note that it does not treat slashes specially ("/foobar/" will be matched by
|
||||||
|
// the prefix "/foo") so you may want to use a trailing slash here.
|
||||||
|
//
|
||||||
|
// Also note that the setting of Router.StrictSlash() has no effect on routes
|
||||||
|
// with a PathPrefix matcher.
|
||||||
|
func (r *Route) PathPrefix(tpl string) *Route {
|
||||||
|
r.err = r.addRegexpMatcher(tpl, false, true, false)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Queries adds a matcher for URL query values.
|
||||||
|
// It accepts a sequence of key/value pairs. Values may define variables.
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.Queries("foo", "bar", "id", "{id:[0-9]+}")
|
||||||
|
//
|
||||||
|
// The above route will only match if the URL contains the defined queries
|
||||||
|
// values, e.g.: ?foo=bar&id=42.
|
||||||
|
//
|
||||||
|
// It the value is an empty string, it will match any value if the key is set.
|
||||||
|
//
|
||||||
|
// Variables can define an optional regexp pattern to be matched:
|
||||||
|
//
|
||||||
|
// - {name} matches anything until the next slash.
|
||||||
|
//
|
||||||
|
// - {name:pattern} matches the given regexp pattern.
|
||||||
|
func (r *Route) Queries(pairs ...string) *Route {
|
||||||
|
length := len(pairs)
|
||||||
|
if length%2 != 0 {
|
||||||
|
r.err = fmt.Errorf(
|
||||||
|
"mux: number of parameters must be multiple of 2, got %v", pairs)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for i := 0; i < length; i += 2 {
|
||||||
|
if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], false, false, true); r.err != nil {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schemes --------------------------------------------------------------------
|
||||||
|
|
||||||
|
// schemeMatcher matches the request against URL schemes.
|
||||||
|
type schemeMatcher []string
|
||||||
|
|
||||||
|
func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
||||||
|
return matchInArray(m, r.URL.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schemes adds a matcher for URL schemes.
|
||||||
|
// It accepts a sequence of schemes to be matched, e.g.: "http", "https".
|
||||||
|
func (r *Route) Schemes(schemes ...string) *Route {
|
||||||
|
for k, v := range schemes {
|
||||||
|
schemes[k] = strings.ToLower(v)
|
||||||
|
}
|
||||||
|
return r.addMatcher(schemeMatcher(schemes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildVarsFunc --------------------------------------------------------------
|
||||||
|
|
||||||
|
// BuildVarsFunc is the function signature used by custom build variable
|
||||||
|
// functions (which can modify route variables before a route's URL is built).
|
||||||
|
type BuildVarsFunc func(map[string]string) map[string]string
|
||||||
|
|
||||||
|
// BuildVarsFunc adds a custom function to be used to modify build variables
|
||||||
|
// before a route's URL is built.
|
||||||
|
func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
|
||||||
|
r.buildVarsFunc = f
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subrouter ------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Subrouter creates a subrouter for the route.
|
||||||
|
//
|
||||||
|
// It will test the inner routes only if the parent route matched. For example:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// s := r.Host("www.example.com").Subrouter()
|
||||||
|
// s.HandleFunc("/products/", ProductsHandler)
|
||||||
|
// s.HandleFunc("/products/{key}", ProductHandler)
|
||||||
|
// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
|
||||||
|
//
|
||||||
|
// Here, the routes registered in the subrouter won't be tested if the host
|
||||||
|
// doesn't match.
|
||||||
|
func (r *Route) Subrouter() *Router {
|
||||||
|
router := &Router{parent: r, strictSlash: r.strictSlash}
|
||||||
|
r.addMatcher(router)
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// URL building
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// URL builds a URL for the route.
|
||||||
|
//
|
||||||
|
// It accepts a sequence of key/value pairs for the route variables. For
|
||||||
|
// example, given this route:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
||||||
|
// Name("article")
|
||||||
|
//
|
||||||
|
// ...a URL for it can be built using:
|
||||||
|
//
|
||||||
|
// url, err := r.Get("article").URL("category", "technology", "id", "42")
|
||||||
|
//
|
||||||
|
// ...which will return an url.URL with the following path:
|
||||||
|
//
|
||||||
|
// "/articles/technology/42"
|
||||||
|
//
|
||||||
|
// This also works for host variables:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.Host("{subdomain}.domain.com").
|
||||||
|
// HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
||||||
|
// Name("article")
|
||||||
|
//
|
||||||
|
// // url.String() will be "http://news.domain.com/articles/technology/42"
|
||||||
|
// url, err := r.Get("article").URL("subdomain", "news",
|
||||||
|
// "category", "technology",
|
||||||
|
// "id", "42")
|
||||||
|
//
|
||||||
|
// All variables defined in the route are required, and their values must
|
||||||
|
// conform to the corresponding patterns.
|
||||||
|
func (r *Route) URL(pairs ...string) (*url.URL, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return nil, r.err
|
||||||
|
}
|
||||||
|
if r.regexp == nil {
|
||||||
|
return nil, errors.New("mux: route doesn't have a host or path")
|
||||||
|
}
|
||||||
|
values, err := r.prepareVars(pairs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var scheme, host, path string
|
||||||
|
if r.regexp.host != nil {
|
||||||
|
// Set a default scheme.
|
||||||
|
scheme = "http"
|
||||||
|
if host, err = r.regexp.host.url(values); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.regexp.path != nil {
|
||||||
|
if path, err = r.regexp.path.url(values); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &url.URL{
|
||||||
|
Scheme: scheme,
|
||||||
|
Host: host,
|
||||||
|
Path: path,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLHost builds the host part of the URL for a route. See Route.URL().
|
||||||
|
//
|
||||||
|
// The route must have a host defined.
|
||||||
|
func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return nil, r.err
|
||||||
|
}
|
||||||
|
if r.regexp == nil || r.regexp.host == nil {
|
||||||
|
return nil, errors.New("mux: route doesn't have a host")
|
||||||
|
}
|
||||||
|
values, err := r.prepareVars(pairs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
host, err := r.regexp.host.url(values)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: host,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLPath builds the path part of the URL for a route. See Route.URL().
|
||||||
|
//
|
||||||
|
// The route must have a path defined.
|
||||||
|
func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return nil, r.err
|
||||||
|
}
|
||||||
|
if r.regexp == nil || r.regexp.path == nil {
|
||||||
|
return nil, errors.New("mux: route doesn't have a path")
|
||||||
|
}
|
||||||
|
values, err := r.prepareVars(pairs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
path, err := r.regexp.path.url(values)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &url.URL{
|
||||||
|
Path: path,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPathTemplate returns the template used to build the
|
||||||
|
// route match.
|
||||||
|
// This is useful for building simple REST API documentation and for instrumentation
|
||||||
|
// against third-party services.
|
||||||
|
// An error will be returned if the route does not define a path.
|
||||||
|
func (r *Route) GetPathTemplate() (string, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return "", r.err
|
||||||
|
}
|
||||||
|
if r.regexp == nil || r.regexp.path == nil {
|
||||||
|
return "", errors.New("mux: route doesn't have a path")
|
||||||
|
}
|
||||||
|
return r.regexp.path.template, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHostTemplate returns the template used to build the
|
||||||
|
// route match.
|
||||||
|
// This is useful for building simple REST API documentation and for instrumentation
|
||||||
|
// against third-party services.
|
||||||
|
// An error will be returned if the route does not define a host.
|
||||||
|
func (r *Route) GetHostTemplate() (string, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return "", r.err
|
||||||
|
}
|
||||||
|
if r.regexp == nil || r.regexp.host == nil {
|
||||||
|
return "", errors.New("mux: route doesn't have a host")
|
||||||
|
}
|
||||||
|
return r.regexp.host.template, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareVars converts the route variable pairs into a map. If the route has a
|
||||||
|
// BuildVarsFunc, it is invoked.
|
||||||
|
func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {
|
||||||
|
m, err := mapFromPairsToString(pairs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return r.buildVars(m), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Route) buildVars(m map[string]string) map[string]string {
|
||||||
|
if r.parent != nil {
|
||||||
|
m = r.parent.buildVars(m)
|
||||||
|
}
|
||||||
|
if r.buildVarsFunc != nil {
|
||||||
|
m = r.buildVarsFunc(m)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// parentRoute
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// parentRoute allows routes to know about parent host and path definitions.
|
||||||
|
type parentRoute interface {
|
||||||
|
getNamedRoutes() map[string]*Route
|
||||||
|
getRegexpGroup() *routeRegexpGroup
|
||||||
|
buildVars(map[string]string) map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// getNamedRoutes returns the map where named routes are registered.
|
||||||
|
func (r *Route) getNamedRoutes() map[string]*Route {
|
||||||
|
if r.parent == nil {
|
||||||
|
// During tests router is not always set.
|
||||||
|
r.parent = NewRouter()
|
||||||
|
}
|
||||||
|
return r.parent.getNamedRoutes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRegexpGroup returns regexp definitions from this route.
|
||||||
|
func (r *Route) getRegexpGroup() *routeRegexpGroup {
|
||||||
|
if r.regexp == nil {
|
||||||
|
if r.parent == nil {
|
||||||
|
// During tests router is not always set.
|
||||||
|
r.parent = NewRouter()
|
||||||
|
}
|
||||||
|
regexp := r.parent.getRegexpGroup()
|
||||||
|
if regexp == nil {
|
||||||
|
r.regexp = new(routeRegexpGroup)
|
||||||
|
} else {
|
||||||
|
// Copy.
|
||||||
|
r.regexp = &routeRegexpGroup{
|
||||||
|
host: regexp.host,
|
||||||
|
path: regexp.path,
|
||||||
|
queries: regexp.queries,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.regexp
|
||||||
|
}
|
19
vendor/github.com/mastertinner/adapters/LICENSE
generated
vendored
Normal file
19
vendor/github.com/mastertinner/adapters/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2017 Tobias Fuhrimann
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
40
vendor/github.com/mastertinner/adapters/README.md
generated
vendored
Normal file
40
vendor/github.com/mastertinner/adapters/README.md
generated
vendored
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
# Adapters
|
||||||
|
|
||||||
|
Adapters is a collection of useful HTTP middleware or "Adapters". They follow the Adapter Pattern described by Mat Ryer in his blog post [Writing middleware in #golang and how Go makes it so much fun.](https://medium.com/@matryer/writing-middleware-in-golang-and-how-go-makes-it-so-much-fun-4375c1246e81)
|
||||||
|
|
||||||
|
Adapters can be chained in any way and will be executed in the order they are specified.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/mastertinner/adapters"
|
||||||
|
"github.com/mastertinner/adapters/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IndexHandler says what it loves
|
||||||
|
func IndexHandler() http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
logger := log.New(os.Stdout, "", log.Ldate|log.Ltime)
|
||||||
|
http.Handle("/", adapters.Adapt(IndexHandler(), logging.Handler(logger)))
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adapters
|
||||||
|
|
||||||
|
This package contains the following adapters:
|
||||||
|
|
||||||
|
* Logging: Logs the request and the time it took to serve it
|
||||||
|
* OAuth2: Checks if a request is authenticated through [OAuth 2](https://oauth.net/2/) using [Redis](https://redis.io/) as a cache
|
12
vendor/github.com/mastertinner/adapters/adapt.go
generated
vendored
Normal file
12
vendor/github.com/mastertinner/adapters/adapt.go
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package adapters
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// Adapt adds adapters to an HTTP handler.
|
||||||
|
func Adapt(h http.Handler, adapters ...Adapter) http.Handler {
|
||||||
|
for i := len(adapters) - 1; i >= 0; i-- {
|
||||||
|
h = adapters[i](h)
|
||||||
|
}
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
6
vendor/github.com/mastertinner/adapters/adapter.go
generated
vendored
Normal file
6
vendor/github.com/mastertinner/adapters/adapter.go
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package adapters
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// Adapter is an HTTP middleware.
|
||||||
|
type Adapter func(http.Handler) http.Handler
|
27
vendor/github.com/mastertinner/adapters/logging/logging.go
generated
vendored
Normal file
27
vendor/github.com/mastertinner/adapters/logging/logging.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mastertinner/adapters"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler logs HTTP requests.
|
||||||
|
func Handler(logger *log.Logger) adapters.Adapter {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer func(start time.Time) {
|
||||||
|
logger.Printf(
|
||||||
|
"%s\t%s\t%s",
|
||||||
|
r.Method,
|
||||||
|
r.RequestURI,
|
||||||
|
time.Since(start),
|
||||||
|
)
|
||||||
|
}(time.Now())
|
||||||
|
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
1
vendor/github.com/mastertinner/adapters/oauth2/.gitignore
generated
vendored
Normal file
1
vendor/github.com/mastertinner/adapters/oauth2/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
vendor
|
38
vendor/github.com/mastertinner/adapters/oauth2/glide.lock
generated
vendored
Normal file
38
vendor/github.com/mastertinner/adapters/oauth2/glide.lock
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
hash: 2930195423c156b85ae0be9ef95d0dfcaa988b54bba4f088f767f05b3a108912
|
||||||
|
updated: 2017-05-01T10:50:48.686241497+02:00
|
||||||
|
imports:
|
||||||
|
- name: github.com/golang/protobuf
|
||||||
|
version: 69b215d01a5606c843240eab4937eab3acee6530
|
||||||
|
subpackages:
|
||||||
|
- proto
|
||||||
|
- name: github.com/mastertinner/adapters
|
||||||
|
version: 5510790dd79e084a0b849ec99df4403af9344b45
|
||||||
|
- name: github.com/satori/go.uuid
|
||||||
|
version: 879c5887cd475cd7864858769793b2ceb0d44feb
|
||||||
|
- name: golang.org/x/net
|
||||||
|
version: 6b27048ae5e6ad1ef927e72e437531493de612fe
|
||||||
|
subpackages:
|
||||||
|
- context
|
||||||
|
- name: golang.org/x/oauth2
|
||||||
|
version: b9780ec78894ab900c062d58ee3076cd9b2a4501
|
||||||
|
subpackages:
|
||||||
|
- internal
|
||||||
|
- name: google.golang.org/appengine
|
||||||
|
version: 2e4a801b39fc199db615bfca7d0b9f8cd9580599
|
||||||
|
subpackages:
|
||||||
|
- internal
|
||||||
|
- internal/base
|
||||||
|
- internal/datastore
|
||||||
|
- internal/log
|
||||||
|
- internal/remote_api
|
||||||
|
- internal/urlfetch
|
||||||
|
- urlfetch
|
||||||
|
- name: gopkg.in/redis.v5
|
||||||
|
version: a16aeec10ff407b1e7be6dd35797ccf5426ef0f0
|
||||||
|
subpackages:
|
||||||
|
- internal
|
||||||
|
- internal/consistenthash
|
||||||
|
- internal/hashtag
|
||||||
|
- internal/pool
|
||||||
|
- internal/proto
|
||||||
|
testImports: []
|
7
vendor/github.com/mastertinner/adapters/oauth2/glide.yaml
generated
vendored
Normal file
7
vendor/github.com/mastertinner/adapters/oauth2/glide.yaml
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package: github.com/mastertinner/adapters/oauth2
|
||||||
|
import:
|
||||||
|
- package: golang.org/x/oauth2
|
||||||
|
- package: gopkg.in/redis.v5
|
||||||
|
version: ^5.2.9
|
||||||
|
- package: github.com/satori/go.uuid
|
||||||
|
version: ^1.1.0
|
147
vendor/github.com/mastertinner/adapters/oauth2/oauth2.go
generated
vendored
Normal file
147
vendor/github.com/mastertinner/adapters/oauth2/oauth2.go
generated
vendored
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
package oauth2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/redis.v5"
|
||||||
|
|
||||||
|
"github.com/mastertinner/adapters"
|
||||||
|
"github.com/satori/go.uuid"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
cookieName = "sess_cookie"
|
||||||
|
tokenExpiration = 4 * 24 * time.Hour
|
||||||
|
)
|
||||||
|
|
||||||
|
// tokenWithScope is an OAuth2 token with its scope.
|
||||||
|
type tokenWithScope struct {
|
||||||
|
Token *oauth2.Token
|
||||||
|
Scope string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler checks if a request is authenticated through OAuth2.
|
||||||
|
func Handler(cache *redis.Client, config *oauth2.Config, stateString string, tokenContextKey interface{}, scopeContextKey interface{}) adapters.Adapter {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var sessionCookie *http.Cookie
|
||||||
|
cookies := r.Cookies()
|
||||||
|
|
||||||
|
// Get session cookie from cookies
|
||||||
|
for _, c := range cookies {
|
||||||
|
if strings.EqualFold(c.Name, cookieName) {
|
||||||
|
sessionCookie = c
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sessionCookie == nil {
|
||||||
|
url := config.AuthCodeURL(stateString, oauth2.AccessTypeOnline)
|
||||||
|
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tok, err := tokenFromCache(cache, sessionCookie.Value)
|
||||||
|
if err != nil || tok.Token == nil || !tok.Token.Valid() {
|
||||||
|
url := config.AuthCodeURL(stateString, oauth2.AccessTypeOnline)
|
||||||
|
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
ctx = context.WithValue(ctx, tokenContextKey, tok.Token)
|
||||||
|
ctx = context.WithValue(ctx, scopeContextKey, tok.Scope)
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallbackHandler creates a token and saves it to the cache.
|
||||||
|
func CallbackHandler(cache *redis.Client, config *oauth2.Config, stateString string) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
err := r.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
code := http.StatusInternalServerError
|
||||||
|
http.Error(w, http.StatusText(code), code)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
state := r.FormValue("state")
|
||||||
|
if state != stateString {
|
||||||
|
url := config.AuthCodeURL(stateString, oauth2.AccessTypeOnline)
|
||||||
|
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
code := r.FormValue("code")
|
||||||
|
t, err := config.Exchange(context.Background(), code)
|
||||||
|
if err != nil {
|
||||||
|
url := config.AuthCodeURL(stateString, oauth2.AccessTypeOnline)
|
||||||
|
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add scope to token info
|
||||||
|
tok := &tokenWithScope{
|
||||||
|
Token: t,
|
||||||
|
Scope: t.Extra("scope").(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
cookieVal := uuid.NewV4().String()
|
||||||
|
|
||||||
|
// Setup the cookie and set it
|
||||||
|
cookieToSend := &http.Cookie{
|
||||||
|
Name: cookieName,
|
||||||
|
Value: cookieVal,
|
||||||
|
MaxAge: 0,
|
||||||
|
Secure: false,
|
||||||
|
HttpOnly: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
http.SetCookie(w, cookieToSend)
|
||||||
|
|
||||||
|
// Serialize token and insert to cache
|
||||||
|
srlzdToken, err := json.Marshal(&tok)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error marshalling token:", err.Error())
|
||||||
|
url := config.AuthCodeURL(stateString, oauth2.AccessTypeOnline)
|
||||||
|
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cache.Set(cookieVal, srlzdToken, tokenExpiration).Err()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error adding token to cache:", err.Error())
|
||||||
|
url := config.AuthCodeURL(stateString, oauth2.AccessTypeOnline)
|
||||||
|
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// tokenFromCache retrieves a tokenURL from the cache.
|
||||||
|
func tokenFromCache(cache *redis.Client, cookieID string) (*tokenWithScope, error) {
|
||||||
|
serializedToken, err := cache.Get(cookieID).Result()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("error finding token in cache")
|
||||||
|
}
|
||||||
|
|
||||||
|
var tok *tokenWithScope
|
||||||
|
err = json.Unmarshal([]byte(serializedToken), &tok)
|
||||||
|
if err != nil || tok == nil {
|
||||||
|
return nil, errors.New("error unmarshalling token")
|
||||||
|
}
|
||||||
|
|
||||||
|
return tok, nil
|
||||||
|
}
|
2
vendor/github.com/minio/minio-go/.gitignore
generated
vendored
Normal file
2
vendor/github.com/minio/minio-go/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
*~
|
||||||
|
*.test
|
20
vendor/github.com/minio/minio-go/.travis.yml
generated
vendored
Normal file
20
vendor/github.com/minio/minio-go/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
sudo: false
|
||||||
|
language: go
|
||||||
|
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
|
||||||
|
env:
|
||||||
|
- ARCH=x86_64
|
||||||
|
- ARCH=i686
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.5.3
|
||||||
|
- 1.6
|
||||||
|
- 1.7.4
|
||||||
|
- 1.8
|
||||||
|
|
||||||
|
script:
|
||||||
|
- diff -au <(gofmt -d .) <(printf "")
|
||||||
|
- go vet ./...
|
||||||
|
- go test -short -race -v ./...
|
23
vendor/github.com/minio/minio-go/CONTRIBUTING.md
generated
vendored
Normal file
23
vendor/github.com/minio/minio-go/CONTRIBUTING.md
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
|
||||||
|
### Developer Guidelines
|
||||||
|
|
||||||
|
``minio-go`` welcomes your contribution. To make the process as seamless as possible, we ask for the following:
|
||||||
|
|
||||||
|
* Go ahead and fork the project and make your changes. We encourage pull requests to discuss code changes.
|
||||||
|
- Fork it
|
||||||
|
- Create your feature branch (git checkout -b my-new-feature)
|
||||||
|
- Commit your changes (git commit -am 'Add some feature')
|
||||||
|
- Push to the branch (git push origin my-new-feature)
|
||||||
|
- Create new Pull Request
|
||||||
|
|
||||||
|
* When you're ready to create a pull request, be sure to:
|
||||||
|
- Have test cases for the new code. If you have questions about how to do it, please ask in your pull request.
|
||||||
|
- Run `go fmt`
|
||||||
|
- Squash your commits into a single commit. `git rebase -i`. It's okay to force update your pull request.
|
||||||
|
- Make sure `go test -race ./...` and `go build` completes.
|
||||||
|
NOTE: go test runs functional tests and requires you to have a AWS S3 account. Set them as environment variables
|
||||||
|
``ACCESS_KEY`` and ``SECRET_KEY``. To run shorter version of the tests please use ``go test -short -race ./...``
|
||||||
|
|
||||||
|
* Read [Effective Go](https://github.com/golang/go/wiki/CodeReviewComments) article from Golang project
|
||||||
|
- `minio-go` project is strictly conformant with Golang style
|
||||||
|
- if you happen to observe offending code, please feel free to send a pull request
|
202
vendor/github.com/minio/minio-go/LICENSE
generated
vendored
Normal file
202
vendor/github.com/minio/minio-go/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
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 [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
19
vendor/github.com/minio/minio-go/MAINTAINERS.md
generated
vendored
Normal file
19
vendor/github.com/minio/minio-go/MAINTAINERS.md
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# For maintainers only
|
||||||
|
|
||||||
|
## Responsibilities
|
||||||
|
|
||||||
|
Please go through this link [Maintainer Responsibility](https://gist.github.com/abperiasamy/f4d9b31d3186bbd26522)
|
||||||
|
|
||||||
|
### Making new releases
|
||||||
|
|
||||||
|
Edit `libraryVersion` constant in `api.go`.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ grep libraryVersion api.go
|
||||||
|
libraryVersion = "0.3.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git tag 0.3.0
|
||||||
|
$ git push --tags
|
||||||
|
```
|
250
vendor/github.com/minio/minio-go/README.md
generated
vendored
Normal file
250
vendor/github.com/minio/minio-go/README.md
generated
vendored
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
# Minio Go Client SDK for Amazon S3 Compatible Cloud Storage [![Slack](https://slack.minio.io/slack?type=svg)](https://slack.minio.io) [![Sourcegraph](https://sourcegraph.com/github.com/minio/minio-go/-/badge.svg)](https://sourcegraph.com/github.com/minio/minio-go?badge)
|
||||||
|
|
||||||
|
The Minio Go Client SDK provides simple APIs to access any Amazon S3 compatible object storage.
|
||||||
|
|
||||||
|
**Supported cloud storage providers:**
|
||||||
|
|
||||||
|
- AWS Signature Version 4
|
||||||
|
- Amazon S3
|
||||||
|
- Minio
|
||||||
|
|
||||||
|
- AWS Signature Version 2
|
||||||
|
- Google Cloud Storage (Compatibility Mode)
|
||||||
|
- Openstack Swift + Swift3 middleware
|
||||||
|
- Ceph Object Gateway
|
||||||
|
- Riak CS
|
||||||
|
|
||||||
|
This quickstart guide will show you how to install the Minio client SDK, connect to Minio, and provide a walkthrough for a simple file uploader. For a complete list of APIs and examples, please take a look at the [Go Client API Reference](https://docs.minio.io/docs/golang-client-api-reference).
|
||||||
|
|
||||||
|
This document assumes that you have a working [Go development environment](https://docs.minio.io/docs/how-to-install-golang).
|
||||||
|
|
||||||
|
## Download from Github
|
||||||
|
```sh
|
||||||
|
go get -u github.com/minio/minio-go
|
||||||
|
```
|
||||||
|
|
||||||
|
## Initialize Minio Client
|
||||||
|
Minio client requires the following four parameters specified to connect to an Amazon S3 compatible object storage.
|
||||||
|
|
||||||
|
| Parameter | Description|
|
||||||
|
| :--- | :--- |
|
||||||
|
| endpoint | URL to object storage service. |
|
||||||
|
| accessKeyID | Access key is the user ID that uniquely identifies your account. |
|
||||||
|
| secretAccessKey | Secret key is the password to your account. |
|
||||||
|
| secure | Set this value to 'true' to enable secure (HTTPS) access. |
|
||||||
|
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/minio/minio-go"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
endpoint := "play.minio.io:9000"
|
||||||
|
accessKeyID := "Q3AM3UQ867SPQQA43P2F"
|
||||||
|
secretAccessKey := "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG"
|
||||||
|
useSSL := true
|
||||||
|
|
||||||
|
// Initialize minio client object.
|
||||||
|
minioClient, err := minio.New(endpoint, accessKeyID, secretAccessKey, useSSL)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("%v", minioClient) // minioClient is now setup
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start Example - File Uploader
|
||||||
|
This example program connects to an object storage server, creates a bucket and uploads a file to the bucket.
|
||||||
|
|
||||||
|
We will use the Minio server running at [https://play.minio.io:9000](https://play.minio.io:9000) in this example. Feel free to use this service for testing and development. Access credentials shown in this example are open to the public.
|
||||||
|
|
||||||
|
### FileUploader.go
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/minio/minio-go"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
endpoint := "play.minio.io:9000"
|
||||||
|
accessKeyID := "Q3AM3UQ867SPQQA43P2F"
|
||||||
|
secretAccessKey := "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG"
|
||||||
|
useSSL := true
|
||||||
|
|
||||||
|
// Initialize minio client object.
|
||||||
|
minioClient, err := minio.New(endpoint, accessKeyID, secretAccessKey, useSSL)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a new bucket called mymusic.
|
||||||
|
bucketName := "mymusic"
|
||||||
|
location := "us-east-1"
|
||||||
|
|
||||||
|
err = minioClient.MakeBucket(bucketName, location)
|
||||||
|
if err != nil {
|
||||||
|
// Check to see if we already own this bucket (which happens if you run this twice)
|
||||||
|
exists, err := minioClient.BucketExists(bucketName)
|
||||||
|
if err == nil && exists {
|
||||||
|
log.Printf("We already own %s\n", bucketName)
|
||||||
|
} else {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("Successfully created %s\n", bucketName)
|
||||||
|
|
||||||
|
// Upload the zip file
|
||||||
|
objectName := "golden-oldies.zip"
|
||||||
|
filePath := "/tmp/golden-oldies.zip"
|
||||||
|
contentType := "application/zip"
|
||||||
|
|
||||||
|
// Upload the zip file with FPutObject
|
||||||
|
n, err := minioClient.FPutObject(bucketName, objectName, filePath, contentType)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Successfully uploaded %s of size %d\n", objectName, n)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run FileUploader
|
||||||
|
```sh
|
||||||
|
go run file-uploader.go
|
||||||
|
2016/08/13 17:03:28 Successfully created mymusic
|
||||||
|
2016/08/13 17:03:40 Successfully uploaded golden-oldies.zip of size 16253413
|
||||||
|
|
||||||
|
mc ls play/mymusic/
|
||||||
|
[2016-05-27 16:02:16 PDT] 17MiB golden-oldies.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
The full API Reference is available here.
|
||||||
|
|
||||||
|
* [Complete API Reference](https://docs.minio.io/docs/golang-client-api-reference)
|
||||||
|
|
||||||
|
### API Reference : Bucket Operations
|
||||||
|
|
||||||
|
* [`MakeBucket`](https://docs.minio.io/docs/golang-client-api-reference#MakeBucket)
|
||||||
|
* [`ListBuckets`](https://docs.minio.io/docs/golang-client-api-reference#ListBuckets)
|
||||||
|
* [`BucketExists`](https://docs.minio.io/docs/golang-client-api-reference#BucketExists)
|
||||||
|
* [`RemoveBucket`](https://docs.minio.io/docs/golang-client-api-reference#RemoveBucket)
|
||||||
|
* [`ListObjects`](https://docs.minio.io/docs/golang-client-api-reference#ListObjects)
|
||||||
|
* [`ListObjectsV2`](https://docs.minio.io/docs/golang-client-api-reference#ListObjectsV2)
|
||||||
|
* [`ListIncompleteUploads`](https://docs.minio.io/docs/golang-client-api-reference#ListIncompleteUploads)
|
||||||
|
|
||||||
|
### API Reference : Bucket policy Operations
|
||||||
|
|
||||||
|
* [`SetBucketPolicy`](https://docs.minio.io/docs/golang-client-api-reference#SetBucketPolicy)
|
||||||
|
* [`GetBucketPolicy`](https://docs.minio.io/docs/golang-client-api-reference#GetBucketPolicy)
|
||||||
|
* [`ListBucketPolicies`](https://docs.minio.io/docs/golang-client-api-reference#ListBucketPolicies)
|
||||||
|
|
||||||
|
### API Reference : Bucket notification Operations
|
||||||
|
|
||||||
|
* [`SetBucketNotification`](https://docs.minio.io/docs/golang-client-api-reference#SetBucketNotification)
|
||||||
|
* [`GetBucketNotification`](https://docs.minio.io/docs/golang-client-api-reference#GetBucketNotification)
|
||||||
|
* [`RemoveAllBucketNotification`](https://docs.minio.io/docs/golang-client-api-reference#RemoveAllBucketNotification)
|
||||||
|
* [`ListenBucketNotification`](https://docs.minio.io/docs/golang-client-api-reference#ListenBucketNotification) (Minio Extension)
|
||||||
|
|
||||||
|
### API Reference : File Object Operations
|
||||||
|
|
||||||
|
* [`FPutObject`](https://docs.minio.io/docs/golang-client-api-reference#FPutObject)
|
||||||
|
* [`FGetObject`](https://docs.minio.io/docs/golang-client-api-reference#FPutObject)
|
||||||
|
|
||||||
|
### API Reference : Object Operations
|
||||||
|
|
||||||
|
* [`GetObject`](https://docs.minio.io/docs/golang-client-api-reference#GetObject)
|
||||||
|
* [`PutObject`](https://docs.minio.io/docs/golang-client-api-reference#PutObject)
|
||||||
|
* [`PutObjectStreaming`](https://docs.minio.io/docs/golang-client-api-reference#PutObjectStreaming)
|
||||||
|
* [`StatObject`](https://docs.minio.io/docs/golang-client-api-reference#StatObject)
|
||||||
|
* [`CopyObject`](https://docs.minio.io/docs/golang-client-api-reference#CopyObject)
|
||||||
|
* [`RemoveObject`](https://docs.minio.io/docs/golang-client-api-reference#RemoveObject)
|
||||||
|
* [`RemoveObjects`](https://docs.minio.io/docs/golang-client-api-reference#RemoveObjects)
|
||||||
|
* [`RemoveIncompleteUpload`](https://docs.minio.io/docs/golang-client-api-reference#RemoveIncompleteUpload)
|
||||||
|
|
||||||
|
### API Reference: Encrypted Object Operations
|
||||||
|
|
||||||
|
* [`GetEncryptedObject`](https://docs.minio.io/docs/golang-client-api-reference#GetEncryptedObject)
|
||||||
|
* [`PutEncryptedObject`](https://docs.minio.io/docs/golang-client-api-reference#PutEncryptedObject)
|
||||||
|
|
||||||
|
### API Reference : Presigned Operations
|
||||||
|
|
||||||
|
* [`PresignedGetObject`](https://docs.minio.io/docs/golang-client-api-reference#PresignedGetObject)
|
||||||
|
* [`PresignedPutObject`](https://docs.minio.io/docs/golang-client-api-reference#PresignedPutObject)
|
||||||
|
* [`PresignedPostPolicy`](https://docs.minio.io/docs/golang-client-api-reference#PresignedPostPolicy)
|
||||||
|
|
||||||
|
### API Reference : Client custom settings
|
||||||
|
* [`SetAppInfo`](http://docs.minio.io/docs/golang-client-api-reference#SetAppInfo)
|
||||||
|
* [`SetCustomTransport`](http://docs.minio.io/docs/golang-client-api-reference#SetCustomTransport)
|
||||||
|
* [`TraceOn`](http://docs.minio.io/docs/golang-client-api-reference#TraceOn)
|
||||||
|
* [`TraceOff`](http://docs.minio.io/docs/golang-client-api-reference#TraceOff)
|
||||||
|
|
||||||
|
|
||||||
|
## Full Examples
|
||||||
|
|
||||||
|
#### Full Examples : Bucket Operations
|
||||||
|
|
||||||
|
* [makebucket.go](https://github.com/minio/minio-go/blob/master/examples/s3/makebucket.go)
|
||||||
|
* [listbuckets.go](https://github.com/minio/minio-go/blob/master/examples/s3/listbuckets.go)
|
||||||
|
* [bucketexists.go](https://github.com/minio/minio-go/blob/master/examples/s3/bucketexists.go)
|
||||||
|
* [removebucket.go](https://github.com/minio/minio-go/blob/master/examples/s3/removebucket.go)
|
||||||
|
* [listobjects.go](https://github.com/minio/minio-go/blob/master/examples/s3/listobjects.go)
|
||||||
|
* [listobjectsV2.go](https://github.com/minio/minio-go/blob/master/examples/s3/listobjectsV2.go)
|
||||||
|
* [listincompleteuploads.go](https://github.com/minio/minio-go/blob/master/examples/s3/listincompleteuploads.go)
|
||||||
|
|
||||||
|
#### Full Examples : Bucket policy Operations
|
||||||
|
|
||||||
|
* [setbucketpolicy.go](https://github.com/minio/minio-go/blob/master/examples/s3/setbucketpolicy.go)
|
||||||
|
* [getbucketpolicy.go](https://github.com/minio/minio-go/blob/master/examples/s3/getbucketpolicy.go)
|
||||||
|
* [listbucketpolicies.go](https://github.com/minio/minio-go/blob/master/examples/s3/listbucketpolicies.go)
|
||||||
|
|
||||||
|
#### Full Examples : Bucket notification Operations
|
||||||
|
|
||||||
|
* [setbucketnotification.go](https://github.com/minio/minio-go/blob/master/examples/s3/setbucketnotification.go)
|
||||||
|
* [getbucketnotification.go](https://github.com/minio/minio-go/blob/master/examples/s3/getbucketnotification.go)
|
||||||
|
* [removeallbucketnotification.go](https://github.com/minio/minio-go/blob/master/examples/s3/removeallbucketnotification.go)
|
||||||
|
* [listenbucketnotification.go](https://github.com/minio/minio-go/blob/master/examples/minio/listenbucketnotification.go) (Minio Extension)
|
||||||
|
|
||||||
|
#### Full Examples : File Object Operations
|
||||||
|
|
||||||
|
* [fputobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/fputobject.go)
|
||||||
|
* [fgetobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/fgetobject.go)
|
||||||
|
|
||||||
|
#### Full Examples : Object Operations
|
||||||
|
|
||||||
|
* [putobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/putobject.go)
|
||||||
|
* [getobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/getobject.go)
|
||||||
|
* [statobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/statobject.go)
|
||||||
|
* [copyobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/copyobject.go)
|
||||||
|
* [removeobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/removeobject.go)
|
||||||
|
* [removeincompleteupload.go](https://github.com/minio/minio-go/blob/master/examples/s3/removeincompleteupload.go)
|
||||||
|
* [removeobjects.go](https://github.com/minio/minio-go/blob/master/examples/s3/removeobjects.go)
|
||||||
|
|
||||||
|
#### Full Examples : Encrypted Object Operations
|
||||||
|
|
||||||
|
* [put-encrypted-object.go](https://github.com/minio/minio-go/blob/master/examples/s3/put-encrypted-object.go)
|
||||||
|
* [get-encrypted-object.go](https://github.com/minio/minio-go/blob/master/examples/s3/get-encrypted-object.go)
|
||||||
|
|
||||||
|
#### Full Examples : Presigned Operations
|
||||||
|
* [presignedgetobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/presignedgetobject.go)
|
||||||
|
* [presignedputobject.go](https://github.com/minio/minio-go/blob/master/examples/s3/presignedputobject.go)
|
||||||
|
* [presignedpostpolicy.go](https://github.com/minio/minio-go/blob/master/examples/s3/presignedpostpolicy.go)
|
||||||
|
|
||||||
|
## Explore Further
|
||||||
|
* [Complete Documentation](https://docs.minio.io)
|
||||||
|
* [Minio Go Client SDK API Reference](https://docs.minio.io/docs/golang-client-api-reference)
|
||||||
|
* [Go Music Player App Full Application Example](https://docs.minio.io/docs/go-music-player-app)
|
||||||
|
|
||||||
|
## Contribute
|
||||||
|
|
||||||
|
[Contributors Guide](https://github.com/minio/minio-go/blob/master/CONTRIBUTING.md)
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/minio/minio-go.svg)](https://travis-ci.org/minio/minio-go)
|
||||||
|
[![Build status](https://ci.appveyor.com/api/projects/status/1d05e6nvxcelmrak?svg=true)](https://ci.appveyor.com/project/harshavardhana/minio-go)
|
||||||
|
|
83
vendor/github.com/minio/minio-go/api-datatypes.go
generated
vendored
Normal file
83
vendor/github.com/minio/minio-go/api-datatypes.go
generated
vendored
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package minio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BucketInfo container for bucket metadata.
|
||||||
|
type BucketInfo struct {
|
||||||
|
// The name of the bucket.
|
||||||
|
Name string `json:"name"`
|
||||||
|
// Date the bucket was created.
|
||||||
|
CreationDate time.Time `json:"creationDate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectInfo container for object metadata.
|
||||||
|
type ObjectInfo struct {
|
||||||
|
// An ETag is optionally set to md5sum of an object. In case of multipart objects,
|
||||||
|
// ETag is of the form MD5SUM-N where MD5SUM is md5sum of all individual md5sums of
|
||||||
|
// each parts concatenated into one string.
|
||||||
|
ETag string `json:"etag"`
|
||||||
|
|
||||||
|
Key string `json:"name"` // Name of the object
|
||||||
|
LastModified time.Time `json:"lastModified"` // Date and time the object was last modified.
|
||||||
|
Size int64 `json:"size"` // Size in bytes of the object.
|
||||||
|
ContentType string `json:"contentType"` // A standard MIME type describing the format of the object data.
|
||||||
|
|
||||||
|
// Collection of additional metadata on the object.
|
||||||
|
// eg: x-amz-meta-*, content-encoding etc.
|
||||||
|
Metadata http.Header `json:"metadata"`
|
||||||
|
|
||||||
|
// Owner name.
|
||||||
|
Owner struct {
|
||||||
|
DisplayName string `json:"name"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
} `json:"owner"`
|
||||||
|
|
||||||
|
// The class of storage used to store the object.
|
||||||
|
StorageClass string `json:"storageClass"`
|
||||||
|
|
||||||
|
// Error
|
||||||
|
Err error `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectMultipartInfo container for multipart object metadata.
|
||||||
|
type ObjectMultipartInfo struct {
|
||||||
|
// Date and time at which the multipart upload was initiated.
|
||||||
|
Initiated time.Time `type:"timestamp" timestampFormat:"iso8601"`
|
||||||
|
|
||||||
|
Initiator initiator
|
||||||
|
Owner owner
|
||||||
|
|
||||||
|
// The type of storage to use for the object. Defaults to 'STANDARD'.
|
||||||
|
StorageClass string
|
||||||
|
|
||||||
|
// Key of the object for which the multipart upload was initiated.
|
||||||
|
Key string
|
||||||
|
|
||||||
|
// Size in bytes of the object.
|
||||||
|
Size int64
|
||||||
|
|
||||||
|
// Upload ID that identifies the multipart upload.
|
||||||
|
UploadID string `xml:"UploadId"`
|
||||||
|
|
||||||
|
// Error
|
||||||
|
Err error
|
||||||
|
}
|
264
vendor/github.com/minio/minio-go/api-error-response.go
generated
vendored
Normal file
264
vendor/github.com/minio/minio-go/api-error-response.go
generated
vendored
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
/*
|
||||||
|
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015, 2016, 2017 Minio, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package minio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* **** SAMPLE ERROR RESPONSE ****
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Error>
|
||||||
|
<Code>AccessDenied</Code>
|
||||||
|
<Message>Access Denied</Message>
|
||||||
|
<BucketName>bucketName</BucketName>
|
||||||
|
<Key>objectName</Key>
|
||||||
|
<RequestId>F19772218238A85A</RequestId>
|
||||||
|
<HostId>GuWkjyviSiGHizehqpmsD1ndz5NClSP19DOT+s2mv7gXGQ8/X1lhbDGiIJEXpGFD</HostId>
|
||||||
|
</Error>
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ErrorResponse - Is the typed error returned by all API operations.
|
||||||
|
type ErrorResponse struct {
|
||||||
|
XMLName xml.Name `xml:"Error" json:"-"`
|
||||||
|
Code string
|
||||||
|
Message string
|
||||||
|
BucketName string
|
||||||
|
Key string
|
||||||
|
RequestID string `xml:"RequestId"`
|
||||||
|
HostID string `xml:"HostId"`
|
||||||
|
|
||||||
|
// Region where the bucket is located. This header is returned
|
||||||
|
// only in HEAD bucket and ListObjects response.
|
||||||
|
Region string
|
||||||
|
|
||||||
|
// Headers of the returned S3 XML error
|
||||||
|
Headers http.Header `xml:"-" json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToErrorResponse - Returns parsed ErrorResponse struct from body and
|
||||||
|
// http headers.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// import s3 "github.com/minio/minio-go"
|
||||||
|
// ...
|
||||||
|
// ...
|
||||||
|
// reader, stat, err := s3.GetObject(...)
|
||||||
|
// if err != nil {
|
||||||
|
// resp := s3.ToErrorResponse(err)
|
||||||
|
// }
|
||||||
|
// ...
|
||||||
|
func ToErrorResponse(err error) ErrorResponse {
|
||||||
|
switch err := err.(type) {
|
||||||
|
case ErrorResponse:
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
return ErrorResponse{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error - Returns S3 error string.
|
||||||
|
func (e ErrorResponse) Error() string {
|
||||||
|
if e.Message == "" {
|
||||||
|
msg, ok := s3ErrorResponseMap[e.Code]
|
||||||
|
if !ok {
|
||||||
|
msg = fmt.Sprintf("Error response code %s.", e.Code)
|
||||||
|
}
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
return e.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common string for errors to report issue location in unexpected
|
||||||
|
// cases.
|
||||||
|
const (
|
||||||
|
reportIssue = "Please report this issue at https://github.com/minio/minio-go/issues."
|
||||||
|
)
|
||||||
|
|
||||||
|
// httpRespToErrorResponse returns a new encoded ErrorResponse
|
||||||
|
// structure as error.
|
||||||
|
func httpRespToErrorResponse(resp *http.Response, bucketName, objectName string) error {
|
||||||
|
if resp == nil {
|
||||||
|
msg := "Response is empty. " + reportIssue
|
||||||
|
return ErrInvalidArgument(msg)
|
||||||
|
}
|
||||||
|
var errResp ErrorResponse
|
||||||
|
|
||||||
|
err := xmlDecoder(resp.Body, &errResp)
|
||||||
|
// Xml decoding failed with no body, fall back to HTTP headers.
|
||||||
|
if err != nil {
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case http.StatusNotFound:
|
||||||
|
if objectName == "" {
|
||||||
|
errResp = ErrorResponse{
|
||||||
|
Code: "NoSuchBucket",
|
||||||
|
Message: "The specified bucket does not exist.",
|
||||||
|
BucketName: bucketName,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errResp = ErrorResponse{
|
||||||
|
Code: "NoSuchKey",
|
||||||
|
Message: "The specified key does not exist.",
|
||||||
|
BucketName: bucketName,
|
||||||
|
Key: objectName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case http.StatusForbidden:
|
||||||
|
errResp = ErrorResponse{
|
||||||
|
Code: "AccessDenied",
|
||||||
|
Message: "Access Denied.",
|
||||||
|
BucketName: bucketName,
|
||||||
|
Key: objectName,
|
||||||
|
}
|
||||||
|
case http.StatusConflict:
|
||||||
|
errResp = ErrorResponse{
|
||||||
|
Code: "Conflict",
|
||||||
|
Message: "Bucket not empty.",
|
||||||
|
BucketName: bucketName,
|
||||||
|
}
|
||||||
|
case http.StatusPreconditionFailed:
|
||||||
|
errResp = ErrorResponse{
|
||||||
|
Code: "PreconditionFailed",
|
||||||
|
Message: s3ErrorResponseMap["PreconditionFailed"],
|
||||||
|
BucketName: bucketName,
|
||||||
|
Key: objectName,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
errResp = ErrorResponse{
|
||||||
|
Code: resp.Status,
|
||||||
|
Message: resp.Status,
|
||||||
|
BucketName: bucketName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save hodID, requestID and region information
|
||||||
|
// from headers if not available through error XML.
|
||||||
|
if errResp.RequestID == "" {
|
||||||
|
errResp.RequestID = resp.Header.Get("x-amz-request-id")
|
||||||
|
}
|
||||||
|
if errResp.HostID == "" {
|
||||||
|
errResp.HostID = resp.Header.Get("x-amz-id-2")
|
||||||
|
}
|
||||||
|
if errResp.Region == "" {
|
||||||
|
errResp.Region = resp.Header.Get("x-amz-bucket-region")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save headers returned in the API XML error
|
||||||
|
errResp.Headers = resp.Header
|
||||||
|
|
||||||
|
return errResp
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrTransferAccelerationBucket - bucket name is invalid to be used with transfer acceleration.
|
||||||
|
func ErrTransferAccelerationBucket(bucketName string) error {
|
||||||
|
msg := fmt.Sprintf("The name of the bucket used for Transfer Acceleration must be DNS-compliant and must not contain periods (\".\").")
|
||||||
|
return ErrorResponse{
|
||||||
|
Code: "InvalidArgument",
|
||||||
|
Message: msg,
|
||||||
|
BucketName: bucketName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrEntityTooLarge - Input size is larger than supported maximum.
|
||||||
|
func ErrEntityTooLarge(totalSize, maxObjectSize int64, bucketName, objectName string) error {
|
||||||
|
msg := fmt.Sprintf("Your proposed upload size ‘%d’ exceeds the maximum allowed object size ‘%d’ for single PUT operation.", totalSize, maxObjectSize)
|
||||||
|
return ErrorResponse{
|
||||||
|
Code: "EntityTooLarge",
|
||||||
|
Message: msg,
|
||||||
|
BucketName: bucketName,
|
||||||
|
Key: objectName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrEntityTooSmall - Input size is smaller than supported minimum.
|
||||||
|
func ErrEntityTooSmall(totalSize int64, bucketName, objectName string) error {
|
||||||
|
msg := fmt.Sprintf("Your proposed upload size ‘%d’ is below the minimum allowed object size '0B' for single PUT operation.", totalSize)
|
||||||
|
return ErrorResponse{
|
||||||
|
Code: "EntityTooLarge",
|
||||||
|
Message: msg,
|
||||||
|
BucketName: bucketName,
|
||||||
|
Key: objectName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrUnexpectedEOF - Unexpected end of file reached.
|
||||||
|
func ErrUnexpectedEOF(totalRead, totalSize int64, bucketName, objectName string) error {
|
||||||
|
msg := fmt.Sprintf("Data read ‘%s’ is not equal to the size ‘%s’ of the input Reader.",
|
||||||
|
strconv.FormatInt(totalRead, 10), strconv.FormatInt(totalSize, 10))
|
||||||
|
return ErrorResponse{
|
||||||
|
Code: "UnexpectedEOF",
|
||||||
|
Message: msg,
|
||||||
|
BucketName: bucketName,
|
||||||
|
Key: objectName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrInvalidBucketName - Invalid bucket name response.
|
||||||
|
func ErrInvalidBucketName(message string) error {
|
||||||
|
return ErrorResponse{
|
||||||
|
Code: "InvalidBucketName",
|
||||||
|
Message: message,
|
||||||
|
RequestID: "minio",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrInvalidObjectName - Invalid object name response.
|
||||||
|
func ErrInvalidObjectName(message string) error {
|
||||||
|
return ErrorResponse{
|
||||||
|
Code: "NoSuchKey",
|
||||||
|
Message: message,
|
||||||
|
RequestID: "minio",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrInvalidObjectPrefix - Invalid object prefix response is
|
||||||
|
// similar to object name response.
|
||||||
|
var ErrInvalidObjectPrefix = ErrInvalidObjectName
|
||||||
|
|
||||||
|
// ErrInvalidArgument - Invalid argument response.
|
||||||
|
func ErrInvalidArgument(message string) error {
|
||||||
|
return ErrorResponse{
|
||||||
|
Code: "InvalidArgument",
|
||||||
|
Message: message,
|
||||||
|
RequestID: "minio",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrNoSuchBucketPolicy - No Such Bucket Policy response
|
||||||
|
// The specified bucket does not have a bucket policy.
|
||||||
|
func ErrNoSuchBucketPolicy(message string) error {
|
||||||
|
return ErrorResponse{
|
||||||
|
Code: "NoSuchBucketPolicy",
|
||||||
|
Message: message,
|
||||||
|
RequestID: "minio",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrAPINotSupported - API not supported response
|
||||||
|
// The specified API call is not supported
|
||||||
|
func ErrAPINotSupported(message string) error {
|
||||||
|
return ErrorResponse{
|
||||||
|
Code: "APINotSupported",
|
||||||
|
Message: message,
|
||||||
|
RequestID: "minio",
|
||||||
|
}
|
||||||
|
}
|
282
vendor/github.com/minio/minio-go/api-error-response_test.go
generated
vendored
Normal file
282
vendor/github.com/minio/minio-go/api-error-response_test.go
generated
vendored
Normal file
|
@ -0,0 +1,282 @@
|
||||||
|
/*
|
||||||
|
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||||
|
*
|
||||||
|
* 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 bZy applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package minio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tests validate the Error generator function for http response with error.
|
||||||
|
func TestHttpRespToErrorResponse(t *testing.T) {
|
||||||
|
// 'genAPIErrorResponse' generates ErrorResponse for given APIError.
|
||||||
|
// provides a encodable populated response values.
|
||||||
|
genAPIErrorResponse := func(err APIError, bucketName string) ErrorResponse {
|
||||||
|
var errResp = ErrorResponse{}
|
||||||
|
errResp.Code = err.Code
|
||||||
|
errResp.Message = err.Description
|
||||||
|
errResp.BucketName = bucketName
|
||||||
|
return errResp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encodes the response headers into XML format.
|
||||||
|
encodeErr := func(response interface{}) []byte {
|
||||||
|
var bytesBuffer bytes.Buffer
|
||||||
|
bytesBuffer.WriteString(xml.Header)
|
||||||
|
encode := xml.NewEncoder(&bytesBuffer)
|
||||||
|
encode.Encode(response)
|
||||||
|
return bytesBuffer.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// `createAPIErrorResponse` Mocks XML error response from the server.
|
||||||
|
createAPIErrorResponse := func(APIErr APIError, bucketName string) *http.Response {
|
||||||
|
// generate error response.
|
||||||
|
// response body contains the XML error message.
|
||||||
|
resp := &http.Response{}
|
||||||
|
errorResponse := genAPIErrorResponse(APIErr, bucketName)
|
||||||
|
encodedErrorResponse := encodeErr(errorResponse)
|
||||||
|
// write Header.
|
||||||
|
resp.StatusCode = APIErr.HTTPStatusCode
|
||||||
|
resp.Body = ioutil.NopCloser(bytes.NewBuffer(encodedErrorResponse))
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'genErrResponse' contructs error response based http Status Code
|
||||||
|
genErrResponse := func(resp *http.Response, code, message, bucketName, objectName string) ErrorResponse {
|
||||||
|
errResp := ErrorResponse{
|
||||||
|
Code: code,
|
||||||
|
Message: message,
|
||||||
|
BucketName: bucketName,
|
||||||
|
Key: objectName,
|
||||||
|
RequestID: resp.Header.Get("x-amz-request-id"),
|
||||||
|
HostID: resp.Header.Get("x-amz-id-2"),
|
||||||
|
Region: resp.Header.Get("x-amz-bucket-region"),
|
||||||
|
Headers: resp.Header,
|
||||||
|
}
|
||||||
|
return errResp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate invalid argument error.
|
||||||
|
genInvalidError := func(message string) error {
|
||||||
|
errResp := ErrorResponse{
|
||||||
|
Code: "InvalidArgument",
|
||||||
|
Message: message,
|
||||||
|
RequestID: "minio",
|
||||||
|
}
|
||||||
|
return errResp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set common http response headers.
|
||||||
|
setCommonHeaders := func(resp *http.Response) *http.Response {
|
||||||
|
// set headers.
|
||||||
|
resp.Header = make(http.Header)
|
||||||
|
resp.Header.Set("x-amz-request-id", "xyz")
|
||||||
|
resp.Header.Set("x-amz-id-2", "abc")
|
||||||
|
resp.Header.Set("x-amz-bucket-region", "us-east-1")
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate http response with empty body.
|
||||||
|
// Set the StatusCode to the argument supplied.
|
||||||
|
// Sets common headers.
|
||||||
|
genEmptyBodyResponse := func(statusCode int) *http.Response {
|
||||||
|
resp := &http.Response{}
|
||||||
|
// set empty response body.
|
||||||
|
resp.Body = ioutil.NopCloser(bytes.NewBuffer([]byte("")))
|
||||||
|
// set headers.
|
||||||
|
setCommonHeaders(resp)
|
||||||
|
// set status code.
|
||||||
|
resp.StatusCode = statusCode
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode XML error message from the http response body.
|
||||||
|
decodeXMLError := func(resp *http.Response, t *testing.T) error {
|
||||||
|
var errResp ErrorResponse
|
||||||
|
err := xmlDecoder(resp.Body, &errResp)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("XML decoding of response body failed")
|
||||||
|
}
|
||||||
|
return errResp
|
||||||
|
}
|
||||||
|
|
||||||
|
// List of APIErrors used to generate/mock server side XML error response.
|
||||||
|
APIErrors := []APIError{
|
||||||
|
{
|
||||||
|
Code: "NoSuchBucketPolicy",
|
||||||
|
Description: "The specified bucket does not have a bucket policy.",
|
||||||
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// List of expected response.
|
||||||
|
// Used for asserting the actual response.
|
||||||
|
expectedErrResponse := []error{
|
||||||
|
genInvalidError("Response is empty. " + "Please report this issue at https://github.com/minio/minio-go/issues."),
|
||||||
|
decodeXMLError(createAPIErrorResponse(APIErrors[0], "minio-bucket"), t),
|
||||||
|
genErrResponse(setCommonHeaders(&http.Response{}), "NoSuchBucket", "The specified bucket does not exist.", "minio-bucket", ""),
|
||||||
|
genErrResponse(setCommonHeaders(&http.Response{}), "NoSuchKey", "The specified key does not exist.", "minio-bucket", "Asia/"),
|
||||||
|
genErrResponse(setCommonHeaders(&http.Response{}), "AccessDenied", "Access Denied.", "minio-bucket", ""),
|
||||||
|
genErrResponse(setCommonHeaders(&http.Response{}), "Conflict", "Bucket not empty.", "minio-bucket", ""),
|
||||||
|
genErrResponse(setCommonHeaders(&http.Response{}), "Bad Request", "Bad Request", "minio-bucket", ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
// List of http response to be used as input.
|
||||||
|
inputResponses := []*http.Response{
|
||||||
|
nil,
|
||||||
|
createAPIErrorResponse(APIErrors[0], "minio-bucket"),
|
||||||
|
genEmptyBodyResponse(http.StatusNotFound),
|
||||||
|
genEmptyBodyResponse(http.StatusNotFound),
|
||||||
|
genEmptyBodyResponse(http.StatusForbidden),
|
||||||
|
genEmptyBodyResponse(http.StatusConflict),
|
||||||
|
genEmptyBodyResponse(http.StatusBadRequest),
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
bucketName string
|
||||||
|
objectName string
|
||||||
|
inputHTTPResp *http.Response
|
||||||
|
// expected results.
|
||||||
|
expectedResult error
|
||||||
|
// flag indicating whether tests should pass.
|
||||||
|
|
||||||
|
}{
|
||||||
|
{"minio-bucket", "", inputResponses[0], expectedErrResponse[0]},
|
||||||
|
{"minio-bucket", "", inputResponses[1], expectedErrResponse[1]},
|
||||||
|
{"minio-bucket", "", inputResponses[2], expectedErrResponse[2]},
|
||||||
|
{"minio-bucket", "Asia/", inputResponses[3], expectedErrResponse[3]},
|
||||||
|
{"minio-bucket", "", inputResponses[4], expectedErrResponse[4]},
|
||||||
|
{"minio-bucket", "", inputResponses[5], expectedErrResponse[5]},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, testCase := range testCases {
|
||||||
|
actualResult := httpRespToErrorResponse(testCase.inputHTTPResp, testCase.bucketName, testCase.objectName)
|
||||||
|
if !reflect.DeepEqual(testCase.expectedResult, actualResult) {
|
||||||
|
t.Errorf("Test %d: Expected result to be '%#v', but instead got '%#v'", i+1, testCase.expectedResult, actualResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test validates 'ErrEntityTooLarge' error response.
|
||||||
|
func TestErrEntityTooLarge(t *testing.T) {
|
||||||
|
msg := fmt.Sprintf("Your proposed upload size ‘%d’ exceeds the maximum allowed object size ‘%d’ for single PUT operation.", 1000000, 99999)
|
||||||
|
expectedResult := ErrorResponse{
|
||||||
|
Code: "EntityTooLarge",
|
||||||
|
Message: msg,
|
||||||
|
BucketName: "minio-bucket",
|
||||||
|
Key: "Asia/",
|
||||||
|
}
|
||||||
|
actualResult := ErrEntityTooLarge(1000000, 99999, "minio-bucket", "Asia/")
|
||||||
|
if !reflect.DeepEqual(expectedResult, actualResult) {
|
||||||
|
t.Errorf("Expected result to be '%+v', but instead got '%+v'", expectedResult, actualResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test validates 'ErrEntityTooSmall' error response.
|
||||||
|
func TestErrEntityTooSmall(t *testing.T) {
|
||||||
|
msg := fmt.Sprintf("Your proposed upload size ‘%d’ is below the minimum allowed object size '0B' for single PUT operation.", -1)
|
||||||
|
expectedResult := ErrorResponse{
|
||||||
|
Code: "EntityTooLarge",
|
||||||
|
Message: msg,
|
||||||
|
BucketName: "minio-bucket",
|
||||||
|
Key: "Asia/",
|
||||||
|
}
|
||||||
|
actualResult := ErrEntityTooSmall(-1, "minio-bucket", "Asia/")
|
||||||
|
if !reflect.DeepEqual(expectedResult, actualResult) {
|
||||||
|
t.Errorf("Expected result to be '%+v', but instead got '%+v'", expectedResult, actualResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test validates 'ErrUnexpectedEOF' error response.
|
||||||
|
func TestErrUnexpectedEOF(t *testing.T) {
|
||||||
|
msg := fmt.Sprintf("Data read ‘%s’ is not equal to the size ‘%s’ of the input Reader.",
|
||||||
|
strconv.FormatInt(100, 10), strconv.FormatInt(101, 10))
|
||||||
|
expectedResult := ErrorResponse{
|
||||||
|
Code: "UnexpectedEOF",
|
||||||
|
Message: msg,
|
||||||
|
BucketName: "minio-bucket",
|
||||||
|
Key: "Asia/",
|
||||||
|
}
|
||||||
|
actualResult := ErrUnexpectedEOF(100, 101, "minio-bucket", "Asia/")
|
||||||
|
if !reflect.DeepEqual(expectedResult, actualResult) {
|
||||||
|
t.Errorf("Expected result to be '%+v', but instead got '%+v'", expectedResult, actualResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test validates 'ErrInvalidBucketName' error response.
|
||||||
|
func TestErrInvalidBucketName(t *testing.T) {
|
||||||
|
expectedResult := ErrorResponse{
|
||||||
|
Code: "InvalidBucketName",
|
||||||
|
Message: "Invalid Bucket name",
|
||||||
|
RequestID: "minio",
|
||||||
|
}
|
||||||
|
actualResult := ErrInvalidBucketName("Invalid Bucket name")
|
||||||
|
if !reflect.DeepEqual(expectedResult, actualResult) {
|
||||||
|
t.Errorf("Expected result to be '%+v', but instead got '%+v'", expectedResult, actualResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test validates 'ErrInvalidObjectName' error response.
|
||||||
|
func TestErrInvalidObjectName(t *testing.T) {
|
||||||
|
expectedResult := ErrorResponse{
|
||||||
|
Code: "NoSuchKey",
|
||||||
|
Message: "Invalid Object Key",
|
||||||
|
RequestID: "minio",
|
||||||
|
}
|
||||||
|
actualResult := ErrInvalidObjectName("Invalid Object Key")
|
||||||
|
if !reflect.DeepEqual(expectedResult, actualResult) {
|
||||||
|
t.Errorf("Expected result to be '%+v', but instead got '%+v'", expectedResult, actualResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test validates 'ErrInvalidArgument' response.
|
||||||
|
func TestErrInvalidArgument(t *testing.T) {
|
||||||
|
expectedResult := ErrorResponse{
|
||||||
|
Code: "InvalidArgument",
|
||||||
|
Message: "Invalid Argument",
|
||||||
|
RequestID: "minio",
|
||||||
|
}
|
||||||
|
actualResult := ErrInvalidArgument("Invalid Argument")
|
||||||
|
if !reflect.DeepEqual(expectedResult, actualResult) {
|
||||||
|
t.Errorf("Expected result to be '%+v', but instead got '%+v'", expectedResult, actualResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests if the Message field is missing.
|
||||||
|
func TestErrWithoutMessage(t *testing.T) {
|
||||||
|
errResp := ErrorResponse{
|
||||||
|
Code: "AccessDenied",
|
||||||
|
RequestID: "minio",
|
||||||
|
}
|
||||||
|
if errResp.Error() != "Access Denied." {
|
||||||
|
t.Errorf("Expected \"Access Denied.\", got %s", errResp)
|
||||||
|
}
|
||||||
|
errResp = ErrorResponse{
|
||||||
|
Code: "InvalidArgument",
|
||||||
|
RequestID: "minio",
|
||||||
|
}
|
||||||
|
if errResp.Error() != "Error response code InvalidArgument." {
|
||||||
|
t.Errorf("Expected \"Error response code InvalidArgument.\", got %s", errResp)
|
||||||
|
}
|
||||||
|
}
|
111
vendor/github.com/minio/minio-go/api-get-object-file.go
generated
vendored
Normal file
111
vendor/github.com/minio/minio-go/api-get-object-file.go
generated
vendored
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
* Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2015 Minio, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package minio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FGetObject - download contents of an object to a local file.
|
||||||
|
func (c Client) FGetObject(bucketName, objectName, filePath string) error {
|
||||||
|
// Input validation.
|
||||||
|
if err := isValidBucketName(bucketName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := isValidObjectName(objectName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify if destination already exists.
|
||||||
|
st, err := os.Stat(filePath)
|
||||||
|
if err == nil {
|
||||||
|
// If the destination exists and is a directory.
|
||||||
|
if st.IsDir() {
|
||||||
|
return ErrInvalidArgument("fileName is a directory.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proceed if file does not exist. return for all other errors.
|
||||||
|
if err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract top level directory.
|
||||||
|
objectDir, _ := filepath.Split(filePath)
|
||||||
|
if objectDir != "" {
|
||||||
|
// Create any missing top level directories.
|
||||||
|
if err := os.MkdirAll(objectDir, 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gather md5sum.
|
||||||
|
objectStat, err := c.StatObject(bucketName, objectName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to a temporary file "fileName.part.minio" before saving.
|
||||||
|
filePartPath := filePath + objectStat.ETag + ".part.minio"
|
||||||
|
|
||||||
|
// If exists, open in append mode. If not create it as a part file.
|
||||||
|
filePart, err := os.OpenFile(filePartPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue Stat to get the current offset.
|
||||||
|
st, err = filePart.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize get object request headers to set the
|
||||||
|
// appropriate range offsets to read from.
|
||||||
|
reqHeaders := NewGetReqHeaders()
|
||||||
|
if st.Size() > 0 {
|
||||||
|
reqHeaders.SetRange(st.Size(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek to current position for incoming reader.
|
||||||
|
objectReader, objectStat, err := c.getObject(bucketName, objectName, reqHeaders)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to the part file.
|
||||||
|
if _, err = io.CopyN(filePart, objectReader, objectStat.Size); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the file before rename, this is specifically needed for Windows users.
|
||||||
|
if err = filePart.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safely completed. Now commit by renaming to actual filename.
|
||||||
|
if err = os.Rename(filePartPath, filePath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return.
|
||||||
|
return nil
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue