error-pages/internal/config/codes.go
2024-07-03 18:12:13 +04:00

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