Compare commits

..

No commits in common. "4e99dd5057b159276c29fab7cf7af199f7345844" and "5d60552f123821fa17565c698bc93dae58df1042" have entirely different histories.

11 changed files with 36 additions and 633 deletions

View File

@ -67,8 +67,6 @@ func CompileTo(target *Pattern, uncompiledPattern string) error {
target.init(defaultFieldTagName)
target.template = uncompiledPattern
s := uncompiledPattern
for {
index := strings.IndexRune(s, '{')

44
doc.go
View File

@ -1,43 +1,12 @@
/*
Package pathmatch provides pattern matching for path templates.
Package pathmatch provides pattern matching for paths.
A path template might look something like the following:
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).
/v1/users/{user_id}
The matches can be loaded into variables (when using pathmatch.Find());
or can be loaded into a struct (when using pathmatch.Pattern.FindAndLoad()).
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
Example Usage:
var pattern pathmatch.Pattern
@ -66,7 +35,7 @@ Example Usage
fmt.Printf("user_id = %q \n", userId) // user_id = "bMM_kJFMEV"
fmt.Printf("vehicle_id = %q \n", vehicleId) // vehicle_id = "o_bcU.RZGK"
Alternate Example Usage
Alternate Example Usage:
var pattern pathmatch.Pattern
@ -95,5 +64,6 @@ Alternate Example Usage
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"
*/
package pathmatch

View File

@ -1,26 +0,0 @@
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}
}

View File

@ -32,7 +32,6 @@ import (
// }
type Pattern struct {
mutex sync.RWMutex
template string
bits []string
names []string
namesSet map[string]struct{}

View File

@ -12,11 +12,6 @@ var (
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) {
if nil == pattern {
return false, errNilReceiver
@ -38,32 +33,23 @@ func (pattern *Pattern) Find(path string, args ...interface{}) (bool, error) {
s = s[len(bit):]
case wildcardBit:
if "" == s {
return false, nil
}
index := strings.IndexRune(s, '/')
var value string
switch {
default:
return doesNotMatter, errThisShouldNeverHappen
case -1 == index:
value = s
case 0 <= index:
value = s[:index]
if -1 == index {
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[len(value):]
s = s[index:]
} else {
return doesNotMatter, errThisShouldNeverHappen
}
}
if "" != s {
return false, nil
}
return true, nil

View File

@ -1,9 +1,7 @@
package pathmatch_test
package pathmatch
import (
"github.com/reiver/go-pathmatch"
"testing"
)
@ -11,13 +9,13 @@ import (
func TestFind(t *testing.T) {
tests := []struct{
Pattern *pathmatch.Pattern
Pattern *Pattern
Args []interface{}
Path string
ExpectedArgs []string
}{
{
Pattern: pathmatch.MustCompile("/{this}/{that}/{these}/{those}"),
Pattern: MustCompile("/{this}/{that}/{these}/{those}"),
Args: []interface{}{new(string), new(string), new(string), new(string), },
Path: "/apple/banana/cherry/grape",
ExpectedArgs: []string{"apple","banana","cherry","grape"},
@ -26,65 +24,65 @@ func TestFind(t *testing.T) {
{
Pattern: pathmatch.MustCompile("/user/{sessionKey}"),
Pattern: MustCompile("/user/{sessionKey}"),
Args: []interface{}{new(string), },
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij",
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
},
{
Pattern: pathmatch.MustCompile("/user/{sessionKey}/"),
Pattern: MustCompile("/user/{sessionKey}/"),
Args: []interface{}{new(string), },
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/",
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
},
{
Pattern: pathmatch.MustCompile("/user/{sessionKey}/vehicle"),
Pattern: MustCompile("/user/{sessionKey}/vehicle"),
Args: []interface{}{new(string), },
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle",
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
},
{
Pattern: pathmatch.MustCompile("/user/{sessionKey}/vehicle/"),
Pattern: MustCompile("/user/{sessionKey}/vehicle/"),
Args: []interface{}{new(string), },
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/",
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
},
{
Pattern: pathmatch.MustCompile("/user/{sessionKey}/vehicle/DEFAULT"),
Pattern: MustCompile("/user/{sessionKey}/vehicle/DEFAULT"),
Args: []interface{}{new(string), },
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/DEFAULT",
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
},
{
Pattern: pathmatch.MustCompile("/user/{sessionKey}/vehicle/DEFAULT/"),
Pattern: MustCompile("/user/{sessionKey}/vehicle/DEFAULT/"),
Args: []interface{}{new(string), },
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/DEFAULT/",
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
},
{
Pattern: pathmatch.MustCompile("/user/{sessionKey}/vehicle/{vehicleIdcode}"),
Pattern: MustCompile("/user/{sessionKey}/vehicle/{vehicleIdcode}"),
Args: []interface{}{new(string), new(string), },
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/DEFAULT",
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij", "DEFAULT"},
},
{
Pattern: pathmatch.MustCompile("/user/{sessionKey}/vehicle/{vehicleIdcode}/"),
Pattern: MustCompile("/user/{sessionKey}/vehicle/{vehicleIdcode}/"),
Args: []interface{}{new(string), new(string), },
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/DEFAULT/",
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij", "DEFAULT"},
},
{
Pattern: pathmatch.MustCompile("/user/{sessionKey}/vehicle/{vehicleIdcode}"),
Pattern: MustCompile("/user/{sessionKey}/vehicle/{vehicleIdcode}"),
Args: []interface{}{new(string), new(string), },
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/N9Z_tiv7",
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij", "N9Z_tiv7"},
},
{
Pattern: pathmatch.MustCompile("/user/{sessionKey}/vehicle/{vehicleIdcode}/"),
Pattern: MustCompile("/user/{sessionKey}/vehicle/{vehicleIdcode}/"),
Args: []interface{}{new(string), new(string), },
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/N9Z_tiv7/",
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij", "N9Z_tiv7"},

View File

@ -14,13 +14,7 @@ var (
)
// 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) {
func (pattern *Pattern) FindAndLoad(path string, strct interface{}) (bool, error) {
if nil == pattern {
return false, errNilReceiver
}
@ -45,59 +39,12 @@ func (pattern *Pattern) FindAndLoad(path string, dest interface{}) (bool, error)
return false, nil
}
reflectedValue := reflect.ValueOf(dest)
reflectedValue := reflect.ValueOf(strct)
if reflect.Ptr != reflectedValue.Kind() {
//@TODO: change error
return doesNotMatter, errExpectedAPointerToAStruct
}
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()

View File

@ -4,13 +4,11 @@ package pathmatch
import (
"github.com/fatih/structs"
"reflect"
"testing"
)
func TestFindAndLoadStrucs(t *testing.T) {
func TestFindAndLoad(t *testing.T) {
tests := []struct{
Pattern *Pattern
@ -168,73 +166,3 @@ func TestFindAndLoadStrucs(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
}
}
}

View File

@ -1,23 +0,0 @@
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
}

View File

@ -1,366 +0,0 @@
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
}
}
}

View File

@ -1,8 +0,0 @@
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
}