mirror of
https://github.com/tarampampam/error-pages.git
synced 2024-08-30 18:22:40 +00:00
125 lines
3.4 KiB
Go
125 lines
3.4 KiB
Go
package config
|
|
|
|
import (
|
|
"slices"
|
|
"strconv"
|
|
)
|
|
|
|
type (
|
|
CodeDescription struct {
|
|
// Message is a short description of the HTTP error.
|
|
Message string
|
|
|
|
// Description is a longer description of the HTTP error.
|
|
Description string
|
|
}
|
|
|
|
// Codes is a map of HTTP codes to their descriptions.
|
|
//
|
|
// The codes may be written in a non-strict manner. For example, they may be "4xx", "4XX", or "4**".
|
|
// If the map contains both "404" and "4xx" keys, and we search for "404", the "404" key will be returned.
|
|
// However, if we search for "405", "400", or any non-existing code that starts with "4" and its length is 3,
|
|
// the value under the key "4xx" will be retrieved.
|
|
//
|
|
// The length of the code (in string format) is matter.
|
|
Codes map[string]CodeDescription // map[http_code]description
|
|
)
|
|
|
|
// Find searches the closest match for the given HTTP code, written in a non-strict manner. Read [Codes] for more
|
|
// information.
|
|
func (c Codes) Find(httpCode uint16) (CodeDescription, bool) { //nolint:funlen,gocyclo
|
|
if len(c) == 0 { // empty map, fast return
|
|
return CodeDescription{}, false
|
|
}
|
|
|
|
var code = strconv.FormatUint(uint64(httpCode), 10)
|
|
|
|
if desc, ok := c[code]; ok { // search for the exact match
|
|
return desc, true
|
|
}
|
|
|
|
var (
|
|
keysMap = make(map[string][]rune, len(c))
|
|
codeRunes = []rune(code)
|
|
)
|
|
|
|
for key := range c { // take only the keys that are the same length and start with the same character or a wildcard
|
|
if kr := []rune(key); len(kr) > 0 && len(kr) == len(codeRunes) && isWildcardOr(kr[0], codeRunes[0]) {
|
|
keysMap[key] = kr
|
|
}
|
|
}
|
|
|
|
if len(keysMap) == 0 { // no matches found using the first rune comparison
|
|
return CodeDescription{}, false
|
|
}
|
|
|
|
var matchedMap = make(map[string]uint16, len(keysMap)) // map[mapKey]wildcardMatchedCount
|
|
|
|
for mapKey, keyRunes := range keysMap { // search for the closest match
|
|
var wildcardMatchedCount uint16 = 0
|
|
|
|
for i := 0; i < len(codeRunes); i++ { // loop through each httpCode rune
|
|
var keyRune, codeRune = keyRunes[i], codeRunes[i]
|
|
|
|
if wm := isWildcard(keyRune); wm || keyRune == codeRune {
|
|
if wm {
|
|
wildcardMatchedCount++
|
|
}
|
|
|
|
if i == len(codeRunes)-1 { // is the last rune?
|
|
matchedMap[mapKey] = wildcardMatchedCount
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
if len(matchedMap) == 0 { // no matches found
|
|
return CodeDescription{}, false
|
|
} else if len(matchedMap) == 1 { // only one match found
|
|
for mapKey := range matchedMap {
|
|
return c[mapKey], true
|
|
}
|
|
}
|
|
|
|
// multiple matches found, find the most specific one based on the wildcard matched count (pick the one with the
|
|
// least wildcards)
|
|
var (
|
|
minCount uint16
|
|
key string
|
|
)
|
|
|
|
for mapKey, count := range matchedMap {
|
|
if minCount == 0 || count < minCount {
|
|
minCount, key = count, mapKey
|
|
}
|
|
}
|
|
|
|
return c[key], true
|
|
}
|
|
|
|
func isWildcard(r rune) bool { return r == '*' || r == 'x' || r == 'X' }
|
|
func isWildcardOr(r, or rune) bool { return isWildcard(r) || r == or }
|
|
|
|
// Codes returns all HTTP codes sorted alphabetically.
|
|
func (c Codes) Codes() []string {
|
|
var codes = make([]string, 0, len(c))
|
|
|
|
for code := range c {
|
|
codes = append(codes, code)
|
|
}
|
|
|
|
slices.Sort(codes)
|
|
|
|
return codes
|
|
}
|
|
|
|
// Has checks if the HTTP code exists.
|
|
func (c Codes) Has(code string) (found bool) { _, found = c[code]; return } //nolint:nlreturn
|
|
|
|
// Get returns the HTTP code description by the specified code, if it exists.
|
|
func (c Codes) Get(code string) (data CodeDescription, ok bool) { data, ok = c[code]; return } //nolint:nlreturn
|