Update vendor and makefile

Change-Id: I25bd3edf05652c1215eba4d98a965bee310c99f8
This commit is contained in:
Harry Zhang 2017-06-09 12:27:53 +08:00
parent 0ff4a00a71
commit f3545f50a4
2634 changed files with 118 additions and 1583183 deletions

View File

@ -1,6 +1,6 @@
{
"ImportPath": "k8s.io/apimachinery",
"GoVersion": "go1.7",
"ImportPath": "github.com/openstack/stackube",
"GoVersion": "go1.8",
"GodepVersion": "v79",
"Packages": [
"./..."
@ -15,23 +15,6 @@
"ImportPath": "github.com/PuerkitoBio/urlesc",
"Rev": "5bd2802263f21d8788851d5305584c82a5c75d7e"
},
{
"ImportPath": "github.com/davecgh/go-spew/spew",
"Rev": "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d"
},
{
"ImportPath": "github.com/docker/spdystream",
"Rev": "449fdfce4d962303d702fec724ef0ad181c92528"
},
{
"ImportPath": "github.com/docker/spdystream/spdy",
"Rev": "449fdfce4d962303d702fec724ef0ad181c92528"
},
{
"ImportPath": "github.com/elazarl/goproxy",
"Comment": "v1.0-104-gc4fc265",
"Rev": "c4fc26588b6ef8af07a191fcb6476387bdd46711"
},
{
"ImportPath": "github.com/emicklei/go-restful",
"Comment": "2.2.0-4-gff4f55a",
@ -42,14 +25,6 @@
"Comment": "2.2.0-4-gff4f55a",
"Rev": "ff4f55a206334ef123e4f79bbf348980da81ca46"
},
{
"ImportPath": "github.com/evanphx/json-patch",
"Rev": "ba18e35c5c1b36ef6334cad706eb681153d2d379"
},
{
"ImportPath": "github.com/ghodss/yaml",
"Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee"
},
{
"ImportPath": "github.com/go-openapi/jsonpointer",
"Rev": "46af16f9f7b149af66e5d1bd010e3574dc06de98"
@ -80,26 +55,10 @@
"ImportPath": "github.com/golang/glog",
"Rev": "44145f04b68cf362d9c4df2182967c2275eaefed"
},
{
"ImportPath": "github.com/golang/groupcache/lru",
"Rev": "02826c3e79038b59d737d3b1c0a1d937f71a4433"
},
{
"ImportPath": "github.com/golang/protobuf/proto",
"Rev": "4bd1920723d7b7c925de087aa32e2187708897f7"
},
{
"ImportPath": "github.com/google/gofuzz",
"Rev": "44d81051d367757e1c7c6a5a86423ece9afcf63c"
},
{
"ImportPath": "github.com/hashicorp/golang-lru",
"Rev": "a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4"
},
{
"ImportPath": "github.com/hashicorp/golang-lru/simplelru",
"Rev": "a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4"
},
{
"ImportPath": "github.com/mailru/easyjson/buffer",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
@ -112,18 +71,10 @@
"ImportPath": "github.com/mailru/easyjson/jwriter",
"Rev": "d5b7844b561a7bc640052f1b935f7b800330d7e0"
},
{
"ImportPath": "github.com/pborman/uuid",
"Rev": "ca53cad383cad2479bbba7f7a1a05797ec1386e4"
},
{
"ImportPath": "github.com/spf13/pflag",
"Rev": "9ff6c6923cfffbcd502984b8e0c80539a94968b7"
},
{
"ImportPath": "github.com/ugorji/go/codec",
"Rev": "ded73eae5db7e7a0ef6f55aace87a2873c5d2b74"
},
{
"ImportPath": "golang.org/x/net/http2",
"Rev": "f2499483f923065a842d38eb4c7f1927e6fc6e6d"
@ -186,8 +137,84 @@
"Rev": "3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4"
},
{
"ImportPath": "gopkg.in/yaml.v2",
"Rev": "53feefa2559fb8dfa8d81baad31be332c97d6c77"
"ImportPath": "k8s.io/apimachinery/pkg/api/resource",
"Rev": "2de00c78cb6d6127fb51b9531c1b3def1cbcac8c"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1",
"Rev": "2de00c78cb6d6127fb51b9531c1b3def1cbcac8c"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/conversion",
"Rev": "2de00c78cb6d6127fb51b9531c1b3def1cbcac8c"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/conversion/queryparams",
"Rev": "2de00c78cb6d6127fb51b9531c1b3def1cbcac8c"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/fields",
"Rev": "2de00c78cb6d6127fb51b9531c1b3def1cbcac8c"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/labels",
"Rev": "2de00c78cb6d6127fb51b9531c1b3def1cbcac8c"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/openapi",
"Rev": "2de00c78cb6d6127fb51b9531c1b3def1cbcac8c"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime",
"Rev": "2de00c78cb6d6127fb51b9531c1b3def1cbcac8c"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/runtime/schema",
"Rev": "2de00c78cb6d6127fb51b9531c1b3def1cbcac8c"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/selection",
"Rev": "2de00c78cb6d6127fb51b9531c1b3def1cbcac8c"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/types",
"Rev": "2de00c78cb6d6127fb51b9531c1b3def1cbcac8c"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/errors",
"Rev": "2de00c78cb6d6127fb51b9531c1b3def1cbcac8c"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/intstr",
"Rev": "2de00c78cb6d6127fb51b9531c1b3def1cbcac8c"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/net",
"Rev": "2de00c78cb6d6127fb51b9531c1b3def1cbcac8c"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/runtime",
"Rev": "2de00c78cb6d6127fb51b9531c1b3def1cbcac8c"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/sets",
"Rev": "2de00c78cb6d6127fb51b9531c1b3def1cbcac8c"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/validation",
"Rev": "2de00c78cb6d6127fb51b9531c1b3def1cbcac8c"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/wait",
"Rev": "2de00c78cb6d6127fb51b9531c1b3def1cbcac8c"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/watch",
"Rev": "2de00c78cb6d6127fb51b9531c1b3def1cbcac8c"
},
{
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/reflect",
"Rev": "2de00c78cb6d6127fb51b9531c1b3def1cbcac8c"
}
]
}

View File

@ -12,7 +12,7 @@ BASE_DIR := $(shell basename $(PWD))
# Keep an existing GOPATH, make a private one if it is undefined
GOPATH_DEFAULT := $(PWD)/.go
export GOPATH ?= $(GOPATH_DEFAULT)
PKG := $(shell awk '/^package: / { print $$2 }' glide.yaml)
PKG := git.openstack.org/openstack/stackube
DEST := $(GOPATH)/src/$(PKG)
GOFLAGS :=
@ -27,19 +27,19 @@ all: build
.PHONY: depend
depend: work
cd $(DEST) && glide install
# cd $(DEST) && glide install
.PHONY: depend-update
depend-update: work
cd $(DEST) && glide update
# cd $(DEST) && glide update
.PHONY: build
build: depend
cd $(DEST) && go build $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)'' ./...
cd $(DEST) && go build $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./...
.PHONY: install
install: depend
cd $(DEST) && go install $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)'' ./...
cd $(DEST) && go install $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./...
.PHONY: test
test: test-unit

98
glide.lock generated
View File

@ -1,98 +0,0 @@
hash: aa28871e98a60b6b588574caba38da993b57576bdeb7beba3d65a8d41c585775
updated: 2017-06-01T14:59:16.583022179+08:00
imports:
- name: github.com/emicklei/go-restful
version: ff4f55a206334ef123e4f79bbf348980da81ca46
subpackages:
- log
- name: github.com/emicklei/go-restful-swagger12
version: dcef7f55730566d41eae5db10e7d6981829720f6
- name: github.com/go-openapi/jsonpointer
version: 46af16f9f7b149af66e5d1bd010e3574dc06de98
- name: github.com/go-openapi/jsonreference
version: 13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272
- name: github.com/go-openapi/spec
version: 6aced65f8501fe1217321abf0749d354824ba2ff
- name: github.com/go-openapi/swag
version: 1d0bd113de87027671077d3c71eb3ac5d7dbba72
- name: github.com/gogo/protobuf
version: c0656edd0d9eab7c66d1eb0c568f9039345796f7
subpackages:
- proto
- sortkeys
- name: github.com/golang/glog
version: 44145f04b68cf362d9c4df2182967c2275eaefed
- name: github.com/google/gofuzz
version: 44d81051d367757e1c7c6a5a86423ece9afcf63c
- name: github.com/mailru/easyjson
version: d5b7844b561a7bc640052f1b935f7b800330d7e0
subpackages:
- buffer
- jlexer
- jwriter
- name: github.com/mjibson/appstats
version: 0542d5f0e87ea3a8fa4174322b9532f5d04f9fa8
- name: github.com/PuerkitoBio/purell
version: 8a290539e2e8629dbc4e6bad948158f790ec31f4
- name: github.com/PuerkitoBio/urlesc
version: 5bd2802263f21d8788851d5305584c82a5c75d7e
- name: github.com/spf13/pflag
version: 9ff6c6923cfffbcd502984b8e0c80539a94968b7
- name: golang.org/x/crypto
version: 080743b21912ae11ad9222778a048b795a5aae00
subpackages:
- ssh/terminal
- name: golang.org/x/net
version: f2499483f923065a842d38eb4c7f1927e6fc6e6d
subpackages:
- http2
- http2/hpack
- idna
- lex/httplex
- name: golang.org/x/text
version: 2910a502d2bf9e43193af9d68ca516529614eed3
subpackages:
- cases
- internal/tag
- language
- runes
- secure/bidirule
- secure/precis
- transform
- unicode/bidi
- unicode/norm
- width
- name: google.golang.org/appengine
version: 150dc57a1b433e64154302bdc40b6bb8aefa313a
subpackages:
- datastore
- memcache
- user
- name: gopkg.in/inf.v0
version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
- name: gopkg.in/vmihailenco/msgpack.v2
version: f4f8982de4ef0de18be76456617cc3f5d8d8141e
- name: k8s.io/apimachinery
version: 2de00c78cb6d6127fb51b9531c1b3def1cbcac8c
subpackages:
- pkg/api/resource
- pkg/apis/meta/v1
- pkg/conversion
- pkg/conversion/queryparams
- pkg/fields
- pkg/labels
- pkg/openapi
- pkg/runtime
- pkg/runtime/schema
- pkg/selection
- pkg/types
- pkg/util/errors
- pkg/util/intstr
- pkg/util/net
- pkg/util/runtime
- pkg/util/sets
- pkg/util/validation
- pkg/util/wait
- pkg/watch
- third_party/forked/golang/reflect
testImports: []

View File

@ -1,21 +0,0 @@
package: git.openstack.org/openstack/stackube
import:
- package: k8s.io/apimachinery
subpackages:
- pkg/apis/meta/v1
- pkg/runtime
- pkg/runtime/schema
- package: github.com/emicklei/go-restful-swagger12
version: 1.0.1
- package: github.com/mjibson/appstats
- package: google.golang.org/appengine
version: v1.0.0
subpackages:
- memcache
- datastore
- user
- package: gopkg.in/vmihailenco/msgpack.v2
version: v2.9.1
- package: golang.org/x/crypto
subpackages:
- ssh/terminal

View File

@ -3,7 +3,7 @@
# Used in both CI jobs and locally
#
# Install the following tools:
# * glide
# * godep
# Get OS
case $(uname -s) in
@ -31,24 +31,10 @@ case $(uname -s) in
;;
esac
case $OS in
darwin)
if which brew 1>/dev/null; then
if ! which glide 1>/dev/null; then
brew install glide
fi
else
echo "Homebrew not found, install Glide from source?"
fi
;;
xenial)
APT_GET="DEBIAN_FRONTEND=noninteractive \
apt-get -q --option "Dpkg::Options::=--force-confold" \
--assume-yes"
if ! which add-apt-repository 1>/dev/null; then
sudo $APT_GET install software-properties-common
fi
sudo add-apt-repository --yes ppa:masterminds/glide && sudo apt-get update
sudo $APT_GET install glide
;;
esac
if which go 1>/dev/null; then
if ! which go 1>/dev/null; then
go get -u -v github.com/tools/godep
fi
else
echo "go not found, install golang from source?"
fi

View File

@ -1,57 +0,0 @@
package purell
import (
"testing"
)
var (
safeUrl = "HttPS://..iaMHost..Test:443/paTh^A%ef//./%41PaTH/..//?"
usuallySafeUrl = "HttPS://..iaMHost..Test:443/paTh^A%ef//./%41PaTH/../final/"
unsafeUrl = "HttPS://..www.iaMHost..Test:443/paTh^A%ef//./%41PaTH/../final/index.html?t=val1&a=val4&z=val5&a=val1#fragment"
allDWORDUrl = "HttPS://1113982867:/paTh^A%ef//./%41PaTH/../final/index.html?t=val1&a=val4&z=val5&a=val1#fragment"
allOctalUrl = "HttPS://0102.0146.07.0223:/paTh^A%ef//./%41PaTH/../final/index.html?t=val1&a=val4&z=val5&a=val1#fragment"
allHexUrl = "HttPS://0x42660793:/paTh^A%ef//./%41PaTH/../final/index.html?t=val1&a=val4&z=val5&a=val1#fragment"
allCombinedUrl = "HttPS://..0x42660793.:/paTh^A%ef//./%41PaTH/../final/index.html?t=val1&a=val4&z=val5&a=val1#fragment"
)
func BenchmarkSafe(b *testing.B) {
for i := 0; i < b.N; i++ {
NormalizeURLString(safeUrl, FlagsSafe)
}
}
func BenchmarkUsuallySafe(b *testing.B) {
for i := 0; i < b.N; i++ {
NormalizeURLString(usuallySafeUrl, FlagsUsuallySafeGreedy)
}
}
func BenchmarkUnsafe(b *testing.B) {
for i := 0; i < b.N; i++ {
NormalizeURLString(unsafeUrl, FlagsUnsafeGreedy)
}
}
func BenchmarkAllDWORD(b *testing.B) {
for i := 0; i < b.N; i++ {
NormalizeURLString(allDWORDUrl, FlagsAllGreedy)
}
}
func BenchmarkAllOctal(b *testing.B) {
for i := 0; i < b.N; i++ {
NormalizeURLString(allOctalUrl, FlagsAllGreedy)
}
}
func BenchmarkAllHex(b *testing.B) {
for i := 0; i < b.N; i++ {
NormalizeURLString(allHexUrl, FlagsAllGreedy)
}
}
func BenchmarkAllCombined(b *testing.B) {
for i := 0; i < b.N; i++ {
NormalizeURLString(allCombinedUrl, FlagsAllGreedy)
}
}

View File

@ -1,9 +0,0 @@
PASS
BenchmarkSafe 500000 6131 ns/op
BenchmarkUsuallySafe 200000 7864 ns/op
BenchmarkUnsafe 100000 28560 ns/op
BenchmarkAllDWORD 50000 38722 ns/op
BenchmarkAllOctal 50000 40941 ns/op
BenchmarkAllHex 50000 44063 ns/op
BenchmarkAllCombined 50000 33613 ns/op
ok github.com/PuerkitoBio/purell 17.404s

View File

@ -1,35 +0,0 @@
package purell
import (
"fmt"
"net/url"
)
func ExampleNormalizeURLString() {
if normalized, err := NormalizeURLString("hTTp://someWEBsite.com:80/Amazing%3f/url/",
FlagLowercaseScheme|FlagLowercaseHost|FlagUppercaseEscapes); err != nil {
panic(err)
} else {
fmt.Print(normalized)
}
// Output: http://somewebsite.com:80/Amazing%3F/url/
}
func ExampleMustNormalizeURLString() {
normalized := MustNormalizeURLString("hTTpS://someWEBsite.com:443/Amazing%fa/url/",
FlagsUnsafeGreedy)
fmt.Print(normalized)
// Output: http://somewebsite.com/Amazing%FA/url
}
func ExampleNormalizeURL() {
if u, err := url.Parse("Http://SomeUrl.com:8080/a/b/.././c///g?c=3&a=1&b=9&c=0#target"); err != nil {
panic(err)
} else {
normalized := NormalizeURL(u, FlagsUsuallySafeGreedy|FlagRemoveDuplicateSlashes|FlagRemoveFragment)
fmt.Print(normalized)
}
// Output: http://someurl.com:8080/a/c/g?c=3&a=1&b=9&c=0
}

View File

@ -1,768 +0,0 @@
package purell
import (
"fmt"
"net/url"
"testing"
)
type testCase struct {
nm string
src string
flgs NormalizationFlags
res string
parsed bool
}
var (
cases = [...]*testCase{
&testCase{
"LowerScheme",
"HTTP://www.SRC.ca",
FlagLowercaseScheme,
"http://www.SRC.ca",
false,
},
&testCase{
"LowerScheme2",
"http://www.SRC.ca",
FlagLowercaseScheme,
"http://www.SRC.ca",
false,
},
&testCase{
"LowerHost",
"HTTP://www.SRC.ca/",
FlagLowercaseHost,
"http://www.src.ca/", // Since Go1.1, scheme is automatically lowercased
false,
},
&testCase{
"UpperEscapes",
`http://www.whatever.com/Some%aa%20Special%8Ecases/`,
FlagUppercaseEscapes,
"http://www.whatever.com/Some%AA%20Special%8Ecases/",
false,
},
&testCase{
"UnnecessaryEscapes",
`http://www.toto.com/%41%42%2E%44/%32%33%52%2D/%5f%7E`,
FlagDecodeUnnecessaryEscapes,
"http://www.toto.com/AB.D/23R-/_~",
false,
},
&testCase{
"RemoveDefaultPort",
"HTTP://www.SRC.ca:80/",
FlagRemoveDefaultPort,
"http://www.SRC.ca/", // Since Go1.1, scheme is automatically lowercased
false,
},
&testCase{
"RemoveDefaultPort2",
"HTTP://www.SRC.ca:80",
FlagRemoveDefaultPort,
"http://www.SRC.ca", // Since Go1.1, scheme is automatically lowercased
false,
},
&testCase{
"RemoveDefaultPort3",
"HTTP://www.SRC.ca:8080",
FlagRemoveDefaultPort,
"http://www.SRC.ca:8080", // Since Go1.1, scheme is automatically lowercased
false,
},
&testCase{
"Safe",
"HTTP://www.SRC.ca:80/to%1ato%8b%ee/OKnow%41%42%43%7e",
FlagsSafe,
"http://www.src.ca/to%1Ato%8B%EE/OKnowABC~",
false,
},
&testCase{
"BothLower",
"HTTP://www.SRC.ca:80/to%1ato%8b%ee/OKnow%41%42%43%7e",
FlagLowercaseHost | FlagLowercaseScheme,
"http://www.src.ca:80/to%1Ato%8B%EE/OKnowABC~",
false,
},
&testCase{
"RemoveTrailingSlash",
"HTTP://www.SRC.ca:80/",
FlagRemoveTrailingSlash,
"http://www.SRC.ca:80", // Since Go1.1, scheme is automatically lowercased
false,
},
&testCase{
"RemoveTrailingSlash2",
"HTTP://www.SRC.ca:80/toto/titi/",
FlagRemoveTrailingSlash,
"http://www.SRC.ca:80/toto/titi", // Since Go1.1, scheme is automatically lowercased
false,
},
&testCase{
"RemoveTrailingSlash3",
"HTTP://www.SRC.ca:80/toto/titi/fin/?a=1",
FlagRemoveTrailingSlash,
"http://www.SRC.ca:80/toto/titi/fin?a=1", // Since Go1.1, scheme is automatically lowercased
false,
},
&testCase{
"AddTrailingSlash",
"HTTP://www.SRC.ca:80",
FlagAddTrailingSlash,
"http://www.SRC.ca:80/", // Since Go1.1, scheme is automatically lowercased
false,
},
&testCase{
"AddTrailingSlash2",
"HTTP://www.SRC.ca:80/toto/titi.html",
FlagAddTrailingSlash,
"http://www.SRC.ca:80/toto/titi.html/", // Since Go1.1, scheme is automatically lowercased
false,
},
&testCase{
"AddTrailingSlash3",
"HTTP://www.SRC.ca:80/toto/titi/fin?a=1",
FlagAddTrailingSlash,
"http://www.SRC.ca:80/toto/titi/fin/?a=1", // Since Go1.1, scheme is automatically lowercased
false,
},
&testCase{
"RemoveDotSegments",
"HTTP://root/a/b/./../../c/",
FlagRemoveDotSegments,
"http://root/c/", // Since Go1.1, scheme is automatically lowercased
false,
},
&testCase{
"RemoveDotSegments2",
"HTTP://root/../a/b/./../c/../d",
FlagRemoveDotSegments,
"http://root/a/d", // Since Go1.1, scheme is automatically lowercased
false,
},
&testCase{
"UsuallySafe",
"HTTP://www.SRC.ca:80/to%1ato%8b%ee/./c/d/../OKnow%41%42%43%7e/?a=b#test",
FlagsUsuallySafeGreedy,
"http://www.src.ca/to%1Ato%8B%EE/c/OKnowABC~?a=b#test",
false,
},
&testCase{
"RemoveDirectoryIndex",
"HTTP://root/a/b/c/default.aspx",
FlagRemoveDirectoryIndex,
"http://root/a/b/c/", // Since Go1.1, scheme is automatically lowercased
false,
},
&testCase{
"RemoveDirectoryIndex2",
"HTTP://root/a/b/c/default#a=b",
FlagRemoveDirectoryIndex,
"http://root/a/b/c/default#a=b", // Since Go1.1, scheme is automatically lowercased
false,
},
&testCase{
"RemoveFragment",
"HTTP://root/a/b/c/default#toto=tata",
FlagRemoveFragment,
"http://root/a/b/c/default", // Since Go1.1, scheme is automatically lowercased
false,
},
&testCase{
"ForceHTTP",
"https://root/a/b/c/default#toto=tata",
FlagForceHTTP,
"http://root/a/b/c/default#toto=tata",
false,
},
&testCase{
"RemoveDuplicateSlashes",
"https://root/a//b///c////default#toto=tata",
FlagRemoveDuplicateSlashes,
"https://root/a/b/c/default#toto=tata",
false,
},
&testCase{
"RemoveDuplicateSlashes2",
"https://root//a//b///c////default#toto=tata",
FlagRemoveDuplicateSlashes,
"https://root/a/b/c/default#toto=tata",
false,
},
&testCase{
"RemoveWWW",
"https://www.root/a/b/c/",
FlagRemoveWWW,
"https://root/a/b/c/",
false,
},
&testCase{
"RemoveWWW2",
"https://WwW.Root/a/b/c/",
FlagRemoveWWW,
"https://Root/a/b/c/",
false,
},
&testCase{
"AddWWW",
"https://Root/a/b/c/",
FlagAddWWW,
"https://www.Root/a/b/c/",
false,
},
&testCase{
"SortQuery",
"http://root/toto/?b=4&a=1&c=3&b=2&a=5",
FlagSortQuery,
"http://root/toto/?a=1&a=5&b=2&b=4&c=3",
false,
},
&testCase{
"RemoveEmptyQuerySeparator",
"http://root/toto/?",
FlagRemoveEmptyQuerySeparator,
"http://root/toto/",
false,
},
&testCase{
"Unsafe",
"HTTPS://www.RooT.com/toto/t%45%1f///a/./b/../c/?z=3&w=2&a=4&w=1#invalid",
FlagsUnsafeGreedy,
"http://root.com/toto/tE%1F/a/c?a=4&w=1&w=2&z=3",
false,
},
&testCase{
"Safe2",
"HTTPS://www.RooT.com/toto/t%45%1f///a/./b/../c/?z=3&w=2&a=4&w=1#invalid",
FlagsSafe,
"https://www.root.com/toto/tE%1F///a/./b/../c/?z=3&w=2&a=4&w=1#invalid",
false,
},
&testCase{
"UsuallySafe2",
"HTTPS://www.RooT.com/toto/t%45%1f///a/./b/../c/?z=3&w=2&a=4&w=1#invalid",
FlagsUsuallySafeGreedy,
"https://www.root.com/toto/tE%1F///a/c?z=3&w=2&a=4&w=1#invalid",
false,
},
&testCase{
"AddTrailingSlashBug",
"http://src.ca/",
FlagsAllNonGreedy,
"http://www.src.ca/",
false,
},
&testCase{
"SourceModified",
"HTTPS://www.RooT.com/toto/t%45%1f///a/./b/../c/?z=3&w=2&a=4&w=1#invalid",
FlagsUnsafeGreedy,
"http://root.com/toto/tE%1F/a/c?a=4&w=1&w=2&z=3",
true,
},
&testCase{
"IPv6-1",
"http://[2001:db8:1f70::999:de8:7648:6e8]/test",
FlagsSafe | FlagRemoveDotSegments,
"http://[2001:db8:1f70::999:de8:7648:6e8]/test",
false,
},
&testCase{
"IPv6-2",
"http://[::ffff:192.168.1.1]/test",
FlagsSafe | FlagRemoveDotSegments,
"http://[::ffff:192.168.1.1]/test",
false,
},
&testCase{
"IPv6-3",
"http://[::ffff:192.168.1.1]:80/test",
FlagsSafe | FlagRemoveDotSegments,
"http://[::ffff:192.168.1.1]/test",
false,
},
&testCase{
"IPv6-4",
"htTps://[::fFff:192.168.1.1]:443/test",
FlagsSafe | FlagRemoveDotSegments,
"https://[::ffff:192.168.1.1]/test",
false,
},
&testCase{
"FTP",
"ftp://user:pass@ftp.foo.net/foo/bar",
FlagsSafe | FlagRemoveDotSegments,
"ftp://user:pass@ftp.foo.net/foo/bar",
false,
},
&testCase{
"Standard-1",
"http://www.foo.com:80/foo",
FlagsSafe | FlagRemoveDotSegments,
"http://www.foo.com/foo",
false,
},
&testCase{
"Standard-2",
"http://www.foo.com:8000/foo",
FlagsSafe | FlagRemoveDotSegments,
"http://www.foo.com:8000/foo",
false,
},
&testCase{
"Standard-3",
"http://www.foo.com/%7ebar",
FlagsSafe | FlagRemoveDotSegments,
"http://www.foo.com/~bar",
false,
},
&testCase{
"Standard-4",
"http://www.foo.com/%7Ebar",
FlagsSafe | FlagRemoveDotSegments,
"http://www.foo.com/~bar",
false,
},
&testCase{
"Standard-5",
"http://USER:pass@www.Example.COM/foo/bar",
FlagsSafe | FlagRemoveDotSegments,
"http://USER:pass@www.example.com/foo/bar",
false,
},
&testCase{
"Standard-6",
"http://test.example/?a=%26&b=1",
FlagsSafe | FlagRemoveDotSegments,
"http://test.example/?a=%26&b=1",
false,
},
&testCase{
"Standard-7",
"http://test.example/%25/?p=%20val%20%25",
FlagsSafe | FlagRemoveDotSegments,
"http://test.example/%25/?p=%20val%20%25",
false,
},
&testCase{
"Standard-8",
"http://test.example/path/with a%20space+/",
FlagsSafe | FlagRemoveDotSegments,
"http://test.example/path/with%20a%20space+/",
false,
},
&testCase{
"Standard-9",
"http://test.example/?",
FlagsSafe | FlagRemoveDotSegments,
"http://test.example/",
false,
},
&testCase{
"Standard-10",
"http://a.COM/path/?b&a",
FlagsSafe | FlagRemoveDotSegments,
"http://a.com/path/?b&a",
false,
},
&testCase{
"StandardCasesAddTrailingSlash",
"http://test.example?",
FlagsSafe | FlagAddTrailingSlash,
"http://test.example/",
false,
},
&testCase{
"OctalIP-1",
"http://0123.011.0.4/",
FlagsSafe | FlagDecodeOctalHost,
"http://0123.011.0.4/",
false,
},
&testCase{
"OctalIP-2",
"http://0102.0146.07.0223/",
FlagsSafe | FlagDecodeOctalHost,
"http://66.102.7.147/",
false,
},
&testCase{
"OctalIP-3",
"http://0102.0146.07.0223.:23/",
FlagsSafe | FlagDecodeOctalHost,
"http://66.102.7.147.:23/",
false,
},
&testCase{
"OctalIP-4",
"http://USER:pass@0102.0146.07.0223../",
FlagsSafe | FlagDecodeOctalHost,
"http://USER:pass@66.102.7.147../",
false,
},
&testCase{
"DWORDIP-1",
"http://123.1113982867/",
FlagsSafe | FlagDecodeDWORDHost,
"http://123.1113982867/",
false,
},
&testCase{
"DWORDIP-2",
"http://1113982867/",
FlagsSafe | FlagDecodeDWORDHost,
"http://66.102.7.147/",
false,
},
&testCase{
"DWORDIP-3",
"http://1113982867.:23/",
FlagsSafe | FlagDecodeDWORDHost,
"http://66.102.7.147.:23/",
false,
},
&testCase{
"DWORDIP-4",
"http://USER:pass@1113982867../",
FlagsSafe | FlagDecodeDWORDHost,
"http://USER:pass@66.102.7.147../",
false,
},
&testCase{
"HexIP-1",
"http://0x123.1113982867/",
FlagsSafe | FlagDecodeHexHost,
"http://0x123.1113982867/",
false,
},
&testCase{
"HexIP-2",
"http://0x42660793/",
FlagsSafe | FlagDecodeHexHost,
"http://66.102.7.147/",
false,
},
&testCase{
"HexIP-3",
"http://0x42660793.:23/",
FlagsSafe | FlagDecodeHexHost,
"http://66.102.7.147.:23/",
false,
},
&testCase{
"HexIP-4",
"http://USER:pass@0x42660793../",
FlagsSafe | FlagDecodeHexHost,
"http://USER:pass@66.102.7.147../",
false,
},
&testCase{
"UnnecessaryHostDots-1",
"http://.www.foo.com../foo/bar.html",
FlagsSafe | FlagRemoveUnnecessaryHostDots,
"http://www.foo.com/foo/bar.html",
false,
},
&testCase{
"UnnecessaryHostDots-2",
"http://www.foo.com./foo/bar.html",
FlagsSafe | FlagRemoveUnnecessaryHostDots,
"http://www.foo.com/foo/bar.html",
false,
},
&testCase{
"UnnecessaryHostDots-3",
"http://www.foo.com.:81/foo",
FlagsSafe | FlagRemoveUnnecessaryHostDots,
"http://www.foo.com:81/foo",
false,
},
&testCase{
"UnnecessaryHostDots-4",
"http://www.example.com./",
FlagsSafe | FlagRemoveUnnecessaryHostDots,
"http://www.example.com/",
false,
},
&testCase{
"EmptyPort-1",
"http://www.thedraymin.co.uk:/main/?p=308",
FlagsSafe | FlagRemoveEmptyPortSeparator,
"http://www.thedraymin.co.uk/main/?p=308",
false,
},
&testCase{
"EmptyPort-2",
"http://www.src.ca:",
FlagsSafe | FlagRemoveEmptyPortSeparator,
"http://www.src.ca",
false,
},
&testCase{
"Slashes-1",
"http://test.example/foo/bar/.",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"http://test.example/foo/bar/",
false,
},
&testCase{
"Slashes-2",
"http://test.example/foo/bar/./",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"http://test.example/foo/bar/",
false,
},
&testCase{
"Slashes-3",
"http://test.example/foo/bar/..",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"http://test.example/foo/",
false,
},
&testCase{
"Slashes-4",
"http://test.example/foo/bar/../",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"http://test.example/foo/",
false,
},
&testCase{
"Slashes-5",
"http://test.example/foo/bar/../baz",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"http://test.example/foo/baz",
false,
},
&testCase{
"Slashes-6",
"http://test.example/foo/bar/../..",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"http://test.example/",
false,
},
&testCase{
"Slashes-7",
"http://test.example/foo/bar/../../",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"http://test.example/",
false,
},
&testCase{
"Slashes-8",
"http://test.example/foo/bar/../../baz",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"http://test.example/baz",
false,
},
&testCase{
"Slashes-9",
"http://test.example/foo/bar/../../../baz",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"http://test.example/baz",
false,
},
&testCase{
"Slashes-10",
"http://test.example/foo/bar/../../../../baz",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"http://test.example/baz",
false,
},
&testCase{
"Slashes-11",
"http://test.example/./foo",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"http://test.example/foo",
false,
},
&testCase{
"Slashes-12",
"http://test.example/../foo",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"http://test.example/foo",
false,
},
&testCase{
"Slashes-13",
"http://test.example/foo.",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"http://test.example/foo.",
false,
},
&testCase{
"Slashes-14",
"http://test.example/.foo",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"http://test.example/.foo",
false,
},
&testCase{
"Slashes-15",
"http://test.example/foo..",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"http://test.example/foo..",
false,
},
&testCase{
"Slashes-16",
"http://test.example/..foo",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"http://test.example/..foo",
false,
},
&testCase{
"Slashes-17",
"http://test.example/./../foo",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"http://test.example/foo",
false,
},
&testCase{
"Slashes-18",
"http://test.example/./foo/.",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"http://test.example/foo/",
false,
},
&testCase{
"Slashes-19",
"http://test.example/foo/./bar",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"http://test.example/foo/bar",
false,
},
&testCase{
"Slashes-20",
"http://test.example/foo/../bar",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"http://test.example/bar",
false,
},
&testCase{
"Slashes-21",
"http://test.example/foo//",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"http://test.example/foo/",
false,
},
&testCase{
"Slashes-22",
"http://test.example/foo///bar//",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"http://test.example/foo/bar/",
false,
},
&testCase{
"Relative",
"foo/bar",
FlagsAllGreedy,
"foo/bar",
false,
},
&testCase{
"Relative-1",
"./../foo",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"foo",
false,
},
&testCase{
"Relative-2",
"./foo/bar/../baz/../bang/..",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"foo/",
false,
},
&testCase{
"Relative-3",
"foo///bar//",
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
"foo/bar/",
false,
},
&testCase{
"Relative-4",
"www.youtube.com",
FlagsUsuallySafeGreedy,
"www.youtube.com",
false,
},
/*&testCase{
"UrlNorm-5",
"http://ja.wikipedia.org/wiki/%E3%82%AD%E3%83%A3%E3%82%BF%E3%83%94%E3%83%A9%E3%83%BC%E3%82%B8%E3%83%A3%E3%83%91%E3%83%B3",
FlagsSafe | FlagRemoveDotSegments,
"http://ja.wikipedia.org/wiki/\xe3\x82\xad\xe3\x83\xa3\xe3\x82\xbf\xe3\x83\x94\xe3\x83\xa9\xe3\x83\xbc\xe3\x82\xb8\xe3\x83\xa3\xe3\x83\x91\xe3\x83\xb3",
false,
},
&testCase{
"UrlNorm-1",
"http://test.example/?a=%e3%82%82%26",
FlagsAllGreedy,
"http://test.example/?a=\xe3\x82\x82%26",
false,
},*/
}
)
func TestRunner(t *testing.T) {
for _, tc := range cases {
runCase(tc, t)
}
}
func runCase(tc *testCase, t *testing.T) {
t.Logf("running %s...", tc.nm)
if tc.parsed {
u, e := url.Parse(tc.src)
if e != nil {
t.Errorf("%s - FAIL : %s", tc.nm, e)
return
} else {
NormalizeURL(u, tc.flgs)
if s := u.String(); s != tc.res {
t.Errorf("%s - FAIL expected '%s', got '%s'", tc.nm, tc.res, s)
}
}
} else {
if s, e := NormalizeURLString(tc.src, tc.flgs); e != nil {
t.Errorf("%s - FAIL : %s", tc.nm, e)
} else if s != tc.res {
t.Errorf("%s - FAIL expected '%s', got '%s'", tc.nm, tc.res, s)
}
}
}
func TestDecodeUnnecessaryEscapesAll(t *testing.T) {
var url = "http://host/"
for i := 0; i < 256; i++ {
url += fmt.Sprintf("%%%02x", i)
}
if s, e := NormalizeURLString(url, FlagDecodeUnnecessaryEscapes); e != nil {
t.Fatalf("Got error %s", e.Error())
} else {
const want = "http://host/%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20!%22%23$%25&'()*+,-./0123456789:;%3C=%3E%3F@ABCDEFGHIJKLMNOPQRSTUVWXYZ[%5C]%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF"
if s != want {
t.Errorf("DecodeUnnecessaryEscapesAll:\nwant\n%s\ngot\n%s", want, s)
}
}
}
func TestEncodeNecessaryEscapesAll(t *testing.T) {
var url = "http://host/"
for i := 0; i < 256; i++ {
if i != 0x25 {
url += string(i)
}
}
if s, e := NormalizeURLString(url, FlagEncodeNecessaryEscapes); e != nil {
t.Fatalf("Got error %s", e.Error())
} else {
const want = "http://host/%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20!%22#$&'()*+,-./0123456789:;%3C=%3E?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[%5C]%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F%C2%80%C2%81%C2%82%C2%83%C2%84%C2%85%C2%86%C2%87%C2%88%C2%89%C2%8A%C2%8B%C2%8C%C2%8D%C2%8E%C2%8F%C2%90%C2%91%C2%92%C2%93%C2%94%C2%95%C2%96%C2%97%C2%98%C2%99%C2%9A%C2%9B%C2%9C%C2%9D%C2%9E%C2%9F%C2%A0%C2%A1%C2%A2%C2%A3%C2%A4%C2%A5%C2%A6%C2%A7%C2%A8%C2%A9%C2%AA%C2%AB%C2%AC%C2%AD%C2%AE%C2%AF%C2%B0%C2%B1%C2%B2%C2%B3%C2%B4%C2%B5%C2%B6%C2%B7%C2%B8%C2%B9%C2%BA%C2%BB%C2%BC%C2%BD%C2%BE%C2%BF%C3%80%C3%81%C3%82%C3%83%C3%84%C3%85%C3%86%C3%87%C3%88%C3%89%C3%8A%C3%8B%C3%8C%C3%8D%C3%8E%C3%8F%C3%90%C3%91%C3%92%C3%93%C3%94%C3%95%C3%96%C3%97%C3%98%C3%99%C3%9A%C3%9B%C3%9C%C3%9D%C3%9E%C3%9F%C3%A0%C3%A1%C3%A2%C3%A3%C3%A4%C3%A5%C3%A6%C3%A7%C3%A8%C3%A9%C3%AA%C3%AB%C3%AC%C3%AD%C3%AE%C3%AF%C3%B0%C3%B1%C3%B2%C3%B3%C3%B4%C3%B5%C3%B6%C3%B7%C3%B8%C3%B9%C3%BA%C3%BB%C3%BC%C3%BD%C3%BE%C3%BF"
if s != want {
t.Errorf("EncodeNecessaryEscapesAll:\nwant\n%s\ngot\n%s", want, s)
}
}
}

