149 lines
3.7 KiB
Go
149 lines
3.7 KiB
Go
package pathmatch
|
||
|
||
import (
|
||
"strings"
|
||
)
|
||
|
||
const (
|
||
defaultFieldTagName = "match"
|
||
wildcardBit = "{}"
|
||
)
|
||
|
||
var (
|
||
errMissingEndingRightBraceToMatchBeginningLeftBrace = newPatternSyntaxError(`Missing ending "}" (to match beginning "{").`)
|
||
errSlashInsideOfBraces = newPatternSyntaxError(`"/" inside of "{...}".`)
|
||
errLeftBraceInsideOfBraces = newPatternSyntaxError(`"{" 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 then 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) {
|
||
|
||
var pattern Pattern
|
||
|
||
err := CompileTo(&pattern, uncompiledPattern)
|
||
if nil != err {
|
||
return nil, err
|
||
}
|
||
|
||
return &pattern, nil
|
||
}
|
||
|
||
// CompileTo takes an uncompiled pattern, in the form of a Go string (ex: "/users/{userId}/vehicles/{vehicleId}"),
|
||
// and compiles the pattern to the ‘target’.
|
||
//
|
||
// The compiled pattern can then 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:
|
||
//
|
||
// var pattern patchmatch.Pattern
|
||
//
|
||
// err := pathmath.CompileTo(&pattern, "/users/{user_id}")
|
||
// if nil != err {
|
||
// fmt.Printf("ERROR Compiling: %v\n", err)
|
||
// return
|
||
// }
|
||
func CompileTo(target *Pattern, uncompiledPattern string) error {
|
||
if nil == target {
|
||
return errNilTarget
|
||
}
|
||
|
||
target.mutex.Lock()
|
||
defer target.mutex.Unlock()
|
||
|
||
target.init(defaultFieldTagName)
|
||
|
||
target.template = uncompiledPattern
|
||
|
||
s := uncompiledPattern
|
||
for {
|
||
index := strings.IndexRune(s, '{')
|
||
if -1 == index {
|
||
target.bits = append(target.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.
|
||
target.bits = append(target.bits, bit)
|
||
}
|
||
s = s[1+index:]
|
||
if "" == s {
|
||
break
|
||
}
|
||
|
||
index = strings.IndexRune(s, '}')
|
||
if -1 == index {
|
||
return 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 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 errLeftBraceInsideOfBraces
|
||
}
|
||
|
||
|
||
bit = s[:index]
|
||
|
||
|
||
// Match names should be unique, within a pattern.
|
||
if _, ok := target.namesSet[bit]; ok {
|
||
return newPatternSyntaxError("Duplicate match name: %q.", bit)
|
||
}
|
||
|
||
|
||
target.names = append(target.names, bit)
|
||
target.namesSet[bit] = struct{}{}
|
||
target.bits = append(target.bits, wildcardBit)
|
||
s = s[1+index:]
|
||
if "" == s {
|
||
break
|
||
}
|
||
}
|
||
|
||
|
||
return 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 {
|
||
var pattern Pattern
|
||
|
||
if err := CompileTo(&pattern, uncompiledPattern); nil != err {
|
||
panic(err)
|
||
} else {
|
||
return &pattern
|
||
}
|
||
}
|