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