View File

@ -1,52 +0,0 @@
package purell
import (
"testing"
)
// Test cases merged from PR #1
// Originally from https://github.com/jehiah/urlnorm/blob/master/test_urlnorm.py
func assertMap(t *testing.T, cases map[string]string, f NormalizationFlags) {
for bad, good := range cases {
s, e := NormalizeURLString(bad, f)
if e != nil {
t.Errorf("%s normalizing %v to %v", e.Error(), bad, good)
} else {
if s != good {
t.Errorf("source: %v expected: %v got: %v", bad, good, s)
}
}
}
}
// This tests normalization to a unicode representation
// precent escapes for unreserved values are unescaped to their unicode value
// tests normalization to idna domains
// test ip word handling, ipv6 address handling, and trailing domain periods
// in general, this matches google chromes unescaping for things in the address bar.
// spaces are converted to '+' (perhaphs controversial)
// http://code.google.com/p/google-url/ probably is another good reference for this approach
func TestUrlnorm(t *testing.T) {
testcases := map[string]string{
"http://test.example/?a=%e3%82%82%26": "http://test.example/?a=%e3%82%82%26",
//"http://test.example/?a=%e3%82%82%26": "http://test.example/?a=\xe3\x82\x82%26", //should return a unicode character
"http://s.xn--q-bga.DE/": "http://s.xn--q-bga.de/", //should be in idna format
"http://XBLA\u306eXbox.com": "http://xn--xblaxbox-jf4g.com", //test utf8 and unicode
"http://президент.рф": "http://xn--d1abbgf6aiiy.xn--p1ai",
"http://ПРЕЗИДЕНТ.РФ": "http://xn--d1abbgf6aiiy.xn--p1ai",
"http://\u00e9.com": "http://xn--9ca.com",
"http://e\u0301.com": "http://xn--9ca.com",
"http://ja.wikipedia.org/wiki/%E3%82%AD%E3%83%A3%E3%82%BF%E3%83%94%E3%83%A9%E3%83%BC%E3%82%B8%E3%83%A3%E3%83%91%E3%83%B3": "http://ja.wikipedia.org/wiki/%E3%82%AD%E3%83%A3%E3%82%BF%E3%83%94%E3%83%A9%E3%83%BC%E3%82%B8%E3%83%A3%E3%83%91%E3%83%B3",
//"http://ja.wikipedia.org/wiki/%E3%82%AD%E3%83%A3%E3%82%BF%E3%83%94%E3%83%A9%E3%83%BC%E3%82%B8%E3%83%A3%E3%83%91%E3%83%B3": "http://ja.wikipedia.org/wiki/\xe3\x82\xad\xe3\x83\xa3\xe3\x82\xbf\xe3\x83\x94\xe3\x83\xa9\xe3\x83\xbc\xe3\x82\xb8\xe3\x83\xa3\xe3\x83\x91\xe3\x83\xb3",
"http://test.example/\xe3\x82\xad": "http://test.example/%E3%82%AD",
//"http://test.example/\xe3\x82\xad": "http://test.example/\xe3\x82\xad",
"http://test.example/?p=%23val#test-%23-val%25": "http://test.example/?p=%23val#test-%23-val%25", //check that %23 (#) is not escaped where it shouldn't be
"http://test.domain/I%C3%B1t%C3%ABrn%C3%A2ti%C3%B4n%EF%BF%BDliz%C3%A6ti%C3%B8n": "http://test.domain/I%C3%B1t%C3%ABrn%C3%A2ti%C3%B4n%EF%BF%BDliz%C3%A6ti%C3%B8n",
//"http://test.domain/I%C3%B1t%C3%ABrn%C3%A2ti%C3%B4n%EF%BF%BDliz%C3%A6ti%C3%B8n": "http://test.domain/I\xc3\xb1t\xc3\xabrn\xc3\xa2ti\xc3\xb4n\xef\xbf\xbdliz\xc3\xa6ti\xc3\xb8n",
}
assertMap(t, testcases, FlagsSafe|FlagRemoveDotSegments)
}

View File

@ -1,641 +0,0 @@
// 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.
package urlesc
import (
"net/url"
"testing"
)
type URLTest struct {
in string
out *url.URL
roundtrip string // expected result of reserializing the URL; empty means same as "in".
}
var urltests = []URLTest{
// no path
{
"http://www.google.com",
&url.URL{
Scheme: "http",
Host: "www.google.com",
},
"",
},
// path
{
"http://www.google.com/",
&url.URL{
Scheme: "http",
Host: "www.google.com",
Path: "/",
},
"",
},
// path with hex escaping
{
"http://www.google.com/file%20one%26two",
&url.URL{
Scheme: "http",
Host: "www.google.com",
Path: "/file one&two",
},
"http://www.google.com/file%20one&two",
},
// user
{
"ftp://webmaster@www.google.com/",
&url.URL{
Scheme: "ftp",
User: url.User("webmaster"),
Host: "www.google.com",
Path: "/",
},
"",
},
// escape sequence in username
{
"ftp://john%20doe@www.google.com/",
&url.URL{
Scheme: "ftp",
User: url.User("john doe"),
Host: "www.google.com",
Path: "/",
},
"ftp://john%20doe@www.google.com/",
},
// query
{
"http://www.google.com/?q=go+language",
&url.URL{
Scheme: "http",
Host: "www.google.com",
Path: "/",
RawQuery: "q=go+language",
},
"",
},
// query with hex escaping: NOT parsed
{
"http://www.google.com/?q=go%20language",
&url.URL{
Scheme: "http",
Host: "www.google.com",
Path: "/",
RawQuery: "q=go%20language",
},
"",
},
// %20 outside query
{
"http://www.google.com/a%20b?q=c+d",
&url.URL{
Scheme: "http",
Host: "www.google.com",
Path: "/a b",
RawQuery: "q=c+d",
},
"",
},
// path without leading /, so no parsing
{
"http:www.google.com/?q=go+language",
&url.URL{
Scheme: "http",
Opaque: "www.google.com/",
RawQuery: "q=go+language",
},
"http:www.google.com/?q=go+language",
},
// path without leading /, so no parsing
{
"http:%2f%2fwww.google.com/?q=go+language",
&url.URL{
Scheme: "http",
Opaque: "%2f%2fwww.google.com/",
RawQuery: "q=go+language",
},
"http:%2f%2fwww.google.com/?q=go+language",
},
// non-authority with path
{
"mailto:/webmaster@golang.org",
&url.URL{
Scheme: "mailto",
Path: "/webmaster@golang.org",
},
"mailto:///webmaster@golang.org", // unfortunate compromise
},
// non-authority
{
"mailto:webmaster@golang.org",
&url.URL{
Scheme: "mailto",
Opaque: "webmaster@golang.org",
},
"",
},
// unescaped :// in query should not create a scheme
{
"/foo?query=http://bad",
&url.URL{
Path: "/foo",
RawQuery: "query=http://bad",
},
"",
},
// leading // without scheme should create an authority
{
"//foo",
&url.URL{
Host: "foo",
},
"",
},
// leading // without scheme, with userinfo, path, and query
{
"//user@foo/path?a=b",
&url.URL{
User: url.User("user"),
Host: "foo",
Path: "/path",
RawQuery: "a=b",
},
"",
},
// Three leading slashes isn't an authority, but doesn't return an error.
// (We can't return an error, as this code is also used via
// ServeHTTP -> ReadRequest -> Parse, which is arguably a
// different URL parsing context, but currently shares the
// same codepath)
{
"///threeslashes",
&url.URL{
Path: "///threeslashes",
},
"",
},
{
"http://user:password@google.com",
&url.URL{
Scheme: "http",
User: url.UserPassword("user", "password"),
Host: "google.com",
},
"http://user:password@google.com",
},
// unescaped @ in username should not confuse host
{
"http://j@ne:password@google.com",
&url.URL{
Scheme: "http",
User: url.UserPassword("j@ne", "password"),
Host: "google.com",
},
"http://j%40ne:password@google.com",
},
// unescaped @ in password should not confuse host
{
"http://jane:p@ssword@google.com",
&url.URL{
Scheme: "http",
User: url.UserPassword("jane", "p@ssword"),
Host: "google.com",
},
"http://jane:p%40ssword@google.com",
},
{
"http://j@ne:password@google.com/p@th?q=@go",
&url.URL{
Scheme: "http",
User: url.UserPassword("j@ne", "password"),
Host: "google.com",
Path: "/p@th",
RawQuery: "q=@go",
},
"http://j%40ne:password@google.com/p@th?q=@go",
},
{
"http://www.google.com/?q=go+language#foo",
&url.URL{
Scheme: "http",
Host: "www.google.com",
Path: "/",
RawQuery: "q=go+language",
Fragment: "foo",
},
"",
},
{
"http://www.google.com/?q=go+language#foo%26bar",
&url.URL{
Scheme: "http",
Host: "www.google.com",
Path: "/",
RawQuery: "q=go+language",
Fragment: "foo&bar",
},
"http://www.google.com/?q=go+language#foo&bar",
},
{
"file:///home/adg/rabbits",
&url.URL{
Scheme: "file",
Host: "",
Path: "/home/adg/rabbits",
},
"file:///home/adg/rabbits",
},
// "Windows" paths are no exception to the rule.
// See golang.org/issue/6027, especially comment #9.
{
"file:///C:/FooBar/Baz.txt",
&url.URL{
Scheme: "file",
Host: "",
Path: "/C:/FooBar/Baz.txt",
},
"file:///C:/FooBar/Baz.txt",
},
// case-insensitive scheme
{
"MaIlTo:webmaster@golang.org",
&url.URL{
Scheme: "mailto",
Opaque: "webmaster@golang.org",
},
"mailto:webmaster@golang.org",
},
// Relative path
{
"a/b/c",
&url.URL{
Path: "a/b/c",
},
"a/b/c",
},
// escaped '?' in username and password
{
"http://%3Fam:pa%3Fsword@google.com",
&url.URL{
Scheme: "http",
User: url.UserPassword("?am", "pa?sword"),
Host: "google.com",
},
"",
},
// escaped '?' and '#' in path
{
"http://example.com/%3F%23",
&url.URL{
Scheme: "http",
Host: "example.com",
Path: "?#",
},
"",
},
// unescaped [ ] ! ' ( ) * in path
{
"http://example.com/[]!'()*",
&url.URL{
Scheme: "http",
Host: "example.com",
Path: "[]!'()*",
},
"http://example.com/[]!'()*",
},
// escaped : / ? # [ ] @ in username and password
{
"http://%3A%2F%3F:%23%5B%5D%40@example.com",
&url.URL{
Scheme: "http",
User: url.UserPassword(":/?", "#[]@"),
Host: "example.com",
},
"",
},
// unescaped ! $ & ' ( ) * + , ; = in username and password
{
"http://!$&'():*+,;=@example.com",
&url.URL{
Scheme: "http",
User: url.UserPassword("!$&'()", "*+,;="),
Host: "example.com",
},
"",
},
// unescaped = : / . ? = in query component
{
"http://example.com/?q=http://google.com/?q=",
&url.URL{
Scheme: "http",
Host: "example.com",
Path: "/",
RawQuery: "q=http://google.com/?q=",
},
"",
},
// unescaped : / ? [ ] @ ! $ & ' ( ) * + , ; = in fragment
{
"http://example.com/#:/?%23[]@!$&'()*+,;=",
&url.URL{
Scheme: "http",
Host: "example.com",
Path: "/",
Fragment: ":/?#[]@!$&'()*+,;=",
},
"",
},
}
func DoTestString(t *testing.T, parse func(string) (*url.URL, error), name string, tests []URLTest) {
for _, tt := range tests {
u, err := parse(tt.in)
if err != nil {
t.Errorf("%s(%q) returned error %s", name, tt.in, err)
continue
}
expected := tt.in
if len(tt.roundtrip) > 0 {
expected = tt.roundtrip
}
s := Escape(u)
if s != expected {
t.Errorf("Escape(%s(%q)) == %q (expected %q)", name, tt.in, s, expected)
}
}
}
func TestURLString(t *testing.T) {
DoTestString(t, url.Parse, "Parse", urltests)
// no leading slash on path should prepend
// slash on String() call
noslash := URLTest{
"http://www.google.com/search",
&url.URL{
Scheme: "http",
Host: "www.google.com",
Path: "search",
},
"",
}
s := Escape(noslash.out)
if s != noslash.in {
t.Errorf("Expected %s; go %s", noslash.in, s)
}
}
type EscapeTest struct {
in string
out string
err error
}
var escapeTests = []EscapeTest{
{
"",
"",
nil,
},
{
"abc",
"abc",
nil,
},
{
"one two",
"one+two",
nil,
},
{
"10%",
"10%25",
nil,
},
{
" ?&=#+%!<>#\"{}|\\^[]`☺\t:/@$'()*,;",
"+?%26%3D%23%2B%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09%3A/%40%24%27%28%29%2A%2C%3B",
nil,
},
}
func TestEscape(t *testing.T) {
for _, tt := range escapeTests {
actual := QueryEscape(tt.in)
if tt.out != actual {
t.Errorf("QueryEscape(%q) = %q, want %q", tt.in, actual, tt.out)
}
// for bonus points, verify that escape:unescape is an identity.
roundtrip, err := url.QueryUnescape(actual)
if roundtrip != tt.in || err != nil {
t.Errorf("QueryUnescape(%q) = %q, %s; want %q, %s", actual, roundtrip, err, tt.in, "[no error]")
}
}
}
var resolveReferenceTests = []struct {
base, rel, expected string
}{
// Absolute URL references
{"http://foo.com?a=b", "https://bar.com/", "https://bar.com/"},
{"http://foo.com/", "https://bar.com/?a=b", "https://bar.com/?a=b"},
{"http://foo.com/bar", "mailto:foo@example.com", "mailto:foo@example.com"},
// Path-absolute references
{"http://foo.com/bar", "/baz", "http://foo.com/baz"},
{"http://foo.com/bar?a=b#f", "/baz", "http://foo.com/baz"},
{"http://foo.com/bar?a=b", "/baz?c=d", "http://foo.com/baz?c=d"},
// Scheme-relative
{"https://foo.com/bar?a=b", "//bar.com/quux", "https://bar.com/quux"},
// Path-relative references:
// ... current directory
{"http://foo.com", ".", "http://foo.com/"},
{"http://foo.com/bar", ".", "http://foo.com/"},
{"http://foo.com/bar/", ".", "http://foo.com/bar/"},
// ... going down
{"http://foo.com", "bar", "http://foo.com/bar"},
{"http://foo.com/", "bar", "http://foo.com/bar"},
{"http://foo.com/bar/baz", "quux", "http://foo.com/bar/quux"},
// ... going up
{"http://foo.com/bar/baz", "../quux", "http://foo.com/quux"},
{"http://foo.com/bar/baz", "../../../../../quux", "http://foo.com/quux"},
{"http://foo.com/bar", "..", "http://foo.com/"},
{"http://foo.com/bar/baz", "./..", "http://foo.com/"},
// ".." in the middle (issue 3560)
{"http://foo.com/bar/baz", "quux/dotdot/../tail", "http://foo.com/bar/quux/tail"},
{"http://foo.com/bar/baz", "quux/./dotdot/../tail", "http://foo.com/bar/quux/tail"},
{"http://foo.com/bar/baz", "quux/./dotdot/.././tail", "http://foo.com/bar/quux/tail"},
{"http://foo.com/bar/baz", "quux/./dotdot/./../tail", "http://foo.com/bar/quux/tail"},
{"http://foo.com/bar/baz", "quux/./dotdot/dotdot/././../../tail", "http://foo.com/bar/quux/tail"},
{"http://foo.com/bar/baz", "quux/./dotdot/dotdot/./.././../tail", "http://foo.com/bar/quux/tail"},
{"http://foo.com/bar/baz", "quux/./dotdot/dotdot/dotdot/./../../.././././tail", "http://foo.com/bar/quux/tail"},
{"http://foo.com/bar/baz", "quux/./dotdot/../dotdot/../dot/./tail/..", "http://foo.com/bar/quux/dot/"},
// Remove any dot-segments prior to forming the target URI.
// http://tools.ietf.org/html/rfc3986#section-5.2.4
{"http://foo.com/dot/./dotdot/../foo/bar", "../baz", "http://foo.com/dot/baz"},
// Triple dot isn't special
{"http://foo.com/bar", "...", "http://foo.com/..."},
// Fragment
{"http://foo.com/bar", ".#frag", "http://foo.com/#frag"},
// RFC 3986: Normal Examples
// http://tools.ietf.org/html/rfc3986#section-5.4.1
{"http://a/b/c/d;p?q", "g:h", "g:h"},
{"http://a/b/c/d;p?q", "g", "http://a/b/c/g"},
{"http://a/b/c/d;p?q", "./g", "http://a/b/c/g"},
{"http://a/b/c/d;p?q", "g/", "http://a/b/c/g/"},
{"http://a/b/c/d;p?q", "/g", "http://a/g"},
{"http://a/b/c/d;p?q", "//g", "http://g"},
{"http://a/b/c/d;p?q", "?y", "http://a/b/c/d;p?y"},
{"http://a/b/c/d;p?q", "g?y", "http://a/b/c/g?y"},
{"http://a/b/c/d;p?q", "#s", "http://a/b/c/d;p?q#s"},
{"http://a/b/c/d;p?q", "g#s", "http://a/b/c/g#s"},
{"http://a/b/c/d;p?q", "g?y#s", "http://a/b/c/g?y#s"},
{"http://a/b/c/d;p?q", ";x", "http://a/b/c/;x"},
{"http://a/b/c/d;p?q", "g;x", "http://a/b/c/g;x"},
{"http://a/b/c/d;p?q", "g;x?y#s", "http://a/b/c/g;x?y#s"},
{"http://a/b/c/d;p?q", "", "http://a/b/c/d;p?q"},
{"http://a/b/c/d;p?q", ".", "http://a/b/c/"},
{"http://a/b/c/d;p?q", "./", "http://a/b/c/"},
{"http://a/b/c/d;p?q", "..", "http://a/b/"},
{"http://a/b/c/d;p?q", "../", "http://a/b/"},
{"http://a/b/c/d;p?q", "../g", "http://a/b/g"},
{"http://a/b/c/d;p?q", "../..", "http://a/"},
{"http://a/b/c/d;p?q", "../../", "http://a/"},
{"http://a/b/c/d;p?q", "../../g", "http://a/g"},
// RFC 3986: Abnormal Examples
// http://tools.ietf.org/html/rfc3986#section-5.4.2
{"http://a/b/c/d;p?q", "../../../g", "http://a/g"},
{"http://a/b/c/d;p?q", "../../../../g", "http://a/g"},
{"http://a/b/c/d;p?q", "/./g", "http://a/g"},
{"http://a/b/c/d;p?q", "/../g", "http://a/g"},
{"http://a/b/c/d;p?q", "g.", "http://a/b/c/g."},
{"http://a/b/c/d;p?q", ".g", "http://a/b/c/.g"},
{"http://a/b/c/d;p?q", "g..", "http://a/b/c/g.."},
{"http://a/b/c/d;p?q", "..g", "http://a/b/c/..g"},
{"http://a/b/c/d;p?q", "./../g", "http://a/b/g"},
{"http://a/b/c/d;p?q", "./g/.", "http://a/b/c/g/"},
{"http://a/b/c/d;p?q", "g/./h", "http://a/b/c/g/h"},
{"http://a/b/c/d;p?q", "g/../h", "http://a/b/c/h"},
{"http://a/b/c/d;p?q", "g;x=1/./y", "http://a/b/c/g;x=1/y"},
{"http://a/b/c/d;p?q", "g;x=1/../y", "http://a/b/c/y"},
{"http://a/b/c/d;p?q", "g?y/./x", "http://a/b/c/g?y/./x"},
{"http://a/b/c/d;p?q", "g?y/../x", "http://a/b/c/g?y/../x"},
{"http://a/b/c/d;p?q", "g#s/./x", "http://a/b/c/g#s/./x"},
{"http://a/b/c/d;p?q", "g#s/../x", "http://a/b/c/g#s/../x"},
// Extras.
{"https://a/b/c/d;p?q", "//g?q", "https://g?q"},
{"https://a/b/c/d;p?q", "//g#s", "https://g#s"},
{"https://a/b/c/d;p?q", "//g/d/e/f?y#s", "https://g/d/e/f?y#s"},
{"https://a/b/c/d;p#s", "?y", "https://a/b/c/d;p?y"},
{"https://a/b/c/d;p?q#s", "?y", "https://a/b/c/d;p?y"},
}
func TestResolveReference(t *testing.T) {
mustParse := func(url_ string) *url.URL {
u, err := url.Parse(url_)
if err != nil {
t.Fatalf("Expected URL to parse: %q, got error: %v", url_, err)
}
return u
}
opaque := &url.URL{Scheme: "scheme", Opaque: "opaque"}
for _, test := range resolveReferenceTests {
base := mustParse(test.base)
rel := mustParse(test.rel)
url := base.ResolveReference(rel)
if Escape(url) != test.expected {
t.Errorf("URL(%q).ResolveReference(%q) == %q, got %q", test.base, test.rel, test.expected, Escape(url))
}
// Ensure that new instances are returned.
if base == url {
t.Errorf("Expected URL.ResolveReference to return new URL instance.")
}
// Test the convenience wrapper too.
url, err := base.Parse(test.rel)
if err != nil {
t.Errorf("URL(%q).Parse(%q) failed: %v", test.base, test.rel, err)
} else if Escape(url) != test.expected {
t.Errorf("URL(%q).Parse(%q) == %q, got %q", test.base, test.rel, test.expected, Escape(url))
} else if base == url {
// Ensure that new instances are returned for the wrapper too.
t.Errorf("Expected URL.Parse to return new URL instance.")
}
// Ensure Opaque resets the URL.
url = base.ResolveReference(opaque)
if *url != *opaque {
t.Errorf("ResolveReference failed to resolve opaque URL: want %#v, got %#v", url, opaque)
}
// Test the convenience wrapper with an opaque URL too.
url, err = base.Parse("scheme:opaque")
if err != nil {
t.Errorf(`URL(%q).Parse("scheme:opaque") failed: %v`, test.base, err)
} else if *url != *opaque {
t.Errorf("Parse failed to resolve opaque URL: want %#v, got %#v", url, opaque)
} else if base == url {
// Ensure that new instances are returned, again.
t.Errorf("Expected URL.Parse to return new URL instance.")
}
}
}
type shouldEscapeTest struct {
in byte
mode encoding
escape bool
}
var shouldEscapeTests = []shouldEscapeTest{
// Unreserved characters (§2.3)
{'a', encodePath, false},
{'a', encodeUserPassword, false},
{'a', encodeQueryComponent, false},
{'a', encodeFragment, false},
{'z', encodePath, false},
{'A', encodePath, false},
{'Z', encodePath, false},
{'0', encodePath, false},
{'9', encodePath, false},
{'-', encodePath, false},
{'-', encodeUserPassword, false},
{'-', encodeQueryComponent, false},
{'-', encodeFragment, false},
{'.', encodePath, false},
{'_', encodePath, false},
{'~', encodePath, false},
// User information (§3.2.1)
{':', encodeUserPassword, true},
{'/', encodeUserPassword, true},
{'?', encodeUserPassword, true},
{'@', encodeUserPassword, true},
{'$', encodeUserPassword, false},
{'&', encodeUserPassword, false},
{'+', encodeUserPassword, false},
{',', encodeUserPassword, false},
{';', encodeUserPassword, false},
{'=', encodeUserPassword, false},
}
func TestShouldEscape(t *testing.T) {
for _, tt := range shouldEscapeTests {
if shouldEscape(tt.in, tt.mode) != tt.escape {
t.Errorf("shouldEscape(%q, %v) returned %v; expected %v", tt.in, tt.mode, !tt.escape, tt.escape)
}
}
}

View File

@ -1,4 +0,0 @@
language: go
go:
- 1.x

View File

