error-pages/internal/tpl/error_pages.go
2021-10-06 22:38:00 +05:00

137 lines
3.5 KiB
Go

package tpl
import (
"bytes"
"sync"
"github.com/pkg/errors"
)
type (
// ErrorPages is a error page templates generator.
ErrorPages struct {
mu sync.RWMutex
templates map[string][]byte // map[template_name]raw_content
pages map[string]*pageProperties // map[page_code]props
state map[string]map[string][]byte // map[template_name]map[page_code]content
}
pageProperties struct {
message, description string
}
)
var (
ErrUnknownTemplate = errors.New("unknown template") // unknown template
ErrUnknownPageCode = errors.New("unknown page code") // unknown page code
)
// NewErrorPages creates ErrorPages templates generator.
func NewErrorPages() ErrorPages {
return ErrorPages{
templates: make(map[string][]byte),
pages: make(map[string]*pageProperties),
state: make(map[string]map[string][]byte),
}
}
// AddTemplate to the generator. Template can contain the special placeholders for the error code, message and
// description:
// {{ code }} - for the code
// {{ message }} - for the message
// {{ description }} - for the description
func (e *ErrorPages) AddTemplate(templateName string, content []byte) {
e.mu.Lock()
defer e.mu.Unlock()
e.templates[templateName] = content
e.state[templateName] = make(map[string][]byte)
for code, props := range e.pages { // update the state
e.state[templateName][code] = e.makeReplaces(content, code, props.message, props.description)
}
}
// AddPage with the passed code, message and description. This page will ba available for the all templates.
func (e *ErrorPages) AddPage(code, message, description string) {
e.mu.Lock()
defer e.mu.Unlock()
e.pages[code] = &pageProperties{message, description}
for templateName, content := range e.templates { // update the state
e.state[templateName][code] = e.makeReplaces(content, code, message, description)
}
}
// GetPage with passed template name and error code.
func (e *ErrorPages) GetPage(templateName, code string) (content []byte, err error) {
e.mu.RLock()
defer e.mu.RUnlock()
if pages, templateExists := e.state[templateName]; templateExists {
if c, pageExists := pages[code]; pageExists {
content = c
} else {
err = ErrUnknownPageCode
}
} else {
err = ErrUnknownTemplate
}
return
}
// IteratePages will call the passed function for each page and template.
func (e *ErrorPages) IteratePages(fn func(template, code string, content []byte) error) error {
e.mu.RLock()
defer e.mu.RUnlock()
for tplName, codes := range e.state {
for code, content := range codes {
if err := fn(tplName, code, content); err != nil {
return err
}
}
}
return nil
}
const (
tknCode byte = iota + 1
tknMessage
tknDescription
)
var tknSets = map[byte][][]byte{ //nolint:gochecknoglobals
tknCode: {[]byte("{{code}}"), []byte("{{ code }}")},
tknMessage: {[]byte("{{message}}"), []byte("{{ message }}")},
tknDescription: {[]byte("{{description}}"), []byte("{{ description }}")},
}
func (e *ErrorPages) makeReplaces(where []byte, code, message, description string) []byte {
for tkn, set := range tknSets {
var replaceWith []byte
switch tkn {
case tknCode:
replaceWith = []byte(code)
case tknMessage:
replaceWith = []byte(message)
case tknDescription:
replaceWith = []byte(description)
default:
panic("tpl: unsupported token") // this is like a fuse, will never occur during normal usage
}
if len(replaceWith) > 0 {
for i := 0; i < len(set); i++ {
where = bytes.ReplaceAll(where, set[i], replaceWith)
}
}
}
return where
}