Fix the dependency issue (#231)

This commit is contained in:
Robbie Zhang
2018-06-21 12:09:42 -07:00
committed by GitHub
parent 027b76651d
commit 6ec1098bb8
16629 changed files with 74837 additions and 4975021 deletions

View File

@@ -1,180 +0,0 @@
# Denco [![Build Status](https://travis-ci.org/naoina/denco.png?branch=master)](https://travis-ci.org/naoina/denco)
The fast and flexible HTTP request router for [Go](http://golang.org).
Denco is based on Double-Array implementation of [Kocha-urlrouter](https://github.com/naoina/kocha-urlrouter).
However, Denco is optimized and some features added.
## Features
* Fast (See [go-http-routing-benchmark](https://github.com/naoina/go-http-routing-benchmark))
* [URL patterns](#url-patterns) (`/foo/:bar` and `/foo/*wildcard`)
* Small (but enough) URL router API
* HTTP request multiplexer like `http.ServeMux`
## Installation
go get -u github.com/go-openapi/runtime/middleware/denco
## Using as HTTP request multiplexer
```go
package main
import (
"fmt"
"log"
"net/http"
"github.com/go-openapi/runtime/middleware/denco"
)
func Index(w http.ResponseWriter, r *http.Request, params denco.Params) {
fmt.Fprintf(w, "Welcome to Denco!\n")
}
func User(w http.ResponseWriter, r *http.Request, params denco.Params) {
fmt.Fprintf(w, "Hello %s!\n", params.Get("name"))
}
func main() {
mux := denco.NewMux()
handler, err := mux.Build([]denco.Handler{
mux.GET("/", Index),
mux.GET("/user/:name", User),
mux.POST("/user/:name", User),
})
if err != nil {
panic(err)
}
log.Fatal(http.ListenAndServe(":8080", handler))
}
```
## Using as URL router
```go
package main
import (
"fmt"
"github.com/go-openapi/runtime/middleware/denco"
)
type route struct {
name string
}
func main() {
router := denco.New()
router.Build([]denco.Record{
{"/", &route{"root"}},
{"/user/:id", &route{"user"}},
{"/user/:name/:id", &route{"username"}},
{"/static/*filepath", &route{"static"}},
})
data, params, found := router.Lookup("/")
// print `&main.route{name:"root"}, denco.Params(nil), true`.
fmt.Printf("%#v, %#v, %#v\n", data, params, found)
data, params, found = router.Lookup("/user/hoge")
// print `&main.route{name:"user"}, denco.Params{denco.Param{Name:"id", Value:"hoge"}}, true`.
fmt.Printf("%#v, %#v, %#v\n", data, params, found)
data, params, found = router.Lookup("/user/hoge/7")
// print `&main.route{name:"username"}, denco.Params{denco.Param{Name:"name", Value:"hoge"}, denco.Param{Name:"id", Value:"7"}}, true`.
fmt.Printf("%#v, %#v, %#v\n", data, params, found)
data, params, found = router.Lookup("/static/path/to/file")
// print `&main.route{name:"static"}, denco.Params{denco.Param{Name:"filepath", Value:"path/to/file"}}, true`.
fmt.Printf("%#v, %#v, %#v\n", data, params, found)
}
```
See [Godoc](http://godoc.org/github.com/go-openapi/runtime/middleware/denco) for more details.
## Getting the value of path parameter
You can get the value of path parameter by 2 ways.
1. Using [`denco.Params.Get`](http://godoc.org/github.com/go-openapi/runtime/middleware/denco#Params.Get) method
2. Find by loop
```go
package main
import (
"fmt"
"github.com/go-openapi/runtime/middleware/denco"
)
func main() {
router := denco.New()
if err := router.Build([]denco.Record{
{"/user/:name/:id", "route1"},
}); err != nil {
panic(err)
}
// 1. Using denco.Params.Get method.
_, params, _ := router.Lookup("/user/alice/1")
name := params.Get("name")
if name != "" {
fmt.Printf("Hello %s.\n", name) // prints "Hello alice.".
}
// 2. Find by loop.
for _, param := range params {
if param.Name == "name" {
fmt.Printf("Hello %s.\n", name) // prints "Hello alice.".
}
}
}
```
## URL patterns
Denco's route matching strategy is "most nearly matching".
When routes `/:name` and `/alice` have been built, URI `/alice` matches the route `/alice`, not `/:name`.
Because URI `/alice` is more match with the route `/alice` than `/:name`.
For more example, when routes below have been built:
```
/user/alice
/user/:name
/user/:name/:id
/user/alice/:id
/user/:id/bob
```
Routes matching are:
```
/user/alice => "/user/alice" (no match with "/user/:name")
/user/bob => "/user/:name"
/user/naoina/1 => "/user/:name/1"
/user/alice/1 => "/user/alice/:id" (no match with "/user/:name/:id")
/user/1/bob => "/user/:id/bob" (no match with "/user/:name/:id")
/user/alice/bob => "/user/alice/:id" (no match with "/user/:name/:id" and "/user/:id/bob")
```
## Limitation
Denco has some limitations below.
* Number of param records (such as `/:name`) must be less than 2^22
* Number of elements of internal slice must be less than 2^22
## Benchmarks
cd $GOPATH/github.com/go-openapi/runtime/middleware/denco
go test -bench . -benchmem
## License
Denco is licensed under the MIT License.

View File

@@ -1,452 +0,0 @@
// Package denco provides fast URL router.
package denco
import (
"fmt"
"sort"
"strings"
)
const (
// ParamCharacter is a special character for path parameter.
ParamCharacter = ':'
// WildcardCharacter is a special character for wildcard path parameter.
WildcardCharacter = '*'
// TerminationCharacter is a special character for end of path.
TerminationCharacter = '#'
// MaxSize is max size of records and internal slice.
MaxSize = (1 << 22) - 1
)
// Router represents a URL router.
type Router struct {
// SizeHint expects the maximum number of path parameters in records to Build.
// SizeHint will be used to determine the capacity of the memory to allocate.
// By default, SizeHint will be determined from given records to Build.
SizeHint int
static map[string]interface{}
param *doubleArray
}
// New returns a new Router.
func New() *Router {
return &Router{
SizeHint: -1,
static: make(map[string]interface{}),
param: newDoubleArray(),
}
}
// Lookup returns data and path parameters that associated with path.
// params is a slice of the Param that arranged in the order in which parameters appeared.
// e.g. when built routing path is "/path/to/:id/:name" and given path is "/path/to/1/alice". params order is [{"id": "1"}, {"name": "alice"}], not [{"name": "alice"}, {"id": "1"}].
func (rt *Router) Lookup(path string) (data interface{}, params Params, found bool) {
if data, found := rt.static[path]; found {
return data, nil, true
}
if len(rt.param.node) == 1 {
return nil, nil, false
}
nd, params, found := rt.param.lookup(path, make([]Param, 0, rt.SizeHint), 1)
if !found {
return nil, nil, false
}
for i := 0; i < len(params); i++ {
params[i].Name = nd.paramNames[i]
}
return nd.data, params, true
}
// Build builds URL router from records.
func (rt *Router) Build(records []Record) error {
statics, params := makeRecords(records)
if len(params) > MaxSize {
return fmt.Errorf("denco: too many records")
}
if rt.SizeHint < 0 {
rt.SizeHint = 0
for _, p := range params {
size := 0
for _, k := range p.Key {
if k == ParamCharacter || k == WildcardCharacter {
size++
}
}
if size > rt.SizeHint {
rt.SizeHint = size
}
}
}
for _, r := range statics {
rt.static[r.Key] = r.Value
}
if err := rt.param.build(params, 1, 0, make(map[int]struct{})); err != nil {
return err
}
return nil
}
// Param represents name and value of path parameter.
type Param struct {
Name string
Value string
}
// Params represents the name and value of path parameters.
type Params []Param
// Get gets the first value associated with the given name.
// If there are no values associated with the key, Get returns "".
func (ps Params) Get(name string) string {
for _, p := range ps {
if p.Name == name {
return p.Value
}
}
return ""
}
type doubleArray struct {
bc []baseCheck
node []*node
}
func newDoubleArray() *doubleArray {
return &doubleArray{
bc: []baseCheck{0},
node: []*node{nil}, // A start index is adjusting to 1 because 0 will be used as a mark of non-existent node.
}
}
// baseCheck contains BASE, CHECK and Extra flags.
// From the top, 22bits of BASE, 2bits of Extra flags and 8bits of CHECK.
//
// BASE (22bit) | Extra flags (2bit) | CHECK (8bit)
// |----------------------|--|--------|
// 32 10 8 0
type baseCheck uint32
func (bc baseCheck) Base() int {
return int(bc >> 10)
}
func (bc *baseCheck) SetBase(base int) {
*bc |= baseCheck(base) << 10
}
func (bc baseCheck) Check() byte {
return byte(bc)
}
func (bc *baseCheck) SetCheck(check byte) {
*bc |= baseCheck(check)
}
func (bc baseCheck) IsEmpty() bool {
return bc&0xfffffcff == 0
}
func (bc baseCheck) IsSingleParam() bool {
return bc&paramTypeSingle == paramTypeSingle
}
func (bc baseCheck) IsWildcardParam() bool {
return bc&paramTypeWildcard == paramTypeWildcard
}
func (bc baseCheck) IsAnyParam() bool {
return bc&paramTypeAny != 0
}
func (bc *baseCheck) SetSingleParam() {
*bc |= (1 << 8)
}
func (bc *baseCheck) SetWildcardParam() {
*bc |= (1 << 9)
}
const (
paramTypeSingle = 0x0100
paramTypeWildcard = 0x0200
paramTypeAny = 0x0300
)
func (da *doubleArray) lookup(path string, params []Param, idx int) (*node, []Param, bool) {
indices := make([]uint64, 0, 1)
for i := 0; i < len(path); i++ {
if da.bc[idx].IsAnyParam() {
indices = append(indices, (uint64(i)<<32)|(uint64(idx)&0xffffffff))
}
c := path[i]
if idx = nextIndex(da.bc[idx].Base(), c); idx >= len(da.bc) || da.bc[idx].Check() != c {
goto BACKTRACKING
}
}
if next := nextIndex(da.bc[idx].Base(), TerminationCharacter); next < len(da.bc) && da.bc[next].Check() == TerminationCharacter {
return da.node[da.bc[next].Base()], params, true
}
BACKTRACKING:
for j := len(indices) - 1; j >= 0; j-- {
i, idx := int(indices[j]>>32), int(indices[j]&0xffffffff)
if da.bc[idx].IsSingleParam() {
idx := nextIndex(da.bc[idx].Base(), ParamCharacter)
if idx >= len(da.bc) {
break
}
next := NextSeparator(path, i)
params := append(params, Param{Value: path[i:next]})
if nd, params, found := da.lookup(path[next:], params, idx); found {
return nd, params, true
}
}
if da.bc[idx].IsWildcardParam() {
idx := nextIndex(da.bc[idx].Base(), WildcardCharacter)
params := append(params, Param{Value: path[i:]})
return da.node[da.bc[idx].Base()], params, true
}
}
return nil, nil, false
}
// build builds double-array from records.
func (da *doubleArray) build(srcs []*record, idx, depth int, usedBase map[int]struct{}) error {
sort.Stable(recordSlice(srcs))
base, siblings, leaf, err := da.arrange(srcs, idx, depth, usedBase)
if err != nil {
return err
}
if leaf != nil {
nd, err := makeNode(leaf)
if err != nil {
return err
}
da.bc[idx].SetBase(len(da.node))
da.node = append(da.node, nd)
}
for _, sib := range siblings {
da.setCheck(nextIndex(base, sib.c), sib.c)
}
for _, sib := range siblings {
records := srcs[sib.start:sib.end]
switch sib.c {
case ParamCharacter:
for _, r := range records {
next := NextSeparator(r.Key, depth+1)
name := r.Key[depth+1 : next]
r.paramNames = append(r.paramNames, name)
r.Key = r.Key[next:]
}
da.bc[idx].SetSingleParam()
if err := da.build(records, nextIndex(base, sib.c), 0, usedBase); err != nil {
return err
}
case WildcardCharacter:
r := records[0]
name := r.Key[depth+1 : len(r.Key)-1]
r.paramNames = append(r.paramNames, name)
r.Key = ""
da.bc[idx].SetWildcardParam()
if err := da.build(records, nextIndex(base, sib.c), 0, usedBase); err != nil {
return err
}
default:
if err := da.build(records, nextIndex(base, sib.c), depth+1, usedBase); err != nil {
return err
}
}
}
return nil
}
// setBase sets BASE.
func (da *doubleArray) setBase(i, base int) {
da.bc[i].SetBase(base)
}
// setCheck sets CHECK.
func (da *doubleArray) setCheck(i int, check byte) {
da.bc[i].SetCheck(check)
}
// findEmptyIndex returns an index of unused BASE/CHECK node.
func (da *doubleArray) findEmptyIndex(start int) int {
i := start
for ; i < len(da.bc); i++ {
if da.bc[i].IsEmpty() {
break
}
}
return i
}
// findBase returns good BASE.
func (da *doubleArray) findBase(siblings []sibling, start int, usedBase map[int]struct{}) (base int) {
for idx, firstChar := start+1, siblings[0].c; ; idx = da.findEmptyIndex(idx + 1) {
base = nextIndex(idx, firstChar)
if _, used := usedBase[base]; used {
continue
}
i := 0
for ; i < len(siblings); i++ {
next := nextIndex(base, siblings[i].c)
if len(da.bc) <= next {
da.bc = append(da.bc, make([]baseCheck, next-len(da.bc)+1)...)
}
if !da.bc[next].IsEmpty() {
break
}
}
if i == len(siblings) {
break
}
}
usedBase[base] = struct{}{}
return base
}
func (da *doubleArray) arrange(records []*record, idx, depth int, usedBase map[int]struct{}) (base int, siblings []sibling, leaf *record, err error) {
siblings, leaf, err = makeSiblings(records, depth)
if err != nil {
return -1, nil, nil, err
}
if len(siblings) < 1 {
return -1, nil, leaf, nil
}
base = da.findBase(siblings, idx, usedBase)
if base > MaxSize {
return -1, nil, nil, fmt.Errorf("denco: too many elements of internal slice")
}
da.setBase(idx, base)
return base, siblings, leaf, err
}
// node represents a node of Double-Array.
type node struct {
data interface{}
// Names of path parameters.
paramNames []string
}
// makeNode returns a new node from record.
func makeNode(r *record) (*node, error) {
dups := make(map[string]bool)
for _, name := range r.paramNames {
if dups[name] {
return nil, fmt.Errorf("denco: path parameter `%v' is duplicated in the key `%v'", name, r.Key)
}
dups[name] = true
}
return &node{data: r.Value, paramNames: r.paramNames}, nil
}
// sibling represents an intermediate data of build for Double-Array.
type sibling struct {
// An index of start of duplicated characters.
start int
// An index of end of duplicated characters.
end int
// A character of sibling.
c byte
}
// nextIndex returns a next index of array of BASE/CHECK.
func nextIndex(base int, c byte) int {
return base ^ int(c)
}
// makeSiblings returns slice of sibling.
func makeSiblings(records []*record, depth int) (sib []sibling, leaf *record, err error) {
var (
pc byte
n int
)
for i, r := range records {
if len(r.Key) <= depth {
leaf = r
continue
}
c := r.Key[depth]
switch {
case pc < c:
sib = append(sib, sibling{start: i, c: c})
case pc == c:
continue
default:
return nil, nil, fmt.Errorf("denco: BUG: routing table hasn't been sorted")
}
if n > 0 {
sib[n-1].end = i
}
pc = c
n++
}
if n == 0 {
return nil, leaf, nil
}
sib[n-1].end = len(records)
return sib, leaf, nil
}
// Record represents a record data for router construction.
type Record struct {
// Key for router construction.
Key string
// Result value for Key.
Value interface{}
}
// NewRecord returns a new Record.
func NewRecord(key string, value interface{}) Record {
return Record{
Key: key,
Value: value,
}
}
// record represents a record that use to build the Double-Array.
type record struct {
Record
paramNames []string
}
// makeRecords returns the records that use to build Double-Arrays.
func makeRecords(srcs []Record) (statics, params []*record) {
spChars := string([]byte{ParamCharacter, WildcardCharacter})
termChar := string(TerminationCharacter)
for _, r := range srcs {
if strings.ContainsAny(r.Key, spChars) {
r.Key += termChar
params = append(params, &record{Record: r})
} else {
statics = append(statics, &record{Record: r})
}
}
return statics, params
}
// recordSlice represents a slice of Record for sort and implements the sort.Interface.
type recordSlice []*record
// Len implements the sort.Interface.Len.
func (rs recordSlice) Len() int {
return len(rs)
}
// Less implements the sort.Interface.Less.
func (rs recordSlice) Less(i, j int) bool {
return rs[i].Key < rs[j].Key
}
// Swap implements the sort.Interface.Swap.
func (rs recordSlice) Swap(i, j int) {
rs[i], rs[j] = rs[j], rs[i]
}

View File

@@ -1,178 +0,0 @@
package denco_test
import (
"bytes"
"crypto/rand"
"fmt"
"math/big"
"testing"
"github.com/go-openapi/runtime/middleware/denco"
)
func BenchmarkRouterLookupStatic100(b *testing.B) {
benchmarkRouterLookupStatic(b, 100)
}
func BenchmarkRouterLookupStatic300(b *testing.B) {
benchmarkRouterLookupStatic(b, 300)
}
func BenchmarkRouterLookupStatic700(b *testing.B) {
benchmarkRouterLookupStatic(b, 700)
}
func BenchmarkRouterLookupSingleParam100(b *testing.B) {
records := makeTestSingleParamRecords(100)
benchmarkRouterLookupSingleParam(b, records)
}
func BenchmarkRouterLookupSingleParam300(b *testing.B) {
records := makeTestSingleParamRecords(300)
benchmarkRouterLookupSingleParam(b, records)
}
func BenchmarkRouterLookupSingleParam700(b *testing.B) {
records := makeTestSingleParamRecords(700)
benchmarkRouterLookupSingleParam(b, records)
}
func BenchmarkRouterLookupSingle2Param100(b *testing.B) {
records := makeTestSingle2ParamRecords(100)
benchmarkRouterLookupSingleParam(b, records)
}
func BenchmarkRouterLookupSingle2Param300(b *testing.B) {
records := makeTestSingle2ParamRecords(300)
benchmarkRouterLookupSingleParam(b, records)
}
func BenchmarkRouterLookupSingle2Param700(b *testing.B) {
records := makeTestSingle2ParamRecords(700)
benchmarkRouterLookupSingleParam(b, records)
}
func BenchmarkRouterBuildStatic100(b *testing.B) {
records := makeTestStaticRecords(100)
benchmarkRouterBuild(b, records)
}
func BenchmarkRouterBuildStatic300(b *testing.B) {
records := makeTestStaticRecords(300)
benchmarkRouterBuild(b, records)
}
func BenchmarkRouterBuildStatic700(b *testing.B) {
records := makeTestStaticRecords(700)
benchmarkRouterBuild(b, records)
}
func BenchmarkRouterBuildSingleParam100(b *testing.B) {
records := makeTestSingleParamRecords(100)
benchmarkRouterBuild(b, records)
}
func BenchmarkRouterBuildSingleParam300(b *testing.B) {
records := makeTestSingleParamRecords(300)
benchmarkRouterBuild(b, records)
}
func BenchmarkRouterBuildSingleParam700(b *testing.B) {
records := makeTestSingleParamRecords(700)
benchmarkRouterBuild(b, records)
}
func BenchmarkRouterBuildSingle2Param100(b *testing.B) {
records := makeTestSingle2ParamRecords(100)
benchmarkRouterBuild(b, records)
}
func BenchmarkRouterBuildSingle2Param300(b *testing.B) {
records := makeTestSingle2ParamRecords(300)
benchmarkRouterBuild(b, records)
}
func BenchmarkRouterBuildSingle2Param700(b *testing.B) {
records := makeTestSingle2ParamRecords(700)
benchmarkRouterBuild(b, records)
}
func benchmarkRouterLookupStatic(b *testing.B, n int) {
b.StopTimer()
router := denco.New()
records := makeTestStaticRecords(n)
if err := router.Build(records); err != nil {
b.Fatal(err)
}
record := pickTestRecord(records)
b.StartTimer()
for i := 0; i < b.N; i++ {
if r, _, _ := router.Lookup(record.Key); r != record.Value {
b.Fail()
}
}
}
func benchmarkRouterLookupSingleParam(b *testing.B, records []denco.Record) {
router := denco.New()
if err := router.Build(records); err != nil {
b.Fatal(err)
}
record := pickTestRecord(records)
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, _, found := router.Lookup(record.Key); !found {
b.Fail()
}
}
}
func benchmarkRouterBuild(b *testing.B, records []denco.Record) {
for i := 0; i < b.N; i++ {
router := denco.New()
if err := router.Build(records); err != nil {
b.Fatal(err)
}
}
}
func makeTestStaticRecords(n int) []denco.Record {
records := make([]denco.Record, n)
for i := 0; i < n; i++ {
records[i] = denco.NewRecord("/"+randomString(50), fmt.Sprintf("testroute%d", i))
}
return records
}
func makeTestSingleParamRecords(n int) []denco.Record {
records := make([]denco.Record, n)
for i := 0; i < len(records); i++ {
records[i] = denco.NewRecord(fmt.Sprintf("/user%d/:name", i), fmt.Sprintf("testroute%d", i))
}
return records
}
func makeTestSingle2ParamRecords(n int) []denco.Record {
records := make([]denco.Record, n)
for i := 0; i < len(records); i++ {
records[i] = denco.NewRecord(fmt.Sprintf("/user%d/:name/comment/:id", i), fmt.Sprintf("testroute%d", i))
}
return records
}
func pickTestRecord(records []denco.Record) denco.Record {
return records[len(records)/2]
}
func randomString(n int) string {
const srcStrings = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/"
var buf bytes.Buffer
for i := 0; i < n; i++ {
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(srcStrings)-1)))
if err != nil {
panic(err)
}
buf.WriteByte(srcStrings[num.Int64()])
}
return buf.String()
}

View File

@@ -1,524 +0,0 @@
package denco_test
import (
"fmt"
"math/rand"
"reflect"
"testing"
"time"
"github.com/go-openapi/runtime/middleware/denco"
)
func routes() []denco.Record {
return []denco.Record{
{"/", "testroute0"},
{"/path/to/route", "testroute1"},
{"/path/to/other", "testroute2"},
{"/path/to/route/a", "testroute3"},
{"/path/to/:param", "testroute4"},
{"/gists/:param1/foo/:param2", "testroute12"},
{"/gists/:param1/foo/bar", "testroute11"},
{"/:param1/:param2/foo/:param3", "testroute13"},
{"/path/to/wildcard/*routepath", "testroute5"},
{"/path/to/:param1/:param2", "testroute6"},
{"/path/to/:param1/sep/:param2", "testroute7"},
{"/:year/:month/:day", "testroute8"},
{"/user/:id", "testroute9"},
{"/a/to/b/:param/*routepath", "testroute10"},
}
}
var realURIs = []denco.Record{
{"/authorizations", "/authorizations"},
{"/authorizations/:id", "/authorizations/:id"},
{"/applications/:client_id/tokens/:access_token", "/applications/:client_id/tokens/:access_token"},
{"/events", "/events"},
{"/repos/:owner/:repo/events", "/repos/:owner/:repo/events"},
{"/networks/:owner/:repo/events", "/networks/:owner/:repo/events"},
{"/orgs/:org/events", "/orgs/:org/events"},
{"/users/:user/received_events", "/users/:user/received_events"},
{"/users/:user/received_events/public", "/users/:user/received_events/public"},
{"/users/:user/events", "/users/:user/events"},
{"/users/:user/events/public", "/users/:user/events/public"},
{"/users/:user/events/orgs/:org", "/users/:user/events/orgs/:org"},
{"/feeds", "/feeds"},
{"/notifications", "/notifications"},
{"/repos/:owner/:repo/notifications", "/repos/:owner/:repo/notifications"},
{"/notifications/threads/:id", "/notifications/threads/:id"},
{"/notifications/threads/:id/subscription", "/notifications/threads/:id/subscription"},
{"/repos/:owner/:repo/stargazers", "/repos/:owner/:repo/stargazers"},
{"/users/:user/starred", "/users/:user/starred"},
{"/user/starred", "/user/starred"},
{"/user/starred/:owner/:repo", "/user/starred/:owner/:repo"},
{"/repos/:owner/:repo/subscribers", "/repos/:owner/:repo/subscribers"},
{"/users/:user/subscriptions", "/users/:user/subscriptions"},
{"/user/subscriptions", "/user/subscriptions"},
{"/repos/:owner/:repo/subscription", "/repos/:owner/:repo/subscription"},
{"/user/subscriptions/:owner/:repo", "/user/subscriptions/:owner/:repo"},
{"/users/:user/gists", "/users/:user/gists"},
{"/gists", "/gists"},
{"/gists/:id", "/gists/:id"},
{"/gists/:id/star", "/gists/:id/star"},
{"/repos/:owner/:repo/git/blobs/:sha", "/repos/:owner/:repo/git/blobs/:sha"},
{"/repos/:owner/:repo/git/commits/:sha", "/repos/:owner/:repo/git/commits/:sha"},
{"/repos/:owner/:repo/git/refs", "/repos/:owner/:repo/git/refs"},
{"/repos/:owner/:repo/git/tags/:sha", "/repos/:owner/:repo/git/tags/:sha"},
{"/repos/:owner/:repo/git/trees/:sha", "/repos/:owner/:repo/git/trees/:sha"},
{"/issues", "/issues"},
{"/user/issues", "/user/issues"},
{"/orgs/:org/issues", "/orgs/:org/issues"},
{"/repos/:owner/:repo/issues", "/repos/:owner/:repo/issues"},
{"/repos/:owner/:repo/issues/:number", "/repos/:owner/:repo/issues/:number"},
{"/repos/:owner/:repo/assignees", "/repos/:owner/:repo/assignees"},
{"/repos/:owner/:repo/assignees/:assignee", "/repos/:owner/:repo/assignees/:assignee"},
{"/repos/:owner/:repo/issues/:number/comments", "/repos/:owner/:repo/issues/:number/comments"},
{"/repos/:owner/:repo/issues/:number/events", "/repos/:owner/:repo/issues/:number/events"},
{"/repos/:owner/:repo/labels", "/repos/:owner/:repo/labels"},
{"/repos/:owner/:repo/labels/:name", "/repos/:owner/:repo/labels/:name"},
{"/repos/:owner/:repo/issues/:number/labels", "/repos/:owner/:repo/issues/:number/labels"},
{"/repos/:owner/:repo/milestones/:number/labels", "/repos/:owner/:repo/milestones/:number/labels"},
{"/repos/:owner/:repo/milestones", "/repos/:owner/:repo/milestones"},
{"/repos/:owner/:repo/milestones/:number", "/repos/:owner/:repo/milestones/:number"},
{"/emojis", "/emojis"},
{"/gitignore/templates", "/gitignore/templates"},
{"/gitignore/templates/:name", "/gitignore/templates/:name"},
{"/meta", "/meta"},
{"/rate_limit", "/rate_limit"},
{"/users/:user/orgs", "/users/:user/orgs"},
{"/user/orgs", "/user/orgs"},
{"/orgs/:org", "/orgs/:org"},
{"/orgs/:org/members", "/orgs/:org/members"},
{"/orgs/:org/members/:user", "/orgs/:org/members/:user"},
{"/orgs/:org/public_members", "/orgs/:org/public_members"},
{"/orgs/:org/public_members/:user", "/orgs/:org/public_members/:user"},
{"/orgs/:org/teams", "/orgs/:org/teams"},
{"/teams/:id", "/teams/:id"},
{"/teams/:id/members", "/teams/:id/members"},
{"/teams/:id/members/:user", "/teams/:id/members/:user"},
{"/teams/:id/repos", "/teams/:id/repos"},
{"/teams/:id/repos/:owner/:repo", "/teams/:id/repos/:owner/:repo"},
{"/user/teams", "/user/teams"},
{"/repos/:owner/:repo/pulls", "/repos/:owner/:repo/pulls"},
{"/repos/:owner/:repo/pulls/:number", "/repos/:owner/:repo/pulls/:number"},
{"/repos/:owner/:repo/pulls/:number/commits", "/repos/:owner/:repo/pulls/:number/commits"},
{"/repos/:owner/:repo/pulls/:number/files", "/repos/:owner/:repo/pulls/:number/files"},
{"/repos/:owner/:repo/pulls/:number/merge", "/repos/:owner/:repo/pulls/:number/merge"},
{"/repos/:owner/:repo/pulls/:number/comments", "/repos/:owner/:repo/pulls/:number/comments"},
{"/user/repos", "/user/repos"},
{"/users/:user/repos", "/users/:user/repos"},
{"/orgs/:org/repos", "/orgs/:org/repos"},
{"/repositories", "/repositories"},
{"/repos/:owner/:repo", "/repos/:owner/:repo"},
{"/repos/:owner/:repo/contributors", "/repos/:owner/:repo/contributors"},
{"/repos/:owner/:repo/languages", "/repos/:owner/:repo/languages"},
{"/repos/:owner/:repo/teams", "/repos/:owner/:repo/teams"},
{"/repos/:owner/:repo/tags", "/repos/:owner/:repo/tags"},
{"/repos/:owner/:repo/branches", "/repos/:owner/:repo/branches"},
{"/repos/:owner/:repo/branches/:branch", "/repos/:owner/:repo/branches/:branch"},
{"/repos/:owner/:repo/collaborators", "/repos/:owner/:repo/collaborators"},
{"/repos/:owner/:repo/collaborators/:user", "/repos/:owner/:repo/collaborators/:user"},
{"/repos/:owner/:repo/comments", "/repos/:owner/:repo/comments"},
{"/repos/:owner/:repo/commits/:sha/comments", "/repos/:owner/:repo/commits/:sha/comments"},
{"/repos/:owner/:repo/comments/:id", "/repos/:owner/:repo/comments/:id"},
{"/repos/:owner/:repo/commits", "/repos/:owner/:repo/commits"},
{"/repos/:owner/:repo/commits/:sha", "/repos/:owner/:repo/commits/:sha"},
{"/repos/:owner/:repo/readme", "/repos/:owner/:repo/readme"},
{"/repos/:owner/:repo/keys", "/repos/:owner/:repo/keys"},
{"/repos/:owner/:repo/keys/:id", "/repos/:owner/:repo/keys/:id"},
{"/repos/:owner/:repo/downloads", "/repos/:owner/:repo/downloads"},
{"/repos/:owner/:repo/downloads/:id", "/repos/:owner/:repo/downloads/:id"},
{"/repos/:owner/:repo/forks", "/repos/:owner/:repo/forks"},
{"/repos/:owner/:repo/hooks", "/repos/:owner/:repo/hooks"},
{"/repos/:owner/:repo/hooks/:id", "/repos/:owner/:repo/hooks/:id"},
{"/repos/:owner/:repo/releases", "/repos/:owner/:repo/releases"},
{"/repos/:owner/:repo/releases/:id", "/repos/:owner/:repo/releases/:id"},
{"/repos/:owner/:repo/releases/:id/assets", "/repos/:owner/:repo/releases/:id/assets"},
{"/repos/:owner/:repo/stats/contributors", "/repos/:owner/:repo/stats/contributors"},
{"/repos/:owner/:repo/stats/commit_activity", "/repos/:owner/:repo/stats/commit_activity"},
{"/repos/:owner/:repo/stats/code_frequency", "/repos/:owner/:repo/stats/code_frequency"},
{"/repos/:owner/:repo/stats/participation", "/repos/:owner/:repo/stats/participation"},
{"/repos/:owner/:repo/stats/punch_card", "/repos/:owner/:repo/stats/punch_card"},
{"/repos/:owner/:repo/statuses/:ref", "/repos/:owner/:repo/statuses/:ref"},
{"/search/repositories", "/search/repositories"},
{"/search/code", "/search/code"},
{"/search/issues", "/search/issues"},
{"/search/users", "/search/users"},
{"/legacy/issues/search/:owner/:repository/:state/:keyword", "/legacy/issues/search/:owner/:repository/:state/:keyword"},
{"/legacy/repos/search/:keyword", "/legacy/repos/search/:keyword"},
{"/legacy/user/search/:keyword", "/legacy/user/search/:keyword"},
{"/legacy/user/email/:email", "/legacy/user/email/:email"},
{"/users/:user", "/users/:user"},
{"/user", "/user"},
{"/users", "/users"},
{"/user/emails", "/user/emails"},
{"/users/:user/followers", "/users/:user/followers"},
{"/user/followers", "/user/followers"},
{"/users/:user/following", "/users/:user/following"},
{"/user/following", "/user/following"},
{"/user/following/:user", "/user/following/:user"},
{"/users/:user/following/:target_user", "/users/:user/following/:target_user"},
{"/users/:user/keys", "/users/:user/keys"},
{"/user/keys", "/user/keys"},
{"/user/keys/:id", "/user/keys/:id"},
{"/people/:userId", "/people/:userId"},
{"/people", "/people"},
{"/activities/:activityId/people/:collection", "/activities/:activityId/people/:collection"},
{"/people/:userId/people/:collection", "/people/:userId/people/:collection"},
{"/people/:userId/openIdConnect", "/people/:userId/openIdConnect"},
{"/people/:userId/activities/:collection", "/people/:userId/activities/:collection"},
{"/activities/:activityId", "/activities/:activityId"},
{"/activities", "/activities"},
{"/activities/:activityId/comments", "/activities/:activityId/comments"},
{"/comments/:commentId", "/comments/:commentId"},
{"/people/:userId/moments/:collection", "/people/:userId/moments/:collection"},
}
type testcase struct {
path string
value interface{}
params []denco.Param
found bool
}
func runLookupTest(t *testing.T, records []denco.Record, testcases []testcase) {
r := denco.New()
if err := r.Build(records); err != nil {
t.Fatal(err)
}
for _, testcase := range testcases {
data, params, found := r.Lookup(testcase.path)
if !reflect.DeepEqual(data, testcase.value) || !reflect.DeepEqual(params, denco.Params(testcase.params)) || !reflect.DeepEqual(found, testcase.found) {
t.Errorf("Router.Lookup(%q) => (%#v, %#v, %#v), want (%#v, %#v, %#v)", testcase.path, data, params, found, testcase.value, denco.Params(testcase.params), testcase.found)
}
}
}
func TestRouter_Lookup(t *testing.T) {
testcases := []testcase{
{"/", "testroute0", nil, true},
{"/gists/1323/foo/bar", "testroute11", []denco.Param{{"param1", "1323"}}, true},
{"/gists/1323/foo/133", "testroute12", []denco.Param{{"param1", "1323"}, {"param2", "133"}}, true},
{"/234/1323/foo/133", "testroute13", []denco.Param{{"param1", "234"}, {"param2", "1323"}, {"param3", "133"}}, true},
{"/path/to/route", "testroute1", nil, true},
{"/path/to/other", "testroute2", nil, true},
{"/path/to/route/a", "testroute3", nil, true},
{"/path/to/hoge", "testroute4", []denco.Param{{"param", "hoge"}}, true},
{"/path/to/wildcard/some/params", "testroute5", []denco.Param{{"routepath", "some/params"}}, true},
{"/path/to/o1/o2", "testroute6", []denco.Param{{"param1", "o1"}, {"param2", "o2"}}, true},
{"/path/to/p1/sep/p2", "testroute7", []denco.Param{{"param1", "p1"}, {"param2", "p2"}}, true},
{"/2014/01/06", "testroute8", []denco.Param{{"year", "2014"}, {"month", "01"}, {"day", "06"}}, true},
{"/user/777", "testroute9", []denco.Param{{"id", "777"}}, true},
{"/a/to/b/p1/some/wildcard/params", "testroute10", []denco.Param{{"param", "p1"}, {"routepath", "some/wildcard/params"}}, true},
{"/missing", nil, nil, false},
}
runLookupTest(t, routes(), testcases)
records := []denco.Record{
{"/", "testroute0"},
{"/:b", "testroute1"},
{"/*wildcard", "testroute2"},
}
testcases = []testcase{
{"/", "testroute0", nil, true},
{"/true", "testroute1", []denco.Param{{"b", "true"}}, true},
{"/foo/bar", "testroute2", []denco.Param{{"wildcard", "foo/bar"}}, true},
}
runLookupTest(t, records, testcases)
records = []denco.Record{
{"/networks/:owner/:repo/events", "testroute0"},
{"/orgs/:org/events", "testroute1"},
{"/notifications/threads/:id", "testroute2"},
}
testcases = []testcase{
{"/networks/:owner/:repo/events", "testroute0", []denco.Param{{"owner", ":owner"}, {"repo", ":repo"}}, true},
{"/orgs/:org/events", "testroute1", []denco.Param{{"org", ":org"}}, true},
{"/notifications/threads/:id", "testroute2", []denco.Param{{"id", ":id"}}, true},
}
runLookupTest(t, records, testcases)
runLookupTest(t, []denco.Record{
{"/", "route2"},
}, []testcase{
{"/user/alice", nil, nil, false},
})
runLookupTest(t, []denco.Record{
{"/user/:name", "route1"},
}, []testcase{
{"/", nil, nil, false},
})
runLookupTest(t, []denco.Record{
{"/*wildcard", "testroute0"},
{"/a/:b", "testroute1"},
}, []testcase{
{"/a", "testroute0", []denco.Param{{"wildcard", "a"}}, true},
})
}
func TestRouter_Lookup_withManyRoutes(t *testing.T) {
n := 1000
rand.Seed(time.Now().UnixNano())
records := make([]denco.Record, n)
for i := 0; i < n; i++ {
records[i] = denco.Record{Key: "/" + randomString(rand.Intn(50)+10), Value: fmt.Sprintf("route%d", i)}
}
router := denco.New()
if err := router.Build(records); err != nil {
t.Fatal(err)
}
for _, r := range records {
data, params, found := router.Lookup(r.Key)
if !reflect.DeepEqual(data, r.Value) || len(params) != 0 || !reflect.DeepEqual(found, true) {
t.Errorf("Router.Lookup(%q) => (%#v, %#v, %#v), want (%#v, %#v, %#v)", r.Key, data, len(params), found, r.Value, 0, true)
}
}
}
func TestRouter_Lookup_realURIs(t *testing.T) {
testcases := []testcase{
{"/authorizations", "/authorizations", nil, true},
{"/authorizations/1", "/authorizations/:id", []denco.Param{{"id", "1"}}, true},
{"/applications/1/tokens/zohRoo7e", "/applications/:client_id/tokens/:access_token", []denco.Param{{"client_id", "1"}, {"access_token", "zohRoo7e"}}, true},
{"/events", "/events", nil, true},
{"/repos/naoina/denco/events", "/repos/:owner/:repo/events", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/networks/naoina/denco/events", "/networks/:owner/:repo/events", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/orgs/something/events", "/orgs/:org/events", []denco.Param{{"org", "something"}}, true},
{"/users/naoina/received_events", "/users/:user/received_events", []denco.Param{{"user", "naoina"}}, true},
{"/users/naoina/received_events/public", "/users/:user/received_events/public", []denco.Param{{"user", "naoina"}}, true},
{"/users/naoina/events", "/users/:user/events", []denco.Param{{"user", "naoina"}}, true},
{"/users/naoina/events/public", "/users/:user/events/public", []denco.Param{{"user", "naoina"}}, true},
{"/users/naoina/events/orgs/something", "/users/:user/events/orgs/:org", []denco.Param{{"user", "naoina"}, {"org", "something"}}, true},
{"/feeds", "/feeds", nil, true},
{"/notifications", "/notifications", nil, true},
{"/repos/naoina/denco/notifications", "/repos/:owner/:repo/notifications", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/notifications/threads/1", "/notifications/threads/:id", []denco.Param{{"id", "1"}}, true},
{"/notifications/threads/2/subscription", "/notifications/threads/:id/subscription", []denco.Param{{"id", "2"}}, true},
{"/repos/naoina/denco/stargazers", "/repos/:owner/:repo/stargazers", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/users/naoina/starred", "/users/:user/starred", []denco.Param{{"user", "naoina"}}, true},
{"/user/starred", "/user/starred", nil, true},
{"/user/starred/naoina/denco", "/user/starred/:owner/:repo", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/subscribers", "/repos/:owner/:repo/subscribers", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/users/naoina/subscriptions", "/users/:user/subscriptions", []denco.Param{{"user", "naoina"}}, true},
{"/user/subscriptions", "/user/subscriptions", nil, true},
{"/repos/naoina/denco/subscription", "/repos/:owner/:repo/subscription", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/user/subscriptions/naoina/denco", "/user/subscriptions/:owner/:repo", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/users/naoina/gists", "/users/:user/gists", []denco.Param{{"user", "naoina"}}, true},
{"/gists", "/gists", nil, true},
{"/gists/1", "/gists/:id", []denco.Param{{"id", "1"}}, true},
{"/gists/2/star", "/gists/:id/star", []denco.Param{{"id", "2"}}, true},
{"/repos/naoina/denco/git/blobs/03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9", "/repos/:owner/:repo/git/blobs/:sha", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"sha", "03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9"}}, true},
{"/repos/naoina/denco/git/commits/03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9", "/repos/:owner/:repo/git/commits/:sha", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"sha", "03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9"}}, true},
{"/repos/naoina/denco/git/refs", "/repos/:owner/:repo/git/refs", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/git/tags/03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9", "/repos/:owner/:repo/git/tags/:sha", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"sha", "03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9"}}, true},
{"/repos/naoina/denco/git/trees/03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9", "/repos/:owner/:repo/git/trees/:sha", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"sha", "03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9"}}, true},
{"/issues", "/issues", nil, true},
{"/user/issues", "/user/issues", nil, true},
{"/orgs/something/issues", "/orgs/:org/issues", []denco.Param{{"org", "something"}}, true},
{"/repos/naoina/denco/issues", "/repos/:owner/:repo/issues", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/issues/1", "/repos/:owner/:repo/issues/:number", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
{"/repos/naoina/denco/assignees", "/repos/:owner/:repo/assignees", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/assignees/foo", "/repos/:owner/:repo/assignees/:assignee", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"assignee", "foo"}}, true},
{"/repos/naoina/denco/issues/1/comments", "/repos/:owner/:repo/issues/:number/comments", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
{"/repos/naoina/denco/issues/1/events", "/repos/:owner/:repo/issues/:number/events", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
{"/repos/naoina/denco/labels", "/repos/:owner/:repo/labels", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/labels/bug", "/repos/:owner/:repo/labels/:name", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"name", "bug"}}, true},
{"/repos/naoina/denco/issues/1/labels", "/repos/:owner/:repo/issues/:number/labels", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
{"/repos/naoina/denco/milestones/1/labels", "/repos/:owner/:repo/milestones/:number/labels", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
{"/repos/naoina/denco/milestones", "/repos/:owner/:repo/milestones", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/milestones/1", "/repos/:owner/:repo/milestones/:number", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
{"/emojis", "/emojis", nil, true},
{"/gitignore/templates", "/gitignore/templates", nil, true},
{"/gitignore/templates/Go", "/gitignore/templates/:name", []denco.Param{{"name", "Go"}}, true},
{"/meta", "/meta", nil, true},
{"/rate_limit", "/rate_limit", nil, true},
{"/users/naoina/orgs", "/users/:user/orgs", []denco.Param{{"user", "naoina"}}, true},
{"/user/orgs", "/user/orgs", nil, true},
{"/orgs/something", "/orgs/:org", []denco.Param{{"org", "something"}}, true},
{"/orgs/something/members", "/orgs/:org/members", []denco.Param{{"org", "something"}}, true},
{"/orgs/something/members/naoina", "/orgs/:org/members/:user", []denco.Param{{"org", "something"}, {"user", "naoina"}}, true},
{"/orgs/something/public_members", "/orgs/:org/public_members", []denco.Param{{"org", "something"}}, true},
{"/orgs/something/public_members/naoina", "/orgs/:org/public_members/:user", []denco.Param{{"org", "something"}, {"user", "naoina"}}, true},
{"/orgs/something/teams", "/orgs/:org/teams", []denco.Param{{"org", "something"}}, true},
{"/teams/1", "/teams/:id", []denco.Param{{"id", "1"}}, true},
{"/teams/2/members", "/teams/:id/members", []denco.Param{{"id", "2"}}, true},
{"/teams/3/members/naoina", "/teams/:id/members/:user", []denco.Param{{"id", "3"}, {"user", "naoina"}}, true},
{"/teams/4/repos", "/teams/:id/repos", []denco.Param{{"id", "4"}}, true},
{"/teams/5/repos/naoina/denco", "/teams/:id/repos/:owner/:repo", []denco.Param{{"id", "5"}, {"owner", "naoina"}, {"repo", "denco"}}, true},
{"/user/teams", "/user/teams", nil, true},
{"/repos/naoina/denco/pulls", "/repos/:owner/:repo/pulls", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/pulls/1", "/repos/:owner/:repo/pulls/:number", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
{"/repos/naoina/denco/pulls/1/commits", "/repos/:owner/:repo/pulls/:number/commits", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
{"/repos/naoina/denco/pulls/1/files", "/repos/:owner/:repo/pulls/:number/files", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
{"/repos/naoina/denco/pulls/1/merge", "/repos/:owner/:repo/pulls/:number/merge", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
{"/repos/naoina/denco/pulls/1/comments", "/repos/:owner/:repo/pulls/:number/comments", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"number", "1"}}, true},
{"/user/repos", "/user/repos", nil, true},
{"/users/naoina/repos", "/users/:user/repos", []denco.Param{{"user", "naoina"}}, true},
{"/orgs/something/repos", "/orgs/:org/repos", []denco.Param{{"org", "something"}}, true},
{"/repositories", "/repositories", nil, true},
{"/repos/naoina/denco", "/repos/:owner/:repo", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/contributors", "/repos/:owner/:repo/contributors", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/languages", "/repos/:owner/:repo/languages", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/teams", "/repos/:owner/:repo/teams", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/tags", "/repos/:owner/:repo/tags", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/branches", "/repos/:owner/:repo/branches", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/branches/master", "/repos/:owner/:repo/branches/:branch", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"branch", "master"}}, true},
{"/repos/naoina/denco/collaborators", "/repos/:owner/:repo/collaborators", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/collaborators/something", "/repos/:owner/:repo/collaborators/:user", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"user", "something"}}, true},
{"/repos/naoina/denco/comments", "/repos/:owner/:repo/comments", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/commits/03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9/comments", "/repos/:owner/:repo/commits/:sha/comments", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"sha", "03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9"}}, true},
{"/repos/naoina/denco/comments/1", "/repos/:owner/:repo/comments/:id", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"id", "1"}}, true},
{"/repos/naoina/denco/commits", "/repos/:owner/:repo/commits", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/commits/03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9", "/repos/:owner/:repo/commits/:sha", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"sha", "03c3bbc7f0d12268b9ca53d4fbfd8dc5ae5697b9"}}, true},
{"/repos/naoina/denco/readme", "/repos/:owner/:repo/readme", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/keys", "/repos/:owner/:repo/keys", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/keys/1", "/repos/:owner/:repo/keys/:id", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"id", "1"}}, true},
{"/repos/naoina/denco/downloads", "/repos/:owner/:repo/downloads", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/downloads/2", "/repos/:owner/:repo/downloads/:id", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"id", "2"}}, true},
{"/repos/naoina/denco/forks", "/repos/:owner/:repo/forks", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/hooks", "/repos/:owner/:repo/hooks", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/hooks/2", "/repos/:owner/:repo/hooks/:id", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"id", "2"}}, true},
{"/repos/naoina/denco/releases", "/repos/:owner/:repo/releases", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/releases/1", "/repos/:owner/:repo/releases/:id", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"id", "1"}}, true},
{"/repos/naoina/denco/releases/1/assets", "/repos/:owner/:repo/releases/:id/assets", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"id", "1"}}, true},
{"/repos/naoina/denco/stats/contributors", "/repos/:owner/:repo/stats/contributors", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/stats/commit_activity", "/repos/:owner/:repo/stats/commit_activity", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/stats/code_frequency", "/repos/:owner/:repo/stats/code_frequency", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/stats/participation", "/repos/:owner/:repo/stats/participation", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/stats/punch_card", "/repos/:owner/:repo/stats/punch_card", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}}, true},
{"/repos/naoina/denco/statuses/master", "/repos/:owner/:repo/statuses/:ref", []denco.Param{{"owner", "naoina"}, {"repo", "denco"}, {"ref", "master"}}, true},
{"/search/repositories", "/search/repositories", nil, true},
{"/search/code", "/search/code", nil, true},
{"/search/issues", "/search/issues", nil, true},
{"/search/users", "/search/users", nil, true},
{"/legacy/issues/search/naoina/denco/closed/test", "/legacy/issues/search/:owner/:repository/:state/:keyword", []denco.Param{{"owner", "naoina"}, {"repository", "denco"}, {"state", "closed"}, {"keyword", "test"}}, true},
{"/legacy/repos/search/test", "/legacy/repos/search/:keyword", []denco.Param{{"keyword", "test"}}, true},
{"/legacy/user/search/test", "/legacy/user/search/:keyword", []denco.Param{{"keyword", "test"}}, true},
{"/legacy/user/email/naoina@kuune.org", "/legacy/user/email/:email", []denco.Param{{"email", "naoina@kuune.org"}}, true},
{"/users/naoina", "/users/:user", []denco.Param{{"user", "naoina"}}, true},
{"/user", "/user", nil, true},
{"/users", "/users", nil, true},
{"/user/emails", "/user/emails", nil, true},
{"/users/naoina/followers", "/users/:user/followers", []denco.Param{{"user", "naoina"}}, true},
{"/user/followers", "/user/followers", nil, true},
{"/users/naoina/following", "/users/:user/following", []denco.Param{{"user", "naoina"}}, true},
{"/user/following", "/user/following", nil, true},
{"/user/following/naoina", "/user/following/:user", []denco.Param{{"user", "naoina"}}, true},
{"/users/naoina/following/target", "/users/:user/following/:target_user", []denco.Param{{"user", "naoina"}, {"target_user", "target"}}, true},
{"/users/naoina/keys", "/users/:user/keys", []denco.Param{{"user", "naoina"}}, true},
{"/user/keys", "/user/keys", nil, true},
{"/user/keys/1", "/user/keys/:id", []denco.Param{{"id", "1"}}, true},
{"/people/me", "/people/:userId", []denco.Param{{"userId", "me"}}, true},
{"/people", "/people", nil, true},
{"/activities/foo/people/vault", "/activities/:activityId/people/:collection", []denco.Param{{"activityId", "foo"}, {"collection", "vault"}}, true},
{"/people/me/people/vault", "/people/:userId/people/:collection", []denco.Param{{"userId", "me"}, {"collection", "vault"}}, true},
{"/people/me/openIdConnect", "/people/:userId/openIdConnect", []denco.Param{{"userId", "me"}}, true},
{"/people/me/activities/vault", "/people/:userId/activities/:collection", []denco.Param{{"userId", "me"}, {"collection", "vault"}}, true},
{"/activities/foo", "/activities/:activityId", []denco.Param{{"activityId", "foo"}}, true},
{"/activities", "/activities", nil, true},
{"/activities/foo/comments", "/activities/:activityId/comments", []denco.Param{{"activityId", "foo"}}, true},
{"/comments/hoge", "/comments/:commentId", []denco.Param{{"commentId", "hoge"}}, true},
{"/people/me/moments/vault", "/people/:userId/moments/:collection", []denco.Param{{"userId", "me"}, {"collection", "vault"}}, true},
}
runLookupTest(t, realURIs, testcases)
}
func TestRouter_Build(t *testing.T) {
// test for duplicate name of path parameters.
func() {
r := denco.New()
if err := r.Build([]denco.Record{
{"/:user/:id/:id", "testroute0"},
{"/:user/:user/:id", "testroute0"},
}); err == nil {
t.Errorf("no error returned by duplicate name of path parameters")
}
}()
}
func TestRouter_Build_withoutSizeHint(t *testing.T) {
for _, v := range []struct {
keys []string
sizeHint int
}{
{[]string{"/user"}, 0},
{[]string{"/user/:id"}, 1},
{[]string{"/user/:id/post"}, 1},
{[]string{"/user/:id/:group"}, 2},
{[]string{"/user/:id/post/:cid"}, 2},
{[]string{"/user/:id/post/:cid", "/admin/:id/post/:cid"}, 2},
{[]string{"/user/:id", "/admin/:id/post/:cid"}, 2},
{[]string{"/user/:id/post/:cid", "/admin/:id/post/:cid/:type"}, 3},
} {
r := denco.New()
actual := r.SizeHint
expect := -1
if !reflect.DeepEqual(actual, expect) {
t.Errorf(`before Build; Router.SizeHint => (%[1]T=%#[1]v); want (%[2]T=%#[2]v)`, actual, expect)
}
records := make([]denco.Record, len(v.keys))
for i, k := range v.keys {
records[i] = denco.Record{Key: k, Value: "value"}
}
if err := r.Build(records); err != nil {
t.Fatal(err)
}
actual = r.SizeHint
expect = v.sizeHint
if !reflect.DeepEqual(actual, expect) {
t.Errorf(`Router.Build(%#v); Router.SizeHint => (%[2]T=%#[2]v); want (%[3]T=%#[3]v)`, records, actual, expect)
}
}
}
func TestRouter_Build_withSizeHint(t *testing.T) {
for _, v := range []struct {
key string
sizeHint int
expect int
}{
{"/user", 0, 0},
{"/user", 1, 1},
{"/user", 2, 2},
{"/user/:id", 3, 3},
{"/user/:id/:group", 0, 0},
{"/user/:id/:group", 1, 1},
} {
r := denco.New()
r.SizeHint = v.sizeHint
records := []denco.Record{
{v.key, "value"},
}
if err := r.Build(records); err != nil {
t.Fatal(err)
}
actual := r.SizeHint
expect := v.expect
if !reflect.DeepEqual(actual, expect) {
t.Errorf(`Router.Build(%#v); Router.SizeHint => (%[2]T=%#[2]v); want (%[3]T=%#[3]v)`, records, actual, expect)
}
}
}
func TestParams_Get(t *testing.T) {
params := denco.Params([]denco.Param{
{"name1", "value1"},
{"name2", "value2"},
{"name3", "value3"},
{"name1", "value4"},
})
for _, v := range []struct{ value, expected string }{
{"name1", "value1"},
{"name2", "value2"},
{"name3", "value3"},
{"name4", ""},
} {
actual := params.Get(v.value)
expected := v.expected
if !reflect.DeepEqual(actual, expected) {
t.Errorf("Params.Get(%q) => %#v, want %#v", v.value, actual, expected)
}
}
}

View File

@@ -1,106 +0,0 @@
package denco
import (
"net/http"
)
// Mux represents a multiplexer for HTTP request.
type Mux struct{}
// NewMux returns a new Mux.
func NewMux() *Mux {
return &Mux{}
}
// GET is shorthand of Mux.Handler("GET", path, handler).
func (m *Mux) GET(path string, handler HandlerFunc) Handler {
return m.Handler("GET", path, handler)
}
// POST is shorthand of Mux.Handler("POST", path, handler).
func (m *Mux) POST(path string, handler HandlerFunc) Handler {
return m.Handler("POST", path, handler)
}
// PUT is shorthand of Mux.Handler("PUT", path, handler).
func (m *Mux) PUT(path string, handler HandlerFunc) Handler {
return m.Handler("PUT", path, handler)
}
// HEAD is shorthand of Mux.Handler("HEAD", path, handler).
func (m *Mux) HEAD(path string, handler HandlerFunc) Handler {
return m.Handler("HEAD", path, handler)
}
// Handler returns a handler for HTTP method.
func (m *Mux) Handler(method, path string, handler HandlerFunc) Handler {
return Handler{
Method: method,
Path: path,
Func: handler,
}
}
// Build builds a http.Handler.
func (m *Mux) Build(handlers []Handler) (http.Handler, error) {
recordMap := make(map[string][]Record)
for _, h := range handlers {
recordMap[h.Method] = append(recordMap[h.Method], NewRecord(h.Path, h.Func))
}
mux := newServeMux()
for m, records := range recordMap {
router := New()
if err := router.Build(records); err != nil {
return nil, err
}
mux.routers[m] = router
}
return mux, nil
}
// Handler represents a handler of HTTP request.
type Handler struct {
// Method is an HTTP method.
Method string
// Path is a routing path for handler.
Path string
// Func is a function of handler of HTTP request.
Func HandlerFunc
}
// The HandlerFunc type is aliased to type of handler function.
type HandlerFunc func(w http.ResponseWriter, r *http.Request, params Params)
type serveMux struct {
routers map[string]*Router
}
func newServeMux() *serveMux {
return &serveMux{
routers: make(map[string]*Router),
}
}
// ServeHTTP implements http.Handler interface.
func (mux *serveMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
handler, params := mux.handler(r.Method, r.URL.Path)
handler(w, r, params)
}
func (mux *serveMux) handler(method, path string) (HandlerFunc, []Param) {
if router, found := mux.routers[method]; found {
if handler, params, found := router.Lookup(path); found {
return handler.(HandlerFunc), params
}
}
return NotFound, nil
}
// NotFound replies to the request with an HTTP 404 not found error.
// NotFound is called when unknown HTTP method or a handler not found.
// If you want to use the your own NotFound handler, please overwrite this variable.
var NotFound = func(w http.ResponseWriter, r *http.Request, _ Params) {
http.NotFound(w, r)
}

View File

@@ -1,106 +0,0 @@
package denco_test
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/go-openapi/runtime/middleware/denco"
)
func testHandlerFunc(w http.ResponseWriter, r *http.Request, params denco.Params) {
fmt.Fprintf(w, "method: %s, path: %s, params: %v", r.Method, r.URL.Path, params)
}
func TestMux(t *testing.T) {
mux := denco.NewMux()
handler, err := mux.Build([]denco.Handler{
mux.GET("/", testHandlerFunc),
mux.GET("/user/:name", testHandlerFunc),
mux.POST("/user/:name", testHandlerFunc),
mux.HEAD("/user/:name", testHandlerFunc),
mux.PUT("/user/:name", testHandlerFunc),
mux.Handler("GET", "/user/handler", testHandlerFunc),
mux.Handler("POST", "/user/handler", testHandlerFunc),
{"PUT", "/user/inference", testHandlerFunc},
})
if err != nil {
t.Fatal(err)
}
server := httptest.NewServer(handler)
defer server.Close()
for _, v := range []struct {
status int
method, path, expected string
}{
{200, "GET", "/", "method: GET, path: /, params: []"},
{200, "GET", "/user/alice", "method: GET, path: /user/alice, params: [{name alice}]"},
{200, "POST", "/user/bob", "method: POST, path: /user/bob, params: [{name bob}]"},
{200, "HEAD", "/user/alice", ""},
{200, "PUT", "/user/bob", "method: PUT, path: /user/bob, params: [{name bob}]"},
{404, "POST", "/", "404 page not found\n"},
{404, "GET", "/unknown", "404 page not found\n"},
{404, "POST", "/user/alice/1", "404 page not found\n"},
{200, "GET", "/user/handler", "method: GET, path: /user/handler, params: []"},
{200, "POST", "/user/handler", "method: POST, path: /user/handler, params: []"},
{200, "PUT", "/user/inference", "method: PUT, path: /user/inference, params: []"},
} {
req, err := http.NewRequest(v.method, server.URL+v.path, nil)
if err != nil {
t.Error(err)
continue
}
res, err := http.DefaultClient.Do(req)
if err != nil {
t.Error(err)
continue
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Error(err)
continue
}
actual := string(body)
expected := v.expected
if res.StatusCode != v.status || actual != expected {
t.Errorf(`%s "%s" => %#v %#v, want %#v %#v`, v.method, v.path, res.StatusCode, actual, v.status, expected)
}
}
}
func TestNotFound(t *testing.T) {
mux := denco.NewMux()
handler, err := mux.Build([]denco.Handler{})
if err != nil {
t.Fatal(err)
}
server := httptest.NewServer(handler)
defer server.Close()
origNotFound := denco.NotFound
defer func() {
denco.NotFound = origNotFound
}()
denco.NotFound = func(w http.ResponseWriter, r *http.Request, params denco.Params) {
w.WriteHeader(http.StatusServiceUnavailable)
fmt.Fprintf(w, "method: %s, path: %s, params: %v", r.Method, r.URL.Path, params)
}
res, err := http.Get(server.URL)
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatal(err)
}
actual := string(body)
expected := "method: GET, path: /, params: []"
if res.StatusCode != http.StatusServiceUnavailable || actual != expected {
t.Errorf(`GET "/" => %#v %#v, want %#v %#v`, res.StatusCode, actual, http.StatusServiceUnavailable, expected)
}
}

View File

@@ -1,12 +0,0 @@
package denco
// NextSeparator returns an index of next separator in path.
func NextSeparator(path string, start int) int {
for start < len(path) {
if c := path[start]; c == '/' || c == TerminationCharacter {
break
}
start++
}
return start
}

View File

@@ -1,31 +0,0 @@
package denco_test
import (
"reflect"
"testing"
"github.com/go-openapi/runtime/middleware/denco"
)
func TestNextSeparator(t *testing.T) {
for _, testcase := range []struct {
path string
start int
expected interface{}
}{
{"/path/to/route", 0, 0},
{"/path/to/route", 1, 5},
{"/path/to/route", 9, 14},
{"/path.html", 1, 10},
{"/foo/bar.html", 1, 4},
{"/foo/bar.html/baz.png", 5, 13},
{"/foo/bar.html/baz.png", 14, 21},
{"path#", 0, 4},
} {
actual := denco.NextSeparator(testcase.path, testcase.start)
expected := testcase.expected
if !reflect.DeepEqual(actual, expected) {
t.Errorf("path = %q, start = %v expect %v, but %v", testcase.path, testcase.start, expected, actual)
}
}
}