@ -1,46 +0,0 @@
Change history of swagger
=
2017-01-30
- moved from go-restful/swagger to go-restful-swagger12
2015-10-16
- add type override mechanism for swagger models (MR 254, nathanejohnson)
- replace uses of wildcard in generated apidocs (issue 251)
2015-05-25
- (api break) changed the type of Properties in Model
- (api break) changed the type of Models in ApiDeclaration
- (api break) changed the parameter type of PostBuildDeclarationMapFunc
2015-04-09
- add ModelBuildable interface for customization of Model
2015-03-17
- preserve order of Routes per WebService in Swagger listing
- fix use of $ref and type in Swagger models
- add api version to listing
2014-11-14
- operation parameters are now sorted using ordering path,query,form,header,body
2014-11-12
- respect omitempty tag value for embedded structs
- expose ApiVersion of WebService to Swagger ApiDeclaration
2014-05-29
- (api add) Ability to define custom http.Handler to serve swagger-ui static files
2014-05-04
- (fix) include model for array element type of response
2014-01-03
- (fix) do not add primitive type to the Api models
2013-11-27
- (fix) make Swagger work for WebServices with root ("/" or "") paths
2013-10-29
- (api add) package variable LogInfo to customize logging function
2013-10-15
- upgraded to spec version 1.2 (https://github.com/wordnik/swagger-core/wiki/1.2-transition)

View File

@ -1,22 +0,0 @@
Copyright (c) 2017 Ernest Micklei
MIT License
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.

View File

@ -1,83 +0,0 @@
# go-restful-swagger12
[![Build Status](https://travis-ci.org/emicklei/go-restful-swagger12.png)](https://travis-ci.org/emicklei/go-restful-swagger12)
[![GoDoc](https://godoc.org/github.com/emicklei/go-restful-swagger12?status.svg)](https://godoc.org/github.com/emicklei/go-restful-swagger12)
How to use Swagger UI with go-restful
=
Get the Swagger UI sources (version 1.2 only)
git clone https://github.com/wordnik/swagger-ui.git
The project contains a "dist" folder.
Its contents has all the Swagger UI files you need.
The `index.html` has an `url` set to `http://petstore.swagger.wordnik.com/api/api-docs`.
You need to change that to match your WebService JSON endpoint e.g. `http://localhost:8080/apidocs.json`
Now, you can install the Swagger WebService for serving the Swagger specification in JSON.
config := swagger.Config{
WebServices: restful.RegisteredWebServices(),
ApiPath: "/apidocs.json",
SwaggerPath: "/apidocs/",
SwaggerFilePath: "/Users/emicklei/Projects/swagger-ui/dist"}
swagger.InstallSwaggerService(config)
Documenting Structs
--
Currently there are 2 ways to document your structs in the go-restful Swagger.
###### By using struct tags
- Use tag "description" to annotate a struct field with a description to show in the UI
- Use tag "modelDescription" to annotate the struct itself with a description to show in the UI. The tag can be added in an field of the struct and in case that there are multiple definition, they will be appended with an empty line.
###### By using the SwaggerDoc method
Here is an example with an `Address` struct and the documentation for each of the fields. The `""` is a special entry for **documenting the struct itself**.
type Address struct {
Country string `json:"country,omitempty"`
PostCode int `json:"postcode,omitempty"`
}
func (Address) SwaggerDoc() map[string]string {
return map[string]string{
"": "Address doc",
"country": "Country doc",
"postcode": "PostCode doc",
}
}
This example will generate a JSON like this
{
"Address": {
"id": "Address",
"description": "Address doc",
"properties": {
"country": {
"type": "string",
"description": "Country doc"
},
"postcode": {
"type": "integer",
"format": "int32",
"description": "PostCode doc"
}
}
}
}
**Very Important Notes:**
- `SwaggerDoc()` is using a **NON-Pointer** receiver (e.g. func (Address) and not func (*Address))
- The returned map should use as key the name of the field as defined in the JSON parameter (e.g. `"postcode"` and not `"PostCode"`)
Notes
--
- The Nickname of an Operation is automatically set by finding the name of the function. You can override it using RouteBuilder.Operation(..)
- The WebServices field of swagger.Config can be used to control which service you want to expose and document ; you can have multiple configs and therefore multiple endpoints.
© 2017, ernestmicklei.com. MIT License. Contributions welcome.

View File

@ -1,64 +0,0 @@
package swagger
// Copyright 2015 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.
import (
"bytes"
"encoding/json"
)
// ApiDeclarationList maintains an ordered list of ApiDeclaration.
type ApiDeclarationList struct {
List []ApiDeclaration
}
// At returns the ApiDeclaration by its path unless absent, then ok is false
func (l *ApiDeclarationList) At(path string) (a ApiDeclaration, ok bool) {
for _, each := range l.List {
if each.ResourcePath == path {
return each, true
}
}
return a, false
}
// Put adds or replaces a ApiDeclaration with this name
func (l *ApiDeclarationList) Put(path string, a ApiDeclaration) {
// maybe replace existing
for i, each := range l.List {
if each.ResourcePath == path {
// replace
l.List[i] = a
return
}
}
// add
l.List = append(l.List, a)
}
// Do enumerates all the properties, each with its assigned name
func (l *ApiDeclarationList) Do(block func(path string, decl ApiDeclaration)) {
for _, each := range l.List {
block(each.ResourcePath, each)
}
}
// MarshalJSON writes the ModelPropertyList as if it was a map[string]ModelProperty
func (l ApiDeclarationList) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
encoder := json.NewEncoder(&buf)
buf.WriteString("{\n")
for i, each := range l.List {
buf.WriteString("\"")
buf.WriteString(each.ResourcePath)
buf.WriteString("\": ")
encoder.Encode(each)
if i < len(l.List)-1 {
buf.WriteString(",\n")
}
}
buf.WriteString("}")
return buf.Bytes(), nil
}

View File

@ -1,46 +0,0 @@
package swagger
import (
"net/http"
"reflect"
"github.com/emicklei/go-restful"
)
// PostBuildDeclarationMapFunc can be used to modify the api declaration map.
type PostBuildDeclarationMapFunc func(apiDeclarationMap *ApiDeclarationList)
// MapSchemaFormatFunc can be used to modify typeName at definition time.
type MapSchemaFormatFunc func(typeName string) string
// MapModelTypeNameFunc can be used to return the desired typeName for a given
// type. It will return false if the default name should be used.
type MapModelTypeNameFunc func(t reflect.Type) (string, bool)
type Config struct {
// url where the services are available, e.g. http://localhost:8080
// if left empty then the basePath of Swagger is taken from the actual request
WebServicesUrl string
// path where the JSON api is avaiable , e.g. /apidocs
ApiPath string
// [optional] path where the swagger UI will be served, e.g. /swagger
SwaggerPath string
// [optional] location of folder containing Swagger HTML5 application index.html
SwaggerFilePath string
// api listing is constructed from this list of restful WebServices.
WebServices []*restful.WebService
// will serve all static content (scripts,pages,images)
StaticHandler http.Handler
// [optional] on default CORS (Cross-Origin-Resource-Sharing) is enabled.
DisableCORS bool
// Top-level API version. Is reflected in the resource listing.
ApiVersion string
// If set then call this handler after building the complete ApiDeclaration Map
PostBuildHandler PostBuildDeclarationMapFunc
// Swagger global info struct
Info Info
// [optional] If set, model builder should call this handler to get addition typename-to-swagger-format-field conversion.
SchemaFormatHandler MapSchemaFormatFunc
// [optional] If set, model builder should call this handler to retrieve the name for a given type.
ModelTypeNameHandler MapModelTypeNameFunc
}

View File

@ -1,467 +0,0 @@
package swagger
import (
"encoding/json"
"reflect"
"strings"
)
// ModelBuildable is used for extending Structs that need more control over
// how the Model appears in the Swagger api declaration.
type ModelBuildable interface {
PostBuildModel(m *Model) *Model
}
type modelBuilder struct {
Models *ModelList
Config *Config
}
type documentable interface {
SwaggerDoc() map[string]string
}
// Check if this structure has a method with signature func (<theModel>) SwaggerDoc() map[string]string
// If it exists, retrive the documentation and overwrite all struct tag descriptions
func getDocFromMethodSwaggerDoc2(model reflect.Type) map[string]string {
if docable, ok := reflect.New(model).Elem().Interface().(documentable); ok {
return docable.SwaggerDoc()
}
return make(map[string]string)
}
// addModelFrom creates and adds a Model to the builder and detects and calls
// the post build hook for customizations
func (b modelBuilder) addModelFrom(sample interface{}) {
if modelOrNil := b.addModel(reflect.TypeOf(sample), ""); modelOrNil != nil {
// allow customizations
if buildable, ok := sample.(ModelBuildable); ok {
modelOrNil = buildable.PostBuildModel(modelOrNil)
b.Models.Put(modelOrNil.Id, *modelOrNil)
}
}
}
func (b modelBuilder) addModel(st reflect.Type, nameOverride string) *Model {
// Turn pointers into simpler types so further checks are
// correct.
if st.Kind() == reflect.Ptr {
st = st.Elem()
}
modelName := b.keyFrom(st)
if nameOverride != "" {
modelName = nameOverride
}
// no models needed for primitive types
if b.isPrimitiveType(modelName) {
return nil
}
// golang encoding/json packages says array and slice values encode as
// JSON arrays, except that []byte encodes as a base64-encoded string.
// If we see a []byte here, treat it at as a primitive type (string)
// and deal with it in buildArrayTypeProperty.
if (st.Kind() == reflect.Slice || st.Kind() == reflect.Array) &&
st.Elem().Kind() == reflect.Uint8 {
return nil
}
// see if we already have visited this model
if _, ok := b.Models.At(modelName); ok {
return nil
}
sm := Model{
Id: modelName,
Required: []string{},
Properties: ModelPropertyList{}}
// reference the model before further initializing (enables recursive structs)
b.Models.Put(modelName, sm)
// check for slice or array
if st.Kind() == reflect.Slice || st.Kind() == reflect.Array {
b.addModel(st.Elem(), "")
return &sm
}
// check for structure or primitive type
if st.Kind() != reflect.Struct {
return &sm
}
fullDoc := getDocFromMethodSwaggerDoc2(st)
modelDescriptions := []string{}
for i := 0; i < st.NumField(); i++ {
field := st.Field(i)
jsonName, modelDescription, prop := b.buildProperty(field, &sm, modelName)
if len(modelDescription) > 0 {
modelDescriptions = append(modelDescriptions, modelDescription)
}
// add if not omitted
if len(jsonName) != 0 {
// update description
if fieldDoc, ok := fullDoc[jsonName]; ok {
prop.Description = fieldDoc
}
// update Required
if b.isPropertyRequired(field) {
sm.Required = append(sm.Required, jsonName)
}
sm.Properties.Put(jsonName, prop)
}
}
// We always overwrite documentation if SwaggerDoc method exists
// "" is special for documenting the struct itself
if modelDoc, ok := fullDoc[""]; ok {
sm.Description = modelDoc
} else if len(modelDescriptions) != 0 {
sm.Description = strings.Join(modelDescriptions, "\n")
}
// update model builder with completed model
b.Models.Put(modelName, sm)
return &sm
}
func (b modelBuilder) isPropertyRequired(field reflect.StructField) bool {
required := true
if jsonTag := field.Tag.Get("json"); jsonTag != "" {
s := strings.Split(jsonTag, ",")
if len(s) > 1 && s[1] == "omitempty" {
return false
}
}
return required
}
func (b modelBuilder) buildProperty(field reflect.StructField, model *Model, modelName string) (jsonName, modelDescription string, prop ModelProperty) {
jsonName = b.jsonNameOfField(field)
if len(jsonName) == 0 {
// empty name signals skip property
return "", "", prop
}
if field.Name == "XMLName" && field.Type.String() == "xml.Name" {
// property is metadata for the xml.Name attribute, can be skipped
return "", "", prop
}
if tag := field.Tag.Get("modelDescription"); tag != "" {
modelDescription = tag
}
prop.setPropertyMetadata(field)
if prop.Type != nil {
return jsonName, modelDescription, prop
}
fieldType := field.Type
// check if type is doing its own marshalling
marshalerType := reflect.TypeOf((*json.Marshaler)(nil)).Elem()
if fieldType.Implements(marshalerType) {
var pType = "string"
if prop.Type == nil {
prop.Type = &pType
}
if prop.Format == "" {
prop.Format = b.jsonSchemaFormat(b.keyFrom(fieldType))
}
return jsonName, modelDescription, prop
}
// check if annotation says it is a string
if jsonTag := field.Tag.Get("json"); jsonTag != "" {
s := strings.Split(jsonTag, ",")
if len(s) > 1 && s[1] == "string" {
stringt := "string"
prop.Type = &stringt
return jsonName, modelDescription, prop
}
}
fieldKind := fieldType.Kind()
switch {
case fieldKind == reflect.Struct:
jsonName, prop := b.buildStructTypeProperty(field, jsonName, model)
return jsonName, modelDescription, prop
case fieldKind == reflect.Slice || fieldKind == reflect.Array:
jsonName, prop := b.buildArrayTypeProperty(field, jsonName, modelName)
return jsonName, modelDescription, prop
case fieldKind == reflect.Ptr:
jsonName, prop := b.buildPointerTypeProperty(field, jsonName, modelName)
return jsonName, modelDescription, prop
case fieldKind == reflect.String:
stringt := "string"
prop.Type = &stringt
return jsonName, modelDescription, prop
case fieldKind == reflect.Map:
// if it's a map, it's unstructured, and swagger 1.2 can't handle it
objectType := "object"
prop.Type = &objectType
return jsonName, modelDescription, prop
}
fieldTypeName := b.keyFrom(fieldType)
if b.isPrimitiveType(fieldTypeName) {
mapped := b.jsonSchemaType(fieldTypeName)
prop.Type = &mapped
prop.Format = b.jsonSchemaFormat(fieldTypeName)
return jsonName, modelDescription, prop
}
modelType := b.keyFrom(fieldType)
prop.Ref = &modelType
if fieldType.Name() == "" { // override type of anonymous structs
nestedTypeName := modelName + "." + jsonName
prop.Ref = &nestedTypeName
b.addModel(fieldType, nestedTypeName)
}
return jsonName, modelDescription, prop
}
func hasNamedJSONTag(field reflect.StructField) bool {
parts := strings.Split(field.Tag.Get("json"), ",")
if len(parts) == 0 {
return false
}
for _, s := range parts[1:] {
if s == "inline" {
return false
}
}
return len(parts[0]) > 0
}
func (b modelBuilder) buildStructTypeProperty(field reflect.StructField, jsonName string, model *Model) (nameJson string, prop ModelProperty) {
prop.setPropertyMetadata(field)
// Check for type override in tag
if prop.Type != nil {
return jsonName, prop
}
fieldType := field.Type
// check for anonymous
if len(fieldType.Name()) == 0 {
// anonymous
anonType := model.Id + "." + jsonName
b.addModel(fieldType, anonType)
prop.Ref = &anonType
return jsonName, prop
}
if field.Name == fieldType.Name() && field.Anonymous && !hasNamedJSONTag(field) {
// embedded struct
sub := modelBuilder{new(ModelList), b.Config}
sub.addModel(fieldType, "")
subKey := sub.keyFrom(fieldType)
// merge properties from sub
subModel, _ := sub.Models.At(subKey)
subModel.Properties.Do(func(k string, v ModelProperty) {
model.Properties.Put(k, v)
// if subModel says this property is required then include it
required := false
for _, each := range subModel.Required {
if k == each {
required = true
break
}
}
if required {
model.Required = append(model.Required, k)
}
})
// add all new referenced models
sub.Models.Do(func(key string, sub Model) {
if key != subKey {
if _, ok := b.Models.At(key); !ok {
b.Models.Put(key, sub)
}
}
})
// empty name signals skip property
return "", prop
}
// simple struct
b.addModel(fieldType, "")
var pType = b.keyFrom(fieldType)
prop.Ref = &pType
return jsonName, prop
}
func (b modelBuilder) buildArrayTypeProperty(field reflect.StructField, jsonName, modelName string) (nameJson string, prop ModelProperty) {
// check for type override in tags
prop.setPropertyMetadata(field)
if prop.Type != nil {
return jsonName, prop
}
fieldType := field.Type
if fieldType.Elem().Kind() == reflect.Uint8 {
stringt := "string"
prop.Type = &stringt
return jsonName, prop
}
var pType = "array"
prop.Type = &pType
isPrimitive := b.isPrimitiveType(fieldType.Elem().Name())
elemTypeName := b.getElementTypeName(modelName, jsonName, fieldType.Elem())
prop.Items = new(Item)
if isPrimitive {
mapped := b.jsonSchemaType(elemTypeName)
prop.Items.Type = &mapped
} else {
prop.Items.Ref = &elemTypeName
}
// add|overwrite model for element type
if fieldType.Elem().Kind() == reflect.Ptr {
fieldType = fieldType.Elem()
}
if !isPrimitive {
b.addModel(fieldType.Elem(), elemTypeName)
}
return jsonName, prop
}
func (b modelBuilder) buildPointerTypeProperty(field reflect.StructField, jsonName, modelName string) (nameJson string, prop ModelProperty) {
prop.setPropertyMetadata(field)
// Check for type override in tags
if prop.Type != nil {
return jsonName, prop
}
fieldType := field.Type
// override type of pointer to list-likes
if fieldType.Elem().Kind() == reflect.Slice || fieldType.Elem().Kind() == reflect.Array {
var pType = "array"
prop.Type = &pType
isPrimitive := b.isPrimitiveType(fieldType.Elem().Elem().Name())
elemName := b.getElementTypeName(modelName, jsonName, fieldType.Elem().Elem())
if isPrimitive {
primName := b.jsonSchemaType(elemName)
prop.Items = &Item{Ref: &primName}
} else {
prop.Items = &Item{Ref: &elemName}
}
if !isPrimitive {
// add|overwrite model for element type
b.addModel(fieldType.Elem().Elem(), elemName)
}
} else {
// non-array, pointer type
fieldTypeName := b.keyFrom(fieldType.Elem())
var pType = b.jsonSchemaType(fieldTypeName) // no star, include pkg path
if b.isPrimitiveType(fieldTypeName) {
prop.Type = &pType
prop.Format = b.jsonSchemaFormat(fieldTypeName)
return jsonName, prop
}
prop.Ref = &pType
elemName := ""
if fieldType.Elem().Name() == "" {
elemName = modelName + "." + jsonName
prop.Ref = &elemName
}
b.addModel(fieldType.Elem(), elemName)
}
return jsonName, prop
}
func (b modelBuilder) getElementTypeName(modelName, jsonName string, t reflect.Type) string {
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Name() == "" {
return modelName + "." + jsonName
}
return b.keyFrom(t)
}
func (b modelBuilder) keyFrom(st reflect.Type) string {
key := st.String()
if b.Config != nil && b.Config.ModelTypeNameHandler != nil {
if name, ok := b.Config.ModelTypeNameHandler(st); ok {
key = name
}
}
if len(st.Name()) == 0 { // unnamed type
// Swagger UI has special meaning for [
key = strings.Replace(key, "[]", "||", -1)
}
return key
}
// see also https://golang.org/ref/spec#Numeric_types
func (b modelBuilder) isPrimitiveType(modelName string) bool {
if len(modelName) == 0 {
return false
}
return strings.Contains("uint uint8 uint16 uint32 uint64 int int8 int16 int32 int64 float32 float64 bool string byte rune time.Time", modelName)
}
// jsonNameOfField returns the name of the field as it should appear in JSON format
// An empty string indicates that this field is not part of the JSON representation
func (b modelBuilder) jsonNameOfField(field reflect.StructField) string {
if jsonTag := field.Tag.Get("json"); jsonTag != "" {
s := strings.Split(jsonTag, ",")
if s[0] == "-" {
// empty name signals skip property
return ""
} else if s[0] != "" {
return s[0]
}
}
return field.Name
}
// see also http://json-schema.org/latest/json-schema-core.html#anchor8
func (b modelBuilder) jsonSchemaType(modelName string) string {
schemaMap := map[string]string{
"uint": "integer",
"uint8": "integer",
"uint16": "integer",
"uint32": "integer",
"uint64": "integer",
"int": "integer",
"int8": "integer",
"int16": "integer",
"int32": "integer",
"int64": "integer",
"byte": "integer",
"float64": "number",
"float32": "number",
"bool": "boolean",
"time.Time": "string",
}
mapped, ok := schemaMap[modelName]
if !ok {
return modelName // use as is (custom or struct)
}
return mapped
}
func (b modelBuilder) jsonSchemaFormat(modelName string) string {
if b.Config != nil && b.Config.SchemaFormatHandler != nil {
if mapped := b.Config.SchemaFormatHandler(modelName); mapped != "" {
return mapped
}
}
schemaMap := map[string]string{
"int": "int32",
"int32": "int32",
"int64": "int64",
"byte": "byte",
"uint": "integer",
"uint8": "byte",
"float64": "double",
"float32": "float",
"time.Time": "date-time",
"*time.Time": "date-time",
}
mapped, ok := schemaMap[modelName]
if !ok {
return "" // no format
}
return mapped
}

File diff suppressed because it is too large Load Diff

View File

@ -1,86 +0,0 @@
package swagger
// Copyright 2015 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.
import (
"bytes"
"encoding/json"
)
// NamedModel associates a name with a Model (not using its Id)
type NamedModel struct {
Name string
Model Model
}
// ModelList encapsulates a list of NamedModel (association)
type ModelList struct {
List []NamedModel
}
// Put adds or replaces a Model by its name
func (l *ModelList) Put(name string, model Model) {
for i, each := range l.List {
if each.Name == name {
// replace
l.List[i] = NamedModel{name, model}
return
}
}
// add
l.List = append(l.List, NamedModel{name, model})
}
// At returns a Model by its name, ok is false if absent
func (l *ModelList) At(name string) (m Model, ok bool) {
for _, each := range l.List {
if each.Name == name {
return each.Model, true
}
}
return m, false
}
// Do enumerates all the models, each with its assigned name
func (l *ModelList) Do(block func(name string, value Model)) {
for _, each := range l.List {
block(each.Name, each.Model)
}
}
// MarshalJSON writes the ModelList as if it was a map[string]Model
func (l ModelList) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
encoder := json.NewEncoder(&buf)
buf.WriteString("{\n")
for i, each := range l.List {
buf.WriteString("\"")
buf.WriteString(each.Name)
buf.WriteString("\": ")
encoder.Encode(each.Model)
if i < len(l.List)-1 {
buf.WriteString(",\n")
}
}
buf.WriteString("}")
return buf.Bytes(), nil
}
// UnmarshalJSON reads back a ModelList. This is an expensive operation.
func (l *ModelList) UnmarshalJSON(data []byte) error {
raw := map[string]interface{}{}
json.NewDecoder(bytes.NewReader(data)).Decode(&raw)
for k, v := range raw {
// produces JSON bytes for each value
data, err := json.Marshal(v)
if err != nil {
return err
}
var m Model
json.NewDecoder(bytes.NewReader(data)).Decode(&m)
l.Put(k, m)
}
return nil
}

View File

@ -1,48 +0,0 @@
package swagger
import (
"encoding/json"
"testing"
)
func TestModelList(t *testing.T) {
m := Model{}
m.Id = "m"
l := ModelList{}
l.Put("m", m)
k, ok := l.At("m")
if !ok {
t.Error("want model back")
}
if got, want := k.Id, "m"; got != want {
t.Errorf("got %v want %v", got, want)
}
}
func TestModelList_Marshal(t *testing.T) {
l := ModelList{}
m := Model{Id: "myid"}
l.Put("myid", m)
data, err := json.Marshal(l)
if err != nil {
t.Error(err)
}
if got, want := string(data), `{"myid":{"id":"myid","properties":{}}}`; got != want {
t.Errorf("got %v want %v", got, want)
}
}
func TestModelList_Unmarshal(t *testing.T) {
data := `{"myid":{"id":"myid","properties":{}}}`
l := ModelList{}
if err := json.Unmarshal([]byte(data), &l); err != nil {
t.Error(err)
}
m, ok := l.At("myid")
if !ok {
t.Error("expected myid")
}
if got, want := m.Id, "myid"; got != want {
t.Errorf("got %v want %v", got, want)
}
}

View File

@ -1,81 +0,0 @@
package swagger
import (
"reflect"
"strings"
)
func (prop *ModelProperty) setDescription(field reflect.StructField) {
if tag := field.Tag.Get("description"); tag != "" {
prop.Description = tag
}
}
func (prop *ModelProperty) setDefaultValue(field reflect.StructField) {
if tag := field.Tag.Get("default"); tag != "" {
prop.DefaultValue = Special(tag)
}
}
func (prop *ModelProperty) setEnumValues(field reflect.StructField) {
// We use | to separate the enum values. This value is chosen
// since its unlikely to be useful in actual enumeration values.
if tag := field.Tag.Get("enum"); tag != "" {
prop.Enum = strings.Split(tag, "|")
}
}
func (prop *ModelProperty) setMaximum(field reflect.StructField) {
if tag := field.Tag.Get("maximum"); tag != "" {
prop.Maximum = tag
}
}
func (prop *ModelProperty) setType(field reflect.StructField) {
if tag := field.Tag.Get("type"); tag != "" {
// Check if the first two characters of the type tag are
// intended to emulate slice/array behaviour.
//
// If type is intended to be a slice/array then add the
// overriden type to the array item instead of the main property
if len(tag) > 2 && tag[0:2] == "[]" {
pType := "array"
prop.Type = &pType
prop.Items = new(Item)
iType := tag[2:]
prop.Items.Type = &iType
return
}
prop.Type = &tag
}
}
func (prop *ModelProperty) setMinimum(field reflect.StructField) {
if tag := field.Tag.Get("minimum"); tag != "" {
prop.Minimum = tag
}
}
func (prop *ModelProperty) setUniqueItems(field reflect.StructField) {
tag := field.Tag.Get("unique")
switch tag {
case "true":
v := true
prop.UniqueItems = &v
case "false":
v := false
prop.UniqueItems = &v
}
}
func (prop *ModelProperty) setPropertyMetadata(field reflect.StructField) {
prop.setDescription(field)
prop.setEnumValues(field)
prop.setMinimum(field)
prop.setMaximum(field)
prop.setUniqueItems(field)
prop.setDefaultValue(field)
prop.setType(field)
}

View File

@ -1,70 +0,0 @@
package swagger
import (
"net"
"testing"
)
// clear && go test -v -test.run TestThatExtraTagsAreReadIntoModel ...swagger
func TestThatExtraTagsAreReadIntoModel(t *testing.T) {
type fakeint int
type fakearray string
type Anything struct {
Name string `description:"name" modelDescription:"a test"`
Size int `minimum:"0" maximum:"10"`
Stati string `enum:"off|on" default:"on" modelDescription:"more description"`
ID string `unique:"true"`
FakeInt fakeint `type:"integer"`
FakeArray fakearray `type:"[]string"`
IP net.IP `type:"string"`
Password string
}
m := modelsFromStruct(Anything{})
props, _ := m.At("swagger.Anything")
p1, _ := props.Properties.At("Name")
if got, want := p1.Description, "name"; got != want {
t.Errorf("got %v want %v", got, want)
}
p2, _ := props.Properties.At("Size")
if got, want := p2.Minimum, "0"; got != want {
t.Errorf("got %v want %v", got, want)
}
if got, want := p2.Maximum, "10"; got != want {
t.Errorf("got %v want %v", got, want)
}
p3, _ := props.Properties.At("Stati")
if got, want := p3.Enum[0], "off"; got != want {
t.Errorf("got %v want %v", got, want)
}
if got, want := p3.Enum[1], "on"; got != want {
t.Errorf("got %v want %v", got, want)
}
p4, _ := props.Properties.At("ID")
if got, want := *p4.UniqueItems, true; got != want {
t.Errorf("got %v want %v", got, want)
}
p5, _ := props.Properties.At("Password")
if got, want := *p5.Type, "string"; got != want {
t.Errorf("got %v want %v", got, want)
}
p6, _ := props.Properties.At("FakeInt")
if got, want := *p6.Type, "integer"; got != want {
t.Errorf("got %v want %v", got, want)
}
p7, _ := props.Properties.At("FakeArray")
if got, want := *p7.Type, "array"; got != want {
t.Errorf("got %v want %v", got, want)
}
p7p, _ := props.Properties.At("FakeArray")
if got, want := *p7p.Items.Type, "string"; got != want {
t.Errorf("got %v want %v", got, want)
}
p8, _ := props.Properties.At("IP")
if got, want := *p8.Type, "string"; got != want {
t.Errorf("got %v want %v", got, want)
}
if got, want := props.Description, "a test\nmore description"; got != want {
t.Errorf("got %v want %v", got, want)
}
}

View File

@ -1,87 +0,0 @@
package swagger
// Copyright 2015 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.
import (
"bytes"
"encoding/json"
)
// NamedModelProperty associates a name to a ModelProperty
type NamedModelProperty struct {
Name string
Property ModelProperty
}
// ModelPropertyList encapsulates a list of NamedModelProperty (association)
type ModelPropertyList struct {
List []NamedModelProperty
}
// At returns the ModelPropety by its name unless absent, then ok is false
func (l *ModelPropertyList) At(name string) (p ModelProperty, ok bool) {
for _, each := range l.List {
if each.Name == name {
return each.Property, true
}
}
return p, false
}
// Put adds or replaces a ModelProperty with this name
func (l *ModelPropertyList) Put(name string, prop ModelProperty) {
// maybe replace existing
for i, each := range l.List {
if each.Name == name {
// replace
l.List[i] = NamedModelProperty{Name: name, Property: prop}
return
}
}
// add
l.List = append(l.List, NamedModelProperty{Name: name, Property: prop})
}
// Do enumerates all the properties, each with its assigned name
func (l *ModelPropertyList) Do(block func(name string, value ModelProperty)) {
for _, each := range l.List {
block(each.Name, each.Property)
}
}
// MarshalJSON writes the ModelPropertyList as if it was a map[string]ModelProperty
func (l ModelPropertyList) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
encoder := json.NewEncoder(&buf)
buf.WriteString("{\n")
for i, each := range l.List {
buf.WriteString("\"")
buf.WriteString(each.Name)
buf.WriteString("\": ")
encoder.Encode(each.Property)
if i < len(l.List)-1 {
buf.WriteString(",\n")
}
}
buf.WriteString("}")
return buf.Bytes(), nil
}
// UnmarshalJSON reads back a ModelPropertyList. This is an expensive operation.
func (l *ModelPropertyList) UnmarshalJSON(data []byte) error {
raw := map[string]interface{}{}
json.NewDecoder(bytes.NewReader(data)).Decode(&raw)
for k, v := range raw {
// produces JSON bytes for each value
data, err := json.Marshal(v)
if err != nil {
return err
}
var m ModelProperty
json.NewDecoder(bytes.NewReader(data)).Decode(&m)
l.Put(k, m)
}
return nil
}

View File

@ -1,47 +0,0 @@
package swagger
import (
"encoding/json"
"testing"
)
func TestModelPropertyList(t *testing.T) {
l := ModelPropertyList{}
p := ModelProperty{Description: "d"}
l.Put("p", p)
q, ok := l.At("p")
if !ok {
t.Error("expected p")
}
if got, want := q.Description, "d"; got != want {
t.Errorf("got %v want %v", got, want)
}
}
func TestModelPropertyList_Marshal(t *testing.T) {
l := ModelPropertyList{}
p := ModelProperty{Description: "d"}
l.Put("p", p)
data, err := json.Marshal(l)
if err != nil {
t.Error(err)
}
if got, want := string(data), `{"p":{"description":"d"}}`; got != want {
t.Errorf("got %v want %v", got, want)
}
}
func TestModelPropertyList_Unmarshal(t *testing.T) {
data := `{"p":{"description":"d"}}`
l := ModelPropertyList{}
if err := json.Unmarshal([]byte(data), &l); err != nil {
t.Error(err)
}
m, ok := l.At("p")
if !ok {
t.Error("expected p")
}
if got, want := m.Description, "d"; got != want {
t.Errorf("got %v want %v", got, want)
}
}

View File

@ -1,36 +0,0 @@
package swagger
// Copyright 2015 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.
import "github.com/emicklei/go-restful"
type orderedRouteMap struct {
elements map[string][]restful.Route
keys []string
}
func newOrderedRouteMap() *orderedRouteMap {
return &orderedRouteMap{
elements: map[string][]restful.Route{},
keys: []string{},
}
}
func (o *orderedRouteMap) Add(key string, route restful.Route) {
routes, ok := o.elements[key]
if ok {
routes = append(routes, route)
o.elements[key] = routes
return
}
o.elements[key] = []restful.Route{route}
o.keys = append(o.keys, key)
}
func (o *orderedRouteMap) Do(block func(key string, routes []restful.Route)) {
for _, k := range o.keys {
block(k, o.elements[k])
}
}

View File

@ -1,29 +0,0 @@
package swagger
import (
"testing"
"github.com/emicklei/go-restful"
)
// go test -v -test.run TestOrderedRouteMap ...swagger
func TestOrderedRouteMap(t *testing.T) {
m := newOrderedRouteMap()
r1 := restful.Route{Path: "/r1"}
r2 := restful.Route{Path: "/r2"}
m.Add("a", r1)
m.Add("b", r2)
m.Add("b", r1)
m.Add("d", r2)
m.Add("c", r2)
order := ""
m.Do(func(k string, routes []restful.Route) {
order += k
if len(routes) == 0 {
t.Fail()
}
})
if order != "abdc" {
t.Fail()
}
}

View File

@ -1,42 +0,0 @@
package swagger
import "testing"
type Boat struct {
Length int `json:"-"` // on default, this makes the fields not required
Weight int `json:"-"`
}
// PostBuildModel is from swagger.ModelBuildable
func (b Boat) PostBuildModel(m *Model) *Model {
// override required
m.Required = []string{"Length", "Weight"}
// add model property (just to test is can be added; is this a real usecase?)
extraType := "string"
m.Properties.Put("extra", ModelProperty{
Description: "extra description",
DataTypeFields: DataTypeFields{
Type: &extraType,
},
})
return m
}
func TestCustomPostModelBuilde(t *testing.T) {
testJsonFromStruct(t, Boat{}, `{
"swagger.Boat": {
"id": "swagger.Boat",
"required": [
"Length",
"Weight"
],
"properties": {
"extra": {
"type": "string",
"description": "extra description"
}
}
}
}`)
}

View File

@ -1,185 +0,0 @@
// Package swagger implements the structures of the Swagger
// https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md
package swagger
const swaggerVersion = "1.2"
// 4.3.3 Data Type Fields
type DataTypeFields struct {
Type *string `json:"type,omitempty"` // if Ref not used
Ref *string `json:"$ref,omitempty"` // if Type not used
Format string `json:"format,omitempty"`
DefaultValue Special `json:"defaultValue,omitempty"`
Enum []string `json:"enum,omitempty"`
Minimum string `json:"minimum,omitempty"`
Maximum string `json:"maximum,omitempty"`
Items *Item `json:"items,omitempty"`
UniqueItems *bool `json:"uniqueItems,omitempty"`
}
type Special string
// 4.3.4 Items Object
type Item struct {
Type *string `json:"type,omitempty"`
Ref *string `json:"$ref,omitempty"`
Format string `json:"format,omitempty"`
}
// 5.1 Resource Listing
type ResourceListing struct {
SwaggerVersion string `json:"swaggerVersion"` // e.g 1.2
Apis []Resource `json:"apis"`
ApiVersion string `json:"apiVersion"`
Info Info `json:"info"`
Authorizations []Authorization `json:"authorizations,omitempty"`
}
// 5.1.2 Resource Object
type Resource struct {
Path string `json:"path"` // relative or absolute, must start with /
Description string `json:"description"`
}
// 5.1.3 Info Object
type Info struct {
Title string `json:"title"`
Description string `json:"description"`
TermsOfServiceUrl string `json:"termsOfServiceUrl,omitempty"`
Contact string `json:"contact,omitempty"`
License string `json:"license,omitempty"`
LicenseUrl string `json:"licenseUrl,omitempty"`
}
// 5.1.5
type Authorization struct {
Type string `json:"type"`
PassAs string `json:"passAs"`
Keyname string `json:"keyname"`
Scopes []Scope `json:"scopes"`
GrantTypes []GrantType `json:"grandTypes"`
}
// 5.1.6, 5.2.11
type Scope struct {
// Required. The name of the scope.
Scope string `json:"scope"`
// Recommended. A short description of the scope.
Description string `json:"description"`
}
// 5.1.7
type GrantType struct {
Implicit Implicit `json:"implicit"`
AuthorizationCode AuthorizationCode `json:"authorization_code"`
}
// 5.1.8 Implicit Object
type Implicit struct {
// Required. The login endpoint definition.
loginEndpoint LoginEndpoint `json:"loginEndpoint"`
// An optional alternative name to standard "access_token" OAuth2 parameter.
TokenName string `json:"tokenName"`
}
// 5.1.9 Authorization Code Object
type AuthorizationCode struct {
TokenRequestEndpoint TokenRequestEndpoint `json:"tokenRequestEndpoint"`
TokenEndpoint TokenEndpoint `json:"tokenEndpoint"`
}
// 5.1.10 Login Endpoint Object
type LoginEndpoint struct {
// Required. The URL of the authorization endpoint for the implicit grant flow. The value SHOULD be in a URL format.
Url string `json:"url"`
}
// 5.1.11 Token Request Endpoint Object
type TokenRequestEndpoint struct {
// Required. The URL of the authorization endpoint for the authentication code grant flow. The value SHOULD be in a URL format.
Url string `json:"url"`
// An optional alternative name to standard "client_id" OAuth2 parameter.
ClientIdName string `json:"clientIdName"`
// An optional alternative name to the standard "client_secret" OAuth2 parameter.
ClientSecretName string `json:"clientSecretName"`
}
// 5.1.12 Token Endpoint Object
type TokenEndpoint struct {
// Required. The URL of the token endpoint for the authentication code grant flow. The value SHOULD be in a URL format.
Url string `json:"url"`
// An optional alternative name to standard "access_token" OAuth2 parameter.
TokenName string `json:"tokenName"`
}
// 5.2 API Declaration
type ApiDeclaration struct {
SwaggerVersion string `json:"swaggerVersion"`
ApiVersion string `json:"apiVersion"`
BasePath string `json:"basePath"`
ResourcePath string `json:"resourcePath"` // must start with /
Info Info `json:"info"`
Apis []Api `json:"apis,omitempty"`
Models ModelList `json:"models,omitempty"`
Produces []string `json:"produces,omitempty"`
Consumes []string `json:"consumes,omitempty"`
Authorizations []Authorization `json:"authorizations,omitempty"`
}
// 5.2.2 API Object
type Api struct {
Path string `json:"path"` // relative or absolute, must start with /
Description string `json:"description"`
Operations []Operation `json:"operations,omitempty"`
}
// 5.2.3 Operation Object
type Operation struct {
DataTypeFields
Method string `json:"method"`
Summary string `json:"summary,omitempty"`
Notes string `json:"notes,omitempty"`
Nickname string `json:"nickname"`
Authorizations []Authorization `json:"authorizations,omitempty"`
Parameters []Parameter `json:"parameters"`
ResponseMessages []ResponseMessage `json:"responseMessages,omitempty"` // optional
Produces []string `json:"produces,omitempty"`
Consumes []string `json:"consumes,omitempty"`
Deprecated string `json:"deprecated,omitempty"`
}
// 5.2.4 Parameter Object
type Parameter struct {
DataTypeFields
ParamType string `json:"paramType"` // path,query,body,header,form
Name string `json:"name"`
Description string `json:"description"`
Required bool `json:"required"`
AllowMultiple bool `json:"allowMultiple"`
}
// 5.2.5 Response Message Object
type ResponseMessage struct {
Code int `json:"code"`
Message string `json:"message"`
ResponseModel string `json:"responseModel,omitempty"`
}
// 5.2.6, 5.2.7 Models Object
type Model struct {
Id string `json:"id"`
Description string `json:"description,omitempty"`
Required []string `json:"required,omitempty"`
Properties ModelPropertyList `json:"properties"`
SubTypes []string `json:"subTypes,omitempty"`
Discriminator string `json:"discriminator,omitempty"`
}
// 5.2.8 Properties Object
type ModelProperty struct {
DataTypeFields
Description string `json:"description,omitempty"`
}
// 5.2.10
type Authorizations map[string]Authorization

View File

@ -1,21 +0,0 @@
package swagger
type SwaggerBuilder struct {
SwaggerService
}
func NewSwaggerBuilder(config Config) *SwaggerBuilder {
return &SwaggerBuilder{*newSwaggerService(config)}
}
func (sb SwaggerBuilder) ProduceListing() ResourceListing {
return sb.SwaggerService.produceListing()
}
func (sb SwaggerBuilder) ProduceAllDeclarations() map[string]ApiDeclaration {
return sb.SwaggerService.produceAllDeclarations()
}
func (sb SwaggerBuilder) ProduceDeclarations(route string) (*ApiDeclaration, bool) {
return sb.SwaggerService.produceDeclarations(route)
}

View File

@ -1,318 +0,0 @@
package swagger
import (
"encoding/json"
"testing"
"github.com/emicklei/go-restful"
"github.com/emicklei/go-restful-swagger12/test_package"
)
func TestInfoStruct_Issue231(t *testing.T) {
config := Config{
Info: Info{
Title: "Title",
Description: "Description",
TermsOfServiceUrl: "http://example.com",
Contact: "example@example.com",
License: "License",
LicenseUrl: "http://example.com/license.txt",
},
}
sws := newSwaggerService(config)
str, err := json.MarshalIndent(sws.produceListing(), "", " ")
if err != nil {
t.Fatal(err)
}
compareJson(t, string(str), `
{
"apiVersion": "",
"swaggerVersion": "1.2",
"apis": null,
"info": {
"title": "Title",
"description": "Description",
"termsOfServiceUrl": "http://example.com",
"contact": "example@example.com",
"license": "License",
"licenseUrl": "http://example.com/license.txt"
}
}
`)
}
// go test -v -test.run TestThatMultiplePathsOnRootAreHandled ...swagger
func TestThatMultiplePathsOnRootAreHandled(t *testing.T) {
ws1 := new(restful.WebService)
ws1.Route(ws1.GET("/_ping").To(dummy))
ws1.Route(ws1.GET("/version").To(dummy))
cfg := Config{
WebServicesUrl: "http://here.com",
ApiPath: "/apipath",
WebServices: []*restful.WebService{ws1},
}
sws := newSwaggerService(cfg)
decl := sws.composeDeclaration(ws1, "/")
if got, want := len(decl.Apis), 2; got != want {
t.Errorf("got %v want %v", got, want)
}
}
func TestWriteSamples(t *testing.T) {
ws1 := new(restful.WebService)
ws1.Route(ws1.GET("/object").To(dummy).Writes(test_package.TestStruct{}))
ws1.Route(ws1.GET("/array").To(dummy).Writes([]test_package.TestStruct{}))
ws1.Route(ws1.GET("/object_and_array").To(dummy).Writes(struct{ Abc test_package.TestStruct }{}))
cfg := Config{
WebServicesUrl: "http://here.com",
ApiPath: "/apipath",
WebServices: []*restful.WebService{ws1},
}
sws := newSwaggerService(cfg)
decl := sws.composeDeclaration(ws1, "/")
str, err := json.MarshalIndent(decl.Apis, "", " ")
if err != nil {
t.Fatal(err)
}
compareJson(t, string(str), `
[
{
"path": "/object",
"description": "",
"operations": [
{
"type": "test_package.TestStruct",
"method": "GET",
"nickname": "dummy",
"parameters": []
}
]
},
{
"path": "/array",
"description": "",
"operations": [
{
"type": "array",
"items": {
"$ref": "test_package.TestStruct"
},
"method": "GET",
"nickname": "dummy",
"parameters": []
}
]
},
{
"path": "/object_and_array",
"description": "",
"operations": [
{
"type": "struct { Abc test_package.TestStruct }",
"method": "GET",
"nickname": "dummy",
"parameters": []
}
]
}
]`)
str, err = json.MarshalIndent(decl.Models, "", " ")
if err != nil {
t.Fatal(err)
}
compareJson(t, string(str), `
{
"test_package.TestStruct": {
"id": "test_package.TestStruct",
"required": [
"TestField"
],
"properties": {
"TestField": {
"type": "string"
}
}
},
"||test_package.TestStruct": {
"id": "||test_package.TestStruct",
"properties": {}
},
"struct { Abc test_package.TestStruct }": {
"id": "struct { Abc test_package.TestStruct }",
"required": [
"Abc"
],
"properties": {
"Abc": {
"$ref": "test_package.TestStruct"
}
}
}
}`)
}
func TestRoutesWithCommonPart(t *testing.T) {
ws1 := new(restful.WebService)
ws1.Path("/")
ws1.Route(ws1.GET("/foobar").To(dummy).Writes(test_package.TestStruct{}))
ws1.Route(ws1.HEAD("/foobar").To(dummy).Writes(test_package.TestStruct{}))
ws1.Route(ws1.GET("/foo").To(dummy).Writes([]test_package.TestStruct{}))
ws1.Route(ws1.HEAD("/foo").To(dummy).Writes(test_package.TestStruct{}))
cfg := Config{
WebServicesUrl: "http://here.com",
ApiPath: "/apipath",
WebServices: []*restful.WebService{ws1},
}
sws := newSwaggerService(cfg)
decl := sws.composeDeclaration(ws1, "/foo")
str, err := json.MarshalIndent(decl.Apis, "", " ")
if err != nil {
t.Fatal(err)
}
compareJson(t, string(str), `[
{
"path": "/foo",
"description": "",
"operations": [
{
"type": "array",
"items": {
"$ref": "test_package.TestStruct"
},
"method": "GET",
"nickname": "dummy",
"parameters": []
},
{
"type": "test_package.TestStruct",
"method": "HEAD",
"nickname": "dummy",
"parameters": []
}
]
}
]`)
}
// go test -v -test.run TestServiceToApi ...swagger
func TestServiceToApi(t *testing.T) {
ws := new(restful.WebService)
ws.Path("/tests")
ws.Consumes(restful.MIME_JSON)
ws.Produces(restful.MIME_XML)
ws.Route(ws.GET("/a").To(dummy).Writes(sample{}))
ws.Route(ws.PUT("/b").To(dummy).Writes(sample{}))
ws.Route(ws.POST("/c").To(dummy).Writes(sample{}))
ws.Route(ws.DELETE("/d").To(dummy).Writes(sample{}))
ws.Route(ws.GET("/d").To(dummy).Writes(sample{}))
ws.Route(ws.PUT("/c").To(dummy).Writes(sample{}))
ws.Route(ws.POST("/b").To(dummy).Writes(sample{}))
ws.Route(ws.DELETE("/a").To(dummy).Writes(sample{}))
ws.ApiVersion("1.2.3")
cfg := Config{
WebServicesUrl: "http://here.com",
ApiPath: "/apipath",
WebServices: []*restful.WebService{ws},
PostBuildHandler: func(in *ApiDeclarationList) {},
}
sws := newSwaggerService(cfg)
decl := sws.composeDeclaration(ws, "/tests")
// checks
if decl.ApiVersion != "1.2.3" {
t.Errorf("got %v want %v", decl.ApiVersion, "1.2.3")
}
if decl.BasePath != "http://here.com" {
t.Errorf("got %v want %v", decl.BasePath, "http://here.com")
}
if len(decl.Apis) != 4 {
t.Errorf("got %v want %v", len(decl.Apis), 4)
}
pathOrder := ""
for _, each := range decl.Apis {
pathOrder += each.Path
for _, other := range each.Operations {
pathOrder += other.Method
}
}
if pathOrder != "/tests/aGETDELETE/tests/bPUTPOST/tests/cPOSTPUT/tests/dDELETEGET" {
t.Errorf("got %v want %v", pathOrder, "see test source")
}
}
func dummy(i *restful.Request, o *restful.Response) {}
// go test -v -test.run TestIssue78 ...swagger
type Response struct {
Code int
Users *[]User
Items *[]TestItem
}
type User struct {
Id, Name string
}
type TestItem struct {
Id, Name string
}
// clear && go test -v -test.run TestComposeResponseMessages ...swagger
func TestComposeResponseMessages(t *testing.T) {
responseErrors := map[int]restful.ResponseError{}
responseErrors[400] = restful.ResponseError{Code: 400, Message: "Bad Request", Model: TestItem{}}
route := restful.Route{ResponseErrors: responseErrors}
decl := new(ApiDeclaration)
decl.Models = ModelList{}
msgs := composeResponseMessages(route, decl, &Config{})
if msgs[0].ResponseModel != "swagger.TestItem" {
t.Errorf("got %s want swagger.TestItem", msgs[0].ResponseModel)
}
}
func TestIssue78(t *testing.T) {
sws := newSwaggerService(Config{})
models := new(ModelList)
sws.addModelFromSampleTo(&Operation{}, true, Response{Items: &[]TestItem{}}, models)
model, ok := models.At("swagger.Response")
if !ok {
t.Fatal("missing response model")
}
if "swagger.Response" != model.Id {
t.Fatal("wrong model id:" + model.Id)
}
code, ok := model.Properties.At("Code")
if !ok {
t.Fatal("missing code")
}
if "integer" != *code.Type {
t.Fatal("wrong code type:" + *code.Type)
}
items, ok := model.Properties.At("Items")
if !ok {
t.Fatal("missing items")
}
if "array" != *items.Type {
t.Fatal("wrong items type:" + *items.Type)
}
items_items := items.Items
if items_items == nil {
t.Fatal("missing items->items")
}
ref := items_items.Ref
if ref == nil {
t.Fatal("missing $ref")
}
if *ref != "swagger.TestItem" {
t.Fatal("wrong $ref:" + *ref)
}
}

View File

@ -1,443 +0,0 @@
package swagger
import (
"fmt"
"github.com/emicklei/go-restful"
// "github.com/emicklei/hopwatch"
"net/http"
"reflect"
"sort"
"strings"
"github.com/emicklei/go-restful/log"
)
type SwaggerService struct {
config Config
apiDeclarationMap *ApiDeclarationList
}
func newSwaggerService(config Config) *SwaggerService {
sws := &SwaggerService{
config: config,
apiDeclarationMap: new(ApiDeclarationList)}
// Build all ApiDeclarations
for _, each := range config.WebServices {
rootPath := each.RootPath()
// skip the api service itself
if rootPath != config.ApiPath {
if rootPath == "" || rootPath == "/" {
// use routes
for _, route := range each.Routes() {
entry := staticPathFromRoute(route)
_, exists := sws.apiDeclarationMap.At(entry)
if !exists {
sws.apiDeclarationMap.Put(entry, sws.composeDeclaration(each, entry))
}
}
} else { // use root path
sws.apiDeclarationMap.Put(each.RootPath(), sws.composeDeclaration(each, each.RootPath()))
}
}
}
// if specified then call the PostBuilderHandler
if config.PostBuildHandler != nil {
config.PostBuildHandler(sws.apiDeclarationMap)
}
return sws
}
// LogInfo is the function that is called when this package needs to log. It defaults to log.Printf
var LogInfo = func(format string, v ...interface{}) {
// use the restful package-wide logger
log.Printf(format, v...)
}
// InstallSwaggerService add the WebService that provides the API documentation of all services
// conform the Swagger documentation specifcation. (https://github.com/wordnik/swagger-core/wiki).
func InstallSwaggerService(aSwaggerConfig Config) {
RegisterSwaggerService(aSwaggerConfig, restful.DefaultContainer)
}
// RegisterSwaggerService add the WebService that provides the API documentation of all services
// conform the Swagger documentation specifcation. (https://github.com/wordnik/swagger-core/wiki).
func RegisterSwaggerService(config Config, wsContainer *restful.Container) {
sws := newSwaggerService(config)
ws := new(restful.WebService)
ws.Path(config.ApiPath)
ws.Produces(restful.MIME_JSON)
if config.DisableCORS {
ws.Filter(enableCORS)
}
ws.Route(ws.GET("/").To(sws.getListing))
ws.Route(ws.GET("/{a}").To(sws.getDeclarations))
ws.Route(ws.GET("/{a}/{b}").To(sws.getDeclarations))
ws.Route(ws.GET("/{a}/{b}/{c}").To(sws.getDeclarations))
ws.Route(ws.GET("/{a}/{b}/{c}/{d}").To(sws.getDeclarations))
ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}").To(sws.getDeclarations))
ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}/{f}").To(sws.getDeclarations))
ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}/{f}/{g}").To(sws.getDeclarations))
LogInfo("[restful/swagger] listing is available at %v%v", config.WebServicesUrl, config.ApiPath)
wsContainer.Add(ws)
// Check paths for UI serving
if config.StaticHandler == nil && config.SwaggerFilePath != "" && config.SwaggerPath != "" {
swaggerPathSlash := config.SwaggerPath
// path must end with slash /
if "/" != config.SwaggerPath[len(config.SwaggerPath)-1:] {
LogInfo("[restful/swagger] use corrected SwaggerPath ; must end with slash (/)")
swaggerPathSlash += "/"
}
LogInfo("[restful/swagger] %v%v is mapped to folder %v", config.WebServicesUrl, swaggerPathSlash, config.SwaggerFilePath)
wsContainer.Handle(swaggerPathSlash, http.StripPrefix(swaggerPathSlash, http.FileServer(http.Dir(config.SwaggerFilePath))))
//if we define a custom static handler use it
} else if config.StaticHandler != nil && config.SwaggerPath != "" {
swaggerPathSlash := config.SwaggerPath
// path must end with slash /
if "/" != config.SwaggerPath[len(config.SwaggerPath)-1:] {
LogInfo("[restful/swagger] use corrected SwaggerFilePath ; must end with slash (/)")
swaggerPathSlash += "/"
}
LogInfo("[restful/swagger] %v%v is mapped to custom Handler %T", config.WebServicesUrl, swaggerPathSlash, config.StaticHandler)
wsContainer.Handle(swaggerPathSlash, config.StaticHandler)
} else {
LogInfo("[restful/swagger] Swagger(File)Path is empty ; no UI is served")
}
}
func staticPathFromRoute(r restful.Route) string {
static := r.Path
bracket := strings.Index(static, "{")
if bracket <= 1 { // result cannot be empty
return static
}
if bracket != -1 {
static = r.Path[:bracket]
}
if strings.HasSuffix(static, "/") {
return static[:len(static)-1]
} else {
return static
}
}
func enableCORS(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
if origin := req.HeaderParameter(restful.HEADER_Origin); origin != "" {
// prevent duplicate header
if len(resp.Header().Get(restful.HEADER_AccessControlAllowOrigin)) == 0 {
resp.AddHeader(restful.HEADER_AccessControlAllowOrigin, origin)
}
}
chain.ProcessFilter(req, resp)
}
func (sws SwaggerService) getListing(req *restful.Request, resp *restful.Response) {
listing := sws.produceListing()
resp.WriteAsJson(listing)
}
func (sws SwaggerService) produceListing() ResourceListing {
listing := ResourceListing{SwaggerVersion: swaggerVersion, ApiVersion: sws.config.ApiVersion, Info: sws.config.Info}
sws.apiDeclarationMap.Do(func(k string, v ApiDeclaration) {
ref := Resource{Path: k}
if len(v.Apis) > 0 { // use description of first (could still be empty)
ref.Description = v.Apis[0].Description
}
listing.Apis = append(listing.Apis, ref)
})
return listing
}
func (sws SwaggerService) getDeclarations(req *restful.Request, resp *restful.Response) {
decl, ok := sws.produceDeclarations(composeRootPath(req))
if !ok {
resp.WriteErrorString(http.StatusNotFound, "ApiDeclaration not found")
return
}
// unless WebServicesUrl is given
if len(sws.config.WebServicesUrl) == 0 {
// update base path from the actual request
// TODO how to detect https? assume http for now
var host string
// X-Forwarded-Host or Host or Request.Host
hostvalues, ok := req.Request.Header["X-Forwarded-Host"] // apache specific?
if !ok || len(hostvalues) == 0 {
forwarded, ok := req.Request.Header["Host"] // without reverse-proxy
if !ok || len(forwarded) == 0 {
// fallback to Host field
host = req.Request.Host
} else {
host = forwarded[0]
}
} else {
host = hostvalues[0]
}
// inspect Referer for the scheme (http vs https)
scheme := "http"
if referer := req.Request.Header["Referer"]; len(referer) > 0 {
if strings.HasPrefix(referer[0], "https") {
scheme = "https"
}
}
decl.BasePath = fmt.Sprintf("%s://%s", scheme, host)
}
resp.WriteAsJson(decl)
}
func (sws SwaggerService) produceAllDeclarations() map[string]ApiDeclaration {
decls := map[string]ApiDeclaration{}
sws.apiDeclarationMap.Do(func(k string, v ApiDeclaration) {
decls[k] = v
})
return decls
}
func (sws SwaggerService) produceDeclarations(route string) (*ApiDeclaration, bool) {
decl, ok := sws.apiDeclarationMap.At(route)
if !ok {
return nil, false
}
decl.BasePath = sws.config.WebServicesUrl
return &decl, true
}
// composeDeclaration uses all routes and parameters to create a ApiDeclaration
func (sws SwaggerService) composeDeclaration(ws *restful.WebService, pathPrefix string) ApiDeclaration {
decl := ApiDeclaration{
SwaggerVersion: swaggerVersion,
BasePath: sws.config.WebServicesUrl,
ResourcePath: pathPrefix,
Models: ModelList{},
ApiVersion: ws.Version()}
// collect any path parameters
rootParams := []Parameter{}
for _, param := range ws.PathParameters() {
rootParams = append(rootParams, asSwaggerParameter(param.Data()))
}
// aggregate by path
pathToRoutes := newOrderedRouteMap()
for _, other := range ws.Routes() {
if strings.HasPrefix(other.Path, pathPrefix) {
if len(pathPrefix) > 1 && len(other.Path) > len(pathPrefix) && other.Path[len(pathPrefix)] != '/' {
continue
}
pathToRoutes.Add(other.Path, other)
}
}
pathToRoutes.Do(func(path string, routes []restful.Route) {
api := Api{Path: strings.TrimSuffix(withoutWildcard(path), "/"), Description: ws.Documentation()}
voidString := "void"
for _, route := range routes {
operation := Operation{
Method: route.Method,
Summary: route.Doc,
Notes: route.Notes,
// Type gets overwritten if there is a write sample
DataTypeFields: DataTypeFields{Type: &voidString},
Parameters: []Parameter{},
Nickname: route.Operation,
ResponseMessages: composeResponseMessages(route, &decl, &sws.config)}
operation.Consumes = route.Consumes
operation.Produces = route.Produces
// share root params if any
for _, swparam := range rootParams {
operation.Parameters = append(operation.Parameters, swparam)
}
// route specific params
for _, param := range route.ParameterDocs {
operation.Parameters = append(operation.Parameters, asSwaggerParameter(param.Data()))
}
sws.addModelsFromRouteTo(&operation, route, &decl)
api.Operations = append(api.Operations, operation)
}
decl.Apis = append(decl.Apis, api)
})
return decl
}
func withoutWildcard(path string) string {
if strings.HasSuffix(path, ":*}") {
return path[0:len(path)-3] + "}"
}
return path
}
// composeResponseMessages takes the ResponseErrors (if any) and creates ResponseMessages from them.
func composeResponseMessages(route restful.Route, decl *ApiDeclaration, config *Config) (messages []ResponseMessage) {
if route.ResponseErrors == nil {
return messages
}
// sort by code
codes := sort.IntSlice{}
for code := range route.ResponseErrors {
codes = append(codes, code)
}
codes.Sort()
for _, code := range codes {
each := route.ResponseErrors[code]
message := ResponseMessage{
Code: code,
Message: each.Message,
}
if each.Model != nil {
st := reflect.TypeOf(each.Model)
isCollection, st := detectCollectionType(st)
// collection cannot be in responsemodel
if !isCollection {
modelName := modelBuilder{}.keyFrom(st)
modelBuilder{Models: &decl.Models, Config: config}.addModel(st, "")
message.ResponseModel = modelName
}
}
messages = append(messages, message)
}
return
}
// addModelsFromRoute takes any read or write sample from the Route and creates a Swagger model from it.
func (sws SwaggerService) addModelsFromRouteTo(operation *Operation, route restful.Route, decl *ApiDeclaration) {
if route.ReadSample != nil {
sws.addModelFromSampleTo(operation, false, route.ReadSample, &decl.Models)
}
if route.WriteSample != nil {
sws.addModelFromSampleTo(operation, true, route.WriteSample, &decl.Models)
}
}
func detectCollectionType(st reflect.Type) (bool, reflect.Type) {
isCollection := false
if st.Kind() == reflect.Slice || st.Kind() == reflect.Array {
st = st.Elem()
isCollection = true
} else {
if st.Kind() == reflect.Ptr {
if st.Elem().Kind() == reflect.Slice || st.Elem().Kind() == reflect.Array {
st = st.Elem().Elem()
isCollection = true
}
}
}
return isCollection, st
}
// addModelFromSample creates and adds (or overwrites) a Model from a sample resource
func (sws SwaggerService) addModelFromSampleTo(operation *Operation, isResponse bool, sample interface{}, models *ModelList) {
mb := modelBuilder{Models: models, Config: &sws.config}
if isResponse {
sampleType, items := asDataType(sample, &sws.config)
operation.Type = sampleType
operation.Items = items
}
mb.addModelFrom(sample)
}
func asSwaggerParameter(param restful.ParameterData) Parameter {
return Parameter{
DataTypeFields: DataTypeFields{
Type: &param.DataType,
Format: asFormat(param.DataType, param.DataFormat),
DefaultValue: Special(param.DefaultValue),
},
Name: param.Name,
Description: param.Description,
ParamType: asParamType(param.Kind),
Required: param.Required}
}
// Between 1..7 path parameters is supported
func composeRootPath(req *restful.Request) string {
path := "/" + req.PathParameter("a")
b := req.PathParameter("b")
if b == "" {
return path
}
path = path + "/" + b
c := req.PathParameter("c")
if c == "" {
return path
}
path = path + "/" + c
d := req.PathParameter("d")
if d == "" {
return path
}
path = path + "/" + d
e := req.PathParameter("e")
if e == "" {
return path
}
path = path + "/" + e
f := req.PathParameter("f")
if f == "" {
return path
}
path = path + "/" + f
g := req.PathParameter("g")
if g == "" {
return path
}
return path + "/" + g
}
func asFormat(dataType string, dataFormat string) string {
if dataFormat != "" {
return dataFormat
}
return "" // TODO
}
func asParamType(kind int) string {
switch {
case kind == restful.PathParameterKind:
return "path"
case kind == restful.QueryParameterKind:
return "query"
case kind == restful.BodyParameterKind:
return "body"
case kind == restful.HeaderParameterKind:
return "header"
case kind == restful.FormParameterKind:
return "form"
}
return ""
}
func asDataType(any interface{}, config *Config) (*string, *Item) {
// If it's not a collection, return the suggested model name
st := reflect.TypeOf(any)
isCollection, st := detectCollectionType(st)
modelName := modelBuilder{}.keyFrom(st)
// if it's not a collection we are done
if !isCollection {
return &modelName, nil
}
// XXX: This is not very elegant
// We create an Item object referring to the given model
models := ModelList{}
mb := modelBuilder{Models: &models, Config: config}
mb.addModelFrom(any)
elemTypeName := mb.getElementTypeName(modelName, "", st)
item := new(Item)
if mb.isPrimitiveType(elemTypeName) {
mapped := mb.jsonSchemaType(elemTypeName)
item.Type = &mapped
} else {
item.Ref = &elemTypeName
}
tmp := "array"
return &tmp, item
}

