go-pathmatch/compile.go

149 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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
}
}