Compare commits
10 Commits
5d60552f12
...
4e99dd5057
Author | SHA1 | Date |
---|---|---|
Charles Iliya Krempeaux | 4e99dd5057 | |
Charles Iliya Krempeaux | cd58524dc0 | |
Charles Iliya Krempeaux | 5a1e556549 | |
Charles Iliya Krempeaux | ebd12da522 | |
Charles Iliya Krempeaux | ca42c95e52 | |
Charles Iliya Krempeaux | ebbb49df89 | |
Charles Iliya Krempeaux | 9a424db462 | |
Charles Iliya Krempeaux | 64422ea7ec | |
Charles Iliya Krempeaux | 4b0308c64e | |
Charles Iliya Krempeaux | 7bad8c950e |
|
@ -67,6 +67,8 @@ func CompileTo(target *Pattern, uncompiledPattern string) error {
|
||||||
|
|
||||||
target.init(defaultFieldTagName)
|
target.init(defaultFieldTagName)
|
||||||
|
|
||||||
|
target.template = uncompiledPattern
|
||||||
|
|
||||||
s := uncompiledPattern
|
s := uncompiledPattern
|
||||||
for {
|
for {
|
||||||
index := strings.IndexRune(s, '{')
|
index := strings.IndexRune(s, '{')
|
||||||
|
|
44
doc.go
44
doc.go
|
@ -1,12 +1,43 @@
|
||||||
/*
|
/*
|
||||||
Package pathmatch provides pattern matching for paths.
|
Package pathmatch provides pattern matching for path templates.
|
||||||
|
|
||||||
For example, a path could be a file system path, or a path could be a path from a URL (such as an HTTP or HTTPS based URL).
|
A path template might look something like the following:
|
||||||
|
|
||||||
The matches can be loaded into variables (when using pathmatch.Find());
|
/v1/users/{user_id}
|
||||||
or can be loaded into a struct (when using pathmatch.Pattern.FindAndLoad()).
|
|
||||||
|
|
||||||
Example Usage:
|
Or:
|
||||||
|
|
||||||
|
/account={account_name}/user={user_name}/message={message_hash}
|
||||||
|
|
||||||
|
Or:
|
||||||
|
|
||||||
|
/backup/{folder_name}/
|
||||||
|
Or:
|
||||||
|
|
||||||
|
/v2/select/{fields}/from/{table_name}/where/{filters}
|
||||||
|
|
||||||
|
This path template could be a file system path, or a path could be a path from a URL (such as an HTTP or HTTPS based URL).
|
||||||
|
|
||||||
|
To compile one of these pattern templates, you would do something such as:
|
||||||
|
|
||||||
|
var template string = "/v1/users/{user_id}/messages/{message_id}"
|
||||||
|
|
||||||
|
var pattern pathmatch.Pattern
|
||||||
|
|
||||||
|
err := pathmatch.CompileTo(&pattern, template)
|
||||||
|
if nil != err {
|
||||||
|
fmt.Fprintf(os.Stdout, "ERROR: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
(In addition to the pathmatch.CompileTo() func, there is also the pathmatch.Compile(), and
|
||||||
|
pathmatch.MustCompile(). But pathmatch.CompileTo() is recommended over the other 2 options
|
||||||
|
for most cases.)
|
||||||
|
|
||||||
|
One you have the compiled pattern, you would either use pathmatch.Match(), pathmatch.Find(),
|
||||||
|
or pathmatch.FindAndLoad() depending on what you were trying to accomplish.
|
||||||
|
|
||||||
|
Example Usage
|
||||||
|
|
||||||
var pattern pathmatch.Pattern
|
var pattern pathmatch.Pattern
|
||||||
|
|
||||||
|
@ -35,7 +66,7 @@ Example Usage:
|
||||||
fmt.Printf("user_id = %q \n", userId) // user_id = "bMM_kJFMEV"
|
fmt.Printf("user_id = %q \n", userId) // user_id = "bMM_kJFMEV"
|
||||||
fmt.Printf("vehicle_id = %q \n", vehicleId) // vehicle_id = "o_bcU.RZGK"
|
fmt.Printf("vehicle_id = %q \n", vehicleId) // vehicle_id = "o_bcU.RZGK"
|
||||||
|
|
||||||
Alternate Example Usage:
|
Alternate Example Usage
|
||||||
|
|
||||||
var pattern pathmatch.Pattern
|
var pattern pathmatch.Pattern
|
||||||
|
|
||||||
|
@ -64,6 +95,5 @@ Alternate Example Usage:
|
||||||
|
|
||||||
fmt.Printf("user_id = %q \n", data.UserId) // user_id = "bMM_kJFMEV"
|
fmt.Printf("user_id = %q \n", data.UserId) // user_id = "bMM_kJFMEV"
|
||||||
fmt.Printf("vehicle_id = %q \n", data.VehicleId) // vehicle_id = "o_bcU.RZGK"
|
fmt.Printf("vehicle_id = %q \n", data.VehicleId) // vehicle_id = "o_bcU.RZGK"
|
||||||
|
|
||||||
*/
|
*/
|
||||||
package pathmatch
|
package pathmatch
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package pathmatch_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/reiver/go-pathmatch"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExamplePattern_String() {
|
||||||
|
|
||||||
|
var template = "/v1/users/{user_id}"
|
||||||
|
|
||||||
|
var pattern pathmatch.Pattern
|
||||||
|
|
||||||
|
err := pathmatch.CompileTo(&pattern, template)
|
||||||
|
if nil != err {
|
||||||
|
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("pattern: %s", pattern)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// pattern: /v1/users/{user_id}
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ import (
|
||||||
// }
|
// }
|
||||||
type Pattern struct {
|
type Pattern struct {
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
|
template string
|
||||||
bits []string
|
bits []string
|
||||||
names []string
|
names []string
|
||||||
namesSet map[string]struct{}
|
namesSet map[string]struct{}
|
||||||
|
|
|
@ -12,6 +12,11 @@ var (
|
||||||
errThisShouldNeverHappen = newInternalError("This should never happen.")
|
errThisShouldNeverHappen = newInternalError("This should never happen.")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Find compares ‘path’ against its (compiled) pattern template; if it matches it loads the
|
||||||
|
// matches into ‘args’, and then returns true.
|
||||||
|
//
|
||||||
|
// Find may set some, or all of the items in ‘args’ even if it returns false, and even if it
|
||||||
|
// returns an error.
|
||||||
func (pattern *Pattern) Find(path string, args ...interface{}) (bool, error) {
|
func (pattern *Pattern) Find(path string, args ...interface{}) (bool, error) {
|
||||||
if nil == pattern {
|
if nil == pattern {
|
||||||
return false, errNilReceiver
|
return false, errNilReceiver
|
||||||
|
@ -33,24 +38,33 @@ func (pattern *Pattern) Find(path string, args ...interface{}) (bool, error) {
|
||||||
|
|
||||||
s = s[len(bit):]
|
s = s[len(bit):]
|
||||||
case wildcardBit:
|
case wildcardBit:
|
||||||
index := strings.IndexRune(s, '/')
|
if "" == s {
|
||||||
if -1 == index {
|
return false, nil
|
||||||
if err := set(s, argsIndex, args...); nil != err {
|
|
||||||
return doesNotMatter, err
|
|
||||||
}
|
|
||||||
argsIndex++
|
|
||||||
} else if 0 <= index {
|
|
||||||
value := s[:index]
|
|
||||||
if err := set(value, argsIndex, args...); nil != err {
|
|
||||||
return doesNotMatter, err
|
|
||||||
}
|
|
||||||
argsIndex++
|
|
||||||
s = s[index:]
|
|
||||||
} else {
|
|
||||||
return doesNotMatter, errThisShouldNeverHappen
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
index := strings.IndexRune(s, '/')
|
||||||
|
|
||||||
|
var value string
|
||||||
|
switch {
|
||||||
|
default:
|
||||||
|
return doesNotMatter, errThisShouldNeverHappen
|
||||||
|
case -1 == index:
|
||||||
|
value = s
|
||||||
|
case 0 <= index:
|
||||||
|
value = s[:index]
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := set(value, argsIndex, args...); nil != err {
|
||||||
|
return doesNotMatter, err
|
||||||
|
}
|
||||||
|
argsIndex++
|
||||||
|
s = s[len(value):]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if "" != s {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package pathmatch
|
package pathmatch_test
|
||||||
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/reiver/go-pathmatch"
|
||||||
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -9,13 +11,13 @@ import (
|
||||||
func TestFind(t *testing.T) {
|
func TestFind(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct{
|
tests := []struct{
|
||||||
Pattern *Pattern
|
Pattern *pathmatch.Pattern
|
||||||
Args []interface{}
|
Args []interface{}
|
||||||
Path string
|
Path string
|
||||||
ExpectedArgs []string
|
ExpectedArgs []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Pattern: MustCompile("/{this}/{that}/{these}/{those}"),
|
Pattern: pathmatch.MustCompile("/{this}/{that}/{these}/{those}"),
|
||||||
Args: []interface{}{new(string), new(string), new(string), new(string), },
|
Args: []interface{}{new(string), new(string), new(string), new(string), },
|
||||||
Path: "/apple/banana/cherry/grape",
|
Path: "/apple/banana/cherry/grape",
|
||||||
ExpectedArgs: []string{"apple","banana","cherry","grape"},
|
ExpectedArgs: []string{"apple","banana","cherry","grape"},
|
||||||
|
@ -24,65 +26,65 @@ func TestFind(t *testing.T) {
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
Pattern: MustCompile("/user/{sessionKey}"),
|
Pattern: pathmatch.MustCompile("/user/{sessionKey}"),
|
||||||
Args: []interface{}{new(string), },
|
Args: []interface{}{new(string), },
|
||||||
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij",
|
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij",
|
||||||
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
|
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Pattern: MustCompile("/user/{sessionKey}/"),
|
Pattern: pathmatch.MustCompile("/user/{sessionKey}/"),
|
||||||
Args: []interface{}{new(string), },
|
Args: []interface{}{new(string), },
|
||||||
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/",
|
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/",
|
||||||
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
|
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
Pattern: MustCompile("/user/{sessionKey}/vehicle"),
|
Pattern: pathmatch.MustCompile("/user/{sessionKey}/vehicle"),
|
||||||
Args: []interface{}{new(string), },
|
Args: []interface{}{new(string), },
|
||||||
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle",
|
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle",
|
||||||
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
|
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Pattern: MustCompile("/user/{sessionKey}/vehicle/"),
|
Pattern: pathmatch.MustCompile("/user/{sessionKey}/vehicle/"),
|
||||||
Args: []interface{}{new(string), },
|
Args: []interface{}{new(string), },
|
||||||
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/",
|
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/",
|
||||||
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
|
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
Pattern: MustCompile("/user/{sessionKey}/vehicle/DEFAULT"),
|
Pattern: pathmatch.MustCompile("/user/{sessionKey}/vehicle/DEFAULT"),
|
||||||
Args: []interface{}{new(string), },
|
Args: []interface{}{new(string), },
|
||||||
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/DEFAULT",
|
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/DEFAULT",
|
||||||
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
|
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Pattern: MustCompile("/user/{sessionKey}/vehicle/DEFAULT/"),
|
Pattern: pathmatch.MustCompile("/user/{sessionKey}/vehicle/DEFAULT/"),
|
||||||
Args: []interface{}{new(string), },
|
Args: []interface{}{new(string), },
|
||||||
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/DEFAULT/",
|
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/DEFAULT/",
|
||||||
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
|
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
Pattern: MustCompile("/user/{sessionKey}/vehicle/{vehicleIdcode}"),
|
Pattern: pathmatch.MustCompile("/user/{sessionKey}/vehicle/{vehicleIdcode}"),
|
||||||
Args: []interface{}{new(string), new(string), },
|
Args: []interface{}{new(string), new(string), },
|
||||||
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/DEFAULT",
|
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/DEFAULT",
|
||||||
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij", "DEFAULT"},
|
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij", "DEFAULT"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Pattern: MustCompile("/user/{sessionKey}/vehicle/{vehicleIdcode}/"),
|
Pattern: pathmatch.MustCompile("/user/{sessionKey}/vehicle/{vehicleIdcode}/"),
|
||||||
Args: []interface{}{new(string), new(string), },
|
Args: []interface{}{new(string), new(string), },
|
||||||
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/DEFAULT/",
|
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/DEFAULT/",
|
||||||
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij", "DEFAULT"},
|
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij", "DEFAULT"},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
Pattern: MustCompile("/user/{sessionKey}/vehicle/{vehicleIdcode}"),
|
Pattern: pathmatch.MustCompile("/user/{sessionKey}/vehicle/{vehicleIdcode}"),
|
||||||
Args: []interface{}{new(string), new(string), },
|
Args: []interface{}{new(string), new(string), },
|
||||||
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/N9Z_tiv7",
|
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/N9Z_tiv7",
|
||||||
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij", "N9Z_tiv7"},
|
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij", "N9Z_tiv7"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Pattern: MustCompile("/user/{sessionKey}/vehicle/{vehicleIdcode}/"),
|
Pattern: pathmatch.MustCompile("/user/{sessionKey}/vehicle/{vehicleIdcode}/"),
|
||||||
Args: []interface{}{new(string), new(string), },
|
Args: []interface{}{new(string), new(string), },
|
||||||
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/N9Z_tiv7/",
|
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/N9Z_tiv7/",
|
||||||
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij", "N9Z_tiv7"},
|
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij", "N9Z_tiv7"},
|
||||||
|
|
|
@ -14,7 +14,13 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func (pattern *Pattern) FindAndLoad(path string, strct interface{}) (bool, error) {
|
// FindAndLoad compares ‘path’ against its (compiled) pattern template; if it matches
|
||||||
|
// it loads the matches into ‘dest’, and then returns true.
|
||||||
|
//
|
||||||
|
// ‘dest’ can be a pointer struct, or a pointer to a []string.
|
||||||
|
//
|
||||||
|
// Find may set some, or all of the items or fields in ‘dest’ even if it returns false, and even if it returns an error.
|
||||||
|
func (pattern *Pattern) FindAndLoad(path string, dest interface{}) (bool, error) {
|
||||||
if nil == pattern {
|
if nil == pattern {
|
||||||
return false, errNilReceiver
|
return false, errNilReceiver
|
||||||
}
|
}
|
||||||
|
@ -39,12 +45,59 @@ func (pattern *Pattern) FindAndLoad(path string, strct interface{}) (bool, error
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
reflectedValue := reflect.ValueOf(strct)
|
reflectedValue := reflect.ValueOf(dest)
|
||||||
if reflect.Ptr != reflectedValue.Kind() {
|
if reflect.Ptr != reflectedValue.Kind() {
|
||||||
|
//@TODO: change error
|
||||||
return doesNotMatter, errExpectedAPointerToAStruct
|
return doesNotMatter, errExpectedAPointerToAStruct
|
||||||
}
|
}
|
||||||
|
|
||||||
reflectedValueElem := reflectedValue.Elem()
|
reflectedValueElem := reflectedValue.Elem()
|
||||||
|
switch reflectedValueElem.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
var a []string = make([]string, len(args))
|
||||||
|
for i, arg := range args {
|
||||||
|
a[i] = *(arg.(*string))
|
||||||
|
}
|
||||||
|
|
||||||
|
return loadSlice(dest, a...)
|
||||||
|
case reflect.Struct:
|
||||||
|
return pattern.loadStruct(reflectedValueElem, args)
|
||||||
|
default:
|
||||||
|
//@TODO: change error
|
||||||
|
return doesNotMatter, errExpectedAPointerToAStruct
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadSlice(dest interface{}, matches ...string) (bool, error) {
|
||||||
|
if nil == dest {
|
||||||
|
return false, errNilTarget
|
||||||
|
}
|
||||||
|
|
||||||
|
target, casted := dest.(*[]string)
|
||||||
|
if !casted {
|
||||||
|
//@TODO: CHANGE ERROR! ============================
|
||||||
|
return false, errExpectedAPointerToAStruct
|
||||||
|
}
|
||||||
|
if nil == target {
|
||||||
|
return false, errNilTarget
|
||||||
|
}
|
||||||
|
|
||||||
|
*target = (*target)[:0]
|
||||||
|
for _, match := range matches {
|
||||||
|
*target = append(*target, match)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pattern *Pattern) loadStruct(reflectedValueElem reflect.Value, args []interface{}) (bool, error) {
|
||||||
|
if nil == pattern {
|
||||||
|
return false, errNilReceiver
|
||||||
|
}
|
||||||
|
|
||||||
|
if reflect.Struct != reflectedValueElem.Kind() {
|
||||||
|
return doesNotMatter, errExpectedAPointerToAStruct
|
||||||
|
}
|
||||||
|
|
||||||
reflectedValueElemType := reflectedValueElem.Type()
|
reflectedValueElemType := reflectedValueElem.Type()
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,13 @@ package pathmatch
|
||||||
import (
|
import (
|
||||||
"github.com/fatih/structs"
|
"github.com/fatih/structs"
|
||||||
|
|
||||||
|
"reflect"
|
||||||
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func TestFindAndLoad(t *testing.T) {
|
func TestFindAndLoadStrucs(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct{
|
tests := []struct{
|
||||||
Pattern *Pattern
|
Pattern *Pattern
|
||||||
|
@ -166,3 +168,73 @@ func TestFindAndLoad(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFindAndLoadStrings(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct{
|
||||||
|
Pattern *Pattern
|
||||||
|
Path string
|
||||||
|
Expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Pattern: MustCompile("/user/{sessionKey}"),
|
||||||
|
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij",
|
||||||
|
Expected: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Pattern: MustCompile("/user/{sessionKey}/vehicle/{vehicleIdcode}/"),
|
||||||
|
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/DEFAULT/",
|
||||||
|
Expected: []string{
|
||||||
|
"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij",
|
||||||
|
"DEFAULT",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Pattern: MustCompile("/{this}/{that}/{these}/{those}"),
|
||||||
|
Path: "/apple/banana/cherry/grape",
|
||||||
|
Expected: []string{
|
||||||
|
"apple",
|
||||||
|
"banana",
|
||||||
|
"cherry",
|
||||||
|
"grape",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for testNumber, test := range tests {
|
||||||
|
|
||||||
|
var actual []string
|
||||||
|
|
||||||
|
matched, err := test.Pattern.FindAndLoad(test.Path, &actual)
|
||||||
|
if nil != err {
|
||||||
|
t.Errorf("For test #%d, did not expect an error, but actually got one: (%T) %q", testNumber, err, err)
|
||||||
|
t.Logf("\tPATTERN: %q", test.Pattern)
|
||||||
|
t.Logf("\tPATH: %q", test.Path)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !matched {
|
||||||
|
t.Errorf("For test #%d, expected a match, but it didn't.", testNumber)
|
||||||
|
t.Logf("\tPATTERN: %q", test.Pattern)
|
||||||
|
t.Logf("\tPATH: %q", test.Path)
|
||||||
|
t.Log("\t--")
|
||||||
|
t.Logf("\tMATCHED: %t", matched)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected := test.Expected; !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Errorf("For test #%d, did not get what was expected.", testNumber)
|
||||||
|
t.Logf("\tPATTERN: %q", test.Pattern)
|
||||||
|
t.Logf("\tPATH: %q", test.Path)
|
||||||
|
t.Log("\t--")
|
||||||
|
t.Logf("\tEXPECTED: %#v", expected)
|
||||||
|
t.Logf("\tACTUAL: %#v", actual)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
package pathmatch
|
||||||
|
|
||||||
|
// Match returns true if ‘path’ matches the compiled pattern, else returns false if it doesn't match.
|
||||||
|
func (receiver *Pattern) Match(path string) (bool, error) {
|
||||||
|
if nil == receiver {
|
||||||
|
return false, errNilReceiver
|
||||||
|
}
|
||||||
|
|
||||||
|
//@TODO: Is it a good idea to be dynamically creating this?
|
||||||
|
//@TODO: Also, can the struct fields be put in here directly instead?
|
||||||
|
args := []interface{}{}
|
||||||
|
numNames := len(receiver.MatchNames())
|
||||||
|
for i:=0; i<numNames; i++ {
|
||||||
|
args = append(args, new(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
found, err := receiver.Find(path, args...)
|
||||||
|
if nil != err {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return found, nil
|
||||||
|
}
|
|
@ -0,0 +1,366 @@
|
||||||
|
package pathmatch_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/reiver/go-pathmatch"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPatternMatch(t *testing.T) {
|
||||||
|
tests := []struct{
|
||||||
|
Pattern string
|
||||||
|
Path string
|
||||||
|
Expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Pattern: "/v1/help",
|
||||||
|
Path: "/v1/help",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/help",
|
||||||
|
Path: "/v1/help/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/help",
|
||||||
|
Path: "/v1/help/me",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/help",
|
||||||
|
Path: "/v1/help/me/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/help",
|
||||||
|
Path: "/v1/helping",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/help",
|
||||||
|
Path: "/v1/helping/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/help",
|
||||||
|
Path: "/v2/help",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/help",
|
||||||
|
Path: "/v2/HELP",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/help",
|
||||||
|
Path: "/v1/apple",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/help",
|
||||||
|
Path: "/v1/banana",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/help",
|
||||||
|
Path: "/v1/cherry",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Pattern: "/v1/help/",
|
||||||
|
Path: "/v1/help/",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/help/",
|
||||||
|
Path: "/v1/help",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/help/",
|
||||||
|
Path: "/v1/help/me",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/help/",
|
||||||
|
Path: "/v1/help/me/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/help/",
|
||||||
|
Path: "/v1/helping",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/help/",
|
||||||
|
Path: "/v1/helping/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/help/",
|
||||||
|
Path: "/v2/help/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/help/",
|
||||||
|
Path: "/v2/HELP/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/help/",
|
||||||
|
Path: "/v1/apple/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/help/",
|
||||||
|
Path: "/v1/banana/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/help/",
|
||||||
|
Path: "/v1/cherry/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}",
|
||||||
|
Path: "/v1/user/123",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}",
|
||||||
|
Path: "/v1/user/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}",
|
||||||
|
Path: "/v1/user/123/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}",
|
||||||
|
Path: "//v1/user/123",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}",
|
||||||
|
Path: "/v1//user/123",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}",
|
||||||
|
Path: "/v1/user//123",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}",
|
||||||
|
Path: "//v1//user/123",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}",
|
||||||
|
Path: "/v1//user//123",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}",
|
||||||
|
Path: "//v1//user//123",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}",
|
||||||
|
Path: "//v1//user//123//",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}/",
|
||||||
|
Path: "/v1/user/123/",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}/",
|
||||||
|
Path: "/v1/user/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}/",
|
||||||
|
Path: "/v1/user/123",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}/",
|
||||||
|
Path: "//v1/user/123/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}/",
|
||||||
|
Path: "/v1//user/123/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}/",
|
||||||
|
Path: "/v1/user//123/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}/",
|
||||||
|
Path: "//v1//user/123/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}/",
|
||||||
|
Path: "/v1//user//123/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}/",
|
||||||
|
Path: "//v1//user//123/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}/",
|
||||||
|
Path: "//v1//user//123//",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}/contact/{contact_type}",
|
||||||
|
Path: "/v1/user/123/contact/e-mail",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}/contact/{contact_type}",
|
||||||
|
Path: "/v1/user/123/contact/e-mail/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}/contact/{contact_type}",
|
||||||
|
Path: "/v2/user/123/contact/e-mail",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}/contact/{contact_type}",
|
||||||
|
Path: "/v2/user/123/contact/e-mail",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}/contact/{contact_type}/",
|
||||||
|
Path: "/v1/user/123/contact/e-mail/",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}/contact/{contact_type}/",
|
||||||
|
Path: "/v1/user/123/contact/e-mail",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}/contact/{contact_type}/",
|
||||||
|
Path: "/v2/user/123/contact/e-mail/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/user/{user_id}/contact/{contact_type}/",
|
||||||
|
Path: "/v2/user/123/contact/e-mail/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Pattern: "/v1/company/{company_name}",
|
||||||
|
Path: "/v1/company/acme",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/company/{company_name}",
|
||||||
|
Path: "/v1/company/acme/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/company/{company_name}",
|
||||||
|
Path: "/v2/company/acme",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/company/{company_name}",
|
||||||
|
Path: "/v1/user/acme",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/company/{company_name}",
|
||||||
|
Path: "/v1/COMPANY/acme",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Pattern: "/v1/company/{company_name}/",
|
||||||
|
Path: "/v1/company/acme/",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/company/{company_name}/",
|
||||||
|
Path: "/v1/company/acme",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/company/{company_name}/",
|
||||||
|
Path: "/v2/company/acme/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/company/{company_name}/",
|
||||||
|
Path: "/v1/user/acme/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Pattern: "/v1/company/{company_name}/",
|
||||||
|
Path: "/v1/COMPANY/acme/",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for testNumber, test := range tests {
|
||||||
|
var pattern pathmatch.Pattern
|
||||||
|
|
||||||
|
if err := pathmatch.CompileTo(&pattern, test.Pattern); nil != err {
|
||||||
|
t.Errorf("For test #%d, did not expect an error, but actually got one: (%T) %q", testNumber, err, err)
|
||||||
|
t.Errorf("\t: PATTERN: %q", test.Pattern)
|
||||||
|
t.Errorf("\t: PATH: %q", test.Path)
|
||||||
|
t.Errorf("\t: EXPECTED: %t", test.Expected)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
matched, err := pattern.Match(test.Path)
|
||||||
|
if nil != err {
|
||||||
|
t.Errorf("For test #%d, did not expect an error, but actually got one: (%T) %q", testNumber, err, err)
|
||||||
|
t.Errorf("\t: PATTERN: %q", test.Pattern)
|
||||||
|
t.Errorf("\t: PATH: %q", test.Path)
|
||||||
|
t.Errorf("\t: EXPECTED: %t", test.Expected)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected, actual := test.Expected, matched; expected != actual {
|
||||||
|
t.Errorf("For test #%d, expected %t, but actually got %t.", testNumber, expected, actual)
|
||||||
|
t.Errorf("\t: PATTERN: %q", test.Pattern)
|
||||||
|
t.Errorf("\t: PATH: %q", test.Path)
|
||||||
|
t.Errorf("\t: EXPECTED: %t", test.Expected)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package pathmatch
|
||||||
|
|
||||||
|
// String makes pathmatch.Pattern fit the fmt.Stringer interface.
|
||||||
|
//
|
||||||
|
// String returns the (pre-compiled) pattern template.
|
||||||
|
func (receiver Pattern) String() string {
|
||||||
|
return receiver.template
|
||||||
|
}
|
Loading…
Reference in New Issue