View File

@ -1,5 +0,0 @@
package test_package
type TestStruct struct {
TestField string
}

View File

@ -1,86 +0,0 @@
package swagger
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"strings"
"testing"
)
func testJsonFromStructWithConfig(t *testing.T, sample interface{}, expectedJson string, config *Config) bool {
m := modelsFromStructWithConfig(sample, config)
data, _ := json.MarshalIndent(m, " ", " ")
return compareJson(t, string(data), expectedJson)
}
func modelsFromStructWithConfig(sample interface{}, config *Config) *ModelList {
models := new(ModelList)
builder := modelBuilder{Models: models, Config: config}
builder.addModelFrom(sample)
return models
}
func testJsonFromStruct(t *testing.T, sample interface{}, expectedJson string) bool {
return testJsonFromStructWithConfig(t, sample, expectedJson, &Config{})
}
func modelsFromStruct(sample interface{}) *ModelList {
return modelsFromStructWithConfig(sample, &Config{})
}
func compareJson(t *testing.T, actualJsonAsString string, expectedJsonAsString string) bool {
success := false
var actualMap map[string]interface{}
json.Unmarshal([]byte(actualJsonAsString), &actualMap)
var expectedMap map[string]interface{}
err := json.Unmarshal([]byte(expectedJsonAsString), &expectedMap)
if err != nil {
var actualArray []interface{}
json.Unmarshal([]byte(actualJsonAsString), &actualArray)
var expectedArray []interface{}
err := json.Unmarshal([]byte(expectedJsonAsString), &expectedArray)
success = reflect.DeepEqual(actualArray, expectedArray)
if err != nil {
t.Fatalf("Unparsable expected JSON: %s, actual: %v, expected: %v", err, actualJsonAsString, expectedJsonAsString)
}
} else {
success = reflect.DeepEqual(actualMap, expectedMap)
}
if !success {
t.Log("---- expected -----")
t.Log(withLineNumbers(expectedJsonAsString))
t.Log("---- actual -----")
t.Log(withLineNumbers(actualJsonAsString))
t.Log("---- raw -----")
t.Log(actualJsonAsString)
t.Error("there are differences")
return false
}
return true
}
func indexOfNonMatchingLine(actual, expected string) int {
a := strings.Split(actual, "\n")
e := strings.Split(expected, "\n")
size := len(a)
if len(e) < len(a) {
size = len(e)
}
for i := 0; i < size; i++ {
if a[i] != e[i] {
return i
}
}
return -1
}
func withLineNumbers(content string) string {
var buffer bytes.Buffer
lines := strings.Split(content, "\n")
for i, each := range lines {
buffer.WriteString(fmt.Sprintf("%d:%s\n", i, each))
}
return buffer.String()
}

View File

@ -1,51 +0,0 @@
package restful
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
)
func setupCurly(container *Container) []string {
wsCount := 26
rtCount := 26
urisCurly := []string{}
container.Router(CurlyRouter{})
for i := 0; i < wsCount; i++ {
root := fmt.Sprintf("/%s/{%s}/", string(i+97), string(i+97))
ws := new(WebService).Path(root)
for j := 0; j < rtCount; j++ {
sub := fmt.Sprintf("/%s2/{%s2}", string(j+97), string(j+97))
ws.Route(ws.GET(sub).Consumes("application/xml").Produces("application/xml").To(echoCurly))
}
container.Add(ws)
for _, each := range ws.Routes() {
urisCurly = append(urisCurly, "http://bench.com"+each.Path)
}
}
return urisCurly
}
func echoCurly(req *Request, resp *Response) {}
func BenchmarkManyCurly(b *testing.B) {
container := NewContainer()
urisCurly := setupCurly(container)
b.ResetTimer()
for t := 0; t < b.N; t++ {
for r := 0; r < 1000; r++ {
for _, each := range urisCurly {
sendNoReturnTo(each, container, t)
}
}
}
}
func sendNoReturnTo(address string, container *Container, t int) {
httpRequest, _ := http.NewRequest("GET", address, nil)
httpRequest.Header.Set("Accept", "application/xml")
httpWriter := httptest.NewRecorder()
container.dispatch(httpWriter, httpRequest)
}

View File

@ -1,43 +0,0 @@
package restful
import (
"fmt"
"io"
"testing"
)
var uris = []string{}
func setup(container *Container) {
wsCount := 26
rtCount := 26
for i := 0; i < wsCount; i++ {
root := fmt.Sprintf("/%s/{%s}/", string(i+97), string(i+97))
ws := new(WebService).Path(root)
for j := 0; j < rtCount; j++ {
sub := fmt.Sprintf("/%s2/{%s2}", string(j+97), string(j+97))
ws.Route(ws.GET(sub).To(echo))
}
container.Add(ws)
for _, each := range ws.Routes() {
uris = append(uris, "http://bench.com"+each.Path)
}
}
}
func echo(req *Request, resp *Response) {
io.WriteString(resp.ResponseWriter, "echo")
}
func BenchmarkMany(b *testing.B) {
container := NewContainer()
setup(container)
b.ResetTimer()
for t := 0; t < b.N; t++ {
for _, each := range uris {
// println(each)
sendItTo(each, container)
}
}
}

View File

@ -1,125 +0,0 @@
package restful
import (
"bytes"
"compress/gzip"
"compress/zlib"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
// go test -v -test.run TestGzip ...restful
func TestGzip(t *testing.T) {
EnableContentEncoding = true
httpRequest, _ := http.NewRequest("GET", "/test", nil)
httpRequest.Header.Set("Accept-Encoding", "gzip,deflate")
httpWriter := httptest.NewRecorder()
wanted, encoding := wantsCompressedResponse(httpRequest)
if !wanted {
t.Fatal("should accept gzip")
}
if encoding != "gzip" {
t.Fatal("expected gzip")
}
c, err := NewCompressingResponseWriter(httpWriter, encoding)
if err != nil {
t.Fatal(err.Error())
}
c.Write([]byte("Hello World"))
c.Close()
if httpWriter.Header().Get("Content-Encoding") != "gzip" {
t.Fatal("Missing gzip header")
}
reader, err := gzip.NewReader(httpWriter.Body)
if err != nil {
t.Fatal(err.Error())
}
data, err := ioutil.ReadAll(reader)
if err != nil {
t.Fatal(err.Error())
}
if got, want := string(data), "Hello World"; got != want {
t.Errorf("got %v want %v", got, want)
}
}
func TestDeflate(t *testing.T) {
EnableContentEncoding = true
httpRequest, _ := http.NewRequest("GET", "/test", nil)
httpRequest.Header.Set("Accept-Encoding", "deflate,gzip")
httpWriter := httptest.NewRecorder()
wanted, encoding := wantsCompressedResponse(httpRequest)
if !wanted {
t.Fatal("should accept deflate")
}
if encoding != "deflate" {
t.Fatal("expected deflate")
}
c, err := NewCompressingResponseWriter(httpWriter, encoding)
if err != nil {
t.Fatal(err.Error())
}
c.Write([]byte("Hello World"))
c.Close()
if httpWriter.Header().Get("Content-Encoding") != "deflate" {
t.Fatal("Missing deflate header")
}
reader, err := zlib.NewReader(httpWriter.Body)
if err != nil {
t.Fatal(err.Error())
}
data, err := ioutil.ReadAll(reader)
if err != nil {
t.Fatal(err.Error())
}
if got, want := string(data), "Hello World"; got != want {
t.Errorf("got %v want %v", got, want)
}
}
func TestGzipDecompressRequestBody(t *testing.T) {
b := new(bytes.Buffer)
w := newGzipWriter()
w.Reset(b)
io.WriteString(w, `{"msg":"hi"}`)
w.Flush()
w.Close()
req := new(Request)
httpRequest, _ := http.NewRequest("GET", "/", bytes.NewReader(b.Bytes()))
httpRequest.Header.Set("Content-Type", "application/json")
httpRequest.Header.Set("Content-Encoding", "gzip")
req.Request = httpRequest
doc := make(map[string]interface{})
req.ReadEntity(&doc)
if got, want := doc["msg"], "hi"; got != want {
t.Errorf("got %v want %v", got, want)
}
}
func TestZlibDecompressRequestBody(t *testing.T) {
b := new(bytes.Buffer)
w := newZlibWriter()
w.Reset(b)
io.WriteString(w, `{"msg":"hi"}`)
w.Flush()
w.Close()
req := new(Request)
httpRequest, _ := http.NewRequest("GET", "/", bytes.NewReader(b.Bytes()))
httpRequest.Header.Set("Content-Type", "application/json")
httpRequest.Header.Set("Content-Encoding", "deflate")
req.Request = httpRequest
doc := make(map[string]interface{})
req.ReadEntity(&doc)
if got, want := doc["msg"], "hi"; got != want {
t.Errorf("got %v want %v", got, want)
}
}

View File

@ -1,83 +0,0 @@
package restful
import (
"net/http"
"net/http/httptest"
"testing"
)
// go test -v -test.run TestContainer_computeAllowedMethods ...restful
func TestContainer_computeAllowedMethods(t *testing.T) {
wc := NewContainer()
ws1 := new(WebService).Path("/users")
ws1.Route(ws1.GET("{i}").To(dummy))
ws1.Route(ws1.POST("{i}").To(dummy))
wc.Add(ws1)
httpRequest, _ := http.NewRequest("GET", "http://api.his.com/users/1", nil)
rreq := Request{Request: httpRequest}
m := wc.computeAllowedMethods(&rreq)
if len(m) != 2 {
t.Errorf("got %d expected 2 methods, %v", len(m), m)
}
}
func TestContainer_HandleWithFilter(t *testing.T) {
prefilterCalled := false
postfilterCalled := false
httpHandlerCalled := false
wc := NewContainer()
wc.Filter(func(request *Request, response *Response, chain *FilterChain) {
prefilterCalled = true
chain.ProcessFilter(request, response)
})
wc.HandleWithFilter("/", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
httpHandlerCalled = true
w.Write([]byte("ok"))
}))
wc.Filter(func(request *Request, response *Response, chain *FilterChain) {
postfilterCalled = true
chain.ProcessFilter(request, response)
})
recorder := httptest.NewRecorder()
request, _ := http.NewRequest("GET", "/", nil)
wc.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Errorf("unexpected code %d", recorder.Code)
}
if recorder.Body.String() != "ok" {
t.Errorf("unexpected body %s", recorder.Body.String())
}
if !prefilterCalled {
t.Errorf("filter added before calling HandleWithFilter wasn't called")
}
if !postfilterCalled {
t.Errorf("filter added after calling HandleWithFilter wasn't called")
}
if !httpHandlerCalled {
t.Errorf("handler added by calling HandleWithFilter wasn't called")
}
}
func TestContainerAddAndRemove(t *testing.T) {
ws1 := new(WebService).Path("/")
ws2 := new(WebService).Path("/users")
wc := NewContainer()
wc.Add(ws1)
wc.Add(ws2)
wc.Remove(ws2)
if len(wc.webServices) != 1 {
t.Errorf("expected one webservices")
}
if !wc.isRegisteredOnRoot {
t.Errorf("expected on root registered")
}
wc.Remove(ws1)
if len(wc.webServices) > 0 {
t.Errorf("expected zero webservices")
}
if wc.isRegisteredOnRoot {
t.Errorf("expected not on root registered")
}
}

View File

@ -1,129 +0,0 @@
package restful
import (
"net/http"
"net/http/httptest"
"testing"
)
// go test -v -test.run TestCORSFilter_Preflight ...restful
// http://www.html5rocks.com/en/tutorials/cors/#toc-handling-a-not-so-simple-request
func TestCORSFilter_Preflight(t *testing.T) {
tearDown()
ws := new(WebService)
ws.Route(ws.PUT("/cors").To(dummy))
Add(ws)
cors := CrossOriginResourceSharing{
ExposeHeaders: []string{"X-Custom-Header"},
AllowedHeaders: []string{"X-Custom-Header", "X-Additional-Header"},
CookiesAllowed: true,
Container: DefaultContainer}
Filter(cors.Filter)
// Preflight
httpRequest, _ := http.NewRequest("OPTIONS", "http://api.alice.com/cors", nil)
httpRequest.Method = "OPTIONS"
httpRequest.Header.Set(HEADER_Origin, "http://api.bob.com")
httpRequest.Header.Set(HEADER_AccessControlRequestMethod, "PUT")
httpRequest.Header.Set(HEADER_AccessControlRequestHeaders, "X-Custom-Header, X-Additional-Header")
httpWriter := httptest.NewRecorder()
DefaultContainer.Dispatch(httpWriter, httpRequest)
actual := httpWriter.Header().Get(HEADER_AccessControlAllowOrigin)
if "http://api.bob.com" != actual {
t.Fatal("expected: http://api.bob.com but got:" + actual)
}
actual = httpWriter.Header().Get(HEADER_AccessControlAllowMethods)
if "PUT" != actual {
t.Fatal("expected: PUT but got:" + actual)
}
actual = httpWriter.Header().Get(HEADER_AccessControlAllowHeaders)
if "X-Custom-Header, X-Additional-Header" != actual {
t.Fatal("expected: X-Custom-Header, X-Additional-Header but got:" + actual)
}
if !cors.isOriginAllowed("somewhere") {
t.Fatal("origin expected to be allowed")
}
cors.AllowedDomains = []string{"overthere.com"}
if cors.isOriginAllowed("somewhere") {
t.Fatal("origin [somewhere] expected NOT to be allowed")
}
if !cors.isOriginAllowed("overthere.com") {
t.Fatal("origin [overthere] expected to be allowed")
}
}
// go test -v -test.run TestCORSFilter_Actual ...restful
// http://www.html5rocks.com/en/tutorials/cors/#toc-handling-a-not-so-simple-request
func TestCORSFilter_Actual(t *testing.T) {
tearDown()
ws := new(WebService)
ws.Route(ws.PUT("/cors").To(dummy))
Add(ws)
cors := CrossOriginResourceSharing{
ExposeHeaders: []string{"X-Custom-Header"},
AllowedHeaders: []string{"X-Custom-Header", "X-Additional-Header"},
CookiesAllowed: true,
Container: DefaultContainer}
Filter(cors.Filter)
// Actual
httpRequest, _ := http.NewRequest("PUT", "http://api.alice.com/cors", nil)
httpRequest.Header.Set(HEADER_Origin, "http://api.bob.com")
httpRequest.Header.Set("X-Custom-Header", "value")
httpWriter := httptest.NewRecorder()
DefaultContainer.Dispatch(httpWriter, httpRequest)
actual := httpWriter.Header().Get(HEADER_AccessControlAllowOrigin)
if "http://api.bob.com" != actual {
t.Fatal("expected: http://api.bob.com but got:" + actual)
}
if httpWriter.Body.String() != "dummy" {
t.Fatal("expected: dummy but got:" + httpWriter.Body.String())
}
}
var allowedDomainInput = []struct {
domains []string
origin string
allowed bool
}{
{[]string{}, "http://anything.com", true},
{[]string{"example.com"}, "example.com", true},
{[]string{"example.com"}, "not-allowed", false},
{[]string{"not-matching.com", "example.com"}, "example.com", true},
{[]string{".*"}, "example.com", true},
}
// go test -v -test.run TestCORSFilter_AllowedDomains ...restful
func TestCORSFilter_AllowedDomains(t *testing.T) {
for _, each := range allowedDomainInput {
tearDown()
ws := new(WebService)
ws.Route(ws.PUT("/cors").To(dummy))
Add(ws)
cors := CrossOriginResourceSharing{
AllowedDomains: each.domains,
CookiesAllowed: true,
Container: DefaultContainer}
Filter(cors.Filter)
httpRequest, _ := http.NewRequest("PUT", "http://api.his.com/cors", nil)
httpRequest.Header.Set(HEADER_Origin, each.origin)
httpWriter := httptest.NewRecorder()
DefaultContainer.Dispatch(httpWriter, httpRequest)
actual := httpWriter.Header().Get(HEADER_AccessControlAllowOrigin)
if actual != each.origin && each.allowed {
t.Fatal("expected to be accepted")
}
if actual == each.origin && !each.allowed {
t.Fatal("did not expect to be accepted")
}
}
}

View File

@ -1,231 +0,0 @@
package restful
import (
"io"
"net/http"
"testing"
)
var requestPaths = []struct {
// url with path (1) is handled by service with root (2) and remainder has value final (3)
path, root string
}{
{"/", "/"},
{"/p", "/p"},
{"/p/x", "/p/{q}"},
{"/q/x", "/q"},
{"/p/x/", "/p/{q}"},
{"/p/x/y", "/p/{q}"},
{"/q/x/y", "/q"},
{"/z/q", "/{p}/q"},
{"/a/b/c/q", "/"},
}
// go test -v -test.run TestCurlyDetectWebService ...restful
func TestCurlyDetectWebService(t *testing.T) {
ws1 := new(WebService).Path("/")
ws2 := new(WebService).Path("/p")
ws3 := new(WebService).Path("/q")
ws4 := new(WebService).Path("/p/q")
ws5 := new(WebService).Path("/p/{q}")
ws7 := new(WebService).Path("/{p}/q")
var wss = []*WebService{ws1, ws2, ws3, ws4, ws5, ws7}
for _, each := range wss {
t.Logf("path=%s,toks=%v\n", each.pathExpr.Source, each.pathExpr.tokens)
}
router := CurlyRouter{}
ok := true
for i, fixture := range requestPaths {
requestTokens := tokenizePath(fixture.path)
who := router.detectWebService(requestTokens, wss)
if who != nil && who.RootPath() != fixture.root {
t.Logf("[line:%v] Unexpected dispatcher, expected:%v, actual:%v", i, fixture.root, who.RootPath())
ok = false
}
}
if !ok {
t.Fail()
}
}
var serviceDetects = []struct {
path string
found bool
root string
}{
{"/a/b", true, "/{p}/{q}/{r}"},
{"/p/q", true, "/p/q"},
{"/q/p", true, "/q"},
{"/", true, "/"},
{"/p/q/r", true, "/p/q"},
}
// go test -v -test.run Test_detectWebService ...restful
func Test_detectWebService(t *testing.T) {
router := CurlyRouter{}
ws1 := new(WebService).Path("/")
ws2 := new(WebService).Path("/p")
ws3 := new(WebService).Path("/q")
ws4 := new(WebService).Path("/p/q")
ws5 := new(WebService).Path("/p/{q}")
ws6 := new(WebService).Path("/p/{q}/")
ws7 := new(WebService).Path("/{p}/q")
ws8 := new(WebService).Path("/{p}/{q}/{r}")
var wss = []*WebService{ws8, ws7, ws6, ws5, ws4, ws3, ws2, ws1}
for _, fix := range serviceDetects {
requestPath := fix.path
requestTokens := tokenizePath(requestPath)
for _, ws := range wss {
serviceTokens := ws.pathExpr.tokens
matches, score := router.computeWebserviceScore(requestTokens, serviceTokens)
t.Logf("req=%s,toks:%v,ws=%s,toks:%v,score=%d,matches=%v", requestPath, requestTokens, ws.RootPath(), serviceTokens, score, matches)
}
best := router.detectWebService(requestTokens, wss)
if best != nil {
if fix.found {
t.Logf("best=%s", best.RootPath())
} else {
t.Fatalf("should have found:%s", fix.root)
}
}
}
}
var routeMatchers = []struct {
route string
path string
matches bool
paramCount int
staticCount int
}{
// route, request-path
{"/a", "/a", true, 0, 1},
{"/a", "/b", false, 0, 0},
{"/a", "/b", false, 0, 0},
{"/a/{b}/c/", "/a/2/c", true, 1, 2},
{"/{a}/{b}/{c}/", "/a/b", false, 0, 0},
{"/{x:*}", "/", false, 0, 0},
{"/{x:*}", "/a", true, 1, 0},
{"/{x:*}", "/a/b", true, 1, 0},
{"/a/{x:*}", "/a/b", true, 1, 1},
{"/a/{x:[A-Z][A-Z]}", "/a/ZX", true, 1, 1},
{"/basepath/{resource:*}", "/basepath/some/other/location/test.xml", true, 1, 1},
}
// clear && go test -v -test.run Test_matchesRouteByPathTokens ...restful
func Test_matchesRouteByPathTokens(t *testing.T) {
router := CurlyRouter{}
for i, each := range routeMatchers {
routeToks := tokenizePath(each.route)
reqToks := tokenizePath(each.path)
matches, pCount, sCount := router.matchesRouteByPathTokens(routeToks, reqToks)
if matches != each.matches {
t.Fatalf("[%d] unexpected matches outcome route:%s, path:%s, matches:%v", i, each.route, each.path, matches)
}
if pCount != each.paramCount {
t.Fatalf("[%d] unexpected paramCount got:%d want:%d ", i, pCount, each.paramCount)
}
if sCount != each.staticCount {
t.Fatalf("[%d] unexpected staticCount got:%d want:%d ", i, sCount, each.staticCount)
}
}
}
// clear && go test -v -test.run TestExtractParameters_Wildcard1 ...restful
func TestExtractParameters_Wildcard1(t *testing.T) {
params := doExtractParams("/fixed/{var:*}", 2, "/fixed/remainder", t)
if params["var"] != "remainder" {
t.Errorf("parameter mismatch var: %s", params["var"])
}
}
// clear && go test -v -test.run TestExtractParameters_Wildcard2 ...restful
func TestExtractParameters_Wildcard2(t *testing.T) {
params := doExtractParams("/fixed/{var:*}", 2, "/fixed/remain/der", t)
if params["var"] != "remain/der" {
t.Errorf("parameter mismatch var: %s", params["var"])
}
}
// clear && go test -v -test.run TestExtractParameters_Wildcard3 ...restful
func TestExtractParameters_Wildcard3(t *testing.T) {
params := doExtractParams("/static/{var:*}", 2, "/static/test/sub/hi.html", t)
if params["var"] != "test/sub/hi.html" {
t.Errorf("parameter mismatch var: %s", params["var"])
}
}
// clear && go test -v -test.run TestCurly_ISSUE_34 ...restful
func TestCurly_ISSUE_34(t *testing.T) {
ws1 := new(WebService).Path("/")
ws1.Route(ws1.GET("/{type}/{id}").To(curlyDummy))
ws1.Route(ws1.GET("/network/{id}").To(curlyDummy))
croutes := CurlyRouter{}.selectRoutes(ws1, tokenizePath("/network/12"))
if len(croutes) != 2 {
t.Fatal("expected 2 routes")
}
if got, want := croutes[0].route.Path, "/network/{id}"; got != want {
t.Errorf("got %v want %v", got, want)
}
}
// clear && go test -v -test.run TestCurly_ISSUE_34_2 ...restful
func TestCurly_ISSUE_34_2(t *testing.T) {
ws1 := new(WebService)
ws1.Route(ws1.GET("/network/{id}").To(curlyDummy))
ws1.Route(ws1.GET("/{type}/{id}").To(curlyDummy))
croutes := CurlyRouter{}.selectRoutes(ws1, tokenizePath("/network/12"))
if len(croutes) != 2 {
t.Fatal("expected 2 routes")
}
if got, want := croutes[0].route.Path, "/network/{id}"; got != want {
t.Errorf("got %v want %v", got, want)
}
}
// clear && go test -v -test.run TestCurly_JsonHtml ...restful
func TestCurly_JsonHtml(t *testing.T) {
ws1 := new(WebService)
ws1.Path("/")
ws1.Route(ws1.GET("/some.html").To(curlyDummy).Consumes("*/*").Produces("text/html"))
req, _ := http.NewRequest("GET", "/some.html", nil)
req.Header.Set("Accept", "application/json")
_, route, err := CurlyRouter{}.SelectRoute([]*WebService{ws1}, req)
if err == nil {
t.Error("error expected")
}
if route != nil {
t.Error("no route expected")
}
}
// go test -v -test.run TestCurly_ISSUE_137 ...restful
func TestCurly_ISSUE_137(t *testing.T) {
ws1 := new(WebService)
ws1.Route(ws1.GET("/hello").To(curlyDummy))
ws1.Path("/")
req, _ := http.NewRequest("GET", "/", nil)
_, route, _ := CurlyRouter{}.SelectRoute([]*WebService{ws1}, req)
t.Log(route)
if route != nil {
t.Error("no route expected")
}
}
// go test -v -test.run TestCurly_ISSUE_137_2 ...restful
func TestCurly_ISSUE_137_2(t *testing.T) {
ws1 := new(WebService)
ws1.Route(ws1.GET("/hello").To(curlyDummy))
ws1.Path("/")
req, _ := http.NewRequest("GET", "/hello/bob", nil)
_, route, _ := CurlyRouter{}.SelectRoute([]*WebService{ws1}, req)
t.Log(route)
if route != nil {
t.Errorf("no route expected, got %v", route)
}
}
func curlyDummy(req *Request, resp *Response) { io.WriteString(resp.ResponseWriter, "curlyDummy") }

