initial commit. still need to create more tests.
commit
05476fffbb
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2016 Charles Iliya Krempeaux <charles@reptile.ca> :: http://changelog.ca/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,79 @@
|
|||
# go-pathmatch
|
||||
|
||||
A library that provides *pattern matching* for paths, for the Go programming language.
|
||||
|
||||
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).
|
||||
|
||||
|
||||
## Documention
|
||||
|
||||
Online documentation, which includes examples, can be found at: http://godoc.org/github.com/reiver/go-pathmatch
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/reiver/go-pathmatch?status.svg)](https://godoc.org/github.com/reiver/go-pathmatch)
|
||||
|
||||
|
||||
## Example Usage
|
||||
```
|
||||
import (
|
||||
"github.com/reiver/go-pathmatch"
|
||||
)
|
||||
|
||||
// ...
|
||||
|
||||
pattern, err := pathmatch.Compile("/users/{user_id}/vehicles/{vehicle_id}}")
|
||||
if nil != err {
|
||||
//@TODO
|
||||
}
|
||||
|
||||
var userId string
|
||||
var vehicleId string
|
||||
|
||||
didMatch, err := pattern.Match("/users/bMM_kJFMEV/vehicles/o_bcU.RZGK", &userId, &vehicleId)
|
||||
|
||||
if nil != err {
|
||||
//@TODO
|
||||
}
|
||||
|
||||
if didMatch {
|
||||
fmt.Println("The path matched!")
|
||||
|
||||
fmt.Printf("user_id = %q \n", userId) // user_id = "bMM_kJFMEV"
|
||||
fmt.Printf("vehicle_id = %q \n", vehicleId) // vehicle_id = "o_bcU.RZGK"
|
||||
} else {
|
||||
fmt.Println("The patch did not match.")
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively:
|
||||
```
|
||||
import (
|
||||
"github.com/reiver/go-pathmatch"
|
||||
)
|
||||
|
||||
// ...
|
||||
|
||||
pattern, err := pathmatch.Compile("/users/{user_id}/vehicles/{vehicle_id}}")
|
||||
if nil != err {
|
||||
//@TODO
|
||||
}
|
||||
|
||||
data := struct{
|
||||
UserId string `match:"user_id"`
|
||||
VehicleId string `match:"vehicle_id"`
|
||||
}{}
|
||||
|
||||
didMatch, err := pattern.MatchAndLoad("/users/bMM_kJFMEV/vehicles/o_bcU.RZGK", &data)
|
||||
|
||||
if nil != err {
|
||||
//@TODO
|
||||
}
|
||||
|
||||
if didMatch {
|
||||
fmt.Println("The path matched!")
|
||||
|
||||
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"
|
||||
} else {
|
||||
fmt.Println("The patch did not match.")
|
||||
}
|
||||
```
|
|
@ -0,0 +1,121 @@
|
|||
package pathmatch
|
||||
|
||||
|
||||
// BadRequestComplainer is used to represent one of the types of errors that could be returned when
|
||||
// calling the pathmatch.Compile func, the pathmatch.Pattern.Match method, or the pathmatch.Pattern.MatchAndLoad
|
||||
// method. The meaning of this type of error is that the problem was due to something whomever called the func or method did.
|
||||
//
|
||||
// For example, maybe the uncompiled pattern passed to the pathmatch.Compile() func had
|
||||
// a syntax error in it. Or, also for example, maybe the type of parameter passed to
|
||||
// pathmatch.Pattern.Match() was of the wrong type. Etc.
|
||||
//
|
||||
// Example usage of BadRequestComplainer with pathmatch.Compile():
|
||||
//
|
||||
// pattern, err := pathmatch.Compile("/fruits/{there_is_an_error_in_here/") // ← The uncompiled pattern there has an error in it.
|
||||
// if nil != err {
|
||||
// switch err.(type) { // ← Note that we are using a Go type-switch here.
|
||||
//
|
||||
// case pathmatch.BadRequestComplainer: // ← Here we are detecting if the error returned is a pathmatch.BadRequestComplainer.
|
||||
//
|
||||
// fmt.Printf("Something you did when you called pathmatch.Compile() caused an error. The error message was....\n%s\n", err.Error())
|
||||
// return
|
||||
//
|
||||
// case pathmatch.InternalErrorComplainer:
|
||||
//
|
||||
// fmt.Printf("It's not your fault; it's our fault. Something bad happened internally when pathmatch.Compile() was running. The error message was....\n%s\n", err.Error())
|
||||
// return
|
||||
//
|
||||
// default:
|
||||
//
|
||||
// fmt.Printf("Some kind of unexpected error happend: %v", err)
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Somewhat continuing this example (although without the error in the uncompiled pattern), we might then
|
||||
// use the pathmatch.Pattern.Match() method, which could also generate an error that fits the BadRequestComplainer
|
||||
// interface.
|
||||
//
|
||||
// Example usage of BadRequestComplainer with pathmatch.Pattern.Match():
|
||||
//
|
||||
// pattern, err := pathmatch.Compile("/users/{user_id}/cards/{fruit_id}")
|
||||
// if nil != err {
|
||||
// switch err.(type) {
|
||||
//
|
||||
// case pathmatch.BadRequestComplainer:
|
||||
//
|
||||
// fmt.Printf("Something you did when you called pathmatch.Compile() caused an error. The error message was....\n%s\n", err.Error())
|
||||
// return
|
||||
//
|
||||
// case pathmatch.InternalErrorComplainer:
|
||||
//
|
||||
// fmt.Printf("It's not your fault; it's our fault. Something bad happened internally when pathmatch.Compile() was running. The error message was....\n%s\n", err.Error())
|
||||
// return
|
||||
//
|
||||
// default:
|
||||
//
|
||||
// fmt.Printf("Some kind of unexpected error happend: %v", err)
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// var userId string
|
||||
// var cardId string
|
||||
//
|
||||
// didMatch, err := pattern.Match("/users/8sN.oP/cards/X3j_T4", userId, cardId)
|
||||
// if nil != err {
|
||||
// switch err.(type) { // ← Note that we are using a Go type-switch here.
|
||||
//
|
||||
// case pathmatch.BadRequestComplainer: // ← Here we are detecting if the error returned is a pathmatch.BadRequestComplainer.
|
||||
//
|
||||
// fmt.Printf("Something you did when you called pattern.Match() caused an error. The error message was....\n%s\n", err.Error())
|
||||
// return
|
||||
//
|
||||
// case pathmatch.InternalErrorComplainer:
|
||||
//
|
||||
// fmt.Printf("It's not your fault; it's our fault. Something bad happened internally when pattern.Match() was running. The error message was....\n%s\n", err.Error())
|
||||
// return
|
||||
//
|
||||
// default:
|
||||
//
|
||||
// fmt.Printf("Some kind of unexpected error happend: %v", err)
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Note that one can get more specific than just a BadRequestComplainer. For example:
|
||||
// NotEnoughArgumentsComplainer, PatternSyntaxErrorComplainer, UnsupportedArgumentTypeComplainer,
|
||||
// and StructFieldWrongTypeComplainer.
|
||||
//
|
||||
// To be able to detect those more specific error types, put them BEFORE the "case pathmatch.BadRequestComplainer:"
|
||||
// in the type switch. For example:
|
||||
//
|
||||
// pattern, err := pathmatch.Compile("/users/{user_id}/cards/{fruit_id}")
|
||||
// if nil != err {
|
||||
// switch err.(type) {
|
||||
//
|
||||
// case pathmatch.PatternSyntaxErrorComplainer: // ← Here we are detecting if the error returned was due to a syntax error, in the uncompiled pattern. Also note that it comes BEFORE the 'pathmatch.BadRequestComplainer' case; THAT IS IMPORTANT!
|
||||
//
|
||||
// fmt.Printf("The uncompiled pattern passed to pathmatch.Compile() had a syntax error in it. The error message describing the syntax error is....\n%s\n", err.Error())
|
||||
// return
|
||||
//
|
||||
// case pathmatch.BadRequestComplainer:
|
||||
//
|
||||
// fmt.Printf("Something you did when you called pathmatch.Compile() caused an error. The error message was....\n%s\n", err.Error())
|
||||
// return
|
||||
//
|
||||
// case pathmatch.InternalErrorComplainer:
|
||||
//
|
||||
// fmt.Printf("It's not your fault; it's our fault. Something bad happened internally when pathmatch.Compile() was running. The error message was....\n%s\n", err.Error())
|
||||
// return
|
||||
//
|
||||
// default:
|
||||
//
|
||||
// fmt.Printf("Some kind of unexpected error happend: %v", err)
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
type BadRequestComplainer interface {
|
||||
error
|
||||
BadRequestComplainer()
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
package pathmatch
|
||||
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
||||
const (
|
||||
defaultFieldTagName = "match"
|
||||
wildcardBit = "{}"
|
||||
)
|
||||
|
||||
|
||||
var (
|
||||
errMissingEndingRightBraceToMatchBeginningLeftBrace = newPatternSyntaxErrorComplainer(`Missing ending "}" (to match beginning "{").`)
|
||||
errSlashInsideOfBraces = newPatternSyntaxErrorComplainer(`"/" inside of "{...}".`)
|
||||
errLeftBraceInsideOfBraces = newPatternSyntaxErrorComplainer(`"{" inside of "{...}".`)
|
||||
)
|
||||
|
||||
|
||||
// Compile takes an uncompiled pattern, in the form of a Go string (ex: "/users/{userId}/vehicles/{vehicleId}"),
|
||||
// and returns a compiled pattern.
|
||||
//
|
||||
// The compiled pattern can the be used to test if a path matches the pattern it contains.
|
||||
//
|
||||
// If the uncompiled pattern has a syntax error, Compile returns an error.
|
||||
//
|
||||
// Example Usage:
|
||||
//
|
||||
// pattern, err := pathmath.Compile("/users/{user_id}")
|
||||
// if nil != err {
|
||||
// fmt.Printf("ERROR Compiling: %v\n", err)
|
||||
// return
|
||||
// }
|
||||
func Compile(uncompiledPattern string) (Pattern, error) {
|
||||
|
||||
pattern := newPattern(defaultFieldTagName)
|
||||
|
||||
s := uncompiledPattern
|
||||
for {
|
||||
index := strings.IndexRune(s, '{')
|
||||
if -1 == index {
|
||||
pattern.bits = append(pattern.bits, s)
|
||||
break
|
||||
}
|
||||
bit := s[:index]
|
||||
if "" != bit { // This is to deal with the case where a {???} is right at the beginning of the uncompiledPattern.
|
||||
pattern.bits = append(pattern.bits, bit)
|
||||
}
|
||||
s = s[1+index:]
|
||||
if "" == s {
|
||||
break
|
||||
}
|
||||
|
||||
index = strings.IndexRune(s, '}')
|
||||
if -1 == index {
|
||||
return nil, errMissingEndingRightBraceToMatchBeginningLeftBrace
|
||||
}
|
||||
|
||||
// There should not be a slash ("/") before the ending brace ("}").
|
||||
// If there is, it is a syntax error.
|
||||
slashIndex := strings.IndexRune(s, '/')
|
||||
if -1 != slashIndex && slashIndex <= index {
|
||||
return nil, errSlashInsideOfBraces
|
||||
}
|
||||
|
||||
// There should not be another beginning brace ("{") before the ending brace ("}").
|
||||
// If there is, it is a syntax error.
|
||||
anotherLeftBraceIndex := strings.IndexRune(s, '{')
|
||||
if -1 != anotherLeftBraceIndex && anotherLeftBraceIndex <= index {
|
||||
return nil, errLeftBraceInsideOfBraces
|
||||
}
|
||||
|
||||
|
||||
bit = s[:index]
|
||||
|
||||
|
||||
// Match names should be unique, within a pattern.
|
||||
if _, ok := pattern.namesSet[bit]; ok {
|
||||
return nil, newPatternSyntaxErrorComplainer("Duplicate match name: %q.", bit)
|
||||
}
|
||||
|
||||
|
||||
pattern.names = append(pattern.names, bit)
|
||||
pattern.namesSet[bit] = struct{}{}
|
||||
pattern.bits = append(pattern.bits, wildcardBit)
|
||||
s = s[1+index:]
|
||||
if "" == s {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return pattern, nil
|
||||
}
|
||||
|
||||
|
||||
// MustCompile is like Compile except that it never returns an error; but
|
||||
// instead panic()s if there was an error.
|
||||
//
|
||||
// Example Usage:
|
||||
//
|
||||
// pattern := pathmath.MustCompile("/users/{user_id}")
|
||||
//
|
||||
// Note that if one recover()s from the panic(), one can use a Go type-switch
|
||||
// to figure out what kind of error it is.
|
||||
func MustCompile(uncompiledPattern string) Pattern {
|
||||
if pattern, err := Compile(uncompiledPattern); nil != err {
|
||||
panic(err)
|
||||
} else {
|
||||
return pattern
|
||||
}
|
||||
}
|
|
@ -0,0 +1,560 @@
|
|||
package pathmatch
|
||||
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
func TestCompileAndMatchNames(t *testing.T) {
|
||||
|
||||
tests := []struct{
|
||||
UncompiledPattern string
|
||||
ExpectedNames []string
|
||||
ExpectedNamesSet map[string]struct{}
|
||||
ExpectedBits []string
|
||||
}{
|
||||
{
|
||||
UncompiledPattern: "",
|
||||
ExpectedNames:[]string{},
|
||||
ExpectedNamesSet:map[string]struct{}{},
|
||||
ExpectedBits: []string{
|
||||
"",
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
UncompiledPattern: "/",
|
||||
ExpectedNames:[]string{},
|
||||
ExpectedNamesSet:map[string]struct{}{},
|
||||
ExpectedBits: []string{
|
||||
"/",
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
UncompiledPattern: "//",
|
||||
ExpectedNames:[]string{},
|
||||
ExpectedNamesSet:map[string]struct{}{},
|
||||
ExpectedBits: []string{
|
||||
"//",
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
UncompiledPattern: "///",
|
||||
ExpectedNames:[]string{},
|
||||
ExpectedNamesSet:map[string]struct{}{},
|
||||
ExpectedBits: []string{
|
||||
"///",
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
UncompiledPattern: "/{this}",
|
||||
ExpectedNames:[]string{"this"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"this":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
"/",
|
||||
wildcardBit,
|
||||
},
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "/{this}/",
|
||||
ExpectedNames:[]string{"this"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"this":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
},
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "/{this}/{that}",
|
||||
ExpectedNames:[]string{"this","that"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"this":struct{}{},
|
||||
"that":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
},
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "/{this}/{that}/",
|
||||
ExpectedNames:[]string{"this","that"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"this":struct{}{},
|
||||
"that":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
},
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "/{this}/{that}/{these}",
|
||||
ExpectedNames:[]string{"this","that","these"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"this":struct{}{},
|
||||
"that":struct{}{},
|
||||
"these":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
},
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "/{this}/{that}/{these}/",
|
||||
ExpectedNames:[]string{"this","that","these"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"this":struct{}{},
|
||||
"that":struct{}{},
|
||||
"these":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
},
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "/{this}/{that}/{these}/{those}",
|
||||
ExpectedNames:[]string{"this","that","these","those"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"this":struct{}{},
|
||||
"that":struct{}{},
|
||||
"these":struct{}{},
|
||||
"those":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
},
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "/{this}/{that}/{these}/{those}/",
|
||||
ExpectedNames:[]string{"this","that","these","those"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"this":struct{}{},
|
||||
"that":struct{}{},
|
||||
"these":struct{}{},
|
||||
"those":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
UncompiledPattern: "{this}",
|
||||
ExpectedNames:[]string{"this"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"this":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
wildcardBit,
|
||||
},
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "{this}/",
|
||||
ExpectedNames:[]string{"this"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"this":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
wildcardBit,
|
||||
"/",
|
||||
},
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "{this}/{that}",
|
||||
ExpectedNames:[]string{"this","that"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"this":struct{}{},
|
||||
"that":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
},
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "{this}/{that}/",
|
||||
ExpectedNames:[]string{"this","that"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"this":struct{}{},
|
||||
"that":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
},
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "{this}/{that}/{these}",
|
||||
ExpectedNames:[]string{"this","that","these"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"this":struct{}{},
|
||||
"that":struct{}{},
|
||||
"these":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
},
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "{this}/{that}/{these}/",
|
||||
ExpectedNames:[]string{"this","that","these"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"this":struct{}{},
|
||||
"that":struct{}{},
|
||||
"these":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
},
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "{this}/{that}/{these}/{those}",
|
||||
ExpectedNames:[]string{"this","that","these","those"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"this":struct{}{},
|
||||
"that":struct{}{},
|
||||
"these":struct{}{},
|
||||
"those":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
},
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "{this}/{that}/{these}/{those}/",
|
||||
ExpectedNames:[]string{"this","that","these","those"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"this":struct{}{},
|
||||
"that":struct{}{},
|
||||
"these":struct{}{},
|
||||
"those":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
UncompiledPattern: "/apple/banana/cherry",
|
||||
ExpectedNames:[]string{},
|
||||
ExpectedNamesSet:map[string]struct{}{},
|
||||
ExpectedBits: []string{
|
||||
"/apple/banana/cherry",
|
||||
},
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "/apple/banana/cherry/",
|
||||
ExpectedNames:[]string{},
|
||||
ExpectedNamesSet:map[string]struct{}{},
|
||||
ExpectedBits: []string{
|
||||
"/apple/banana/cherry/",
|
||||
},
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "//apple/banana/cherry",
|
||||
ExpectedNames:[]string{},
|
||||
ExpectedNamesSet:map[string]struct{}{},
|
||||
ExpectedBits: []string{
|
||||
"//apple/banana/cherry",
|
||||
},
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "/apple//banana/cherry",
|
||||
ExpectedNames:[]string{},
|
||||
ExpectedNamesSet:map[string]struct{}{},
|
||||
ExpectedBits: []string{
|
||||
"/apple//banana/cherry",
|
||||
},
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "/apple/banana/cherry//",
|
||||
ExpectedNames:[]string{},
|
||||
ExpectedNamesSet:map[string]struct{}{},
|
||||
ExpectedBits: []string{
|
||||
"/apple/banana/cherry//",
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
UncompiledPattern: "/apple/{banana}",
|
||||
ExpectedNames:[]string{ "banana"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"banana":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
"/apple/",
|
||||
wildcardBit,
|
||||
},
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "/apple/{banana}/",
|
||||
ExpectedNames:[]string{ "banana"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"banana":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
"/apple/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
},
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "/apple/{banana}//",
|
||||
ExpectedNames:[]string{ "banana"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"banana":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
"/apple/",
|
||||
wildcardBit,
|
||||
"//",
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
UncompiledPattern: "/apple/{banana}/cherry",
|
||||
ExpectedNames:[]string{ "banana"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"banana":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
"/apple/",
|
||||
wildcardBit,
|
||||
"/cherry",
|
||||
},
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "/apple/{banana}/cherry/",
|
||||
ExpectedNames:[]string{ "banana"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"banana":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
"/apple/",
|
||||
wildcardBit,
|
||||
"/cherry/",
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
UncompiledPattern: "/apple/{banana}/cherry/{grape}",
|
||||
ExpectedNames:[]string{ "banana", "grape"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"banana":struct{}{},
|
||||
"grape":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
"/apple/",
|
||||
wildcardBit,
|
||||
"/cherry/",
|
||||
wildcardBit,
|
||||
},
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "/apple/{banana}/cherry/{grape}/",
|
||||
ExpectedNames:[]string{ "banana", "grape"},
|
||||
ExpectedNamesSet:map[string]struct{}{
|
||||
"banana":struct{}{},
|
||||
"grape":struct{}{},
|
||||
},
|
||||
ExpectedBits: []string{
|
||||
"/apple/",
|
||||
wildcardBit,
|
||||
"/cherry/",
|
||||
wildcardBit,
|
||||
"/",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for testNumber, test := range tests {
|
||||
|
||||
actualPattern, err := Compile(test.UncompiledPattern)
|
||||
if nil != err {
|
||||
t.Errorf("For test #%d, did not expect to receive an error, but actually got one: %v\nPATTERN: %q", testNumber, err, test.UncompiledPattern)
|
||||
continue
|
||||
}
|
||||
|
||||
if expected, actual := test.ExpectedBits, actualPattern.(*internalPattern).bits; len(expected) != len(actual) {
|
||||
t.Errorf("For test #%d, expected compiled pattern to have %d bits, but actually had %d.\nEXPECTED BITS: %#v\nACTUAL BITS: %#v\nPATTERN: %q", testNumber, len(expected), len(actual), expected, actual, test.UncompiledPattern)
|
||||
continue
|
||||
} else {
|
||||
for bitNumber, expectedBit := range expected {
|
||||
|
||||
actualBit := actual[bitNumber]
|
||||
|
||||
if expectedBit != actualBit {
|
||||
t.Errorf("For test #%d and bit #%d, expected %q, but actually got %q.\nEXPECTED BITS: %#v\nACTUAL BITS: %#v\nPATTERN: %q", testNumber, bitNumber, expectedBit, actualBit, expected, actual, test.UncompiledPattern)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if expected, actual := test.ExpectedNames, actualPattern.MatchNames(); len(expected) != len(actual) {
|
||||
t.Errorf("For test #%d, when checking directly, expected compiled pattern to have %d names, but actually had %d.\nEXPECTED NAMES: %#v\nACTUAL NAMES: %#v\nPATTERN: %q", testNumber, len(expected), len(actual), expected, actual, test.UncompiledPattern)
|
||||
continue
|
||||
} else {
|
||||
for nameNumber, expectedName := range expected {
|
||||
|
||||
actualName := actual[nameNumber]
|
||||
|
||||
if expectedName != actualName {
|
||||
t.Errorf("For test #%d and name #%d, expected %q, but actually got %q.\nEXPECTED NAMES: %#v\nACTUAL NAMES: %#v\nPATTERN: %q", testNumber, nameNumber, expectedName, actualName, expected, actual, test.UncompiledPattern)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if expected, actual := test.ExpectedNamesSet, actualPattern.(*internalPattern).namesSet; len(expected) != len(actual) {
|
||||
t.Errorf("For test #%d, when checking directly, expected compiled pattern to have %d names in set, but actually had %d.\nEXPECTED NAMES SET: %#v\nACTUAL NAMES SET: %#v\nPATTERN: %q", testNumber, len(expected), len(actual), expected, actual, test.UncompiledPattern)
|
||||
continue
|
||||
} else {
|
||||
for expectedName, _ := range expected {
|
||||
|
||||
_, ok := actual[expectedName]
|
||||
|
||||
if !ok {
|
||||
t.Errorf("For test #%d, expected name %q to exist, but actually did't.\nEXPECTED NAMES SET: %#v\nACTUAL NAMES SET: %#v\nPATTERN: %q", testNumber, expectedName, expected, actual, test.UncompiledPattern)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if expected, actual := len(test.ExpectedNames), len(actualPattern.MatchNames()); expected != actual {
|
||||
t.Errorf("For test #%d, when checking using .MatchNames(), expected compiled pattern to have %d names, but actually had %d.\nEXPECTED NAMES: %#v\nACTUAL NAMES: %#v\nPATTERN: %q", testNumber, expected, actual, test.ExpectedNames, actualPattern.MatchNames(), test.UncompiledPattern)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestCompileFail(t *testing.T) {
|
||||
|
||||
tests := []struct{
|
||||
UncompiledPattern string
|
||||
ExpectedError string
|
||||
}{
|
||||
{
|
||||
UncompiledPattern: "/users/{userId",
|
||||
ExpectedError: `Bad Request: Syntax Error: Missing ending "}" (to match beginning "{").`,
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "/users/{userId}/vehicles/{vehicleId",
|
||||
ExpectedError: `Bad Request: Syntax Error: Missing ending "}" (to match beginning "{").`,
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "/users/{userId/vehicles/{vehicleId}",
|
||||
ExpectedError: `Bad Request: Syntax Error: "/" inside of "{...}".`,
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "/users/{userId{vehicleId}",
|
||||
ExpectedError: `Bad Request: Syntax Error: "{" inside of "{...}".`,
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "/apple/{fruitId}/banana/{fruitId}",
|
||||
ExpectedError: `Bad Request: Syntax Error: Duplicate match name: "fruitId".`,
|
||||
},
|
||||
{
|
||||
UncompiledPattern: "/fruit/{apple/banana/cherry}",
|
||||
ExpectedError: `Bad Request: Syntax Error: "/" inside of "{...}".`,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for testNumber, test := range tests {
|
||||
_, err := Compile(test.UncompiledPattern)
|
||||
if nil == err {
|
||||
t.Errorf("For test #%d, expected to receive an error, but actually did not get one: %v\nPATTERN: %q", testNumber, err, test.UncompiledPattern)
|
||||
continue
|
||||
}
|
||||
if expected, actual := test.ExpectedError, err.Error(); expected != actual {
|
||||
t.Errorf("For test #%d, expected error message to be %q, but actually was %q.\nPATTERN: %q", testNumber, expected, actual, test.UncompiledPattern)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
Package pathmatch provides pattern matching for paths.
|
||||
|
||||
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).
|
||||
|
||||
The matches can be loaded into variables (when using pathmatch.Match());
|
||||
or can be loaded into a struct (when using pathmatch.Pattern.MatchAndLoad()).
|
||||
|
||||
Example Usage:
|
||||
|
||||
pattern, err := pathmatch.Compile("/users/{user_id}/vehicles/{vehicle_id}}")
|
||||
if nil != err {
|
||||
//@TODO
|
||||
}
|
||||
|
||||
var userId string
|
||||
var vehicleId string
|
||||
|
||||
didMatch, err := pattern.Match("/users/bMM_kJFMEV/vehicles/o_bcU.RZGK", &userId, &vehicleId)
|
||||
|
||||
if nil != err {
|
||||
//@TODO
|
||||
}
|
||||
|
||||
if didMatch {
|
||||
fmt.Println("The path matched!")
|
||||
|
||||
fmt.Printf("user_id = %q \n", userId) // user_id = "bMM_kJFMEV"
|
||||
fmt.Printf("vehicle_id = %q \n", vehicleId) // vehicle_id = "o_bcU.RZGK"
|
||||
} else {
|
||||
fmt.Println("The patch did not match.")
|
||||
}
|
||||
|
||||
Alternate Example Usage:
|
||||
|
||||
pattern, err := pathmatch.Compile("/users/{user_id}/vehicles/{vehicle_id}}")
|
||||
if nil != err {
|
||||
//@TODO
|
||||
}
|
||||
|
||||
data := struct{
|
||||
UserId string `match:"user_id"`
|
||||
VehicleId string `match:"vehicle_id"`
|
||||
}{}
|
||||
|
||||
didMatch, err := pattern.MatchAndLoad("/users/bMM_kJFMEV/vehicles/o_bcU.RZGK", &data)
|
||||
|
||||
if nil != err {
|
||||
//@TODO
|
||||
}
|
||||
|
||||
if didMatch {
|
||||
fmt.Println("The path matched!")
|
||||
|
||||
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"
|
||||
} else {
|
||||
fmt.Println("The patch did not match.")
|
||||
}
|
||||
|
||||
*/
|
||||
package pathmatch
|
|
@ -0,0 +1,57 @@
|
|||
package pathmatch
|
||||
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
|
||||
const (
|
||||
internalErrorMessagePrefix = "Internal Error: "
|
||||
)
|
||||
|
||||
|
||||
type InternalErrorComplainer interface {
|
||||
error
|
||||
InternalErrorComplainer()
|
||||
}
|
||||
|
||||
|
||||
// internalInternalErrorComplainer is the only underlying implementation that fits the
|
||||
// InternalErrorComplainer interface, in this library.
|
||||
type internalInternalErrorComplainer struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
|
||||
// newInternalErrorComplainer creates a new internalInternalErrorComplainer (struct) and
|
||||
// returns it as a InternalErrorComplainer (interface).
|
||||
func newInternalErrorComplainer(format string, a ...interface{}) InternalErrorComplainer {
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
|
||||
err := internalInternalErrorComplainer{
|
||||
msg:msg,
|
||||
}
|
||||
|
||||
return &err
|
||||
}
|
||||
|
||||
|
||||
// Error method is necessary to satisfy the 'error' interface (and the InternalErrorComplainer
|
||||
// interface).
|
||||
func (err *internalInternalErrorComplainer) Error() string {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
buffer.WriteString(internalErrorMessagePrefix)
|
||||
buffer.WriteString(err.msg)
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
|
||||
// InternalErrorComplainer method is necessary to satisfy the 'InternalErrorComplainer' interface.
|
||||
// It exists to make this error type detectable in a Go type-switch.
|
||||
func (err *internalInternalErrorComplainer) InternalErrorComplainer() {
|
||||
// Nothing here.
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package pathmatch
|
||||
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
|
||||
type NotEnoughArgumentsComplainer interface {
|
||||
BadRequestComplainer
|
||||
NotEnoughArgumentsComplainer()
|
||||
|
||||
ExpectedAtLeast() int
|
||||
Actual() int
|
||||
}
|
||||
|
||||
|
||||
// internalNotEnoughArgumentsComplainer is the only underlying implementation that fits the
|
||||
// NotEnoughArgumentsComplainer interface, in this library.
|
||||
type internalNotEnoughArgumentsComplainer struct {
|
||||
expectedAtLeast int
|
||||
actual int
|
||||
}
|
||||
|
||||
|
||||
// newNotEnoughArgumentsComplainer creates a new internalNotEnoughArgumentsComplainer (struct) and
|
||||
// returns it as a NotEnoughArgumentsComplainer (interface).
|
||||
func newNotEnoughArgumentsComplainer(expectedAtLeast int, actual int) NotEnoughArgumentsComplainer {
|
||||
err := internalNotEnoughArgumentsComplainer{
|
||||
expectedAtLeast:expectedAtLeast,
|
||||
actual:actual,
|
||||
}
|
||||
|
||||
return &err
|
||||
}
|
||||
|
||||
|
||||
// Error method is necessary to satisfy the 'error' interface (and the
|
||||
// NotEnoughArgumentsComplainer interface).
|
||||
func (err *internalNotEnoughArgumentsComplainer) Error() string {
|
||||
plural := ""
|
||||
if 1 < err.expectedAtLeast {
|
||||
plural = "s"
|
||||
}
|
||||
return fmt.Sprintf("Bad Request: Not enough arguments. Expected at least %d argument%s, but actually got %d.", err.expectedAtLeast, plural, err.actual)
|
||||
}
|
||||
|
||||
|
||||
// BadRequestComplainer method is necessary to satisfy the 'BadRequestComplainer' interface.
|
||||
// It exists to make this error type detectable in a Go type-switch.
|
||||
func (err *internalNotEnoughArgumentsComplainer) BadRequestComplainer() {
|
||||
// Nothing here.
|
||||
}
|
||||
|
||||
|
||||
// NotEnoughArgumentsComplainer method is necessary to satisfy the 'NotEnoughArgumentsComplainer' interface.
|
||||
// It exists to make this error type detectable in a Go type-switch.
|
||||
func (err *internalNotEnoughArgumentsComplainer) NotEnoughArgumentsComplainer() {
|
||||
// Nothing here.
|
||||
}
|
||||
|
||||
|
||||
func (err *internalNotEnoughArgumentsComplainer) ExpectedAtLeast() int {
|
||||
return err.expectedAtLeast
|
||||
}
|
||||
|
||||
func (err *internalNotEnoughArgumentsComplainer) Actual() int {
|
||||
return err.actual
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package pathmatch
|
||||
|
||||
|
||||
// Pattern represents a compiled pattern. It is what is returned
|
||||
// from calling either the Compile to MustCompile funcs.
|
||||
//
|
||||
// Pattern provides the Match, MatchAndLoad, and MatchNames methods.
|
||||
//
|
||||
// Example Usage:
|
||||
//
|
||||
// pattern, err := pathmath.Compile("/users/{user_id}")
|
||||
// if nil != err {
|
||||
// fmt.Printf("ERROR Compiling: %v\n", err)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// var userId string
|
||||
//
|
||||
// didMatch, err := pattern.Match("/users/123", userId)
|
||||
// if nil != err {
|
||||
// fmt.Printf("ERROR Matching: %v\n", err)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// if didMatch {
|
||||
// fmt.Printf("user_id = %q\n", userId)
|
||||
// } else {
|
||||
// fmt.Println("Did not match.")
|
||||
// }
|
||||
type Pattern interface {
|
||||
Match(string, ...interface{}) (bool, error)
|
||||
MatchAndLoad(string, interface{}) (bool, error)
|
||||
MatchNames() []string
|
||||
}
|
||||
|
||||
|
||||
type internalPattern struct {
|
||||
bits []string
|
||||
names []string
|
||||
namesSet map[string]struct{}
|
||||
fieldTagName string
|
||||
}
|
||||
|
||||
|
||||
func (pattern *internalPattern) MatchNames() []string {
|
||||
|
||||
return pattern.names
|
||||
}
|
||||
|
||||
|
||||
func newPattern(fieldTagName string) *internalPattern {
|
||||
bits := []string{}
|
||||
names := []string{}
|
||||
namesSet := map[string]struct{}{}
|
||||
|
||||
pattern := internalPattern{
|
||||
bits:bits,
|
||||
names:names,
|
||||
namesSet:namesSet,
|
||||
fieldTagName:fieldTagName,
|
||||
}
|
||||
|
||||
return &pattern
|
||||
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package pathmatch
|
||||
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
||||
var (
|
||||
errExpectedAPointerToAStruct = newUnsupportedArgumentTypeComplainer("Expected a pointer to a struct, but wasn't.")
|
||||
)
|
||||
|
||||
|
||||
func (pattern *internalPattern) MatchAndLoad(path string, strct interface{}) (bool, error) {
|
||||
|
||||
//@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(pattern.MatchNames())
|
||||
for i:=0; i<numNames; i++ {
|
||||
args = append(args, new(string))
|
||||
}
|
||||
|
||||
didMatch, err := pattern.Match(path, args...)
|
||||
if nil != err {
|
||||
return doesNotMatter, err
|
||||
}
|
||||
|
||||
if !didMatch {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
reflectedValue := reflect.ValueOf(strct)
|
||||
if reflect.Ptr != reflectedValue.Kind() {
|
||||
return doesNotMatter, errExpectedAPointerToAStruct
|
||||
}
|
||||
|
||||
reflectedValueElem := reflectedValue.Elem()
|
||||
|
||||
reflectedValueElemType := reflectedValueElem.Type()
|
||||
|
||||
numFields := reflectedValueElemType.NumField()
|
||||
for fieldNumber:=0; fieldNumber<numFields; fieldNumber++ {
|
||||
//field := reflectedValueElemType.Field(fieldNumber)
|
||||
|
||||
//fieldTag := field.Tag
|
||||
|
||||
//name := fieldTag.Get(pattern.fieldTagName)
|
||||
|
||||
value := *(args[fieldNumber].(*string))
|
||||
|
||||
err := func(rValue reflect.Value, value string, matchName string) (err error) {
|
||||
|
||||
defer func() {
|
||||
|
||||
if r := recover(); nil != r {
|
||||
// See if we received a message of the form:
|
||||
//
|
||||
// reflect.Set: value of type ??? is not assignable to type ???
|
||||
//
|
||||
// If we did then we interpret this as the programmer using this
|
||||
// trying to load into a struct field of the wrong type.
|
||||
//
|
||||
// We return a special error for that.
|
||||
if s, ok := r.(string); ok {
|
||||
needle := "reflect.Set: value of type "
|
||||
|
||||
if strings.HasPrefix(s, needle) {
|
||||
needle = " is not assignable to type "
|
||||
|
||||
if strings.Contains(s, needle) {
|
||||
err = newStructFieldWrongTypeComplainer(matchName)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("%T %v", r)
|
||||
|
||||
err = errors.New( msg )
|
||||
return
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
rValue.Set( reflect.ValueOf(value) )
|
||||
|
||||
return nil
|
||||
}(reflectedValueElem.Field(fieldNumber), value, pattern.fieldTagName)
|
||||
if nil != err {
|
||||
return doesNotMatter, err
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
package pathmatch
|
||||
|
||||
|
||||
import (
|
||||
"github.com/fatih/structs"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
func TestMatchAndLoad(t *testing.T) {
|
||||
|
||||
tests := []struct{
|
||||
Pattern Pattern
|
||||
StructPtr interface{}
|
||||
Path string
|
||||
Expected map[string]string
|
||||
}{
|
||||
{
|
||||
Pattern: MustCompile("/{this}/{that}/{these}/{those}"),
|
||||
StructPtr: new(struct{
|
||||
This string `match:"this"`
|
||||
That string `match:"that"`
|
||||
These string `match:"these"`
|
||||
Those string `match:"those"`
|
||||
}),
|
||||
Path: "/apple/banana/cherry/grape",
|
||||
Expected: map[string]string{
|
||||
"This":"apple",
|
||||
"That":"banana",
|
||||
"These":"cherry",
|
||||
"Those":"grape",
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Pattern: MustCompile("/user/{sessionKey}"),
|
||||
StructPtr: new(struct{
|
||||
SessionKey string `match:"sessionKey"`
|
||||
}),
|
||||
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij",
|
||||
Expected: map[string]string{
|
||||
"SessionKey":"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pattern: MustCompile("/user/{sessionKey}/"),
|
||||
StructPtr: new(struct{
|
||||
SessionKey string `match:"sessionKey"`
|
||||
}),
|
||||
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/",
|
||||
Expected: map[string]string{
|
||||
"SessionKey":"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Pattern: MustCompile("/user/{sessionKey}/vehicle"),
|
||||
StructPtr: new(struct{
|
||||
SessionKey string `match:"sessionKey"`
|
||||
}),
|
||||
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle",
|
||||
Expected: map[string]string{
|
||||
"SessionKey":"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pattern: MustCompile("/user/{sessionKey}/vehicle/"),
|
||||
StructPtr: new(struct{
|
||||
SessionKey string `match:"sessionKey"`
|
||||
}),
|
||||
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/",
|
||||
Expected: map[string]string{
|
||||
"SessionKey":"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Pattern: MustCompile("/user/{sessionKey}/vehicle/DEFAULT"),
|
||||
StructPtr: new(struct{
|
||||
SessionKey string `match:"sessionKey"`
|
||||
}),
|
||||
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/DEFAULT",
|
||||
Expected: map[string]string{
|
||||
"SessionKey":"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij",
|
||||
},
|
||||
},
|
||||
{
|
||||
Pattern: MustCompile("/user/{sessionKey}/vehicle/DEFAULT/"),
|
||||
StructPtr: new(struct{
|
||||
SessionKey string `match:"sessionKey"`
|
||||
}),
|
||||
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/DEFAULT/",
|
||||
Expected: map[string]string{
|
||||
"SessionKey":"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij",
|
||||
},
|
||||
},
|
||||
/*
|
||||
|
||||
{
|
||||
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: 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: 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: 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"},
|
||||
},
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
for testNumber, test := range tests {
|
||||
|
||||
|
||||
for structFieldName, structFieldValue := range structs.Map(test.StructPtr) {
|
||||
structFieldValueString, ok := structFieldValue.(string)
|
||||
if !ok {
|
||||
t.Errorf("For test #%d, expected test.StructPtr.%s to be of type string, but actually was %T.", testNumber, structFieldName, structFieldValue)
|
||||
continue
|
||||
}
|
||||
|
||||
if expected, actual := "", structFieldValueString; expected != actual {
|
||||
t.Errorf("For test #%d, expected test.StructPtr.%s to (initially) be %q, but actually was %q.", testNumber, structFieldName, expected, actual)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if didMatch, err := test.Pattern.MatchAndLoad(test.Path, test.StructPtr); nil != err {
|
||||
t.Errorf("For test #%d, did not expected an error, but actually got one: %v", testNumber, err)
|
||||
continue
|
||||
} else if !didMatch {
|
||||
t.Errorf("For test #%d, expected path to match pattern, but it didn't.", testNumber)
|
||||
continue
|
||||
}
|
||||
|
||||
for structFieldName, structFieldValue := range structs.Map(test.StructPtr) {
|
||||
structFieldValueString, ok := structFieldValue.(string)
|
||||
if !ok {
|
||||
t.Errorf("For test #%d, expected test.StructPtr.%s to be of type string, but actually was %T.", testNumber, structFieldName, structFieldValue)
|
||||
continue
|
||||
}
|
||||
|
||||
if expected, actual := test.Expected[structFieldName], structFieldValueString; expected != actual {
|
||||
t.Errorf("For test #%d, expected test.StructPtr.%s to (initially) be %q, but actually was %q.", testNumber, structFieldName, expected, actual)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package pathmatch
|
||||
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
||||
const (
|
||||
doesNotMatter = false
|
||||
)
|
||||
|
||||
|
||||
var (
|
||||
errThisShouldNeverHappen = newInternalErrorComplainer("This should never happen.")
|
||||
)
|
||||
|
||||
|
||||
func (pattern *internalPattern) Match(path string, args ...interface{}) (bool, error) {
|
||||
|
||||
s := path
|
||||
|
||||
argsIndex := 0
|
||||
for _, bit := range pattern.bits {
|
||||
|
||||
switch bit {
|
||||
default:
|
||||
if !strings.HasPrefix(s, bit) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
s = s[len(bit):]
|
||||
case wildcardBit:
|
||||
index := strings.IndexRune(s, '/')
|
||||
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[index:]
|
||||
} else {
|
||||
return doesNotMatter, errThisShouldNeverHappen
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
return true, nil
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package pathmatch
|
||||
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
func TestMatch(t *testing.T) {
|
||||
|
||||
tests := []struct{
|
||||
Pattern Pattern
|
||||
Args []interface{}
|
||||
Path string
|
||||
ExpectedArgs []string
|
||||
}{
|
||||
{
|
||||
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"},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Pattern: MustCompile("/user/{sessionKey}"),
|
||||
Args: []interface{}{new(string), },
|
||||
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij",
|
||||
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
|
||||
},
|
||||
{
|
||||
Pattern: MustCompile("/user/{sessionKey}/"),
|
||||
Args: []interface{}{new(string), },
|
||||
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/",
|
||||
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
|
||||
},
|
||||
|
||||
{
|
||||
Pattern: MustCompile("/user/{sessionKey}/vehicle"),
|
||||
Args: []interface{}{new(string), },
|
||||
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle",
|
||||
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
|
||||
},
|
||||
{
|
||||
Pattern: MustCompile("/user/{sessionKey}/vehicle/"),
|
||||
Args: []interface{}{new(string), },
|
||||
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/",
|
||||
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
|
||||
},
|
||||
|
||||
{
|
||||
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: MustCompile("/user/{sessionKey}/vehicle/DEFAULT/"),
|
||||
Args: []interface{}{new(string), },
|
||||
Path: "/user/76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij/vehicle/DEFAULT/",
|
||||
ExpectedArgs: []string{"76M6.mXQfgiGSC_YJ5uXSnWUmELbe8OgOm5n.iZ98Ij"},
|
||||
},
|
||||
|
||||
{
|
||||
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: 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: 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: 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"},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for testNumber, test := range tests {
|
||||
|
||||
for argNumber, arg := range test.Args {
|
||||
argStringPtr, ok := arg.(*string)
|
||||
if !ok {
|
||||
t.Errorf("For test #%d, expected test.Args[%d] to be of type *string, but actually was %T.", testNumber, argNumber, arg)
|
||||
continue
|
||||
}
|
||||
|
||||
if expected, actual := "", *argStringPtr; expected != actual {
|
||||
t.Errorf("For test #%d, expected *test.Args[%d] to (initially) be %q, but actually was %q.", testNumber, argNumber, expected, actual)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if didMatch, err := test.Pattern.Match(test.Path, test.Args...); nil != err {
|
||||
t.Errorf("For test #%d, did not expected an error, but actually got one: %v", testNumber, err)
|
||||
continue
|
||||
} else if !didMatch {
|
||||
t.Errorf("For test #%d, expected path to match pattern, but it didn't.", testNumber)
|
||||
continue
|
||||
}
|
||||
|
||||
for argNumber, arg := range test.Args {
|
||||
argStringPtr, ok := arg.(*string)
|
||||
if !ok {
|
||||
t.Errorf("For test #%d, expected test.Args[%d] to be of type *string, but actually was %T.", testNumber, argNumber, arg)
|
||||
continue
|
||||
}
|
||||
|
||||
if expected, actual := test.ExpectedArgs[argNumber], *argStringPtr; expected != actual {
|
||||
t.Errorf("For test #%d, expected *test.Args[%d] to be %q, but actually was %q.", testNumber, argNumber, expected, actual)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
package pathmatch
|
||||
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
|
||||
// PatternSyntaxErrorComplainer is used to represent a specific kind of BadRequestComplainer error.
|
||||
// Specifically, it represents a syntax error in the uncompiled pattern passed to the
|
||||
// pathmatch.Compile() func.
|
||||
//
|
||||
// Example usage is as follows:
|
||||
//
|
||||
// pattern, err := pathmatch.Compile("/something/{there_is_a_syntax_error_in_this_pattern")
|
||||
// if nil != err {
|
||||
// switch err.(type) {
|
||||
//
|
||||
// case pathmatch.PatternSyntaxErrorComplainer: // ← Here we are detecting if the error returned was due to a syntax error, in the uncompiled pattern. Also note that it comes BEFORE the 'pathmatch.BadRequestComplainer' case; THAT IS IMPORTANT!
|
||||
//
|
||||
// fmt.Printf("The uncompiled pattern passed to pathmatch.Compile() had a syntax error in it. The error message describing the syntax error is....\n%s\n", err.Error())
|
||||
// return
|
||||
//
|
||||
// case pathmatch.BadRequestComplainer:
|
||||
//
|
||||
// fmt.Printf("Something you did when you called pathmatch.Compile() caused an error. The error message was....\n%s\n", err.Error())
|
||||
// return
|
||||
//
|
||||
// case pathmatch.InternalErrorComplainer:
|
||||
//
|
||||
// fmt.Printf("It's not your fault; it's our fault. Something bad happened internally when pathmatch.Compile() was running. The error message was....\n%s\n", err.Error())
|
||||
// return
|
||||
//
|
||||
// default:
|
||||
//
|
||||
// fmt.Printf("Some kind of unexpected error happend: %v", err)
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
type PatternSyntaxErrorComplainer interface {
|
||||
BadRequestComplainer
|
||||
PatternSyntaxErrorComplainer()
|
||||
}
|
||||
|
||||
|
||||
// internalPatternSyntaxErrorComplainer is the only underlying implementation that fits the
|
||||
// PatternSyntaxErrorComplainer interface, in this library.
|
||||
type internalPatternSyntaxErrorComplainer struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
|
||||
// newPatternSyntaxErrorComplainer creates a new internalPatternSyntaxErrorComplainer (struct) and
|
||||
// returns it as a PatternSyntaxErrorComplainer (interface).
|
||||
func newPatternSyntaxErrorComplainer(format string, a ...interface{}) PatternSyntaxErrorComplainer {
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
|
||||
err := internalPatternSyntaxErrorComplainer{
|
||||
msg:msg,
|
||||
}
|
||||
|
||||
return &err
|
||||
}
|
||||
|
||||
|
||||
// Error method is necessary to satisfy the 'error' interface (and the
|
||||
// PatternSyntaxErrorComplainer interface).
|
||||
func (err *internalPatternSyntaxErrorComplainer) Error() string {
|
||||
s := fmt.Sprintf("Bad Request: Syntax Error: %s", err.msg)
|
||||
return s
|
||||
}
|
||||
|
||||
|
||||
// BadRequestComplainer method is necessary to satisfy the 'InternalErrorComplainer' interface.
|
||||
// It exists to make this error type detectable in a Go type-switch.
|
||||
func (err *internalPatternSyntaxErrorComplainer) BadRequestComplainer() {
|
||||
// Nothing here.
|
||||
}
|
||||
|
||||
|
||||
// PatternSyntaxErrorComplainer method is necessary to satisfy the 'PatternSyntaxErrorComplainer' interface.
|
||||
// It exists to make this error type detectable in a Go type-switch.
|
||||
func (err *internalPatternSyntaxErrorComplainer) PatternSyntaxErrorComplainer() {
|
||||
// Nothing here.
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package pathmatch
|
||||
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
|
||||
type ScanErrorComplainer interface {
|
||||
InternalErrorComplainer
|
||||
ScanErrorComplainer()
|
||||
WrappedError() error
|
||||
}
|
||||
|
||||
|
||||
// internalScanErrorComplainer is the only underlying implementation that fits the
|
||||
// ScanErrorComplainer interface, in this library.
|
||||
type internalScanErrorComplainer struct {
|
||||
wrappedError error
|
||||
argumentIndex int
|
||||
argumentType string
|
||||
}
|
||||
|
||||
|
||||
// newScanErrorComplainer creates a new internalScanErrorComplainer (struct) and
|
||||
// returns it as a ScanErrorComplainer (interface).
|
||||
func newScanErrorComplainer(wrappedError error, argumentIndex int, argumentType string) ScanErrorComplainer {
|
||||
err := internalScanErrorComplainer{
|
||||
wrappedError:wrappedError,
|
||||
argumentIndex:argumentIndex,
|
||||
argumentType:argumentType,
|
||||
}
|
||||
|
||||
return &err
|
||||
}
|
||||
|
||||
|
||||
// Error method is necessary to satisfy the 'error' interface (and the
|
||||
// ScanErrorComplainer interface).
|
||||
func (err *internalScanErrorComplainer) Error() string {
|
||||
s := fmt.Sprintf("Internal Error: Received scan error for argument #%d (%s): %q", err.argumentIndex, err.argumentType, err.wrappedError.Error())
|
||||
return s
|
||||
}
|
||||
|
||||
|
||||
// InternalErrorComplainer method is necessary to satisfy the 'InternalErrorComplainer' interface.
|
||||
// It exists to make this error type detectable in a Go type-switch.
|
||||
func (err *internalScanErrorComplainer) InternalErrorComplainer() {
|
||||
// Nothing here.
|
||||
}
|
||||
|
||||
|
||||
// ScanErrorComplainer method is necessary to satisfy the 'ScanErrorComplainer' interface.
|
||||
// It exists to make this error type detectable in a Go type-switch.
|
||||
func (err *internalScanErrorComplainer) ScanErrorComplainer() {
|
||||
// Nothing here.
|
||||
}
|
||||
|
||||
func (err *internalScanErrorComplainer) WrappedError() error {
|
||||
return err.wrappedError
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package pathmatch
|
||||
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
|
||||
func set(value string, argsIndex int, args ...interface{}) error {
|
||||
|
||||
if 0 > argsIndex {
|
||||
return newInternalErrorComplainer("Index value %d is less than zero.", argsIndex)
|
||||
}
|
||||
|
||||
if lenArgs := len(args); argsIndex >= lenArgs {
|
||||
expectedAtLeast := 1+argsIndex
|
||||
actual := lenArgs
|
||||
|
||||
return newNotEnoughArgumentsComplainer(expectedAtLeast, actual)
|
||||
}
|
||||
|
||||
arg := args[argsIndex]
|
||||
|
||||
switch a := arg.(type) {
|
||||
case sql.Scanner:
|
||||
if err := a.Scan(value); nil != err {
|
||||
return newScanErrorComplainer(err, argsIndex, fmt.Sprintf("%T", arg))
|
||||
}
|
||||
case *string:
|
||||
*a = value
|
||||
default:
|
||||
return newUnsupportedIndexedArgumentTypeComplainer(argsIndex, fmt.Sprintf("%T", arg))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,376 @@
|
|||
package pathmatch
|
||||
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
func TestSetStringPointers(t *testing.T) {
|
||||
|
||||
randomness := rand.New(rand.NewSource( time.Now().UTC().UnixNano() ))
|
||||
runes := []rune(".0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz")
|
||||
lenRunes := len(runes)
|
||||
|
||||
randomString := func() string {
|
||||
length := 1 + randomness.Intn(59)
|
||||
rs := make([]rune, length)
|
||||
for i := range rs {
|
||||
rs[i] = runes[randomness.Intn(lenRunes)]
|
||||
}
|
||||
|
||||
return string(rs)
|
||||
}
|
||||
|
||||
|
||||
tests := []struct{
|
||||
Value string
|
||||
ArgsIndex int
|
||||
Args []interface{}
|
||||
}{
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 0,
|
||||
Args: []interface{}{ new(string), },
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 0,
|
||||
Args: []interface{}{ new(string), new(string), },
|
||||
},
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 1,
|
||||
Args: []interface{}{ new(string), new(string), },
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 0,
|
||||
Args: []interface{}{ new(string), new(string), new(string), },
|
||||
},
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 1,
|
||||
Args: []interface{}{ new(string), new(string), new(string), },
|
||||
},
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 2,
|
||||
Args: []interface{}{ new(string), new(string), new(string), },
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for testNumber, test := range tests {
|
||||
|
||||
for argNumber, arg := range test.Args {
|
||||
if argStringPtr, ok := arg.(*string); !ok {
|
||||
t.Errorf("For test #%d, expected test.Args[%d] to be type *string (pointer to string), but actually was: %T", testNumber, argNumber, arg)
|
||||
continue
|
||||
} else if argString := *argStringPtr; "" != argString {
|
||||
t.Errorf("For test #%d, expected test.Args[%d] to be \"\" (empty string), but actually was: %q", testNumber, argNumber, argString)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if err := set(test.Value, test.ArgsIndex, test.Args...); nil != err {
|
||||
t.Errorf("For test #%d, did not expected error when calling set(), but actually received one: %v", testNumber, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for argNumber, arg := range test.Args {
|
||||
if argNumber == test.ArgsIndex {
|
||||
continue
|
||||
}
|
||||
|
||||
if argStringPtr, ok := arg.(*string); !ok {
|
||||
t.Errorf("For test #%d, expected test.Args[%d] to be type *string (pointer to string), but actually was: %T", testNumber, argNumber, arg)
|
||||
continue
|
||||
} else if argString := *argStringPtr; "" != argString {
|
||||
t.Errorf("For test #%d, expected test.Args[%d] to be \"\" (empty string), but actually was: %q", testNumber, argNumber, argString)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if actualPtr, ok := test.Args[test.ArgsIndex].(*string); !ok {
|
||||
t.Errorf("For test #%d, expected test.Args[%d] to be type *string (pointer to string), but actually was: %T", testNumber, test.ArgsIndex, test.Args[test.ArgsIndex])
|
||||
continue
|
||||
} else if expected, actual := test.Value, *actualPtr; expected != actual {
|
||||
t.Errorf("For test #%d, expected test.Args[%d] to be type %q, but actually was %q.", testNumber, test.ArgsIndex, expected, actual)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestSetFail(t *testing.T) {
|
||||
|
||||
const internalError = "internal_error"
|
||||
const notEnoughArguments = "not_enough_arguments"
|
||||
const unsupportedArgumentType = "unsupported_argument_type"
|
||||
|
||||
|
||||
randomness := rand.New(rand.NewSource( time.Now().UTC().UnixNano() ))
|
||||
runes := []rune(".0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz")
|
||||
lenRunes := len(runes)
|
||||
|
||||
randomString := func() string {
|
||||
length := 1 + randomness.Intn(59)
|
||||
rs := make([]rune, length)
|
||||
for i := range rs {
|
||||
rs[i] = runes[randomness.Intn(lenRunes)]
|
||||
}
|
||||
|
||||
return string(rs)
|
||||
}
|
||||
|
||||
|
||||
tests := []struct{
|
||||
Value string
|
||||
ArgsIndex int
|
||||
Args []interface{}
|
||||
ExpectedFit string
|
||||
ExpectedErrorString string
|
||||
}{
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: -1,
|
||||
Args: []interface{}{ new(string), },
|
||||
ExpectedFit: internalError,
|
||||
ExpectedErrorString: "Internal Error: Index value -1 is less than zero.",
|
||||
},
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: -1,
|
||||
Args: []interface{}{ randomString(), },
|
||||
ExpectedFit: internalError,
|
||||
ExpectedErrorString: "Internal Error: Index value -1 is less than zero.",
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: -2,
|
||||
Args: []interface{}{ new(string), },
|
||||
ExpectedFit: internalError,
|
||||
ExpectedErrorString: "Internal Error: Index value -2 is less than zero.",
|
||||
},
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: -2,
|
||||
Args: []interface{}{ randomString(), },
|
||||
ExpectedFit: internalError,
|
||||
ExpectedErrorString: "Internal Error: Index value -2 is less than zero.",
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: -3,
|
||||
Args: []interface{}{ new(string), },
|
||||
ExpectedFit: internalError,
|
||||
ExpectedErrorString: "Internal Error: Index value -3 is less than zero.",
|
||||
},
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: -3,
|
||||
Args: []interface{}{ randomString(), },
|
||||
ExpectedFit: internalError,
|
||||
ExpectedErrorString: "Internal Error: Index value -3 is less than zero.",
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 0,
|
||||
Args: []interface{}{ randomString(), },
|
||||
ExpectedFit: unsupportedArgumentType,
|
||||
ExpectedErrorString: "Bad Request: Type of argument #0 (string) is unsupported. However, type \"*string\" (pointer to string) is supported; did you mean to use a \"*string\" instead?",
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 0,
|
||||
Args: []interface{}{ randomString(), randomString(), },
|
||||
ExpectedFit: unsupportedArgumentType,
|
||||
ExpectedErrorString: "Bad Request: Type of argument #0 (string) is unsupported. However, type \"*string\" (pointer to string) is supported; did you mean to use a \"*string\" instead?",
|
||||
},
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 1,
|
||||
Args: []interface{}{ randomString(), randomString(), },
|
||||
ExpectedFit: unsupportedArgumentType,
|
||||
ExpectedErrorString: "Bad Request: Type of argument #1 (string) is unsupported. However, type \"*string\" (pointer to string) is supported; did you mean to use a \"*string\" instead?",
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 0,
|
||||
Args: []interface{}{ randomString(), randomString(), randomString(), },
|
||||
ExpectedFit: unsupportedArgumentType,
|
||||
ExpectedErrorString: "Bad Request: Type of argument #0 (string) is unsupported. However, type \"*string\" (pointer to string) is supported; did you mean to use a \"*string\" instead?",
|
||||
},
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 1,
|
||||
Args: []interface{}{ randomString(), randomString(), randomString(), },
|
||||
ExpectedFit: unsupportedArgumentType,
|
||||
ExpectedErrorString: "Bad Request: Type of argument #1 (string) is unsupported. However, type \"*string\" (pointer to string) is supported; did you mean to use a \"*string\" instead?",
|
||||
},
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 2,
|
||||
Args: []interface{}{ randomString(), randomString(), randomString(), },
|
||||
ExpectedFit: unsupportedArgumentType,
|
||||
ExpectedErrorString: "Bad Request: Type of argument #2 (string) is unsupported. However, type \"*string\" (pointer to string) is supported; did you mean to use a \"*string\" instead?",
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 0,
|
||||
Args: []interface{}{ },
|
||||
ExpectedFit: notEnoughArguments,
|
||||
ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 1 argument, but actually got 0.",
|
||||
},
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 1,
|
||||
Args: []interface{}{ },
|
||||
ExpectedFit: notEnoughArguments,
|
||||
ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 2 arguments, but actually got 0.",
|
||||
},
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 2,
|
||||
Args: []interface{}{ },
|
||||
ExpectedFit: notEnoughArguments,
|
||||
ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 3 arguments, but actually got 0.",
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 1,
|
||||
Args: []interface{}{ randomString(), },
|
||||
ExpectedFit: notEnoughArguments,
|
||||
ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 2 arguments, but actually got 1.",
|
||||
},
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 2,
|
||||
Args: []interface{}{ randomString(), },
|
||||
ExpectedFit: notEnoughArguments,
|
||||
ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 3 arguments, but actually got 1.",
|
||||
},
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 3,
|
||||
Args: []interface{}{ randomString(), },
|
||||
ExpectedFit: notEnoughArguments,
|
||||
ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 4 arguments, but actually got 1.",
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 2,
|
||||
Args: []interface{}{ randomString(), randomString(), },
|
||||
ExpectedFit: notEnoughArguments,
|
||||
ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 3 arguments, but actually got 2.",
|
||||
},
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 3,
|
||||
Args: []interface{}{ randomString(), randomString(), },
|
||||
ExpectedFit: notEnoughArguments,
|
||||
ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 4 arguments, but actually got 2.",
|
||||
},
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 4,
|
||||
Args: []interface{}{ randomString(), randomString(), },
|
||||
ExpectedFit: notEnoughArguments,
|
||||
ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 5 arguments, but actually got 2.",
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 3,
|
||||
Args: []interface{}{ randomString(), randomString(), randomString(), },
|
||||
ExpectedFit: notEnoughArguments,
|
||||
ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 4 arguments, but actually got 3.",
|
||||
},
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 4,
|
||||
Args: []interface{}{ randomString(), randomString(), randomString(), },
|
||||
ExpectedFit: notEnoughArguments,
|
||||
ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 5 arguments, but actually got 3.",
|
||||
},
|
||||
{
|
||||
Value: randomString(),
|
||||
ArgsIndex: 5,
|
||||
Args: []interface{}{ randomString(), randomString(), randomString(), },
|
||||
ExpectedFit: notEnoughArguments,
|
||||
ExpectedErrorString: "Bad Request: Not enough arguments. Expected at least 6 arguments, but actually got 3.",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for testNumber, test := range tests {
|
||||
|
||||
if err := set(test.Value, test.ArgsIndex, test.Args...); nil == err {
|
||||
t.Errorf("For test #%d, expected error when calling set(), but did not actually received one: %v", testNumber, err)
|
||||
continue
|
||||
} else {
|
||||
switch err.(type) {
|
||||
case InternalErrorComplainer:
|
||||
if expected, actual := internalError, test.ExpectedFit; expected != actual {
|
||||
t.Errorf("For test #%d, did indeed expect an error, but did not expect it to fit the \"InternalErrorComplainer\" interface, but actually did: %T.", testNumber, err)
|
||||
continue
|
||||
}
|
||||
case NotEnoughArgumentsComplainer:
|
||||
if expected, actual := notEnoughArguments, test.ExpectedFit; expected != actual {
|
||||
t.Errorf("For test #%d, did indeed expect an error, but did not expect it to fit the \"NotEnoughArgumentsComplainer\" interface, but actually did: %T.", testNumber, err)
|
||||
continue
|
||||
}
|
||||
case UnsupportedArgumentTypeComplainer:
|
||||
if expected, actual := unsupportedArgumentType, test.ExpectedFit; expected != actual {
|
||||
t.Errorf("For test #%d, did indeed expect an error, but did not expect it to fit the \"UnsupportedArgumentTypeComplainer\" interface, but actually did: %T.", testNumber, err)
|
||||
continue
|
||||
}
|
||||
default:
|
||||
t.Errorf("For test #%d, did indeed expect an error, but did not expect this error: [%v] (%T).", testNumber, err, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if expected, actual := test.ExpectedErrorString, err.Error(); expected != actual {
|
||||
t.Errorf("For test #%d, expected error string to be %q, but actually got %q.", testNumber, expected, actual)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package pathmatch
|
||||
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
|
||||
const structFieldWrongTypeMessagePrefix = "Bad Request: Wrong type for match "
|
||||
|
||||
|
||||
type StructFieldWrongTypeComplainer interface {
|
||||
BadRequestComplainer
|
||||
MatchName() string
|
||||
}
|
||||
|
||||
// internalStructFieldWrongTypeComplainer is the only underlying implementation that fits the
|
||||
// StructFieldWrongTypeComplainer interface, in this library.
|
||||
type internalStructFieldWrongTypeComplainer struct {
|
||||
matchName string
|
||||
}
|
||||
|
||||
// newStructFieldWrongTypeComplainer creates a new internalStructFieldWrongTypeComplainer (struct) and
|
||||
// returns it as a StructFieldWrongTypeComplainer (interface).
|
||||
func newStructFieldWrongTypeComplainer(matchName string) StructFieldWrongTypeComplainer {
|
||||
err := internalStructFieldWrongTypeComplainer{
|
||||
matchName:matchName,
|
||||
}
|
||||
|
||||
return &err
|
||||
}
|
||||
|
||||
|
||||
// Error method is necessary to satisfy the 'error' interface (and the StructFieldWrongTypeComplainer
|
||||
// interface).
|
||||
func (err *internalStructFieldWrongTypeComplainer) Error() string {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
buffer.WriteString(structFieldWrongTypeMessagePrefix)
|
||||
buffer.WriteString(fmt.Sprintf("%q", err.matchName))
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
|
||||
// BadRequestComplainer method is necessary to satisfy the 'BadRequestComplainer' interface.
|
||||
// It exists to make this error type detectable in a Go type-switch.
|
||||
func (err *internalStructFieldWrongTypeComplainer) BadRequestComplainer() {
|
||||
// Nothing here.
|
||||
}
|
||||
|
||||
|
||||
// DependencyName method is necessary to satisfy the 'StructFieldWrongTypeComplainer' interface.
|
||||
func (err *internalStructFieldWrongTypeComplainer) MatchName() string {
|
||||
return err.matchName
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
package pathmatch
|
||||
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
|
||||
type UnsupportedArgumentTypeComplainer interface {
|
||||
BadRequestComplainer
|
||||
UnsupportedArgumentTypeComplainer()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// internalUnsupportedIndexedArgumentTypeComplainer is the only underlying implementation that fits the
|
||||
// UnsupportedArgumentTypeComplainer interface, in this library.
|
||||
type internalUnsupportedIndexedArgumentTypeComplainer struct {
|
||||
argumentIndex int
|
||||
argumentType string
|
||||
}
|
||||
|
||||
|
||||
// newUnsupportedIndexedArgumentTypeComplainer creates a new internalUnsupportedIndexedArgumentTypeComplainer (struct) and
|
||||
// returns it as a UnsupportedArgumentTypeComplainer (interface).
|
||||
func newUnsupportedIndexedArgumentTypeComplainer(argumentIndex int, argumentType string) UnsupportedArgumentTypeComplainer {
|
||||
err := internalUnsupportedIndexedArgumentTypeComplainer{
|
||||
argumentIndex:argumentIndex,
|
||||
argumentType:argumentType,
|
||||
}
|
||||
|
||||
return &err
|
||||
}
|
||||
|
||||
|
||||
// Error method is necessary to satisfy the 'error' interface (and the
|
||||
// UnsupportedArgumentTypeComplainer interface).
|
||||
func (err *internalUnsupportedIndexedArgumentTypeComplainer) Error() string {
|
||||
s := fmt.Sprintf("Bad Request: Type of argument #%d (%s) is unsupported.", err.argumentIndex, err.argumentType)
|
||||
if "string" == err.argumentType {
|
||||
s = fmt.Sprintf("%s However, type \"*string\" (pointer to string) is supported; did you mean to use a \"*string\" instead?", s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
|
||||
// BadRequestComplainer method is necessary to satisfy the 'BadRequestComplainer' interface.
|
||||
// It exists to make this error type detectable in a Go type-switch.
|
||||
func (err *internalUnsupportedIndexedArgumentTypeComplainer) BadRequestComplainer() {
|
||||
// Nothing here.
|
||||
}
|
||||
|
||||
|
||||
// UnsupportedArgumentTypeComplainer method is necessary to satisfy the 'UnsupportedArgumentTypeComplainer' interface.
|
||||
// It exists to make this error type detectable in a Go type-switch.
|
||||
func (err *internalUnsupportedIndexedArgumentTypeComplainer) UnsupportedArgumentTypeComplainer() {
|
||||
// Nothing here.
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// internalUnsupportedArgumentTypeComplainer is the only underlying implementation that fits the
|
||||
// UnsupportedArgumentTypeComplainer interface, in this library.
|
||||
type internalUnsupportedArgumentTypeComplainer struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
|
||||
// newUnsupportedArgumentTypeComplainer creates a new internalUnsupportedArgumentTypeComplainer (struct) and
|
||||
// returns it as a UnsupportedArgumentTypeComplainer (interface).
|
||||
func newUnsupportedArgumentTypeComplainer(format string, a ...interface{}) UnsupportedArgumentTypeComplainer {
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
|
||||
err := internalUnsupportedArgumentTypeComplainer{
|
||||
msg:msg,
|
||||
}
|
||||
|
||||
return &err
|
||||
}
|
||||
|
||||
|
||||
// Error method is necessary to satisfy the 'error' interface (and the
|
||||
// UnsupportedArgumentTypeComplainer interface).
|
||||
func (err *internalUnsupportedArgumentTypeComplainer) Error() string {
|
||||
|
||||
return fmt.Sprintf("Bad Request: Unsupported Argument Type: %s", err.msg)
|
||||
}
|
||||
|
||||
|
||||
// BadRequestComplainer method is necessary to satisfy the 'BadRequestComplainer' interface.
|
||||
// It exists to make this error type detectable in a Go type-switch.
|
||||
func (err *internalUnsupportedArgumentTypeComplainer) BadRequestComplainer() {
|
||||
// Nothing here.
|
||||
}
|
||||
|
||||
|
||||
// UnsupportedArgumentTypeComplainer method is necessary to satisfy the 'UnsupportedArgumentTypeComplainer' interface.
|
||||
// It exists to make this error type detectable in a Go type-switch.
|
||||
func (err *internalUnsupportedArgumentTypeComplainer) UnsupportedArgumentTypeComplainer() {
|
||||
// Nothing here.
|
||||
}
|
Loading…
Reference in New Issue