View File

@ -1,41 +0,0 @@
package restful
import "net/http"
func ExampleOPTIONSFilter() {
// Install the OPTIONS filter on the default Container
Filter(OPTIONSFilter())
}
func ExampleContainer_OPTIONSFilter() {
// Install the OPTIONS filter on a Container
myContainer := new(Container)
myContainer.Filter(myContainer.OPTIONSFilter)
}
func ExampleContainer() {
// The Default container of go-restful uses the http.DefaultServeMux.
// You can create your own Container using restful.NewContainer() and create a new http.Server for that particular container
ws := new(WebService)
wsContainer := NewContainer()
wsContainer.Add(ws)
server := &http.Server{Addr: ":8080", Handler: wsContainer}
server.ListenAndServe()
}
func ExampleCrossOriginResourceSharing() {
// To install this filter on the Default Container use:
cors := CrossOriginResourceSharing{ExposeHeaders: []string{"X-My-Header"}, CookiesAllowed: false, Container: DefaultContainer}
Filter(cors.Filter)
}
func ExampleServiceError() {
resp := new(Response)
resp.WriteEntity(NewError(http.StatusBadRequest, "Non-integer {id} path parameter"))
}
func ExampleBoundedCachedCompressors() {
// Register a compressor provider (gzip/deflate read/write) that uses
// a bounded cache with a maximum of 20 writers and 20 readers.
SetCompressorProvider(NewBoundedCachedCompressors(20, 20))
}

View File

@ -1,69 +0,0 @@
package restful
import (
"bytes"
"fmt"
"io"
"net/http"
"net/http/httptest"
"reflect"
"testing"
)
type keyvalue struct {
readCalled bool
writeCalled bool
}
func (kv *keyvalue) Read(req *Request, v interface{}) error {
//t := reflect.TypeOf(v)
//rv := reflect.ValueOf(v)
kv.readCalled = true
return nil
}
func (kv *keyvalue) Write(resp *Response, status int, v interface{}) error {
t := reflect.TypeOf(v)
rv := reflect.ValueOf(v)
for ix := 0; ix < t.NumField(); ix++ {
sf := t.Field(ix)
io.WriteString(resp, sf.Name)
io.WriteString(resp, "=")
io.WriteString(resp, fmt.Sprintf("%v\n", rv.Field(ix).Interface()))
}
kv.writeCalled = true
return nil
}
// go test -v -test.run TestKeyValueEncoding ...restful
func TestKeyValueEncoding(t *testing.T) {
type Book struct {
Title string
Author string
PublishedYear int
}
kv := new(keyvalue)
RegisterEntityAccessor("application/kv", kv)
b := Book{"Singing for Dummies", "john doe", 2015}
// Write
httpWriter := httptest.NewRecorder()
// Accept Produces
resp := Response{httpWriter, "application/kv,*/*;q=0.8", []string{"application/kv"}, 0, 0, true, nil}
resp.WriteEntity(b)
t.Log(string(httpWriter.Body.Bytes()))
if !kv.writeCalled {
t.Error("Write never called")
}
// Read
bodyReader := bytes.NewReader(httpWriter.Body.Bytes())
httpRequest, _ := http.NewRequest("GET", "/test", bodyReader)
httpRequest.Header.Set("Content-Type", "application/kv; charset=UTF-8")
request := NewRequest(httpRequest)
var bb Book
request.ReadEntity(&bb)
if !kv.readCalled {
t.Error("Read never called")
}
}

View File

@ -1 +0,0 @@
ignore

View File

@ -1 +0,0 @@
ignore

View File

@ -1,20 +0,0 @@
#
# Include your application ID here
#
application: <your_app_id>
version: 1
runtime: go
api_version: go1
handlers:
#
# Regex for all swagger files to make as static content.
# You should create the folder static/swagger and copy
# swagger-ui into it.
#
- url: /apidocs/(.*?)/(.*\.(js|html|css))
static_files: static/swagger/\1/\2
upload: static/swagger/(.*?)/(.*\.(js|html|css))
- url: /.*
script: _go_app

View File

@ -1,18 +0,0 @@
application: <your_app_id>
version: 1
runtime: go
api_version: go1
handlers:
# Regex for all swagger files to make as static content.
# You should create the folder static/swagger and copy
# swagger-ui into it.
#
- url: /apidocs/(.*?)/(.*\.(js|html|css))
static_files: static/swagger/\1/\2
upload: static/swagger/(.*?)/(.*\.(js|html|css))
# Catch all.
- url: /.*
script: _go_app
login: required

View File

@ -1,267 +0,0 @@
package main
import (
"net/http"
"time"
"github.com/emicklei/go-restful"
"github.com/emicklei/go-restful-swagger12"
"google.golang.org/appengine"
"google.golang.org/appengine/datastore"
"google.golang.org/appengine/user"
)
// This example demonstrates a reasonably complete suite of RESTful operations backed
// by DataStore on Google App Engine.
// Our simple example struct.
type Profile struct {
LastModified time.Time `json:"-" xml:"-"`
Email string `json:"-" xml:"-"`
FirstName string `json:"first_name" xml:"first-name"`
NickName string `json:"nick_name" xml:"nick-name"`
LastName string `json:"last_name" xml:"last-name"`
}
type ProfileApi struct {
Path string
}
func gaeUrl() string {
if appengine.IsDevAppServer() {
return "http://localhost:8080"
} else {
// Include your URL on App Engine here.
// I found no way to get AppID without appengine.Context and this always
// based on a http.Request.
return "http://federatedservices.appspot.com"
}
}
func init() {
u := ProfileApi{Path: "/profiles"}
u.register()
// Optionally, you can install the Swagger Service which provides a nice Web UI on your REST API
// You need to download the Swagger HTML5 assets and change the FilePath location in the config below.
// Open <your_app_id>.appspot.com/apidocs and enter
// Place the Swagger UI files into a folder called static/swagger if you wish to use Swagger
// http://<your_app_id>.appspot.com/apidocs.json in the api input field.
// For testing, you can use http://localhost:8080/apidocs.json
config := swagger.Config{
// You control what services are visible
WebServices: restful.RegisteredWebServices(),
WebServicesUrl: gaeUrl(),
ApiPath: "/apidocs.json",
// Optionally, specifiy where the UI is located
SwaggerPath: "/apidocs/",
// GAE support static content which is configured in your app.yaml.
// This example expect the swagger-ui in static/swagger so you should place it there :)
SwaggerFilePath: "static/swagger"}
swagger.InstallSwaggerService(config)
}
func (u ProfileApi) register() {
ws := new(restful.WebService)
ws.
Path(u.Path).
// You can specify consumes and produces per route as well.
Consumes(restful.MIME_JSON, restful.MIME_XML).
Produces(restful.MIME_JSON, restful.MIME_XML)
ws.Route(ws.POST("").To(u.insert).
// Swagger documentation.
Doc("insert a new profile").
Param(ws.BodyParameter("Profile", "representation of a profile").DataType("main.Profile")).
Reads(Profile{}))
ws.Route(ws.GET("/{profile-id}").To(u.read).
// Swagger documentation.
Doc("read a profile").
Param(ws.PathParameter("profile-id", "identifier for a profile").DataType("string")).
Writes(Profile{}))
ws.Route(ws.PUT("/{profile-id}").To(u.update).
// Swagger documentation.
Doc("update an existing profile").
Param(ws.PathParameter("profile-id", "identifier for a profile").DataType("string")).
Param(ws.BodyParameter("Profile", "representation of a profile").DataType("main.Profile")).
Reads(Profile{}))
ws.Route(ws.DELETE("/{profile-id}").To(u.remove).
// Swagger documentation.
Doc("remove a profile").
Param(ws.PathParameter("profile-id", "identifier for a profile").DataType("string")))
restful.Add(ws)
}
// POST http://localhost:8080/profiles
// {"first_name": "Ivan", "nick_name": "Socks", "last_name": "Hawkes"}
//
func (u *ProfileApi) insert(r *restful.Request, w *restful.Response) {
c := appengine.NewContext(r.Request)
// Marshall the entity from the request into a struct.
p := new(Profile)
err := r.ReadEntity(&p)
if err != nil {
w.WriteError(http.StatusNotAcceptable, err)
return
}
// Ensure we start with a sensible value for this field.
p.LastModified = time.Now()
// The profile belongs to this user.
p.Email = user.Current(c).String()
k, err := datastore.Put(c, datastore.NewIncompleteKey(c, "profiles", nil), p)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Let them know the location of the newly created resource.
// TODO: Use a safe Url path append function.
w.AddHeader("Location", u.Path+"/"+k.Encode())
// Return the resultant entity.
w.WriteHeader(http.StatusCreated)
w.WriteEntity(p)
}
// GET http://localhost:8080/profiles/ahdkZXZ-ZmVkZXJhdGlvbi1zZXJ2aWNlc3IVCxIIcHJvZmlsZXMYgICAgICAgAoM
//
func (u ProfileApi) read(r *restful.Request, w *restful.Response) {
c := appengine.NewContext(r.Request)
// Decode the request parameter to determine the key for the entity.
k, err := datastore.DecodeKey(r.PathParameter("profile-id"))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Retrieve the entity from the datastore.
p := Profile{}
if err := datastore.Get(c, k, &p); err != nil {
if err.Error() == "datastore: no such entity" {
http.Error(w, err.Error(), http.StatusNotFound)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
// Check we own the profile before allowing them to view it.
// Optionally, return a 404 instead to help prevent guessing ids.
// TODO: Allow admins access.
if p.Email != user.Current(c).String() {
http.Error(w, "You do not have access to this resource", http.StatusForbidden)
return
}
w.WriteEntity(p)
}
// PUT http://localhost:8080/profiles/ahdkZXZ-ZmVkZXJhdGlvbi1zZXJ2aWNlc3IVCxIIcHJvZmlsZXMYgICAgICAgAoM
// {"first_name": "Ivan", "nick_name": "Socks", "last_name": "Hawkes"}
//
func (u *ProfileApi) update(r *restful.Request, w *restful.Response) {
c := appengine.NewContext(r.Request)
// Decode the request parameter to determine the key for the entity.
k, err := datastore.DecodeKey(r.PathParameter("profile-id"))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Marshall the entity from the request into a struct.
p := new(Profile)
err = r.ReadEntity(&p)
if err != nil {
w.WriteError(http.StatusNotAcceptable, err)
return
}
// Retrieve the old entity from the datastore.
old := Profile{}
if err := datastore.Get(c, k, &old); err != nil {
if err.Error() == "datastore: no such entity" {
http.Error(w, err.Error(), http.StatusNotFound)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
// Check we own the profile before allowing them to update it.
// Optionally, return a 404 instead to help prevent guessing ids.
// TODO: Allow admins access.
if old.Email != user.Current(c).String() {
http.Error(w, "You do not have access to this resource", http.StatusForbidden)
return
}
// Since the whole entity is re-written, we need to assign any invariant fields again
// e.g. the owner of the entity.
p.Email = user.Current(c).String()
// Keep track of the last modification date.
p.LastModified = time.Now()
// Attempt to overwrite the old entity.
_, err = datastore.Put(c, k, p)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Let them know it succeeded.
w.WriteHeader(http.StatusNoContent)
}
// DELETE http://localhost:8080/profiles/ahdkZXZ-ZmVkZXJhdGlvbi1zZXJ2aWNlc3IVCxIIcHJvZmlsZXMYgICAgICAgAoM
//
func (u *ProfileApi) remove(r *restful.Request, w *restful.Response) {
c := appengine.NewContext(r.Request)
// Decode the request parameter to determine the key for the entity.
k, err := datastore.DecodeKey(r.PathParameter("profile-id"))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Retrieve the old entity from the datastore.
old := Profile{}
if err := datastore.Get(c, k, &old); err != nil {
if err.Error() == "datastore: no such entity" {
http.Error(w, err.Error(), http.StatusNotFound)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
// Check we own the profile before allowing them to delete it.
// Optionally, return a 404 instead to help prevent guessing ids.
// TODO: Allow admins access.
if old.Email != user.Current(c).String() {
http.Error(w, "You do not have access to this resource", http.StatusForbidden)
return
}
// Delete the entity.
if err := datastore.Delete(c, k); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
// Success notification.
w.WriteHeader(http.StatusNoContent)
}

View File

@ -1,12 +0,0 @@
package main
import (
"github.com/mjibson/appstats"
)
func stats(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
c := appstats.NewContext(req.Request)
chain.ProcessFilter(req, resp)
c.Stats.Status = resp.StatusCode()
c.Save()
}

View File

@ -1,162 +0,0 @@
package main
import (
"net/http"
"github.com/emicklei/go-restful"
"github.com/emicklei/go-restful-swagger12"
"google.golang.org/appengine"
"google.golang.org/appengine/memcache"
)
// This example is functionally the same as ../restful-user-service.go
// but it`s supposed to run on Goole App Engine (GAE)
//
// contributed by ivanhawkes
type User struct {
Id, Name string
}
type UserService struct {
// normally one would use DAO (data access object)
// but in this example we simple use memcache.
}
func (u UserService) Register() {
ws := new(restful.WebService)
ws.
Path("/users").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well
ws.Route(ws.GET("/{user-id}").To(u.findUser).
// docs
Doc("get a user").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
Writes(User{})) // on the response
ws.Route(ws.PATCH("").To(u.updateUser).
// docs
Doc("update a user").
Reads(User{})) // from the request
ws.Route(ws.PUT("/{user-id}").To(u.createUser).
// docs
Doc("create a user").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
Reads(User{})) // from the request
ws.Route(ws.DELETE("/{user-id}").To(u.removeUser).
// docs
Doc("delete a user").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")))
restful.Add(ws)
}
// GET http://localhost:8080/users/1
//
func (u UserService) findUser(request *restful.Request, response *restful.Response) {
c := appengine.NewContext(request.Request)
id := request.PathParameter("user-id")
usr := new(User)
_, err := memcache.Gob.Get(c, id, &usr)
if err != nil || len(usr.Id) == 0 {
response.WriteErrorString(http.StatusNotFound, "User could not be found.")
} else {
response.WriteEntity(usr)
}
}
// PATCH http://localhost:8080/users
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
//
func (u *UserService) updateUser(request *restful.Request, response *restful.Response) {
c := appengine.NewContext(request.Request)
usr := new(User)
err := request.ReadEntity(&usr)
if err == nil {
item := &memcache.Item{
Key: usr.Id,
Object: &usr,
}
err = memcache.Gob.Set(c, item)
if err != nil {
response.WriteError(http.StatusInternalServerError, err)
return
}
response.WriteEntity(usr)
} else {
response.WriteError(http.StatusInternalServerError, err)
}
}
// PUT http://localhost:8080/users/1
// <User><Id>1</Id><Name>Melissa</Name></User>
//
func (u *UserService) createUser(request *restful.Request, response *restful.Response) {
c := appengine.NewContext(request.Request)
usr := User{Id: request.PathParameter("user-id")}
err := request.ReadEntity(&usr)
if err == nil {
item := &memcache.Item{
Key: usr.Id,
Object: &usr,
}
err = memcache.Gob.Add(c, item)
if err != nil {
response.WriteError(http.StatusInternalServerError, err)
return
}
response.WriteHeader(http.StatusCreated)
response.WriteEntity(usr)
} else {
response.WriteError(http.StatusInternalServerError, err)
}
}
// DELETE http://localhost:8080/users/1
//
func (u *UserService) removeUser(request *restful.Request, response *restful.Response) {
c := appengine.NewContext(request.Request)
id := request.PathParameter("user-id")
err := memcache.Delete(c, id)
if err != nil {
response.WriteError(http.StatusInternalServerError, err)
}
}
func getGaeURL() string {
if appengine.IsDevAppServer() {
return "http://localhost:8080"
} else {
/**
* Include your URL on App Engine here.
* I found no way to get AppID without appengine.Context and this always
* based on a http.Request.
*/
return "http://<your_app_id>.appspot.com"
}
}
func init() {
u := UserService{}
u.Register()
// Optionally, you can install the Swagger Service which provides a nice Web UI on your REST API
// You need to download the Swagger HTML5 assets and change the FilePath location in the config below.
// Open <your_app_id>.appspot.com/apidocs and enter http://<your_app_id>.appspot.com/apidocs.json in the api input field.
config := swagger.Config{
WebServices: restful.RegisteredWebServices(), // you control what services are visible
WebServicesUrl: getGaeURL(),
ApiPath: "/apidocs.json",
// Optionally, specifiy where the UI is located
SwaggerPath: "/apidocs/",
// GAE support static content which is configured in your app.yaml.
// This example expect the swagger-ui in static/swagger so you should place it there :)
SwaggerFilePath: "static/swagger"}
swagger.InstallSwaggerService(config)
}

View File

@ -1,7 +0,0 @@
<!DOCTYPE html>
<html>
<body>
<h1>{{.Text}}</h1>
</body>
</html>

View File

@ -1,34 +0,0 @@
package restPack
import (
restful "github.com/emicklei/go-restful"
"gopkg.in/vmihailenco/msgpack.v2"
)
const MIME_MSGPACK = "application/x-msgpack" // Accept or Content-Type used in Consumes() and/or Produces()
// NewEntityAccessorMPack returns a new EntityReaderWriter for accessing MessagePack content.
// This package is not initialized with such an accessor using the MIME_MSGPACK contentType.
func NewEntityAccessorMsgPack() restful.EntityReaderWriter {
return entityMsgPackAccess{}
}
// entityOctetAccess is a EntityReaderWriter for Octet encoding
type entityMsgPackAccess struct {
}
// Read unmarshalls the value from byte slice and using msgpack to unmarshal
func (e entityMsgPackAccess) Read(req *restful.Request, v interface{}) error {
return msgpack.NewDecoder(req.Request.Body).Decode(v)
}
// Write marshals the value to byte slice and set the Content-Type Header.
func (e entityMsgPackAccess) Write(resp *restful.Response, status int, v interface{}) error {
if v == nil {
resp.WriteHeader(status)
// do not write a nil representation
return nil
}
resp.WriteHeader(status)
return msgpack.NewEncoder(resp).Encode(v)
}

View File

@ -1,160 +0,0 @@
package restPack
import (
"bytes"
"errors"
"log"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"time"
"io/ioutil"
restful "github.com/emicklei/go-restful"
)
func TestMsgPack(t *testing.T) {
// register msg pack entity
restful.RegisterEntityAccessor(MIME_MSGPACK, NewEntityAccessorMsgPack())
type Tool struct {
Name string
Vendor string
}
// Write
httpWriter := httptest.NewRecorder()
mpack := &Tool{Name: "json", Vendor: "apple"}
resp := restful.NewResponse(httpWriter)
resp.SetRequestAccepts("application/x-msgpack,*/*;q=0.8")
err := resp.WriteEntity(mpack)
if err != nil {
t.Errorf("err %v", err)
}
// Read
bodyReader := bytes.NewReader(httpWriter.Body.Bytes())
httpRequest, _ := http.NewRequest("GET", "/test", bodyReader)
httpRequest.Header.Set("Content-Type", MIME_MSGPACK)
request := restful.NewRequest(httpRequest)
readMsgPack := new(Tool)
err = request.ReadEntity(&readMsgPack)
if err != nil {
t.Errorf("err %v", err)
}
if equal := reflect.DeepEqual(mpack, readMsgPack); !equal {
t.Fatalf("should not be error")
}
}
func TestWithWebService(t *testing.T) {
serverURL := "http://127.0.0.1:8090"
go func() {
runRestfulMsgPackRouterServer()
}()
if err := waitForServerUp(serverURL); err != nil {
t.Errorf("%v", err)
}
// send a post request
userData := user{Id: "0001", Name: "Tony"}
msgPackData, err := msgpack.Marshal(userData)
req, err := http.NewRequest("POST", serverURL+"/test/msgpack", bytes.NewBuffer(msgPackData))
req.Header.Set("Content-Type", MIME_MSGPACK)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
t.Errorf("unexpected error in sending req: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Errorf("unexpected response: %v, expected: %v", resp.StatusCode, http.StatusOK)
}
ur := &userResponse{}
expectMsgPackDocument(t, resp, ur)
if ur.Status != statusActive {
t.Fatalf("should not error")
}
log.Printf("user response:%v", ur)
}
func expectMsgPackDocument(t *testing.T, r *http.Response, doc interface{}) {
data, err := ioutil.ReadAll(r.Body)
defer r.Body.Close()
if err != nil {
t.Errorf("ExpectMsgPackDocument: unable to read response body :%v", err)
return
}
// put the body back for re-reads
r.Body = ioutil.NopCloser(bytes.NewReader(data))
err = msgpack.Unmarshal(data, doc)
if err != nil {
t.Errorf("ExpectMsgPackDocument: unable to unmarshal MsgPack:%v", err)
}
}
func runRestfulMsgPackRouterServer() {
container := restful.NewContainer()
register(container)
log.Printf("start listening on localhost:8090")
server := &http.Server{Addr: ":8090", Handler: container}
log.Fatal(server.ListenAndServe())
}
func waitForServerUp(serverURL string) error {
for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(5 * time.Second) {
_, err := http.Get(serverURL + "/")
if err == nil {
return nil
}
}
return errors.New("waiting for server timed out")
}
var (
statusActive = "active"
)
type user struct {
Id, Name string
}
type userResponse struct {
Status string
}
func register(container *restful.Container) {
restful.RegisterEntityAccessor(MIME_MSGPACK, NewEntityAccessorMsgPack())
ws := new(restful.WebService)
ws.
Path("/test").
Consumes(restful.MIME_JSON, MIME_MSGPACK).
Produces(restful.MIME_JSON, MIME_MSGPACK)
// route user api
ws.Route(ws.POST("/msgpack").
To(do).
Reads(user{}).
Writes(userResponse{}))
container.Add(ws)
}
func do(request *restful.Request, response *restful.Response) {
u := &user{}
err := request.ReadEntity(u)
if err != nil {
log.Printf("should be no error, got:%v", err)
}
log.Printf("got:%v", u)
ur := &userResponse{Status: statusActive}
response.SetRequestAccepts(MIME_MSGPACK)
response.WriteEntity(ur)
}

View File

@ -1,68 +0,0 @@
package main
import (
"io"
"log"
"net/http"
"github.com/emicklei/go-restful"
)
// Cross-origin resource sharing (CORS) is a mechanism that allows JavaScript on a web page
// to make XMLHttpRequests to another domain, not the domain the JavaScript originated from.
//
// http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
// http://enable-cors.org/server.html
//
// GET http://localhost:8080/users
//
// GET http://localhost:8080/users/1
//
// PUT http://localhost:8080/users/1
//
// DELETE http://localhost:8080/users/1
//
// OPTIONS http://localhost:8080/users/1 with Header "Origin" set to some domain and
type UserResource struct{}
func (u UserResource) RegisterTo(container *restful.Container) {
ws := new(restful.WebService)
ws.
Path("/users").
Consumes("*/*").
Produces("*/*")
ws.Route(ws.GET("/{user-id}").To(u.nop))
ws.Route(ws.POST("").To(u.nop))
ws.Route(ws.PUT("/{user-id}").To(u.nop))
ws.Route(ws.DELETE("/{user-id}").To(u.nop))
container.Add(ws)
}
func (u UserResource) nop(request *restful.Request, response *restful.Response) {
io.WriteString(response.ResponseWriter, "this would be a normal response")
}
func main() {
wsContainer := restful.NewContainer()
u := UserResource{}
u.RegisterTo(wsContainer)
// Add container filter to enable CORS
cors := restful.CrossOriginResourceSharing{
ExposeHeaders: []string{"X-My-Header"},
AllowedHeaders: []string{"Content-Type", "Accept"},
AllowedMethods: []string{"GET", "POST"},
CookiesAllowed: false,
Container: wsContainer}
wsContainer.Filter(cors.Filter)
// Add container filter to respond to OPTIONS
wsContainer.Filter(wsContainer.OPTIONSFilter)
log.Printf("start listening on localhost:8080")
server := &http.Server{Addr: ":8080", Handler: wsContainer}
log.Fatal(server.ListenAndServe())
}

View File

@ -1,54 +0,0 @@
package main
import (
"github.com/emicklei/go-restful"
"io"
"log"
"net/http"
"os"
"strings"
"time"
)
// This example shows how to create a filter that produces log lines
// according to the Common Log Format, also known as the NCSA standard.
//
// kindly contributed by leehambley
//
// GET http://localhost:8080/ping
var logger *log.Logger = log.New(os.Stdout, "", 0)
func NCSACommonLogFormatLogger() restful.FilterFunction {
return func(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
var username = "-"
if req.Request.URL.User != nil {
if name := req.Request.URL.User.Username(); name != "" {
username = name
}
}
chain.ProcessFilter(req, resp)
logger.Printf("%s - %s [%s] \"%s %s %s\" %d %d",
strings.Split(req.Request.RemoteAddr, ":")[0],
username,
time.Now().Format("02/Jan/2006:15:04:05 -0700"),
req.Request.Method,
req.Request.URL.RequestURI(),
req.Request.Proto,
resp.StatusCode(),
resp.ContentLength(),
)
}
}
func main() {
ws := new(restful.WebService)
ws.Filter(NCSACommonLogFormatLogger())
ws.Route(ws.GET("/ping").To(hello))
restful.Add(ws)
http.ListenAndServe(":8080", nil)
}
func hello(req *restful.Request, resp *restful.Response) {
io.WriteString(resp, "pong")
}

View File

@ -1,35 +0,0 @@
package main
import (
"github.com/emicklei/go-restful"
"io"
"net/http"
)
// This example shows how to create a (Route) Filter that performs Basic Authentication on the Http request.
//
// GET http://localhost:8080/secret
// and use admin,admin for the credentials
func main() {
ws := new(restful.WebService)
ws.Route(ws.GET("/secret").Filter(basicAuthenticate).To(secret))
restful.Add(ws)
http.ListenAndServe(":8080", nil)
}
func basicAuthenticate(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
encoded := req.Request.Header.Get("Authorization")
// usr/pwd = admin/admin
// real code does some decoding
if len(encoded) == 0 || "Basic YWRtaW46YWRtaW4=" != encoded {
resp.AddHeader("WWW-Authenticate", "Basic realm=Protected Area")
resp.WriteErrorString(401, "401: Not Authorized")
return
}
chain.ProcessFilter(req, resp)
}
func secret(req *restful.Request, resp *restful.Response) {
io.WriteString(resp, "42")
}

View File

@ -1,65 +0,0 @@
package main
import (
"github.com/emicklei/go-restful"
"io"
"log"
"os"
"runtime/pprof"
)
// ProfilingService is a WebService that can start/stop a CPU profile and write results to a file
// GET /{rootPath}/start will activate CPU profiling
// GET /{rootPath}/stop will stop profiling
//
// NewProfileService("/profiler", "ace.prof").AddWebServiceTo(restful.DefaultContainer)
//
type ProfilingService struct {
rootPath string // the base (root) of the service, e.g. /profiler
cpuprofile string // the output filename to write profile results, e.g. myservice.prof
cpufile *os.File // if not nil, then profiling is active
}
func NewProfileService(rootPath string, outputFilename string) *ProfilingService {
ps := new(ProfilingService)
ps.rootPath = rootPath
ps.cpuprofile = outputFilename
return ps
}
// Add this ProfileService to a restful Container
func (p ProfilingService) AddWebServiceTo(container *restful.Container) {
ws := new(restful.WebService)
ws.Path(p.rootPath).Consumes("*/*").Produces(restful.MIME_JSON)
ws.Route(ws.GET("/start").To(p.startProfiler))
ws.Route(ws.GET("/stop").To(p.stopProfiler))
container.Add(ws)
}
func (p *ProfilingService) startProfiler(req *restful.Request, resp *restful.Response) {
if p.cpufile != nil {
io.WriteString(resp.ResponseWriter, "[restful] CPU profiling already running")
return // error?
}
cpufile, err := os.Create(p.cpuprofile)
if err != nil {
log.Fatal(err)
}
// remember for close
p.cpufile = cpufile
pprof.StartCPUProfile(cpufile)
io.WriteString(resp.ResponseWriter, "[restful] CPU profiling started, writing on:"+p.cpuprofile)
}
func (p *ProfilingService) stopProfiler(req *restful.Request, resp *restful.Response) {
if p.cpufile == nil {
io.WriteString(resp.ResponseWriter, "[restful] CPU profiling not active")
return // error?
}
pprof.StopCPUProfile()
p.cpufile.Close()
p.cpufile = nil
io.WriteString(resp.ResponseWriter, "[restful] CPU profiling stopped, closing:"+p.cpuprofile)
}
func main() {} // exists for example compilation only

View File

@ -1,107 +0,0 @@
package main
import (
"log"
"net/http"
"github.com/emicklei/go-restful"
)
// This example has the same service definition as restful-user-resource
// but uses a different router (CurlyRouter) that does not use regular expressions
//
// POST http://localhost:8080/users
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
//
// GET http://localhost:8080/users/1
//
// PUT http://localhost:8080/users/1
// <User><Id>1</Id><Name>Melissa</Name></User>
//
// DELETE http://localhost:8080/users/1
//
type User struct {
Id, Name string
}
type UserResource struct {
// normally one would use DAO (data access object)
users map[string]User
}
func (u UserResource) Register(container *restful.Container) {
ws := new(restful.WebService)
ws.
Path("/users").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well
ws.Route(ws.GET("/{user-id}").To(u.findUser))
ws.Route(ws.POST("").To(u.updateUser))
ws.Route(ws.PUT("/{user-id}").To(u.createUser))
ws.Route(ws.DELETE("/{user-id}").To(u.removeUser))
container.Add(ws)
}
// GET http://localhost:8080/users/1
//
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
id := request.PathParameter("user-id")
usr := u.users[id]
if len(usr.Id) == 0 {
response.AddHeader("Content-Type", "text/plain")
response.WriteErrorString(http.StatusNotFound, "User could not be found.")
} else {
response.WriteEntity(usr)
}
}
// POST http://localhost:8080/users
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
//
func (u *UserResource) updateUser(request *restful.Request, response *restful.Response) {
usr := new(User)
err := request.ReadEntity(&usr)
if err == nil {
u.users[usr.Id] = *usr
response.WriteEntity(usr)
} else {
response.AddHeader("Content-Type", "text/plain")
response.WriteErrorString(http.StatusInternalServerError, err.Error())
}
}
// PUT http://localhost:8080/users/1
// <User><Id>1</Id><Name>Melissa</Name></User>
//
func (u *UserResource) createUser(request *restful.Request, response *restful.Response) {
usr := User{Id: request.PathParameter("user-id")}
err := request.ReadEntity(&usr)
if err == nil {
u.users[usr.Id] = usr
response.WriteHeaderAndEntity(http.StatusCreated, usr)
} else {
response.AddHeader("Content-Type", "text/plain")
response.WriteErrorString(http.StatusInternalServerError, err.Error())
}
}
// DELETE http://localhost:8080/users/1
//
func (u *UserResource) removeUser(request *restful.Request, response *restful.Response) {
id := request.PathParameter("user-id")
delete(u.users, id)
}
func main() {
wsContainer := restful.NewContainer()
wsContainer.Router(restful.CurlyRouter{})
u := UserResource{map[string]User{}}
u.Register(wsContainer)
log.Printf("start listening on localhost:8080")
server := &http.Server{Addr: ":8080", Handler: wsContainer}
log.Fatal(server.ListenAndServe())
}

View File

@ -1,149 +0,0 @@
package main
import (
"bytes"
"errors"
"log"
"net/http"
"testing"
"time"
"github.com/emicklei/go-restful"
)
type User struct {
Id, Name string
}
type UserResource struct {
users map[string]User
}
func (u UserResource) Register(container *restful.Container) {
ws := new(restful.WebService)
ws.
Path("/users").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML)
ws.Route(ws.GET("/{user-id}").To(u.findUser))
ws.Route(ws.POST("").To(u.updateUser))
ws.Route(ws.PUT("/{user-id}").To(u.createUser))
ws.Route(ws.DELETE("/{user-id}").To(u.removeUser))
container.Add(ws)
}
// GET http://localhost:8090/users/1
//
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
id := request.PathParameter("user-id")
usr := u.users[id]
if len(usr.Id) == 0 {
response.AddHeader("Content-Type", "text/plain")
response.WriteErrorString(http.StatusNotFound, "User could not be found.")
} else {
response.WriteEntity(usr)
}
}
// POST http://localhost:8090/users
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
//
func (u *UserResource) updateUser(request *restful.Request, response *restful.Response) {
usr := new(User)
err := request.ReadEntity(&usr)
if err == nil {
u.users[usr.Id] = *usr
response.WriteEntity(usr)
} else {
response.AddHeader("Content-Type", "text/plain")
response.WriteErrorString(http.StatusInternalServerError, err.Error())
}
}
// PUT http://localhost:8090/users/1
// <User><Id>1</Id><Name>Melissa</Name></User>
//
func (u *UserResource) createUser(request *restful.Request, response *restful.Response) {
usr := User{Id: request.PathParameter("user-id")}
err := request.ReadEntity(&usr)
if err == nil {
u.users[usr.Id] = usr
response.WriteHeader(http.StatusCreated)
response.WriteEntity(usr)
} else {
response.AddHeader("Content-Type", "text/plain")
response.WriteErrorString(http.StatusInternalServerError, err.Error())
}
}
// DELETE http://localhost:8090/users/1
//
func (u *UserResource) removeUser(request *restful.Request, response *restful.Response) {
id := request.PathParameter("user-id")
delete(u.users, id)
}
func RunRestfulCurlyRouterServer() {
wsContainer := restful.NewContainer()
wsContainer.Router(restful.CurlyRouter{})
u := UserResource{map[string]User{}}
u.Register(wsContainer)
log.Printf("start listening on localhost:8090")
server := &http.Server{Addr: ":8090", Handler: wsContainer}
log.Fatal(server.ListenAndServe())
}
func waitForServerUp(serverURL string) error {
for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(5 * time.Second) {
_, err := http.Get(serverURL + "/")
if err == nil {
return nil
}
}
return errors.New("waiting for server timed out")
}
func TestServer(t *testing.T) {
serverURL := "http://localhost:8090"
go func() {
RunRestfulCurlyRouterServer()
}()
if err := waitForServerUp(serverURL); err != nil {
t.Errorf("%v", err)
}
// GET should give a 405
resp, err := http.Get(serverURL + "/users/")
if err != nil {
t.Errorf("unexpected error in GET /users/: %v", err)
}
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Errorf("unexpected response: %v, expected: %v", resp.StatusCode, http.StatusOK)
}
// Send a POST request.
var jsonStr = []byte(`{"id":"1","name":"user1"}`)
req, err := http.NewRequest("POST", serverURL+"/users/", bytes.NewBuffer(jsonStr))
req.Header.Set("Content-Type", restful.MIME_JSON)
client := &http.Client{}
resp, err = client.Do(req)
if err != nil {
t.Errorf("unexpected error in sending req: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Errorf("unexpected response: %v, expected: %v", resp.StatusCode, http.StatusOK)
}
// Test that GET works.
resp, err = http.Get(serverURL + "/users/1")
if err != nil {
t.Errorf("unexpected error in GET /users/1: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Errorf("unexpected response: %v, expected: %v", resp.StatusCode, http.StatusOK)
}
}

View File

@ -1,61 +0,0 @@
package main
import (
"github.com/emicklei/go-restful"
"log"
"net/http"
)
type User struct {
Id, Name string
}
type UserList struct {
Users []User
}
//
// This example shows how to use the CompressingResponseWriter by a Filter
// such that encoding can be enabled per WebService or per Route (instead of per container)
// Using restful.DefaultContainer.EnableContentEncoding(true) will encode all responses served by WebServices in the DefaultContainer.
//
// Set Accept-Encoding to gzip or deflate
// GET http://localhost:8080/users/42
// and look at the response headers
func main() {
restful.Add(NewUserService())
log.Printf("start listening on localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func NewUserService() *restful.WebService {
ws := new(restful.WebService)
ws.
Path("/users").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML)
// install a response encoding filter
ws.Route(ws.GET("/{user-id}").Filter(encodingFilter).To(findUser))
return ws
}
// Route Filter (defines FilterFunction)
func encodingFilter(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
log.Printf("[encoding-filter] %s,%s\n", req.Request.Method, req.Request.URL)
// wrap responseWriter into a compressing one
compress, _ := restful.NewCompressingResponseWriter(resp.ResponseWriter, restful.ENCODING_GZIP)
resp.ResponseWriter = compress
defer func() {
compress.Close()
}()
chain.ProcessFilter(req, resp)
}
// GET http://localhost:8080/users/42
//
func findUser(request *restful.Request, response *restful.Response) {
log.Printf("findUser")
response.WriteEntity(User{"42", "Gandalf"})
}

View File

@ -1,114 +0,0 @@
package main
import (
"github.com/emicklei/go-restful"
"log"
"net/http"
"time"
)
type User struct {
Id, Name string
}
type UserList struct {
Users []User
}
// This example show how to create and use the three different Filters (Container,WebService and Route)
// When applied to the restful.DefaultContainer, we refer to them as a global filter.
//
// GET http://locahost:8080/users/42
// and see the logging per filter (try repeating this request)
func main() {
// install a global (=DefaultContainer) filter (processed before any webservice in the DefaultContainer)
restful.Filter(globalLogging)
restful.Add(NewUserService())
log.Printf("start listening on localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func NewUserService() *restful.WebService {
ws := new(restful.WebService)
ws.
Path("/users").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML)
// install a webservice filter (processed before any route)
ws.Filter(webserviceLogging).Filter(measureTime)
// install a counter filter
ws.Route(ws.GET("").Filter(NewCountFilter().routeCounter).To(getAllUsers))
// install 2 chained route filters (processed before calling findUser)
ws.Route(ws.GET("/{user-id}").Filter(routeLogging).Filter(NewCountFilter().routeCounter).To(findUser))
return ws
}
// Global Filter
func globalLogging(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
log.Printf("[global-filter (logger)] %s,%s\n", req.Request.Method, req.Request.URL)
chain.ProcessFilter(req, resp)
}
// WebService Filter
func webserviceLogging(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
log.Printf("[webservice-filter (logger)] %s,%s\n", req.Request.Method, req.Request.URL)
chain.ProcessFilter(req, resp)
}
// WebService (post-process) Filter (as a struct that defines a FilterFunction)
func measureTime(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
now := time.Now()
chain.ProcessFilter(req, resp)
log.Printf("[webservice-filter (timer)] %v\n", time.Now().Sub(now))
}
// Route Filter (defines FilterFunction)
func routeLogging(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
log.Printf("[route-filter (logger)] %s,%s\n", req.Request.Method, req.Request.URL)
chain.ProcessFilter(req, resp)
}
// Route Filter (as a struct that defines a FilterFunction)
// CountFilter implements a FilterFunction for counting requests.
type CountFilter struct {
count int
counter chan int // for go-routine safe count increments
}
// NewCountFilter creates and initializes a new CountFilter.
func NewCountFilter() *CountFilter {
c := new(CountFilter)
c.counter = make(chan int)
go func() {
for {
c.count += <-c.counter
}
}()
return c
}
// routeCounter increments the count of the filter (through a channel)
func (c *CountFilter) routeCounter(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
c.counter <- 1
log.Printf("[route-filter (counter)] count:%d", c.count)
chain.ProcessFilter(req, resp)
}
// GET http://localhost:8080/users
//
func getAllUsers(request *restful.Request, response *restful.Response) {
log.Printf("getAllUsers")
response.WriteEntity(UserList{[]User{{"42", "Gandalf"}, {"3.14", "Pi"}}})
}
// GET http://localhost:8080/users/42
//
func findUser(request *restful.Request, response *restful.Response) {
log.Printf("findUser")
response.WriteEntity(User{"42", "Gandalf"})
}

View File

@ -1,62 +0,0 @@
package main
import (
"fmt"
"github.com/emicklei/go-restful"
"github.com/gorilla/schema"
"io"
"net/http"
)
// This example shows how to handle a POST of a HTML form that uses the standard x-www-form-urlencoded content-type.
// It uses the gorilla web tool kit schema package to decode the form data into a struct.
//
// GET http://localhost:8080/profiles
//
type Profile struct {
Name string
Age int
}
var decoder *schema.Decoder
func main() {
decoder = schema.NewDecoder()
ws := new(restful.WebService)
ws.Route(ws.POST("/profiles").Consumes("application/x-www-form-urlencoded").To(postAdddress))
ws.Route(ws.GET("/profiles").To(addresssForm))
restful.Add(ws)
http.ListenAndServe(":8080", nil)
}
func postAdddress(req *restful.Request, resp *restful.Response) {
err := req.Request.ParseForm()
if err != nil {
resp.WriteErrorString(http.StatusBadRequest, err.Error())
return
}
p := new(Profile)
err = decoder.Decode(p, req.Request.PostForm)
if err != nil {
resp.WriteErrorString(http.StatusBadRequest, err.Error())
return
}
io.WriteString(resp.ResponseWriter, fmt.Sprintf("<html><body>Name=%s, Age=%d</body></html>", p.Name, p.Age))
}
func addresssForm(req *restful.Request, resp *restful.Response) {
io.WriteString(resp.ResponseWriter,
`<html>
<body>
<h1>Enter Profile</h1>
<form method="post">
<label>Name:</label>
<input type="text" name="Name"/>
<label>Age:</label>
<input type="text" name="Age"/>
<input type="Submit" />
</form>
</body>
</html>`)
}

View File

@ -1,22 +0,0 @@
package main
import (
"github.com/emicklei/go-restful"
"io"
"net/http"
)
// This example shows the minimal code needed to get a restful.WebService working.
//
// GET http://localhost:8080/hello
func main() {
ws := new(restful.WebService)
ws.Route(ws.GET("/hello").To(hello))
restful.Add(ws)
http.ListenAndServe(":8080", nil)
}
func hello(req *restful.Request, resp *restful.Response) {
io.WriteString(resp, "world")
}

View File

@ -1,35 +0,0 @@
package main
import (
"log"
"net/http"
"text/template"
"github.com/emicklei/go-restful"
)
// This example shows how to serve a HTML page using the standard Go template engine.
//
// GET http://localhost:8080/
func main() {
ws := new(restful.WebService)
ws.Route(ws.GET("/").To(home))
restful.Add(ws)
print("open browser on http://localhost:8080/\n")
http.ListenAndServe(":8080", nil)
}
type Message struct {
Text string
}
func home(req *restful.Request, resp *restful.Response) {
p := &Message{"restful-html-template demo"}
// you might want to cache compiled templates
t, err := template.ParseFiles("home.html")
if err != nil {
log.Fatalf("Template gave: %s", err)
}
t.Execute(resp.ResponseWriter, p)
}

View File

@ -1,43 +0,0 @@
package main
import (
"github.com/emicklei/go-restful"
"io"
"log"
"net/http"
)
// This example shows how to have a program with 2 WebServices containers
// each having a http server listening on its own port.
//
// The first "hello" is added to the restful.DefaultContainer (and uses DefaultServeMux)
// For the second "hello", a new container and ServeMux is created
// and requires a new http.Server with the container being the Handler.
// This first server is spawn in its own go-routine such that the program proceeds to create the second.
//
// GET http://localhost:8080/hello
// GET http://localhost:8081/hello
func main() {
ws := new(restful.WebService)
ws.Route(ws.GET("/hello").To(hello))
restful.Add(ws)
go func() {
http.ListenAndServe(":8080", nil)
}()
container2 := restful.NewContainer()
ws2 := new(restful.WebService)
ws2.Route(ws2.GET("/hello").To(hello2))
container2.Add(ws2)
server := &http.Server{Addr: ":8081", Handler: container2}
log.Fatal(server.ListenAndServe())
}
func hello(req *restful.Request, resp *restful.Response) {
io.WriteString(resp, "default world")
}
func hello2(req *restful.Request, resp *restful.Response) {
io.WriteString(resp, "second world")
}

View File

@ -1,24 +0,0 @@
package main
import (
"io"
"net/http"
"github.com/emicklei/go-restful"
)
// This example shows how to use a WebService filter that passed the Http headers to disable browser cacheing.
//
// GET http://localhost:8080/hello
func main() {
ws := new(restful.WebService)
ws.Filter(restful.NoBrowserCacheFilter)
ws.Route(ws.GET("/hello").To(hello))
restful.Add(ws)
http.ListenAndServe(":8080", nil)
}
func hello(req *restful.Request, resp *restful.Response) {
io.WriteString(resp, "world")
}

View File

@ -1,51 +0,0 @@
package main
import (
"github.com/emicklei/go-restful"
"io"
"log"
"net/http"
)
// This example shows how to use the OPTIONSFilter on a Container
//
// OPTIONS http://localhost:8080/users
//
// OPTIONS http://localhost:8080/users/1
type UserResource struct{}
func (u UserResource) RegisterTo(container *restful.Container) {
ws := new(restful.WebService)
ws.
Path("/users").
Consumes("*/*").
Produces("*/*")
ws.Route(ws.GET("/{user-id}").To(u.nop))
ws.Route(ws.POST("").To(u.nop))
ws.Route(ws.PUT("/{user-id}").To(u.nop))
ws.Route(ws.DELETE("/{user-id}").To(u.nop))
container.Add(ws)
}
func (u UserResource) nop(request *restful.Request, response *restful.Response) {
io.WriteString(response.ResponseWriter, "this would be a normal response")
}
func main() {
wsContainer := restful.NewContainer()
u := UserResource{}
u.RegisterTo(wsContainer)
// Add container filter to respond to OPTIONS
wsContainer.Filter(wsContainer.OPTIONSFilter)
// For use on the default container, you can write
// restful.Filter(restful.OPTIONSFilter())
log.Printf("start listening on localhost:8080")
server := &http.Server{Addr: ":8080", Handler: wsContainer}
log.Fatal(server.ListenAndServe())
}

View File

@ -1,26 +0,0 @@
package main
import (
. "github.com/emicklei/go-restful"
"io"
"net/http"
)
// This example shows how to a Route that matches the "tail" of a path.
// Requires the use of a CurlyRouter and the star "*" path parameter pattern.
//
// GET http://localhost:8080/basepath/some/other/location/test.xml
func main() {
DefaultContainer.Router(CurlyRouter{})
ws := new(WebService)
ws.Route(ws.GET("/basepath/{resource:*}").To(staticFromPathParam))
Add(ws)
println("[go-restful] serve path tails from http://localhost:8080/basepath")
http.ListenAndServe(":8080", nil)
}
func staticFromPathParam(req *Request, resp *Response) {
io.WriteString(resp, "Tail="+req.PathParameter("resource"))
}

View File

@ -1,98 +0,0 @@
package main
import (
"github.com/emicklei/go-restful"
"io"
"log"
"net/http"
)
// This example shows how the different types of filters are called in the request-response flow.
// The call chain is logged on the console when sending an http request.
//
// GET http://localhost:8080/1
// GET http://localhost:8080/2
var indentLevel int
func container_filter_A(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
log.Printf("url path:%v\n", req.Request.URL)
trace("container_filter_A: before", 1)
chain.ProcessFilter(req, resp)
trace("container_filter_A: after", -1)
}
func container_filter_B(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
trace("container_filter_B: before", 1)
chain.ProcessFilter(req, resp)
trace("container_filter_B: after", -1)
}
func service_filter_A(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
trace("service_filter_A: before", 1)
chain.ProcessFilter(req, resp)
trace("service_filter_A: after", -1)
}
func service_filter_B(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
trace("service_filter_B: before", 1)
chain.ProcessFilter(req, resp)
trace("service_filter_B: after", -1)
}
func route_filter_A(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
trace("route_filter_A: before", 1)
chain.ProcessFilter(req, resp)
trace("route_filter_A: after", -1)
}
func route_filter_B(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
trace("route_filter_B: before", 1)
chain.ProcessFilter(req, resp)
trace("route_filter_B: after", -1)
}
func trace(what string, delta int) {
indented := what
if delta < 0 {
indentLevel += delta
}
for t := 0; t < indentLevel; t++ {
indented = "." + indented
}
log.Printf("%s", indented)
if delta > 0 {
indentLevel += delta
}
}
func main() {
restful.Filter(container_filter_A)
restful.Filter(container_filter_B)
ws1 := new(restful.WebService)
ws1.Path("/1")
ws1.Filter(service_filter_A)
ws1.Filter(service_filter_B)
ws1.Route(ws1.GET("").To(doit1).Filter(route_filter_A).Filter(route_filter_B))
ws2 := new(restful.WebService)
ws2.Path("/2")
ws2.Filter(service_filter_A)
ws2.Filter(service_filter_B)
ws2.Route(ws2.GET("").To(doit2).Filter(route_filter_A).Filter(route_filter_B))
restful.Add(ws1)
restful.Add(ws2)
log.Print("go-restful example listing on http://localhost:8080/1 and http://localhost:8080/2")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func doit1(req *restful.Request, resp *restful.Response) {
io.WriteString(resp, "nothing to see in 1")
}
func doit2(req *restful.Request, resp *restful.Response) {
io.WriteString(resp, "nothing to see in 2")
}

View File

@ -1,63 +0,0 @@
package main
import (
"github.com/emicklei/go-restful"
"log"
"net/http"
)
// This example shows how to use methods as RouteFunctions for WebServices.
// The ProductResource has a Register() method that creates and initializes
// a WebService to expose its methods as REST operations.
// The WebService is added to the restful.DefaultContainer.
// A ProductResource is typically created using some data access object.
//
// GET http://localhost:8080/products/1
// POST http://localhost:8080/products
// <Product><Id>1</Id><Title>The First</Title></Product>
type Product struct {
Id, Title string
}
type ProductResource struct {
// typically reference a DAO (data-access-object)
}
func (p ProductResource) getOne(req *restful.Request, resp *restful.Response) {
id := req.PathParameter("id")
log.Println("getting product with id:" + id)
resp.WriteEntity(Product{Id: id, Title: "test"})
}
func (p ProductResource) postOne(req *restful.Request, resp *restful.Response) {
updatedProduct := new(Product)
err := req.ReadEntity(updatedProduct)
if err != nil { // bad request
resp.WriteErrorString(http.StatusBadRequest, err.Error())
return
}
log.Println("updating product with id:" + updatedProduct.Id)
}
func (p ProductResource) Register() {
ws := new(restful.WebService)
ws.Path("/products")
ws.Consumes(restful.MIME_XML)
ws.Produces(restful.MIME_XML)
ws.Route(ws.GET("/{id}").To(p.getOne).
Doc("get the product by its id").
Param(ws.PathParameter("id", "identifier of the product").DataType("string")))
ws.Route(ws.POST("").To(p.postOne).
Doc("update or create a product").
Param(ws.BodyParameter("Product", "a Product (XML)").DataType("main.Product")))
restful.Add(ws)
}
func main() {
ProductResource{}.Register()
http.ListenAndServe(":8080", nil)
}

View File

@ -1,39 +0,0 @@
package main
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/emicklei/go-restful"
)
var (
Result string
)
func TestRouteExtractParameter(t *testing.T) {
// setup service
ws := new(restful.WebService)
ws.Consumes(restful.MIME_XML)
ws.Route(ws.GET("/test/{param}").To(DummyHandler))
restful.Add(ws)
// setup request + writer
bodyReader := strings.NewReader("<Sample><Value>42</Value></Sample>")
httpRequest, _ := http.NewRequest("GET", "/test/THIS", bodyReader)
httpRequest.Header.Set("Content-Type", restful.MIME_XML)
httpWriter := httptest.NewRecorder()
// run
restful.DefaultContainer.ServeHTTP(httpWriter, httpRequest)
if Result != "THIS" {
t.Fatalf("Result is actually: %s", Result)
}
}
func DummyHandler(rq *restful.Request, rp *restful.Response) {
Result = rq.PathParameter("param")
}

View File

@ -1,29 +0,0 @@
package main
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/emicklei/go-restful"
)
// This example show how to test one particular RouteFunction (getIt)
// It uses the httptest.ResponseRecorder to capture output
func getIt(req *restful.Request, resp *restful.Response) {
resp.WriteHeader(204)
}
func TestCallFunction(t *testing.T) {
httpReq, _ := http.NewRequest("GET", "/", nil)
req := restful.NewRequest(httpReq)
recorder := new(httptest.ResponseRecorder)
resp := restful.NewResponse(recorder)
getIt(req, resp)
if recorder.Code != 204 {
t.Fatalf("Missing or wrong status code:%d", recorder.Code)
}
}

View File

@ -1,47 +0,0 @@
package main
import (
"fmt"
"net/http"
"path"
"github.com/emicklei/go-restful"
)
// This example shows how to define methods that serve static files
// It uses the standard http.ServeFile method
//
// GET http://localhost:8080/static/test.xml
// GET http://localhost:8080/static/
//
// GET http://localhost:8080/static?resource=subdir/test.xml
var rootdir = "/tmp"
func main() {
restful.DefaultContainer.Router(restful.CurlyRouter{})
ws := new(restful.WebService)
ws.Route(ws.GET("/static/{subpath:*}").To(staticFromPathParam))
ws.Route(ws.GET("/static").To(staticFromQueryParam))
restful.Add(ws)
println("[go-restful] serving files on http://localhost:8080/static from local /tmp")
http.ListenAndServe(":8080", nil)
}
func staticFromPathParam(req *restful.Request, resp *restful.Response) {
actual := path.Join(rootdir, req.PathParameter("subpath"))
fmt.Printf("serving %s ... (from %s)\n", actual, req.PathParameter("subpath"))
http.ServeFile(
resp.ResponseWriter,
req.Request,
actual)
}
func staticFromQueryParam(req *restful.Request, resp *restful.Response) {
http.ServeFile(
resp.ResponseWriter,
req.Request,
path.Join(rootdir, req.QueryParameter("resource")))
}

View File

@ -1,61 +0,0 @@
package main
import (
"log"
"net/http"
"github.com/emicklei/go-restful"
"github.com/emicklei/go-restful-swagger12"
)
type Book struct {
Title string
Author string
}
func main() {
ws := new(restful.WebService)
ws.Path("/books")
ws.Consumes(restful.MIME_JSON, restful.MIME_XML)
ws.Produces(restful.MIME_JSON, restful.MIME_XML)
restful.Add(ws)
ws.Route(ws.GET("/{medium}").To(noop).
Doc("Search all books").
Param(ws.PathParameter("medium", "digital or paperback").DataType("string")).
Param(ws.QueryParameter("language", "en,nl,de").DataType("string")).
Param(ws.HeaderParameter("If-Modified-Since", "last known timestamp").DataType("datetime")).
Do(returns200, returns500))
ws.Route(ws.PUT("/{medium}").To(noop).
Doc("Add a new book").
Param(ws.PathParameter("medium", "digital or paperback").DataType("string")).
Reads(Book{}))
// You can install the Swagger Service which provides a nice Web UI on your REST API
// You need to download the Swagger HTML5 assets and change the FilePath location in the config below.
// Open http://localhost:8080/apidocs and enter http://localhost:8080/apidocs.json in the api input field.
config := swagger.Config{
WebServices: restful.DefaultContainer.RegisteredWebServices(), // you control what services are visible
WebServicesUrl: "http://localhost:8080",
ApiPath: "/apidocs.json",
// Optionally, specifiy where the UI is located
SwaggerPath: "/apidocs/",
SwaggerFilePath: "/Users/emicklei/xProjects/swagger-ui/dist"}
swagger.RegisterSwaggerService(config, restful.DefaultContainer)
log.Printf("start listening on localhost:8080")
server := &http.Server{Addr: ":8080", Handler: restful.DefaultContainer}
log.Fatal(server.ListenAndServe())
}
func noop(req *restful.Request, resp *restful.Response) {}
func returns200(b *restful.RouteBuilder) {
b.Returns(http.StatusOK, "OK", Book{})
}
func returns500(b *restful.RouteBuilder) {
b.Returns(http.StatusInternalServerError, "Bummer, something went wrong", nil)
}

View File

@ -1,152 +0,0 @@
package main
import (
"log"
"net/http"
"strconv"
"github.com/emicklei/go-restful"
"github.com/emicklei/go-restful-swagger12"
)
// This example show a complete (GET,PUT,POST,DELETE) conventional example of
// a REST Resource including documentation to be served by e.g. a Swagger UI
// It is recommended to create a Resource struct (UserResource) that can encapsulate
// an object that provide domain access (a DAO)
// It has a Register method including the complete Route mapping to methods together
// with all the appropriate documentation
//
// POST http://localhost:8080/users
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
//
// GET http://localhost:8080/users/1
//
// PUT http://localhost:8080/users/1
// <User><Id>1</Id><Name>Melissa</Name></User>
//
// DELETE http://localhost:8080/users/1
//
type User struct {
Id, Name string
}
type UserResource struct {
// normally one would use DAO (data access object)
users map[string]User
}
func (u UserResource) Register(container *restful.Container) {
ws := new(restful.WebService)
ws.
Path("/users").
Doc("Manage Users").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well
ws.Route(ws.GET("/{user-id}").To(u.findUser).
// docs
Doc("get a user").
Operation("findUser").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
Writes(User{})) // on the response
ws.Route(ws.PUT("/{user-id}").To(u.updateUser).
// docs
Doc("update a user").
Operation("updateUser").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
ReturnsError(409, "duplicate user-id", nil).
Reads(User{})) // from the request
ws.Route(ws.POST("").To(u.createUser).
// docs
Doc("create a user").
Operation("createUser").
Reads(User{})) // from the request
ws.Route(ws.DELETE("/{user-id}").To(u.removeUser).
// docs
Doc("delete a user").
Operation("removeUser").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")))
container.Add(ws)
}
// GET http://localhost:8080/users/1
//
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
id := request.PathParameter("user-id")
usr := u.users[id]
if len(usr.Id) == 0 {
response.AddHeader("Content-Type", "text/plain")
response.WriteErrorString(http.StatusNotFound, "404: User could not be found.")
return
}
response.WriteEntity(usr)
}
// POST http://localhost:8080/users
// <User><Name>Melissa</Name></User>
//
func (u *UserResource) createUser(request *restful.Request, response *restful.Response) {
usr := new(User)
err := request.ReadEntity(usr)
if err != nil {
response.AddHeader("Content-Type", "text/plain")
response.WriteErrorString(http.StatusInternalServerError, err.Error())
return
}
usr.Id = strconv.Itoa(len(u.users) + 1) // simple id generation
u.users[usr.Id] = *usr
response.WriteHeaderAndEntity(http.StatusCreated, usr)
}
// PUT http://localhost:8080/users/1
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
//
func (u *UserResource) updateUser(request *restful.Request, response *restful.Response) {
usr := new(User)
err := request.ReadEntity(&usr)
if err != nil {
response.AddHeader("Content-Type", "text/plain")
response.WriteErrorString(http.StatusInternalServerError, err.Error())
return
}
u.users[usr.Id] = *usr
response.WriteEntity(usr)
}
// DELETE http://localhost:8080/users/1
//
func (u *UserResource) removeUser(request *restful.Request, response *restful.Response) {
id := request.PathParameter("user-id")
delete(u.users, id)
}
func main() {
// to see what happens in the package, uncomment the following
//restful.TraceLogger(log.New(os.Stdout, "[restful] ", log.LstdFlags|log.Lshortfile))
wsContainer := restful.NewContainer()
u := UserResource{map[string]User{}}
u.Register(wsContainer)
// Optionally, you can install the Swagger Service which provides a nice Web UI on your REST API
// You need to download the Swagger HTML5 assets and change the FilePath location in the config below.
// Open http://localhost:8080/apidocs and enter http://localhost:8080/apidocs.json in the api input field.
config := swagger.Config{
WebServices: wsContainer.RegisteredWebServices(), // you control what services are visible
WebServicesUrl: "http://localhost:8080",
ApiPath: "/apidocs.json",
// Optionally, specifiy where the UI is located
SwaggerPath: "/apidocs/",
SwaggerFilePath: "/Users/emicklei/xProjects/swagger-ui/dist"}
swagger.RegisterSwaggerService(config, wsContainer)
log.Printf("start listening on localhost:8080")
server := &http.Server{Addr: ":8080", Handler: wsContainer}
log.Fatal(server.ListenAndServe())
}

View File

@ -1,143 +0,0 @@
package main
import (
"log"
"net/http"
"github.com/emicklei/go-restful"
"github.com/emicklei/go-restful-swagger12"
)
// This example is functionally the same as the example in restful-user-resource.go
// with the only difference that is served using the restful.DefaultContainer
type User struct {
Id, Name string
}
type UserService struct {
// normally one would use DAO (data access object)
users map[string]User
}
func (u UserService) Register() {
ws := new(restful.WebService)
ws.
Path("/users").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well
ws.Route(ws.GET("/").To(u.findAllUsers).
// docs
Doc("get all users").
Operation("findAllUsers").
Writes([]User{}).
Returns(200, "OK", nil))
ws.Route(ws.GET("/{user-id}").To(u.findUser).
// docs
Doc("get a user").
Operation("findUser").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
Writes(User{}). // on the response
Returns(404, "Not Found", nil))
ws.Route(ws.PUT("/{user-id}").To(u.updateUser).
// docs
Doc("update a user").
Operation("updateUser").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
Reads(User{})) // from the request
ws.Route(ws.PUT("").To(u.createUser).
// docs
Doc("create a user").
Operation("createUser").
Reads(User{})) // from the request
ws.Route(ws.DELETE("/{user-id}").To(u.removeUser).
// docs
Doc("delete a user").
Operation("removeUser").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")))
restful.Add(ws)
}
// GET http://localhost:8080/users
//
func (u UserService) findAllUsers(request *restful.Request, response *restful.Response) {
list := []User{}
for _, each := range u.users {
list = append(list, each)
}
response.WriteEntity(list)
}
// GET http://localhost:8080/users/1
//
func (u UserService) findUser(request *restful.Request, response *restful.Response) {
id := request.PathParameter("user-id")
usr := u.users[id]
if len(usr.Id) == 0 {
response.WriteErrorString(http.StatusNotFound, "User could not be found.")
} else {
response.WriteEntity(usr)
}
}
// PUT http://localhost:8080/users/1
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
//
func (u *UserService) updateUser(request *restful.Request, response *restful.Response) {
usr := new(User)
err := request.ReadEntity(&usr)
if err == nil {
u.users[usr.Id] = *usr
response.WriteEntity(usr)
} else {
response.WriteError(http.StatusInternalServerError, err)
}
}
// PUT http://localhost:8080/users/1
// <User><Id>1</Id><Name>Melissa</Name></User>
//
func (u *UserService) createUser(request *restful.Request, response *restful.Response) {
usr := User{Id: request.PathParameter("user-id")}
err := request.ReadEntity(&usr)
if err == nil {
u.users[usr.Id] = usr
response.WriteHeaderAndEntity(http.StatusCreated, usr)
} else {
response.WriteError(http.StatusInternalServerError, err)
}
}
// DELETE http://localhost:8080/users/1
//
func (u *UserService) removeUser(request *restful.Request, response *restful.Response) {
id := request.PathParameter("user-id")
delete(u.users, id)
}
func main() {
u := UserService{map[string]User{}}
u.Register()
// Optionally, you can install the Swagger Service which provides a nice Web UI on your REST API
// You need to download the Swagger HTML5 assets and change the FilePath location in the config below.
// Open http://localhost:8080/apidocs and enter http://localhost:8080/apidocs.json in the api input field.
config := swagger.Config{
WebServices: restful.RegisteredWebServices(), // you control what services are visible
WebServicesUrl: "http://localhost:8080",
ApiPath: "/apidocs.json",
// Optionally, specifiy where the UI is located
SwaggerPath: "/apidocs/",
SwaggerFilePath: "/Users/emicklei/Projects/swagger-ui/dist"}
swagger.InstallSwaggerService(config)
log.Printf("start listening on localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}

View File

@ -1,141 +0,0 @@
package restful
import (
"io"
"net/http"
"net/http/httptest"
"testing"
)
func setupServices(addGlobalFilter bool, addServiceFilter bool, addRouteFilter bool) {
if addGlobalFilter {
Filter(globalFilter)
}
Add(newTestService(addServiceFilter, addRouteFilter))
}
func tearDown() {
DefaultContainer.webServices = []*WebService{}
DefaultContainer.isRegisteredOnRoot = true // this allows for setupServices multiple times
DefaultContainer.containerFilters = []FilterFunction{}
}
func newTestService(addServiceFilter bool, addRouteFilter bool) *WebService {
ws := new(WebService).Path("")
if addServiceFilter {
ws.Filter(serviceFilter)
}
rb := ws.GET("/foo").To(foo)
if addRouteFilter {
rb.Filter(routeFilter)
}
ws.Route(rb)
ws.Route(ws.GET("/bar").To(bar))
return ws
}
func foo(req *Request, resp *Response) {
io.WriteString(resp.ResponseWriter, "foo")
}
func bar(req *Request, resp *Response) {
io.WriteString(resp.ResponseWriter, "bar")
}
func fail(req *Request, resp *Response) {
http.Error(resp.ResponseWriter, "something failed", http.StatusInternalServerError)
}
func globalFilter(req *Request, resp *Response, chain *FilterChain) {
io.WriteString(resp.ResponseWriter, "global-")
chain.ProcessFilter(req, resp)
}
func serviceFilter(req *Request, resp *Response, chain *FilterChain) {
io.WriteString(resp.ResponseWriter, "service-")
chain.ProcessFilter(req, resp)
}
func routeFilter(req *Request, resp *Response, chain *FilterChain) {
io.WriteString(resp.ResponseWriter, "route-")
chain.ProcessFilter(req, resp)
}
func TestNoFilter(t *testing.T) {
tearDown()
setupServices(false, false, false)
actual := sendIt("http://example.com/foo")
if "foo" != actual {
t.Fatal("expected: foo but got:" + actual)
}
}
func TestGlobalFilter(t *testing.T) {
tearDown()
setupServices(true, false, false)
actual := sendIt("http://example.com/foo")
if "global-foo" != actual {
t.Fatal("expected: global-foo but got:" + actual)
}
}
func TestWebServiceFilter(t *testing.T) {
tearDown()
setupServices(true, true, false)
actual := sendIt("http://example.com/foo")
if "global-service-foo" != actual {
t.Fatal("expected: global-service-foo but got:" + actual)
}
}
func TestRouteFilter(t *testing.T) {
tearDown()
setupServices(true, true, true)
actual := sendIt("http://example.com/foo")
if "global-service-route-foo" != actual {
t.Fatal("expected: global-service-route-foo but got:" + actual)
}
}
func TestRouteFilterOnly(t *testing.T) {
tearDown()
setupServices(false, false, true)
actual := sendIt("http://example.com/foo")
if "route-foo" != actual {
t.Fatal("expected: route-foo but got:" + actual)
}
}
func TestBar(t *testing.T) {
tearDown()
setupServices(false, true, false)
actual := sendIt("http://example.com/bar")
if "service-bar" != actual {
t.Fatal("expected: service-bar but got:" + actual)
}
}
func TestAllFiltersBar(t *testing.T) {
tearDown()
setupServices(true, true, true)
actual := sendIt("http://example.com/bar")
if "global-service-bar" != actual {
t.Fatal("expected: global-service-bar but got:" + actual)
}
}
func sendIt(address string) string {
httpRequest, _ := http.NewRequest("GET", address, nil)
httpRequest.Header.Set("Accept", "*/*")
httpWriter := httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
return httpWriter.Body.String()
}
func sendItTo(address string, container *Container) string {
httpRequest, _ := http.NewRequest("GET", address, nil)
httpRequest.Header.Set("Accept", "*/*")
httpWriter := httptest.NewRecorder()
container.dispatch(httpWriter, httpRequest)
return httpWriter.Body.String()
}

View File

@ -1,212 +0,0 @@
package restful
import (
"io"
"sort"
"testing"
)
//
// Step 1 tests
//
var paths = []struct {
// url with path (1) is handled by service with root (2) and last capturing group has value final (3)
path, root, final string
}{
{"/", "/", "/"},
{"/p", "/p", ""},
{"/p/x", "/p/{q}", ""},
{"/q/x", "/q", "/x"},
{"/p/x/", "/p/{q}", "/"},
{"/p/x/y", "/p/{q}", "/y"},
{"/q/x/y", "/q", "/x/y"},
{"/z/q", "/{p}/q", ""},
{"/a/b/c/q", "/", "/a/b/c/q"},
}
func TestDetectDispatcher(t *testing.T) {
ws1 := new(WebService).Path("/")
ws2 := new(WebService).Path("/p")
ws3 := new(WebService).Path("/q")
ws4 := new(WebService).Path("/p/q")
ws5 := new(WebService).Path("/p/{q}")
ws6 := new(WebService).Path("/p/{q}/")
ws7 := new(WebService).Path("/{p}/q")
var dispatchers = []*WebService{ws1, ws2, ws3, ws4, ws5, ws6, ws7}
wc := NewContainer()
for _, each := range dispatchers {
wc.Add(each)
}
router := RouterJSR311{}
ok := true
for i, fixture := range paths {
who, final, err := router.detectDispatcher(fixture.path, dispatchers)
if err != nil {
t.Logf("error in detection:%v", err)
ok = false
}
if who.RootPath() != fixture.root {
t.Logf("[line:%v] Unexpected dispatcher, expected:%v, actual:%v", i, fixture.root, who.RootPath())
ok = false
}
if final != fixture.final {
t.Logf("[line:%v] Unexpected final, expected:%v, actual:%v", i, fixture.final, final)
ok = false
}
}
if !ok {
t.Fail()
}
}
//
// Step 2 tests
//
// go test -v -test.run TestISSUE_179 ...restful
func TestISSUE_179(t *testing.T) {
ws1 := new(WebService)
ws1.Route(ws1.GET("/v1/category/{param:*}").To(dummy))
routes := RouterJSR311{}.selectRoutes(ws1, "/v1/category/sub/sub")
t.Logf("%v", routes)
}
// go test -v -test.run TestISSUE_30 ...restful
func TestISSUE_30(t *testing.T) {
ws1 := new(WebService).Path("/users")
ws1.Route(ws1.GET("/{id}").To(dummy))
ws1.Route(ws1.POST("/login").To(dummy))
routes := RouterJSR311{}.selectRoutes(ws1, "/login")
if len(routes) != 2 {
t.Fatal("expected 2 routes")
}
if routes[0].Path != "/users/login" {
t.Error("first is", routes[0].Path)
t.Logf("routes:%v", routes)
}
}
// go test -v -test.run TestISSUE_34 ...restful
func TestISSUE_34(t *testing.T) {
ws1 := new(WebService).Path("/")
ws1.Route(ws1.GET("/{type}/{id}").To(dummy))
ws1.Route(ws1.GET("/network/{id}").To(dummy))
routes := RouterJSR311{}.selectRoutes(ws1, "/network/12")
if len(routes) != 2 {
t.Fatal("expected 2 routes")
}
if routes[0].Path != "/network/{id}" {
t.Error("first is", routes[0].Path)
t.Logf("routes:%v", routes)
}
}
// go test -v -test.run TestISSUE_34_2 ...restful
func TestISSUE_34_2(t *testing.T) {
ws1 := new(WebService).Path("/")
// change the registration order
ws1.Route(ws1.GET("/network/{id}").To(dummy))
ws1.Route(ws1.GET("/{type}/{id}").To(dummy))
routes := RouterJSR311{}.selectRoutes(ws1, "/network/12")
if len(routes) != 2 {
t.Fatal("expected 2 routes")
}
if routes[0].Path != "/network/{id}" {
t.Error("first is", routes[0].Path)
}
}
// go test -v -test.run TestISSUE_137 ...restful
func TestISSUE_137(t *testing.T) {
ws1 := new(WebService)
ws1.Route(ws1.GET("/hello").To(dummy))
routes := RouterJSR311{}.selectRoutes(ws1, "/")
t.Log(routes)
if len(routes) > 0 {
t.Error("no route expected")
}
}
func TestSelectRoutesSlash(t *testing.T) {
ws1 := new(WebService).Path("/")
ws1.Route(ws1.GET("").To(dummy))
ws1.Route(ws1.GET("/").To(dummy))
ws1.Route(ws1.GET("/u").To(dummy))
ws1.Route(ws1.POST("/u").To(dummy))
ws1.Route(ws1.POST("/u/v").To(dummy))
ws1.Route(ws1.POST("/u/{w}").To(dummy))
ws1.Route(ws1.POST("/u/{w}/z").To(dummy))
routes := RouterJSR311{}.selectRoutes(ws1, "/u")
checkRoutesContains(routes, "/u", t)
checkRoutesContainsNo(routes, "/u/v", t)
checkRoutesContainsNo(routes, "/", t)
checkRoutesContainsNo(routes, "/u/{w}/z", t)
}
func TestSelectRoutesU(t *testing.T) {
ws1 := new(WebService).Path("/u")
ws1.Route(ws1.GET("").To(dummy))
ws1.Route(ws1.GET("/").To(dummy))
ws1.Route(ws1.GET("/v").To(dummy))
ws1.Route(ws1.POST("/{w}").To(dummy))
ws1.Route(ws1.POST("/{w}/z").To(dummy)) // so full path = /u/{w}/z
routes := RouterJSR311{}.selectRoutes(ws1, "/v") // test against /u/v
checkRoutesContains(routes, "/u/{w}", t)
}
func TestSelectRoutesUsers1(t *testing.T) {
ws1 := new(WebService).Path("/users")
ws1.Route(ws1.POST("").To(dummy))
ws1.Route(ws1.POST("/").To(dummy))
ws1.Route(ws1.PUT("/{id}").To(dummy))
routes := RouterJSR311{}.selectRoutes(ws1, "/1")
checkRoutesContains(routes, "/users/{id}", t)
}
func checkRoutesContains(routes []Route, path string, t *testing.T) {
if !containsRoutePath(routes, path, t) {
for _, r := range routes {
t.Logf("route %v %v", r.Method, r.Path)
}
t.Fatalf("routes should include [%v]:", path)
}
}
func checkRoutesContainsNo(routes []Route, path string, t *testing.T) {
if containsRoutePath(routes, path, t) {
for _, r := range routes {
t.Logf("route %v %v", r.Method, r.Path)
}
t.Fatalf("routes should not include [%v]:", path)
}
}
func containsRoutePath(routes []Route, path string, t *testing.T) bool {
for _, each := range routes {
if each.Path == path {
return true
}
}
return false
}
// go test -v -test.run TestSortableRouteCandidates ...restful
func TestSortableRouteCandidates(t *testing.T) {
fixture := &sortableRouteCandidates{}
r1 := routeCandidate{matchesCount: 0, literalCount: 0, nonDefaultCount: 0}
r2 := routeCandidate{matchesCount: 0, literalCount: 0, nonDefaultCount: 1}
r3 := routeCandidate{matchesCount: 0, literalCount: 1, nonDefaultCount: 1}
r4 := routeCandidate{matchesCount: 1, literalCount: 1, nonDefaultCount: 0}
r5 := routeCandidate{matchesCount: 1, literalCount: 0, nonDefaultCount: 0}
fixture.candidates = append(fixture.candidates, r5, r4, r3, r2, r1)
sort.Sort(sort.Reverse(fixture))
first := fixture.candidates[0]
if first.matchesCount != 1 && first.literalCount != 1 && first.nonDefaultCount != 0 {
t.Fatal("expected r4")
}
last := fixture.candidates[len(fixture.candidates)-1]
if last.matchesCount != 0 && last.literalCount != 0 && last.nonDefaultCount != 0 {
t.Fatal("expected r1")
}
}
func dummy(req *Request, resp *Response) { io.WriteString(resp.ResponseWriter, "dummy") }

View File

@ -1,17 +0,0 @@
package restful
import (
"fmt"
"testing"
)
// go test -v -test.run TestSortMimes ...restful
func TestSortMimes(t *testing.T) {
accept := "text/html; q=0.8, text/plain, image/gif, */*; q=0.01, image/jpeg"
result := sortedMimes(accept)
got := fmt.Sprintf("%v", result)
want := "[{text/plain 1} {image/gif 1} {image/jpeg 1} {text/html 0.8} {*/* 0.01}]"
if got != want {
t.Errorf("bad sort order of mime types:%s", got)
}
}

View File

@ -1,34 +0,0 @@
package restful
import (
"net/http"
"net/http/httptest"
"testing"
)
// go test -v -test.run TestOptionsFilter ...restful
func TestOptionsFilter(t *testing.T) {
tearDown()
ws := new(WebService)
ws.Route(ws.GET("/candy/{kind}").To(dummy))
ws.Route(ws.DELETE("/candy/{kind}").To(dummy))
ws.Route(ws.POST("/candies").To(dummy))
Add(ws)
Filter(OPTIONSFilter())
httpRequest, _ := http.NewRequest("OPTIONS", "http://here.io/candy/gum", nil)
httpWriter := httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
actual := httpWriter.Header().Get(HEADER_Allow)
if "GET,DELETE" != actual {
t.Fatal("expected: GET,DELETE but got:" + actual)
}
httpRequest, _ = http.NewRequest("OPTIONS", "http://here.io/candies", nil)
httpWriter = httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
actual = httpWriter.Header().Get(HEADER_Allow)
if "POST" != actual {
t.Fatal("expected: POST but got:" + actual)
}
}

View File

@ -1,37 +0,0 @@
package restful
import "testing"
var tempregexs = []struct {
template, regex string
literalCount, varCount int
}{
{"", "^(/.*)?$", 0, 0},
{"/a/{b}/c/", "^/a/([^/]+?)/c(/.*)?$", 2, 1},
{"/{a}/{b}/{c-d-e}/", "^/([^/]+?)/([^/]+?)/([^/]+?)(/.*)?$", 0, 3},
{"/{p}/abcde", "^/([^/]+?)/abcde(/.*)?$", 5, 1},
{"/a/{b:*}", "^/a/(.*)(/.*)?$", 1, 1},
{"/a/{b:[a-z]+}", "^/a/([a-z]+)(/.*)?$", 1, 1},
}
func TestTemplateToRegularExpression(t *testing.T) {
ok := true
for i, fixture := range tempregexs {
actual, lCount, vCount, _ := templateToRegularExpression(fixture.template)
if actual != fixture.regex {
t.Logf("regex mismatch, expected:%v , actual:%v, line:%v\n", fixture.regex, actual, i) // 11 = where the data starts
ok = false
}
if lCount != fixture.literalCount {
t.Logf("literal count mismatch, expected:%v , actual:%v, line:%v\n", fixture.literalCount, lCount, i)
ok = false
}
if vCount != fixture.varCount {
t.Logf("variable count mismatch, expected:%v , actual:%v, line:%v\n", fixture.varCount, vCount, i)
ok = false
}
}
if !ok {
t.Fatal("one or more expression did not match")
}
}

View File

@ -1,141 +0,0 @@
package restful
import (
"encoding/json"
"net/http"
"net/url"
"strconv"
"strings"
"testing"
)
func TestQueryParameter(t *testing.T) {
hreq := http.Request{Method: "GET"}
hreq.URL, _ = url.Parse("http://www.google.com/search?q=foo&q=bar")
rreq := Request{Request: &hreq}
if rreq.QueryParameter("q") != "foo" {
t.Errorf("q!=foo %#v", rreq)
}
}
type Anything map[string]interface{}
type Number struct {
ValueFloat float64
ValueInt int64
}
type Sample struct {
Value string
}
func TestReadEntityJson(t *testing.T) {
bodyReader := strings.NewReader(`{"Value" : "42"}`)
httpRequest, _ := http.NewRequest("GET", "/test", bodyReader)
httpRequest.Header.Set("Content-Type", "application/json")
request := &Request{Request: httpRequest}
sam := new(Sample)
request.ReadEntity(sam)
if sam.Value != "42" {
t.Fatal("read failed")
}
}
func TestReadEntityJsonCharset(t *testing.T) {
bodyReader := strings.NewReader(`{"Value" : "42"}`)
httpRequest, _ := http.NewRequest("GET", "/test", bodyReader)
httpRequest.Header.Set("Content-Type", "application/json; charset=UTF-8")
request := NewRequest(httpRequest)
sam := new(Sample)
request.ReadEntity(sam)
if sam.Value != "42" {
t.Fatal("read failed")
}
}
func TestReadEntityJsonNumber(t *testing.T) {
bodyReader := strings.NewReader(`{"Value" : 4899710515899924123}`)
httpRequest, _ := http.NewRequest("GET", "/test", bodyReader)
httpRequest.Header.Set("Content-Type", "application/json")
request := &Request{Request: httpRequest}
any := make(Anything)
request.ReadEntity(&any)
number, ok := any["Value"].(json.Number)
if !ok {
t.Fatal("read failed")
}
vint, err := number.Int64()
if err != nil {
t.Fatal("convert failed")
}
if vint != 4899710515899924123 {
t.Fatal("read failed")
}
vfloat, err := number.Float64()
if err != nil {
t.Fatal("convert failed")
}
// match the default behaviour
vstring := strconv.FormatFloat(vfloat, 'e', 15, 64)
if vstring != "4.899710515899924e+18" {
t.Fatal("convert float64 failed")
}
}
func TestReadEntityJsonLong(t *testing.T) {
bodyReader := strings.NewReader(`{"ValueFloat" : 4899710515899924123, "ValueInt": 4899710515899924123}`)
httpRequest, _ := http.NewRequest("GET", "/test", bodyReader)
httpRequest.Header.Set("Content-Type", "application/json")
request := &Request{Request: httpRequest}
number := new(Number)
request.ReadEntity(&number)
if number.ValueInt != 4899710515899924123 {
t.Fatal("read failed")
}
// match the default behaviour
vstring := strconv.FormatFloat(number.ValueFloat, 'e', 15, 64)
if vstring != "4.899710515899924e+18" {
t.Fatal("convert float64 failed")
}
}
func TestBodyParameter(t *testing.T) {
bodyReader := strings.NewReader(`value1=42&value2=43`)
httpRequest, _ := http.NewRequest("POST", "/test?value1=44", bodyReader) // POST and PUT body parameters take precedence over URL query string
httpRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
request := NewRequest(httpRequest)
v1, err := request.BodyParameter("value1")
if err != nil {
t.Error(err)
}
v2, err := request.BodyParameter("value2")
if err != nil {
t.Error(err)
}
if v1 != "42" || v2 != "43" {
t.Fatal("read failed")
}
}
func TestReadEntityUnkown(t *testing.T) {
bodyReader := strings.NewReader("?")
httpRequest, _ := http.NewRequest("GET", "/test", bodyReader)
httpRequest.Header.Set("Content-Type", "application/rubbish")
request := NewRequest(httpRequest)
sam := new(Sample)
err := request.ReadEntity(sam)
if err == nil {
t.Fatal("read should be in error")
}
}
func TestSetAttribute(t *testing.T) {
bodyReader := strings.NewReader("?")
httpRequest, _ := http.NewRequest("GET", "/test", bodyReader)
request := NewRequest(httpRequest)
request.SetAttribute("go", "there")
there := request.Attribute("go")
if there != "there" {
t.Fatalf("missing request attribute:%v", there)
}
}

View File

@ -1,213 +0,0 @@
package restful
import (
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestWriteHeader(t *testing.T) {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil}
resp.WriteHeader(123)
if resp.StatusCode() != 123 {
t.Errorf("Unexpected status code:%d", resp.StatusCode())
}
}
func TestNoWriteHeader(t *testing.T) {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil}
if resp.StatusCode() != http.StatusOK {
t.Errorf("Unexpected status code:%d", resp.StatusCode())
}
}
type food struct {
Kind string
}
// go test -v -test.run TestMeasureContentLengthXml ...restful
func TestMeasureContentLengthXml(t *testing.T) {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil}
resp.WriteAsXml(food{"apple"})
if resp.ContentLength() != 76 {
t.Errorf("Incorrect measured length:%d", resp.ContentLength())
}
}
// go test -v -test.run TestMeasureContentLengthJson ...restful
func TestMeasureContentLengthJson(t *testing.T) {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil}
resp.WriteAsJson(food{"apple"})
if resp.ContentLength() != 22 {
t.Errorf("Incorrect measured length:%d", resp.ContentLength())
}
}
// go test -v -test.run TestMeasureContentLengthJsonNotPretty ...restful
func TestMeasureContentLengthJsonNotPretty(t *testing.T) {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, false, nil}
resp.WriteAsJson(food{"apple"})
if resp.ContentLength() != 17 { // 16+1 using the Encoder directly yields another /n
t.Errorf("Incorrect measured length:%d", resp.ContentLength())
}
}
// go test -v -test.run TestMeasureContentLengthWriteErrorString ...restful
func TestMeasureContentLengthWriteErrorString(t *testing.T) {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil}
resp.WriteErrorString(404, "Invalid")
if resp.ContentLength() != len("Invalid") {
t.Errorf("Incorrect measured length:%d", resp.ContentLength())
}
}
// go test -v -test.run TestStatusIsPassedToResponse ...restful
func TestStatusIsPassedToResponse(t *testing.T) {
for _, each := range []struct {
write, read int
}{
{write: 204, read: 204},
{write: 304, read: 304},
{write: 200, read: 200},
{write: 400, read: 400},
} {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil}
resp.WriteHeader(each.write)
if got, want := httpWriter.Code, each.read; got != want {
t.Errorf("got %v want %v", got, want)
}
}
}
// go test -v -test.run TestStatusCreatedAndContentTypeJson_Issue54 ...restful
func TestStatusCreatedAndContentTypeJson_Issue54(t *testing.T) {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "application/json", []string{"application/json"}, 0, 0, true, nil}
resp.WriteHeader(201)
resp.WriteAsJson(food{"Juicy"})
if httpWriter.HeaderMap.Get("Content-Type") != "application/json" {
t.Errorf("Expected content type json but got:%s", httpWriter.HeaderMap.Get("Content-Type"))
}
if httpWriter.Code != 201 {
t.Errorf("Expected status 201 but got:%d", httpWriter.Code)
}
}
type errorOnWriteRecorder struct {
*httptest.ResponseRecorder
}
func (e errorOnWriteRecorder) Write(bytes []byte) (int, error) {
return 0, errors.New("fail")
}
// go test -v -test.run TestLastWriteErrorCaught ...restful
func TestLastWriteErrorCaught(t *testing.T) {
httpWriter := errorOnWriteRecorder{httptest.NewRecorder()}
resp := Response{httpWriter, "application/json", []string{"application/json"}, 0, 0, true, nil}
err := resp.WriteAsJson(food{"Juicy"})
if err.Error() != "fail" {
t.Errorf("Unexpected error message:%v", err)
}
}
// go test -v -test.run TestAcceptStarStar_Issue83 ...restful
func TestAcceptStarStar_Issue83(t *testing.T) {
httpWriter := httptest.NewRecorder()
// Accept Produces
resp := Response{httpWriter, "application/bogus,*/*;q=0.8", []string{"application/json"}, 0, 0, true, nil}
resp.WriteEntity(food{"Juicy"})
ct := httpWriter.Header().Get("Content-Type")
if "application/json" != ct {
t.Errorf("Unexpected content type:%s", ct)
}
}
// go test -v -test.run TestAcceptSkipStarStar_Issue83 ...restful
func TestAcceptSkipStarStar_Issue83(t *testing.T) {
httpWriter := httptest.NewRecorder()
// Accept Produces
resp := Response{httpWriter, " application/xml ,*/* ; q=0.8", []string{"application/json", "application/xml"}, 0, 0, true, nil}
resp.WriteEntity(food{"Juicy"})
ct := httpWriter.Header().Get("Content-Type")
if "application/xml" != ct {
t.Errorf("Unexpected content type:%s", ct)
}
}
// go test -v -test.run TestAcceptXmlBeforeStarStar_Issue83 ...restful
func TestAcceptXmlBeforeStarStar_Issue83(t *testing.T) {
httpWriter := httptest.NewRecorder()
// Accept Produces
resp := Response{httpWriter, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", []string{"application/json"}, 0, 0, true, nil}
resp.WriteEntity(food{"Juicy"})
ct := httpWriter.Header().Get("Content-Type")
if "application/json" != ct {
t.Errorf("Unexpected content type:%s", ct)
}
}
// go test -v -test.run TestWriteHeaderNoContent_Issue124 ...restful
func TestWriteHeaderNoContent_Issue124(t *testing.T) {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "text/plain", []string{"text/plain"}, 0, 0, true, nil}
resp.WriteHeader(http.StatusNoContent)
if httpWriter.Code != http.StatusNoContent {
t.Errorf("got %d want %d", httpWriter.Code, http.StatusNoContent)
}
}
// go test -v -test.run TestStatusCreatedAndContentTypeJson_Issue163 ...restful
func TestStatusCreatedAndContentTypeJson_Issue163(t *testing.T) {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "application/json", []string{"application/json"}, 0, 0, true, nil}
resp.WriteHeader(http.StatusNotModified)
if httpWriter.Code != http.StatusNotModified {
t.Errorf("Got %d want %d", httpWriter.Code, http.StatusNotModified)
}
}
func TestWriteHeaderAndEntity_Issue235(t *testing.T) {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "application/json", []string{"application/json"}, 0, 0, true, nil}
var pong = struct {
Foo string `json:"foo"`
}{Foo: "123"}
resp.WriteHeaderAndEntity(404, pong)
if httpWriter.Code != http.StatusNotFound {
t.Errorf("got %d want %d", httpWriter.Code, http.StatusNoContent)
}
if got, want := httpWriter.Header().Get("Content-Type"), "application/json"; got != want {
t.Errorf("got %v want %v", got, want)
}
if !strings.HasPrefix(httpWriter.Body.String(), "{") {
t.Errorf("expected pong struct in json:%s", httpWriter.Body.String())
}
}
func TestWriteEntityNoAcceptMatchWithProduces(t *testing.T) {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "application/bogus", []string{"application/json"}, 0, 0, true, nil}
resp.WriteEntity("done")
if httpWriter.Code != http.StatusOK {
t.Errorf("got %d want %d", httpWriter.Code, http.StatusOK)
}
}
func TestWriteEntityNoAcceptMatchNoProduces(t *testing.T) {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "application/bogus", []string{}, 0, 0, true, nil}
resp.WriteEntity("done")
if httpWriter.Code != http.StatusNotAcceptable {
t.Errorf("got %d want %d", httpWriter.Code, http.StatusNotAcceptable)
}
}

View File

@ -1,76 +0,0 @@
package restful
import (
"testing"
"time"
)
func TestRouteBuilder_PathParameter(t *testing.T) {
p := &Parameter{&ParameterData{Name: "name", Description: "desc"}}
p.AllowMultiple(true)
p.DataType("int")
p.Required(true)
values := map[string]string{"a": "b"}
p.AllowableValues(values)
p.bePath()
b := new(RouteBuilder)
b.function = dummy
b.Param(p)
r := b.Build()
if !r.ParameterDocs[0].Data().AllowMultiple {
t.Error("AllowMultiple invalid")
}
if r.ParameterDocs[0].Data().DataType != "int" {
t.Error("dataType invalid")
}
if !r.ParameterDocs[0].Data().Required {
t.Error("required invalid")
}
if r.ParameterDocs[0].Data().Kind != PathParameterKind {
t.Error("kind invalid")
}
if r.ParameterDocs[0].Data().AllowableValues["a"] != "b" {
t.Error("allowableValues invalid")
}
if b.ParameterNamed("name") == nil {
t.Error("access to parameter failed")
}
}
func TestRouteBuilder(t *testing.T) {
json := "application/json"
b := new(RouteBuilder)
b.To(dummy)
b.Path("/routes").Method("HEAD").Consumes(json).Produces(json).Metadata("test", "test-value").DefaultReturns("default", time.Now())
r := b.Build()
if r.Path != "/routes" {
t.Error("path invalid")
}
if r.Produces[0] != json {
t.Error("produces invalid")
}
if r.Consumes[0] != json {
t.Error("consumes invalid")
}
if r.Operation != "dummy" {
t.Error("Operation not set")
}
if r.Metadata["test"] != "test-value" {
t.Errorf("Metadata not set")
}
if _, ok := r.ResponseErrors[0]; !ok {
t.Fatal("expected default response")
}
}
func TestAnonymousFuncNaming(t *testing.T) {
f1 := func() {}
f2 := func() {}
if got, want := nameOfFunction(f1), "func1"; got != want {
t.Errorf("got %v want %v", got, want)
}
if got, want := nameOfFunction(f2), "func2"; got != want {
t.Errorf("got %v want %v", got, want)
}
}

View File

@ -1,127 +0,0 @@
package restful
import (
"testing"
)
// accept should match produces
func TestMatchesAcceptPlainTextWhenProducePlainTextAsLast(t *testing.T) {
r := Route{Produces: []string{"application/json", "text/plain"}}
if !r.matchesAccept("text/plain") {
t.Errorf("accept should match text/plain")
}
}
// accept should match produces
func TestMatchesAcceptStar(t *testing.T) {
r := Route{Produces: []string{"application/xml"}}
if !r.matchesAccept("*/*") {
t.Errorf("accept should match star")
}
}
// accept should match produces
func TestMatchesAcceptIE(t *testing.T) {
r := Route{Produces: []string{"application/xml"}}
if !r.matchesAccept("text/html, application/xhtml+xml, */*") {
t.Errorf("accept should match star")
}
}
// accept should match produces
func TestMatchesAcceptXml(t *testing.T) {
r := Route{Produces: []string{"application/xml"}}
if r.matchesAccept("application/json") {
t.Errorf("accept should not match json")
}
if !r.matchesAccept("application/xml") {
t.Errorf("accept should match xml")
}
}
// accept should match produces
func TestMatchesAcceptAny(t *testing.T) {
r := Route{Produces: []string{"*/*"}}
if !r.matchesAccept("application/json") {
t.Errorf("accept should match json")
}
if !r.matchesAccept("application/xml") {
t.Errorf("accept should match xml")
}
}
// content type should match consumes
func TestMatchesContentTypeXml(t *testing.T) {
r := Route{Consumes: []string{"application/xml"}}
if r.matchesContentType("application/json") {
t.Errorf("accept should not match json")
}
if !r.matchesContentType("application/xml") {
t.Errorf("accept should match xml")
}
}
// content type should match consumes
func TestMatchesContentTypeCharsetInformation(t *testing.T) {
r := Route{Consumes: []string{"application/json"}}
if !r.matchesContentType("application/json; charset=UTF-8") {
t.Errorf("matchesContentType should ignore charset information")
}
}
func TestMatchesPath_OneParam(t *testing.T) {
params := doExtractParams("/from/{source}", 2, "/from/here", t)
if params["source"] != "here" {
t.Errorf("parameter mismatch here")
}
}
func TestMatchesPath_Slash(t *testing.T) {
params := doExtractParams("/", 0, "/", t)
if len(params) != 0 {
t.Errorf("expected empty parameters")
}
}
func TestMatchesPath_SlashNonVar(t *testing.T) {
params := doExtractParams("/any", 1, "/any", t)
if len(params) != 0 {
t.Errorf("expected empty parameters")
}
}
func TestMatchesPath_TwoVars(t *testing.T) {
params := doExtractParams("/from/{source}/to/{destination}", 4, "/from/AMS/to/NY", t)
if params["source"] != "AMS" {
t.Errorf("parameter mismatch AMS")
}
}
func TestMatchesPath_VarOnFront(t *testing.T) {
params := doExtractParams("{what}/from/{source}/", 3, "who/from/SOS/", t)
if params["source"] != "SOS" {
t.Errorf("parameter mismatch SOS")
}
}
func TestExtractParameters_EmptyValue(t *testing.T) {
params := doExtractParams("/fixed/{var}", 2, "/fixed/", t)
if params["var"] != "" {
t.Errorf("parameter mismatch var")
}
}
func TestTokenizePath(t *testing.T) {
if len(tokenizePath("/")) != 0 {
t.Errorf("not empty path tokens")
}
}
func doExtractParams(routePath string, size int, urlPath string, t *testing.T) map[string]string {
r := Route{Path: routePath}
r.postBuild()
if len(r.pathParts) != size {
t.Fatalf("len not %v %v, but %v", size, r.pathParts, len(r.pathParts))
}
return r.extractParameters(urlPath)
}

View File

@ -1,18 +0,0 @@
package restful
import "testing"
// Use like this:
//
// TraceLogger(testLogger{t})
type testLogger struct {
t *testing.T
}
func (l testLogger) Print(v ...interface{}) {
l.t.Log(v...)
}
func (l testLogger) Printf(format string, v ...interface{}) {
l.t.Logf(format, v...)
}

View File

@ -1,343 +0,0 @@
package restful
import (
"net/http"
"net/http/httptest"
"testing"
)
const (
pathGetFriends = "/get/{userId}/friends"
)
func TestParameter(t *testing.T) {
p := &Parameter{&ParameterData{Name: "name", Description: "desc"}}
p.AllowMultiple(true)
p.DataType("int")
p.Required(true)
values := map[string]string{"a": "b"}
p.AllowableValues(values)
p.bePath()
ws := new(WebService)
ws.Param(p)
if ws.pathParameters[0].Data().Name != "name" {
t.Error("path parameter (or name) invalid")
}
}
func TestWebService_CanCreateParameterKinds(t *testing.T) {
ws := new(WebService)
if ws.BodyParameter("b", "b").Kind() != BodyParameterKind {
t.Error("body parameter expected")
}
if ws.PathParameter("p", "p").Kind() != PathParameterKind {
t.Error("path parameter expected")
}
if ws.QueryParameter("q", "q").Kind() != QueryParameterKind {
t.Error("query parameter expected")
}
}
func TestCapturePanic(t *testing.T) {
tearDown()
Add(newPanicingService())
httpRequest, _ := http.NewRequest("GET", "http://here.com/fire", nil)
httpRequest.Header.Set("Accept", "*/*")
httpWriter := httptest.NewRecorder()
// override the default here
DefaultContainer.DoNotRecover(false)
DefaultContainer.dispatch(httpWriter, httpRequest)
if 500 != httpWriter.Code {
t.Error("500 expected on fire")
}
}
func TestCapturePanicWithEncoded(t *testing.T) {
tearDown()
Add(newPanicingService())
DefaultContainer.EnableContentEncoding(true)
httpRequest, _ := http.NewRequest("GET", "http://here.com/fire", nil)
httpRequest.Header.Set("Accept", "*/*")
httpRequest.Header.Set("Accept-Encoding", "gzip")
httpWriter := httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
if 500 != httpWriter.Code {
t.Error("500 expected on fire, got", httpWriter.Code)
}
}
func TestNotFound(t *testing.T) {
tearDown()
httpRequest, _ := http.NewRequest("GET", "http://here.com/missing", nil)
httpRequest.Header.Set("Accept", "*/*")
httpWriter := httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
if 404 != httpWriter.Code {
t.Error("404 expected on missing")
}
}
func TestMethodNotAllowed(t *testing.T) {
tearDown()
Add(newGetOnlyService())
httpRequest, _ := http.NewRequest("POST", "http://here.com/get", nil)
httpRequest.Header.Set("Accept", "*/*")
httpWriter := httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
if 405 != httpWriter.Code {
t.Error("405 expected method not allowed")
}
}
func TestSelectedRoutePath_Issue100(t *testing.T) {
tearDown()
Add(newSelectedRouteTestingService())
httpRequest, _ := http.NewRequest("GET", "http://here.com/get/232452/friends", nil)
httpRequest.Header.Set("Accept", "*/*")
httpWriter := httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
if http.StatusOK != httpWriter.Code {
t.Error(http.StatusOK, "expected,", httpWriter.Code, "received.")
}
}
func TestContentType415_Issue170(t *testing.T) {
tearDown()
Add(newGetOnlyJsonOnlyService())
httpRequest, _ := http.NewRequest("GET", "http://here.com/get", nil)
httpWriter := httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
if 200 != httpWriter.Code {
t.Errorf("Expected 200, got %d", httpWriter.Code)
}
}
func TestNoContentTypePOST(t *testing.T) {
tearDown()
Add(newPostNoConsumesService())
httpRequest, _ := http.NewRequest("POST", "http://here.com/post", nil)
httpWriter := httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
if 204 != httpWriter.Code {
t.Errorf("Expected 204, got %d", httpWriter.Code)
}
}
func TestContentType415_POST_Issue170(t *testing.T) {
tearDown()
Add(newPostOnlyJsonOnlyService())
httpRequest, _ := http.NewRequest("POST", "http://here.com/post", nil)
httpRequest.Header.Set("Content-Type", "application/json")
httpWriter := httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
if 200 != httpWriter.Code {
t.Errorf("Expected 200, got %d", httpWriter.Code)
}
}
// go test -v -test.run TestContentType406PlainJson ...restful
func TestContentType406PlainJson(t *testing.T) {
tearDown()
TraceLogger(testLogger{t})
Add(newGetPlainTextOrJsonService())
httpRequest, _ := http.NewRequest("GET", "http://here.com/get", nil)
httpRequest.Header.Set("Accept", "text/plain")
httpWriter := httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
if got, want := httpWriter.Code, 200; got != want {
t.Errorf("got %v, want %v", got, want)
}
}
func TestRemoveRoute(t *testing.T) {
tearDown()
TraceLogger(testLogger{t})
ws := newGetPlainTextOrJsonService()
Add(ws)
httpRequest, _ := http.NewRequest("GET", "http://here.com/get", nil)
httpRequest.Header.Set("Accept", "text/plain")
httpWriter := httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
if got, want := httpWriter.Code, 200; got != want {
t.Errorf("got %v, want %v", got, want)
}
// dynamic apis are disabled, should error and do nothing
if err := ws.RemoveRoute("/get", "GET"); err == nil {
t.Error("unexpected non-error")
}
httpWriter = httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
if got, want := httpWriter.Code, 200; got != want {
t.Errorf("got %v, want %v", got, want)
}
ws.SetDynamicRoutes(true)
if err := ws.RemoveRoute("/get", "GET"); err != nil {
t.Errorf("unexpected error %v", err)
}
httpWriter = httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
if got, want := httpWriter.Code, 404; got != want {
t.Errorf("got %v, want %v", got, want)
}
}
func TestRemoveLastRoute(t *testing.T) {
tearDown()
TraceLogger(testLogger{t})
ws := newGetPlainTextOrJsonServiceMultiRoute()
Add(ws)
httpRequest, _ := http.NewRequest("GET", "http://here.com/get", nil)
httpRequest.Header.Set("Accept", "text/plain")
httpWriter := httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
if got, want := httpWriter.Code, 200; got != want {
t.Errorf("got %v, want %v", got, want)
}
// dynamic apis are disabled, should error and do nothing
if err := ws.RemoveRoute("/get", "GET"); err == nil {
t.Error("unexpected non-error")
}
httpWriter = httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
if got, want := httpWriter.Code, 200; got != want {
t.Errorf("got %v, want %v", got, want)
}
ws.SetDynamicRoutes(true)
if err := ws.RemoveRoute("/get", "GET"); err != nil {
t.Errorf("unexpected error %v", err)
}
httpWriter = httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
if got, want := httpWriter.Code, 404; got != want {
t.Errorf("got %v, want %v", got, want)
}
}
// go test -v -test.run TestContentTypeOctet_Issue170 ...restful
func TestContentTypeOctet_Issue170(t *testing.T) {
tearDown()
Add(newGetConsumingOctetStreamService())
// with content-type
httpRequest, _ := http.NewRequest("GET", "http://here.com/get", nil)
httpRequest.Header.Set("Content-Type", MIME_OCTET)
httpWriter := httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
if 200 != httpWriter.Code {
t.Errorf("Expected 200, got %d", httpWriter.Code)
}
// without content-type
httpRequest, _ = http.NewRequest("GET", "http://here.com/get", nil)
httpWriter = httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
if 200 != httpWriter.Code {
t.Errorf("Expected 200, got %d", httpWriter.Code)
}
}
type exampleBody struct{}
func TestParameterDataTypeDefaults(t *testing.T) {
tearDown()
ws := new(WebService)
route := ws.POST("/post").Reads(&exampleBody{})
if route.parameters[0].data.DataType != "*restful.exampleBody" {
t.Errorf("body parameter incorrect name: %#v", route.parameters[0].data)
}
}
func TestParameterDataTypeCustomization(t *testing.T) {
tearDown()
ws := new(WebService)
ws.TypeNameHandler(func(sample interface{}) string {
return "my.custom.type.name"
})
route := ws.POST("/post").Reads(&exampleBody{})
if route.parameters[0].data.DataType != "my.custom.type.name" {
t.Errorf("body parameter incorrect name: %#v", route.parameters[0].data)
}
}
func newPanicingService() *WebService {
ws := new(WebService).Path("")
ws.Route(ws.GET("/fire").To(doPanic))
return ws
}
func newGetOnlyService() *WebService {
ws := new(WebService).Path("")
ws.Route(ws.GET("/get").To(doPanic))
return ws
}
func newPostOnlyJsonOnlyService() *WebService {
ws := new(WebService).Path("")
ws.Consumes("application/json")
ws.Route(ws.POST("/post").To(doNothing))
return ws
}
func newGetOnlyJsonOnlyService() *WebService {
ws := new(WebService).Path("")
ws.Consumes("application/json")
ws.Route(ws.GET("/get").To(doNothing))
return ws
}
func newGetPlainTextOrJsonService() *WebService {
ws := new(WebService).Path("")
ws.Produces("text/plain", "application/json")
ws.Route(ws.GET("/get").To(doNothing))
return ws
}
func newGetPlainTextOrJsonServiceMultiRoute() *WebService {
ws := new(WebService).Path("")
ws.Produces("text/plain", "application/json")
ws.Route(ws.GET("/get").To(doNothing))
ws.Route(ws.GET("/status").To(doNothing))
return ws
}
func newGetConsumingOctetStreamService() *WebService {
ws := new(WebService).Path("")
ws.Consumes("application/octet-stream")
ws.Route(ws.GET("/get").To(doNothing))
return ws
}
func newPostNoConsumesService() *WebService {
ws := new(WebService).Path("")
ws.Route(ws.POST("/post").To(return204))
return ws
}
func newSelectedRouteTestingService() *WebService {
ws := new(WebService).Path("")
ws.Route(ws.GET(pathGetFriends).To(selectedRouteChecker))
return ws
}
func selectedRouteChecker(req *Request, resp *Response) {
if req.SelectedRoutePath() != pathGetFriends {
resp.InternalServerError()
}
}
func doPanic(req *Request, resp *Response) {
println("lightning...")
panic("fire")
}
func doNothing(req *Request, resp *Response) {
}
func return204(req *Request, resp *Response) {
resp.WriteHeader(204)
}

1
vendor/github.com/go-openapi/jsonpointer/.drone.sec generated vendored Normal file
View File

@ -0,0 +1 @@
eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.pDqezepze0YqRx4u6M8GFaWtnVR-utTWZic-GX-RvMATAoYpG4H2sc9tlnGNCxa44dbRY0vY10qfBU7Sno8vkp21fsK42ofGLfen_suum_0ilm0sFS0X-kAwk7TIq5L5lPPKiChPMUiGp5oJW-g5MqMFX1jNiI-4fP-vSM3B3-eyZtJD_O517TgfIRLnblCzqwIkyRmAfPNopi-Fe8Y31TmO2Vd0nFc1Aqro_VaJSACzEVxOHTNpjETcMjlYzwgMXLeiAfLV-5hM0f6DXgHMlLSuMkB_Ndnw25dkB7hreGk4x0tHQ3X9mUfTgLq1hIDoyeeKDIM83Tqw4LBRph20BQ.qd_pNuyi23B0PlWz.JtpO7kqOm0SWOGzWDalkWheHuNd-eDpVbqI9WPAEFDOIBvz7TbsYMBlIYVWEGWbat4mkx_ejxnMn1L1l996NJnyP7eY-QE82cfPJbjx94d0Ob70KZ4DCm_UxcY2t-OKFiPJqxW7MA5jKyDuGD16bdxpjLEoe_cMSEr8FNu-MVG6wcchPcyYyRkqTQSl4mb09KikkAzHjwjo-DcO0f8ps4Uzsoc0aqAAWdE-ocG0YqierLoemjusYMiLH-eLF6MvaLRvHSte-cLzPuYCeZURnBDgxu3i3UApgddnX7g1c7tdGGBGvgCl-tEEDW58Vxgdjksim2S7y3lfoJ8FFzSWeRH2y7Kq04hgew3b2J_RiDB9ejzIopzG8ZGjJa3EO1-i9ORTl12nXK1RdlLGqu604ENaeVOPCIHL-0C8e6_wHdUGHydLZImSxKYSrNvy8resP1D_9t4B-3q2mkS9mhnMONrXbPDVw5QY5mvXlWs0Db99ARwzsl-Qlu0A_tsZwMjWT2I1QMvWPyTRScmMm0FJSv9zStjzxWa_q2GL7Naz1fI4Dd6ZgNJWYYq-mHN5chEeBdIcwb_zMPHczMQXXNL5nmfRGM1aPffkToFWCDpIlI8IXec83ZC6_POxZegS6n9Drrvc.6Nz8EXxs1lWX3ASaCeNElA

View File

@ -1,117 +0,0 @@
## Contribution Guidelines
### Pull requests are always welcome
We are always thrilled to receive pull requests, and do our best to
process them as fast as possible. Not sure if that typo is worth a pull
request? Do it! We will appreciate it.
If your pull request is not accepted on the first try, don't be
discouraged! If there's a problem with the implementation, hopefully you
received feedback on what to improve.
We're trying very hard to keep go-swagger lean and focused. We don't want it
to do everything for everybody. This means that we might decide against
incorporating a new feature. However, there might be a way to implement
that feature *on top of* go-swagger.
### Conventions
Fork the repo and make changes on your fork in a feature branch:
- If it's a bugfix branch, name it XXX-something where XXX is the number of the
issue
- If it's a feature branch, create an enhancement issue to announce your
intentions, and name it XXX-something where XXX is the number of the issue.
Submit unit tests for your changes. Go has a great test framework built in; use
it! Take a look at existing tests for inspiration. Run the full test suite on
your branch before submitting a pull request.
Update the documentation when creating or modifying features. Test
your documentation changes for clarity, concision, and correctness, as
well as a clean documentation build. See ``docs/README.md`` for more
information on building the docs and how docs get released.
Write clean code. Universally formatted code promotes ease of writing, reading,
and maintenance. Always run `gofmt -s -w file.go` on each changed file before
committing your changes. Most editors have plugins that do this automatically.
Pull requests descriptions should be as clear as possible and include a
reference to all the issues that they address.
Pull requests must not contain commits from other users or branches.
Commit messages must start with a capitalized and short summary (max. 50
chars) written in the imperative, followed by an optional, more detailed
explanatory text which is separated from the summary by an empty line.
Code review comments may be added to your pull request. Discuss, then make the
suggested modifications and push additional commits to your feature branch. Be
sure to post a comment after pushing. The new commits will show up in the pull
request automatically, but the reviewers will not be notified unless you
comment.
Before the pull request is merged, make sure that you squash your commits into
logical units of work using `git rebase -i` and `git push -f`. After every
commit the test suite should be passing. Include documentation changes in the
same commit so that a revert would remove all traces of the feature or fix.
Commits that fix or close an issue should include a reference like `Closes #XXX`
or `Fixes #XXX`, which will automatically close the issue when merged.
### Sign your work
The sign-off is a simple line at the end of the explanation for the
patch, which certifies that you wrote it or otherwise have the right to
pass it on as an open-source patch. The rules are pretty simple: if you
can certify the below (from
[developercertificate.org](http://developercertificate.org/)):
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
then you just add a line to every git commit message:
Signed-off-by: Joe Smith <joe@gmail.com>
using your real name (sorry, no pseudonyms or anonymous contributions.)
You can add the sign off when creating the git commit via `git commit -s`.

View File

@ -1,313 +0,0 @@
// Copyright 2013 sigu-399 ( https://github.com/sigu-399 )
//
// 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.
// author sigu-399
// author-github https://github.com/sigu-399
// author-mail sigu.399@gmail.com
//
// repository-name jsonpointer
// repository-desc An implementation of JSON Pointer - Go language
//
// description Automated tests on package.
//
// created 03-03-2013
package jsonpointer
import (
"encoding/json"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
const (
TestDocumentNBItems = 11
TestNodeObjNBItems = 4
TestDocumentString = `{
"foo": ["bar", "baz"],
"obj": { "a":1, "b":2, "c":[3,4], "d":[ {"e":9}, {"f":[50,51]} ] },
"": 0,
"a/b": 1,
"c%d": 2,
"e^f": 3,
"g|h": 4,
"i\\j": 5,
"k\"l": 6,
" ": 7,
"m~n": 8
}`
)
var testDocumentJSON interface{}
type testStructJSON struct {
Foo []string `json:"foo"`
Obj struct {
A int `json:"a"`
B int `json:"b"`
C []int `json:"c"`
D []struct {
E int `json:"e"`
F []int `json:"f"`
} `json:"d"`
} `json:"obj"`
}
type aliasedMap map[string]interface{}
var testStructJSONDoc testStructJSON
var testStructJSONPtr *testStructJSON
func init() {
json.Unmarshal([]byte(TestDocumentString), &testDocumentJSON)
json.Unmarshal([]byte(TestDocumentString), &testStructJSONDoc)
testStructJSONPtr = &testStructJSONDoc
}
func TestEscaping(t *testing.T) {
ins := []string{`/`, `/`, `/a~1b`, `/a~1b`, `/c%d`, `/e^f`, `/g|h`, `/i\j`, `/k"l`, `/ `, `/m~0n`}
outs := []float64{0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8}
for i := range ins {
p, err := New(ins[i])
if err != nil {
t.Errorf("New(%v) error %v", ins[i], err.Error())
}
result, _, err := p.Get(testDocumentJSON)
if err != nil {
t.Errorf("Get(%v) error %v", ins[i], err.Error())
}
if result != outs[i] {
t.Errorf("Get(%v) = %v, expect %v", ins[i], result, outs[i])
}
}
}
func TestFullDocument(t *testing.T) {
in := ``
p, err := New(in)
if err != nil {
t.Errorf("New(%v) error %v", in, err.Error())
}
result, _, err := p.Get(testDocumentJSON)
if err != nil {
t.Errorf("Get(%v) error %v", in, err.Error())
}
if len(result.(map[string]interface{})) != TestDocumentNBItems {
t.Errorf("Get(%v) = %v, expect full document", in, result)
}
result, _, err = p.get(testDocumentJSON, nil)
if err != nil {
t.Errorf("Get(%v) error %v", in, err.Error())
}
if len(result.(map[string]interface{})) != TestDocumentNBItems {
t.Errorf("Get(%v) = %v, expect full document", in, result)
}
}
func TestDecodedTokens(t *testing.T) {
p, err := New("/obj/a~1b")
assert.NoError(t, err)
assert.Equal(t, []string{"obj", "a/b"}, p.DecodedTokens())
}
func TestIsEmpty(t *testing.T) {
p, err := New("")
assert.NoError(t, err)
assert.True(t, p.IsEmpty())
p, err = New("/obj")
assert.NoError(t, err)
assert.False(t, p.IsEmpty())
}
func TestGetSingle(t *testing.T) {
in := `/obj`
_, err := New(in)
assert.NoError(t, err)
result, _, err := GetForToken(testDocumentJSON, "obj")
assert.NoError(t, err)
assert.Len(t, result, TestNodeObjNBItems)
result, _, err = GetForToken(testStructJSONDoc, "Obj")
assert.Error(t, err)
assert.Nil(t, result)
result, _, err = GetForToken(testStructJSONDoc, "Obj2")
assert.Error(t, err)
assert.Nil(t, result)
}
type pointableImpl struct {
a string
}
func (p pointableImpl) JSONLookup(token string) (interface{}, error) {
if token == "some" {
return p.a, nil
}
return nil, fmt.Errorf("object has no field %q", token)
}
func TestPointableInterface(t *testing.T) {
p := &pointableImpl{"hello"}
result, _, err := GetForToken(p, "some")
assert.NoError(t, err)
assert.Equal(t, p.a, result)
result, _, err = GetForToken(p, "something")
assert.Error(t, err)
assert.Nil(t, result)
}
func TestGetNode(t *testing.T) {
in := `/obj`
p, err := New(in)
assert.NoError(t, err)
result, _, err := p.Get(testDocumentJSON)
assert.NoError(t, err)
assert.Len(t, result, TestNodeObjNBItems)
result, _, err = p.Get(aliasedMap(testDocumentJSON.(map[string]interface{})))
assert.NoError(t, err)
assert.Len(t, result, TestNodeObjNBItems)
result, _, err = p.Get(testStructJSONDoc)
assert.NoError(t, err)
assert.Equal(t, testStructJSONDoc.Obj, result)
result, _, err = p.Get(testStructJSONPtr)
assert.NoError(t, err)
assert.Equal(t, testStructJSONDoc.Obj, result)
}
func TestArray(t *testing.T) {
ins := []string{`/foo/0`, `/foo/0`, `/foo/1`}
outs := []string{"bar", "bar", "baz"}
for i := range ins {
p, err := New(ins[i])
assert.NoError(t, err)
result, _, err := p.Get(testStructJSONDoc)
assert.NoError(t, err)
assert.Equal(t, outs[i], result)
result, _, err = p.Get(testStructJSONPtr)
assert.NoError(t, err)
assert.Equal(t, outs[i], result)
result, _, err = p.Get(testDocumentJSON)
assert.NoError(t, err)
assert.Equal(t, outs[i], result)
}
}
func TestOtherThings(t *testing.T) {
_, err := New("abc")
assert.Error(t, err)
p, err := New("")
assert.NoError(t, err)
assert.Equal(t, "", p.String())
p, err = New("/obj/a")
assert.Equal(t, "/obj/a", p.String())
s := Escape("m~n")
assert.Equal(t, "m~0n", s)
s = Escape("m/n")
assert.Equal(t, "m~1n", s)
p, err = New("/foo/3")
assert.NoError(t, err)
_, _, err = p.Get(testDocumentJSON)
assert.Error(t, err)
p, err = New("/foo/a")
assert.NoError(t, err)
_, _, err = p.Get(testDocumentJSON)
assert.Error(t, err)
p, err = New("/notthere")
assert.NoError(t, err)
_, _, err = p.Get(testDocumentJSON)
assert.Error(t, err)
p, err = New("/invalid")
assert.NoError(t, err)
_, _, err = p.Get(1234)
assert.Error(t, err)
p, err = New("/foo/1")
assert.NoError(t, err)
expected := "hello"
bbb := testDocumentJSON.(map[string]interface{})["foo"]
bbb.([]interface{})[1] = "hello"
v, _, err := p.Get(testDocumentJSON)
assert.NoError(t, err)
assert.Equal(t, expected, v)
esc := Escape("a/")
assert.Equal(t, "a~1", esc)
unesc := Unescape(esc)
assert.Equal(t, "a/", unesc)
unesc = Unescape("~01")
assert.Equal(t, "~1", unesc)
assert.Equal(t, "~0~1", Escape("~/"))
assert.Equal(t, "~/", Unescape("~0~1"))
}
func TestObject(t *testing.T) {
ins := []string{`/obj/a`, `/obj/b`, `/obj/c/0`, `/obj/c/1`, `/obj/c/1`, `/obj/d/1/f/0`}
outs := []float64{1, 2, 3, 4, 4, 50}
for i := range ins {
p, err := New(ins[i])
assert.NoError(t, err)
result, _, err := p.Get(testDocumentJSON)
assert.NoError(t, err)
assert.Equal(t, outs[i], result)
result, _, err = p.Get(testStructJSONDoc)
assert.NoError(t, err)
assert.EqualValues(t, outs[i], result)
result, _, err = p.Get(testStructJSONPtr)
assert.NoError(t, err)
assert.EqualValues(t, outs[i], result)
}
}

View File

@ -0,0 +1 @@
eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.Xe40Wx6g5Y-iN0JVMhKyFfubtOId3zAVE564szw_yYGzFNhc_cGZO9F3BtAcJ55CfHG9C_ozn9dpnUDl_zYZoy_6cPCq13Ekb95z8NAC3ekDtbAATsc9HZwRNwI7UfkhstdwxljEouGB01qoLcUn6lFutrou-Ho21COHeDb2caemnPSA-rEAnXkOiBFu0RQ1MIwMygzvHXIHHYNpNwAtXqmiggM10miSjqBM3JmRPxCi7VK6_Rxij5p6LlhmK1BDi8Y6oBh-9BX3--5GAJeWZ6Vof5TnP-Enioia18j8c8KFtfY4q0y6Ednjb-AarLZ12gj695ppkBNJUdTJQmwGwA.fVcz_RiLrUB5fgMS.rjWllDYC6m_NB-ket_LizNEy9mlJ27odBTZQcMKaUqqXZBtWUCmPrOoMXGq-_cc-c7chg7D-WMh9SPQ23pV0P-DY-jsDpbOqHG2STOMEfW9ZREoaOLJXQaWcuBldLjRyWFcq0HGj97LgE6szD1Zlou3bmdHS_Q-U9Up9YQ_8_YnDcESD_cj1w5FZom7HjchKJFeGjQjfDQpoCKCQNMJaavUqy9jHQEeQ_uVocSrETg3GpewDcUF2tuv8uGq7ZZWu7Vl8zmnY1MFTynaGBWzTCSRmCkAXjcsaUheDP_NT5D7k-xUS6LwtqEUiXAXV07SNFraorFj5lnBQZRDlZMYcA3NWR6zHiOxekR9LBYPofst6w1rIqUchj_5m1tDpVTBMPir1eAaFcnJtPgo4ch17OF-kmcmQGLhJI3U7n8wv4sTrmP1dewtRRKrvlJe5r3_6eDiK4xZ8K0rnK1D4g6zuQqU1gA8KaU7pmZkKpFx3Bew4v-6DH32YwQBvAI7Lbb8afou9WsCNB_iswz5XGimP4bifiJRwpWBEz9VGhZFdiw-hZpYWgbxzVb5gtqfTDLIvpbLDmFz1vge16uUQHHVFpo1pSozyr7A60X8qsh9pmmO3RcJ-ZGZBWqiRC-Kl5ejz7WQ.LFoK4Ibi11B2lWQ5WcPSag

View File

@ -1,117 +0,0 @@
## Contribution Guidelines
### Pull requests are always welcome
We are always thrilled to receive pull requests, and do our best to
process them as fast as possible. Not sure if that typo is worth a pull
request? Do it! We will appreciate it.
If your pull request is not accepted on the first try, don't be
discouraged! If there's a problem with the implementation, hopefully you
received feedback on what to improve.
We're trying very hard to keep go-swagger lean and focused. We don't want it
to do everything for everybody. This means that we might decide against
incorporating a new feature. However, there might be a way to implement
that feature *on top of* go-swagger.
### Conventions
Fork the repo and make changes on your fork in a feature branch:
- If it's a bugfix branch, name it XXX-something where XXX is the number of the
issue
- If it's a feature branch, create an enhancement issue to announce your
intentions, and name it XXX-something where XXX is the number of the issue.
Submit unit tests for your changes. Go has a great test framework built in; use
it! Take a look at existing tests for inspiration. Run the full test suite on
your branch before submitting a pull request.
Update the documentation when creating or modifying features. Test
your documentation changes for clarity, concision, and correctness, as
well as a clean documentation build. See ``docs/README.md`` for more
information on building the docs and how docs get released.
Write clean code. Universally formatted code promotes ease of writing, reading,
and maintenance. Always run `gofmt -s -w file.go` on each changed file before
committing your changes. Most editors have plugins that do this automatically.
Pull requests descriptions should be as clear as possible and include a
reference to all the issues that they address.
Pull requests must not contain commits from other users or branches.
Commit messages must start with a capitalized and short summary (max. 50
chars) written in the imperative, followed by an optional, more detailed
explanatory text which is separated from the summary by an empty line.
Code review comments may be added to your pull request. Discuss, then make the
suggested modifications and push additional commits to your feature branch. Be
sure to post a comment after pushing. The new commits will show up in the pull
request automatically, but the reviewers will not be notified unless you
comment.
Before the pull request is merged, make sure that you squash your commits into
logical units of work using `git rebase -i` and `git push -f`. After every
commit the test suite should be passing. Include documentation changes in the
same commit so that a revert would remove all traces of the feature or fix.
Commits that fix or close an issue should include a reference like `Closes #XXX`
or `Fixes #XXX`, which will automatically close the issue when merged.
### Sign your work
The sign-off is a simple line at the end of the explanation for the
patch, which certifies that you wrote it or otherwise have the right to
pass it on as an open-source patch. The rules are pretty simple: if you
can certify the below (from
[developercertificate.org](http://developercertificate.org/)):
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
then you just add a line to every git commit message:
Signed-off-by: Joe Smith <joe@gmail.com>
using your real name (sorry, no pseudonyms or anonymous contributions.)
You can add the sign off when creating the git commit via `git commit -s`.

View File

@ -1,420 +0,0 @@
// Copyright 2013 sigu-399 ( https://github.com/sigu-399 )
//
// 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.
// author sigu-399
// author-github https://github.com/sigu-399
// author-mail sigu.399@gmail.com
//
// repository-name jsonreference
// repository-desc An implementation of JSON Reference - Go language
//
// description Automated tests on package.
//
// created 03-03-2013
package jsonreference
import (
"testing"
"github.com/go-openapi/jsonpointer"
"github.com/stretchr/testify/assert"
)
func TestIsRoot(t *testing.T) {
in := "#"
r1, err := New(in)
assert.NoError(t, err)
assert.True(t, r1.IsRoot())
in = "#/ok"
r1 = MustCreateRef(in)
assert.False(t, r1.IsRoot())
assert.Panics(t, assert.PanicTestFunc(func() {
MustCreateRef("%2")
}))
}
func TestFull(t *testing.T) {
in := "http://host/path/a/b/c#/f/a/b"
r1, err := New(in)
if err != nil {
t.Errorf("New(%v) error %s", in, err.Error())
}
if in != r1.String() {
t.Errorf("New(%v) = %v, expect %v", in, r1.String(), in)
}
if r1.HasFragmentOnly != false {
t.Errorf("New(%v)::HasFragmentOnly %v expect %v", in, r1.HasFragmentOnly, false)
}
if r1.HasFullURL != true {
t.Errorf("New(%v)::HasFullURL %v expect %v", in, r1.HasFullURL, true)
}
if r1.HasURLPathOnly != false {
t.Errorf("New(%v)::HasURLPathOnly %v expect %v", in, r1.HasURLPathOnly, false)
}
if r1.HasFileScheme != false {
t.Errorf("New(%v)::HasFileScheme %v expect %v", in, r1.HasFileScheme, false)
}
if r1.GetPointer().String() != "/f/a/b" {
t.Errorf("New(%v)::GetPointer() %v expect %v", in, r1.GetPointer().String(), "/f/a/b")
}
}
func TestFullURL(t *testing.T) {
in := "http://host/path/a/b/c"
r1, err := New(in)
if err != nil {
t.Errorf("New(%v) error %s", in, err.Error())
}
if in != r1.String() {
t.Errorf("New(%v) = %v, expect %v", in, r1.String(), in)
}
if r1.HasFragmentOnly != false {
t.Errorf("New(%v)::HasFragmentOnly %v expect %v", in, r1.HasFragmentOnly, false)
}
if r1.HasFullURL != true {
t.Errorf("New(%v)::HasFullURL %v expect %v", in, r1.HasFullURL, true)
}
if r1.HasURLPathOnly != false {
t.Errorf("New(%v)::HasURLPathOnly %v expect %v", in, r1.HasURLPathOnly, false)
}
if r1.HasFileScheme != false {
t.Errorf("New(%v)::HasFileScheme %v expect %v", in, r1.HasFileScheme, false)
}
if r1.GetPointer().String() != "" {
t.Errorf("New(%v)::GetPointer() %v expect %v", in, r1.GetPointer().String(), "")
}
}
func TestFragmentOnly(t *testing.T) {
in := "#/fragment/only"
r1, err := New(in)
if err != nil {
t.Errorf("New(%v) error %s", in, err.Error())
}
if in != r1.String() {
t.Errorf("New(%v) = %v, expect %v", in, r1.String(), in)
}
if r1.HasFragmentOnly != true {
t.Errorf("New(%v)::HasFragmentOnly %v expect %v", in, r1.HasFragmentOnly, true)
}
if r1.HasFullURL != false {
t.Errorf("New(%v)::HasFullURL %v expect %v", in, r1.HasFullURL, false)
}
if r1.HasURLPathOnly != false {
t.Errorf("New(%v)::HasURLPathOnly %v expect %v", in, r1.HasURLPathOnly, false)
}
if r1.HasFileScheme != false {
t.Errorf("New(%v)::HasFileScheme %v expect %v", in, r1.HasFileScheme, false)
}
if r1.GetPointer().String() != "/fragment/only" {
t.Errorf("New(%v)::GetPointer() %v expect %v", in, r1.GetPointer().String(), "/fragment/only")
}
p, _ := jsonpointer.New(r1.referenceURL.Fragment)
r2 := Ref{referencePointer: p, HasFragmentOnly: true}
assert.Equal(t, r2.String(), in)
r3 := Ref{referencePointer: p, HasFragmentOnly: false}
assert.Equal(t, r3.String(), in[1:])
}
func TestURLPathOnly(t *testing.T) {
in := "/documents/document.json"
r1, err := New(in)
if err != nil {
t.Errorf("New(%v) error %s", in, err.Error())
}
if in != r1.String() {
t.Errorf("New(%v) = %v, expect %v", in, r1.String(), in)
}
if r1.HasFragmentOnly != false {
t.Errorf("New(%v)::HasFragmentOnly %v expect %v", in, r1.HasFragmentOnly, false)
}
if r1.HasFullURL != false {
t.Errorf("New(%v)::HasFullURL %v expect %v", in, r1.HasFullURL, false)
}
if r1.HasURLPathOnly != true {
t.Errorf("New(%v)::HasURLPathOnly %v expect %v", in, r1.HasURLPathOnly, true)
}
if r1.HasFileScheme != false {
t.Errorf("New(%v)::HasFileScheme %v expect %v", in, r1.HasFileScheme, false)
}
if r1.GetPointer().String() != "" {
t.Errorf("New(%v)::GetPointer() %v expect %v", in, r1.GetPointer().String(), "")
}
}
func TestURLRelativePathOnly(t *testing.T) {
in := "document.json"
r1, err := New(in)
if err != nil {
t.Errorf("New(%v) error %s", in, err.Error())
}
if in != r1.String() {
t.Errorf("New(%v) = %v, expect %v", in, r1.String(), in)
}
if r1.HasFragmentOnly != false {
t.Errorf("New(%v)::HasFragmentOnly %v expect %v", in, r1.HasFragmentOnly, false)
}
if r1.HasFullURL != false {
t.Errorf("New(%v)::HasFullURL %v expect %v", in, r1.HasFullURL, false)
}
if r1.HasURLPathOnly != true {
t.Errorf("New(%v)::HasURLPathOnly %v expect %v", in, r1.HasURLPathOnly, true)
}
if r1.HasFileScheme != false {
t.Errorf("New(%v)::HasFileScheme %v expect %v", in, r1.HasFileScheme, false)
}
if r1.GetPointer().String() != "" {
t.Errorf("New(%v)::GetPointer() %v expect %v", in, r1.GetPointer().String(), "")
}
}
func TestInheritsInValid(t *testing.T) {
in1 := "http://www.test.com/doc.json"
in2 := "#/a/b"
r1, _ := New(in1)
r2 := Ref{}
result, err := r1.Inherits(r2)
assert.Error(t, err)
assert.Nil(t, result)
r1 = Ref{}
r2, _ = New(in2)
result, err = r1.Inherits(r2)
assert.NoError(t, err)
assert.Equal(t, r2, *result)
}
func TestInheritsValid(t *testing.T) {
in1 := "http://www.test.com/doc.json"
in2 := "#/a/b"
out := in1 + in2
r1, _ := New(in1)
r2, _ := New(in2)
result, err := r1.Inherits(r2)
if err != nil {
t.Errorf("Inherits(%s,%s) error %s", r1.String(), r2.String(), err.Error())
}
if result.String() != out {
t.Errorf("Inherits(%s,%s) = %s, expect %s", r1.String(), r2.String(), result.String(), out)
}
if result.GetPointer().String() != "/a/b" {
t.Errorf("result(%v)::GetPointer() %v expect %v", result.String(), result.GetPointer().String(), "/a/b")
}
}
func TestInheritsDifferentHost(t *testing.T) {
in1 := "http://www.test.com/doc.json"
in2 := "http://www.test2.com/doc.json#bla"
r1, _ := New(in1)
r2, _ := New(in2)
result, err := r1.Inherits(r2)
if err != nil {
t.Errorf("Inherits(%s,%s) should not fail. Error: %s", r1.String(), r2.String(), err.Error())
}
if result.String() != in2 {
t.Errorf("Inherits(%s,%s) should be %s but is %s", in1, in2, in2, result)
}
if result.GetPointer().String() != "" {
t.Errorf("result(%v)::GetPointer() %v expect %v", result.String(), result.GetPointer().String(), "")
}
}
func TestFileScheme(t *testing.T) {
in1 := "file:///Users/mac/1.json#a"
in2 := "file:///Users/mac/2.json#b"
r1, _ := New(in1)
r2, _ := New(in2)
if r1.HasFragmentOnly != false {
t.Errorf("New(%v)::HasFragmentOnly %v expect %v", in1, r1.HasFragmentOnly, false)
}
if r1.HasFileScheme != true {
t.Errorf("New(%v)::HasFileScheme %v expect %v", in1, r1.HasFileScheme, true)
}
if r1.HasFullFilePath != true {
t.Errorf("New(%v)::HasFullFilePath %v expect %v", in1, r1.HasFullFilePath, true)
}
if r1.IsCanonical() != true {
t.Errorf("New(%v)::IsCanonical %v expect %v", in1, r1.IsCanonical, true)
}
result, err := r1.Inherits(r2)
if err != nil {
t.Errorf("Inherits(%s,%s) should not fail. Error: %s", r1.String(), r2.String(), err.Error())
}
if result.String() != in2 {
t.Errorf("Inherits(%s,%s) should be %s but is %s", in1, in2, in2, result)
}
if result.GetPointer().String() != "" {
t.Errorf("result(%v)::GetPointer() %v expect %v", result.String(), result.GetPointer().String(), "")
}
}
func TestReferenceResolution(t *testing.T) {
// 5.4. Reference Resolution Examples
// http://tools.ietf.org/html/rfc3986#section-5.4
base := "http://a/b/c/d;p?q"
baseRef, err := New(base)
if err != nil {
t.Errorf("New(%s) failed error: %s", base, err.Error())
}
if baseRef.String() != base {
t.Errorf("New(%s) %s expected %s", base, baseRef.String(), base)
}
checks := []string{
// 5.4.1. Normal Examples
// http://tools.ietf.org/html/rfc3986#section-5.4.1
"g:h", "g:h",
"g", "http://a/b/c/g",
"./g", "http://a/b/c/g",
"g/", "http://a/b/c/g/",
"/g", "http://a/g",
"//g", "http://g",
"?y", "http://a/b/c/d;p?y",
"g?y", "http://a/b/c/g?y",
"#s", "http://a/b/c/d;p?q#s",
"g#s", "http://a/b/c/g#s",
"g?y#s", "http://a/b/c/g?y#s",
";x", "http://a/b/c/;x",
"g;x", "http://a/b/c/g;x",
"g;x?y#s", "http://a/b/c/g;x?y#s",
"", "http://a/b/c/d;p?q",
".", "http://a/b/c/",
"./", "http://a/b/c/",
"..", "http://a/b/",
"../", "http://a/b/",
"../g", "http://a/b/g",
"../..", "http://a/",
"../../", "http://a/",
"../../g", "http://a/g",
// 5.4.2. Abnormal Examples
// http://tools.ietf.org/html/rfc3986#section-5.4.2
"../../../g", "http://a/g",
"../../../../g", "http://a/g",
"/./g", "http://a/g",
"/../g", "http://a/g",
"g.", "http://a/b/c/g.",
".g", "http://a/b/c/.g",
"g..", "http://a/b/c/g..",
"..g", "http://a/b/c/..g",
"./../g", "http://a/b/g",
"./g/.", "http://a/b/c/g/",
"g/./h", "http://a/b/c/g/h",
"g/../h", "http://a/b/c/h",
"g;x=1/./y", "http://a/b/c/g;x=1/y",
"g;x=1/../y", "http://a/b/c/y",
"g?y/./x", "http://a/b/c/g?y/./x",
"g?y/../x", "http://a/b/c/g?y/../x",
"g#s/./x", "http://a/b/c/g#s/./x",
"g#s/../x", "http://a/b/c/g#s/../x",
"http:g", "http:g", // for strict parsers
//"http:g", "http://a/b/c/g", // for backward compatibility
}
for i := 0; i < len(checks); i += 2 {
child := checks[i]
expected := checks[i+1]
// fmt.Printf("%d: %v -> %v\n", i/2, child, expected)
childRef, e := New(child)
if e != nil {
t.Errorf("%d: New(%s) failed error: %s", i/2, child, e.Error())
}
res, e := baseRef.Inherits(childRef)
if res == nil {
t.Errorf("%d: Inherits(%s, %s) nil not expected", i/2, base, child)
}
if e != nil {
t.Errorf("%d: Inherits(%s) failed error: %s", i/2, child, e.Error())
}
if res.String() != expected {
t.Errorf("%d: Inherits(%s, %s) %s expected %s", i/2, base, child, res.String(), expected)
}
}
}

1
vendor/github.com/go-openapi/spec/.drone.sec generated vendored Normal file
View File

@ -0,0 +1 @@
eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.Epk8dDFH8U1RPYIPDpajZO26L5zFJ1wnQNGWxVHHo5cXrWF148kENoZzh35FT9cAxxPS_4CeVVpf59EgvCc8bem1puuj0gBZptn-lYa7iXZdI-ESN2Te7nF5VbZfwbnI62nEikYGyxz-ozL_IFuMl-qWek4iLerF8Z_xh0MZOJ_w8Nog7qb2WQov72d997TJv5ZKjWcRYPbnsAy1q60-Cqxq3a6enhcSPXqpK46nYSXGKfHvognWBJ_pxwkEqIBPN6hE4EfNtJjMf2LFKEdYy02nbHz78d-2YZ8wIUSJ-IWIwn3GTzObdGqRed20Qf3JtWTsOespmexDrLSeo3HW6A.7XaHW-Y1jjRAWt_W.S1Adut62RLOYZc-lN02M0MGczEucch3zIr4J1UPBPnZooWzntiE5UaUz0UdhjHVszQE5hTfG-yocKD1rDQGER6qrLtnJVrCm9J3n4lHglM-xOz1eZln1XKrWcAgZnAKaKSzuAa5scPG4iTHW6RwbWi_PWm04tBJ1yazdjaVo3uvuhflwvU9if7uMPMtscrDesbBVvpG89xmeudiFjX-wjsV5oGBIjz6ukEBAMKzNDMqikNoG4SnGenpxUpjUjMkDXxiC3BC8oL2_myeIfFeEOF066DqEN3CLkqBVO25zdpWAF4Ou2jKv--mgGEb_E1aMgiSoAVBnybene0TKn2IJ8rtkyRdmWlLIRKZdDT3v775C1FPK6-tYzS7NVg9nnuvpta5PhzYNkqI1Ie74Sl0I-RFClhsdx9dLDhoFEKCx2etC4UDX9jhj2u0Y2MrL76dRGE9kEV1hL1fh6HMvS4ZAAWw3Qce4skCjcL-2YyIOHzKjgLGkZsR5cTUQwCJyacVkdHUOUKFdDGZaUzWkFyeZ1oyrlG2d52svaplpU5-vCOVbWkqUN9rOALGPTC51Ur0L7DFx29aDImhaxZqTe2t9mcdqY7VLcO3JgUiD3JKsEet7s2EDeN44MqITv9KBS8wqJW4.sRv4ov0wB0IxTHw90kJy-A

View File

@ -1,117 +0,0 @@
## Contribution Guidelines
### Pull requests are always welcome
We are always thrilled to receive pull requests, and do our best to
process them as fast as possible. Not sure if that typo is worth a pull
request? Do it! We will appreciate it.
If your pull request is not accepted on the first try, don't be
discouraged! If there's a problem with the implementation, hopefully you
received feedback on what to improve.
We're trying very hard to keep go-swagger lean and focused. We don't want it
to do everything for everybody. This means that we might decide against
incorporating a new feature. However, there might be a way to implement
that feature *on top of* go-swagger.
### Conventions
Fork the repo and make changes on your fork in a feature branch:
- If it's a bugfix branch, name it XXX-something where XXX is the number of the
issue
- If it's a feature branch, create an enhancement issue to announce your
intentions, and name it XXX-something where XXX is the number of the issue.
Submit unit tests for your changes. Go has a great test framework built in; use
it! Take a look at existing tests for inspiration. Run the full test suite on
your branch before submitting a pull request.
Update the documentation when creating or modifying features. Test
your documentation changes for clarity, concision, and correctness, as
well as a clean documentation build. See ``docs/README.md`` for more
information on building the docs and how docs get released.
Write clean code. Universally formatted code promotes ease of writing, reading,
and maintenance. Always run `gofmt -s -w file.go` on each changed file before
committing your changes. Most editors have plugins that do this automatically.
Pull requests descriptions should be as clear as possible and include a
reference to all the issues that they address.
Pull requests must not contain commits from other users or branches.
Commit messages must start with a capitalized and short summary (max. 50
chars) written in the imperative, followed by an optional, more detailed
explanatory text which is separated from the summary by an empty line.
Code review comments may be added to your pull request. Discuss, then make the
suggested modifications and push additional commits to your feature branch. Be
sure to post a comment after pushing. The new commits will show up in the pull
request automatically, but the reviewers will not be notified unless you
comment.
Before the pull request is merged, make sure that you squash your commits into
logical units of work using `git rebase -i` and `git push -f`. After every
commit the test suite should be passing. Include documentation changes in the
same commit so that a revert would remove all traces of the feature or fix.
Commits that fix or close an issue should include a reference like `Closes #XXX`
or `Fixes #XXX`, which will automatically close the issue when merged.
### Sign your work
The sign-off is a simple line at the end of the explanation for the
patch, which certifies that you wrote it or otherwise have the right to
pass it on as an open-source patch. The rules are pretty simple: if you
can certify the below (from
[developercertificate.org](http://developercertificate.org/)):
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
then you just add a line to every git commit message:
Signed-off-by: Joe Smith <joe@gmail.com>
using your real name (sorry, no pseudonyms or anonymous contributions.)
You can add the sign off when creating the git commit via `git commit -s`.

View File

@ -1,128 +0,0 @@
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package spec
import (
"testing"
)
func TestSerialization_AuthSerialization(t *testing.T) {
assertSerializeJSON(t, BasicAuth(), `{"type":"basic"}`)
assertSerializeJSON(t, APIKeyAuth("api-key", "header"), `{"type":"apiKey","name":"api-key","in":"header"}`)
assertSerializeJSON(
t,
OAuth2Implicit("http://foo.com/authorization"),
`{"type":"oauth2","flow":"implicit","authorizationUrl":"http://foo.com/authorization"}`)
assertSerializeJSON(
t,
OAuth2Password("http://foo.com/token"),
`{"type":"oauth2","flow":"password","tokenUrl":"http://foo.com/token"}`)
assertSerializeJSON(t,
OAuth2Application("http://foo.com/token"),
`{"type":"oauth2","flow":"application","tokenUrl":"http://foo.com/token"}`)
assertSerializeJSON(
t,
OAuth2AccessToken("http://foo.com/authorization", "http://foo.com/token"),
`{"type":"oauth2","flow":"accessCode","authorizationUrl":"http://foo.com/authorization","tokenUrl":"http://foo.com/token"}`)
auth1 := OAuth2Implicit("http://foo.com/authorization")
auth1.AddScope("email", "read your email")
assertSerializeJSON(
t,
auth1,
`{"type":"oauth2","flow":"implicit","authorizationUrl":"http://foo.com/authorization","scopes":{"email":"read your email"}}`)
auth2 := OAuth2Password("http://foo.com/authorization")
auth2.AddScope("email", "read your email")
assertSerializeJSON(
t,
auth2,
`{"type":"oauth2","flow":"password","tokenUrl":"http://foo.com/authorization","scopes":{"email":"read your email"}}`)
auth3 := OAuth2Application("http://foo.com/token")
auth3.AddScope("email", "read your email")
assertSerializeJSON(
t,
auth3,
`{"type":"oauth2","flow":"application","tokenUrl":"http://foo.com/token","scopes":{"email":"read your email"}}`)
auth4 := OAuth2AccessToken("http://foo.com/authorization", "http://foo.com/token")
auth4.AddScope("email", "read your email")
assertSerializeJSON(
t,
auth4,
`{"type":"oauth2","flow":"accessCode","authorizationUrl":"http://foo.com/authorization","tokenUrl":"http://foo.com/token","scopes":{"email":"read your email"}}`)
}
func TestSerialization_AuthDeserialization(t *testing.T) {
assertParsesJSON(t, `{"type":"basic"}`, BasicAuth())
assertParsesJSON(
t,
`{"in":"header","name":"api-key","type":"apiKey"}`,
APIKeyAuth("api-key", "header"))
assertParsesJSON(
t,
`{"authorizationUrl":"http://foo.com/authorization","flow":"implicit","type":"oauth2"}`,
OAuth2Implicit("http://foo.com/authorization"))
assertParsesJSON(
t,
`{"flow":"password","tokenUrl":"http://foo.com/token","type":"oauth2"}`,
OAuth2Password("http://foo.com/token"))
assertParsesJSON(
t,
`{"flow":"application","tokenUrl":"http://foo.com/token","type":"oauth2"}`,
OAuth2Application("http://foo.com/token"))
assertParsesJSON(
t,
`{"authorizationUrl":"http://foo.com/authorization","flow":"accessCode","tokenUrl":"http://foo.com/token","type":"oauth2"}`,
OAuth2AccessToken("http://foo.com/authorization", "http://foo.com/token"))
auth1 := OAuth2Implicit("http://foo.com/authorization")
auth1.AddScope("email", "read your email")
assertParsesJSON(t,
`{"authorizationUrl":"http://foo.com/authorization","flow":"implicit","scopes":{"email":"read your email"},"type":"oauth2"}`,
auth1)
auth2 := OAuth2Password("http://foo.com/token")
auth2.AddScope("email", "read your email")
assertParsesJSON(t,
`{"flow":"password","scopes":{"email":"read your email"},"tokenUrl":"http://foo.com/token","type":"oauth2"}`,
auth2)
auth3 := OAuth2Application("http://foo.com/token")
auth3.AddScope("email", "read your email")
assertParsesJSON(t,
`{"flow":"application","scopes":{"email":"read your email"},"tokenUrl":"http://foo.com/token","type":"oauth2"}`,
auth3)
auth4 := OAuth2AccessToken("http://foo.com/authorization", "http://foo.com/token")
auth4.AddScope("email", "read your email")
assertParsesJSON(
t,
`{"authorizationUrl":"http://foo.com/authorization","flow":"accessCode","scopes":{"email":"read your email"},"tokenUrl":"http://foo.com/token","type":"oauth2"}`,
auth4)
}

View File

@ -1,37 +0,0 @@
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package spec
import (
"testing"
)
var contactInfoJSON = `{"name":"wordnik api team","url":"http://developer.wordnik.com","email":"some@mailayada.dkdkd"}`
var contactInfoYAML = `name: wordnik api team
url: http://developer.wordnik.com
email: some@mailayada.dkdkd
`
var contactInfo = ContactInfo{
Name: "wordnik api team",
URL: "http://developer.wordnik.com",
Email: "some@mailayada.dkdkd",
}
func TestIntegrationContactInfo(t *testing.T) {
assertSerializeJSON(t, contactInfo, contactInfoJSON)
assertSerializeYAML(t, contactInfo, contactInfoYAML)
assertParsesJSON(t, contactInfoJSON, contactInfo)
assertParsesYAML(t, contactInfoYAML, contactInfo)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,29 +0,0 @@
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package spec
import (
"testing"
)
func TestIntegrationExternalDocs(t *testing.T) {
var extDocs = ExternalDocumentation{"the name", "the url"}
const extDocsYAML = "description: the name\nurl: the url\n"
const extDocsJSON = `{"description":"the name","url":"the url"}`
assertSerializeJSON(t, extDocs, extDocsJSON)
assertSerializeYAML(t, extDocs, extDocsYAML)
assertParsesJSON(t, extDocsJSON, extDocs)
assertParsesYAML(t, extDocsYAML, extDocs)
}

Some files were not shown because too many files have changed in this diff Show More