Write an openapi spec for the worker API and use `deepmap/oapi-codegen`
to generate scaffolding for the server-side using the `labstack/echo`
server.
Incidentally, echo by default returns the errors in the same format that
worker API always has:
{ "message": "..." }
The API itself is unchanged to make this change easier to understand. It
will be changed to better suit our needs in future commits.
317 lines
8.9 KiB
Go
317 lines
8.9 KiB
Go
// Package fasttemplate implements simple and fast template library.
|
|
//
|
|
// Fasttemplate is faster than text/template, strings.Replace
|
|
// and strings.Replacer.
|
|
//
|
|
// Fasttemplate ideally fits for fast and simple placeholders' substitutions.
|
|
package fasttemplate
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"github.com/valyala/bytebufferpool"
|
|
"io"
|
|
)
|
|
|
|
// ExecuteFunc calls f on each template tag (placeholder) occurrence.
|
|
//
|
|
// Returns the number of bytes written to w.
|
|
//
|
|
// This function is optimized for constantly changing templates.
|
|
// Use Template.ExecuteFunc for frozen templates.
|
|
func ExecuteFunc(template, startTag, endTag string, w io.Writer, f TagFunc) (int64, error) {
|
|
s := unsafeString2Bytes(template)
|
|
a := unsafeString2Bytes(startTag)
|
|
b := unsafeString2Bytes(endTag)
|
|
|
|
var nn int64
|
|
var ni int
|
|
var err error
|
|
for {
|
|
n := bytes.Index(s, a)
|
|
if n < 0 {
|
|
break
|
|
}
|
|
ni, err = w.Write(s[:n])
|
|
nn += int64(ni)
|
|
if err != nil {
|
|
return nn, err
|
|
}
|
|
|
|
s = s[n+len(a):]
|
|
n = bytes.Index(s, b)
|
|
if n < 0 {
|
|
// cannot find end tag - just write it to the output.
|
|
ni, _ = w.Write(a)
|
|
nn += int64(ni)
|
|
break
|
|
}
|
|
|
|
ni, err = f(w, unsafeBytes2String(s[:n]))
|
|
nn += int64(ni)
|
|
s = s[n+len(b):]
|
|
}
|
|
ni, err = w.Write(s)
|
|
nn += int64(ni)
|
|
|
|
return nn, err
|
|
}
|
|
|
|
// Execute substitutes template tags (placeholders) with the corresponding
|
|
// values from the map m and writes the result to the given writer w.
|
|
//
|
|
// Substitution map m may contain values with the following types:
|
|
// * []byte - the fastest value type
|
|
// * string - convenient value type
|
|
// * TagFunc - flexible value type
|
|
//
|
|
// Returns the number of bytes written to w.
|
|
//
|
|
// This function is optimized for constantly changing templates.
|
|
// Use Template.Execute for frozen templates.
|
|
func Execute(template, startTag, endTag string, w io.Writer, m map[string]interface{}) (int64, error) {
|
|
return ExecuteFunc(template, startTag, endTag, w, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
|
|
}
|
|
|
|
// ExecuteFuncString calls f on each template tag (placeholder) occurrence
|
|
// and substitutes it with the data written to TagFunc's w.
|
|
//
|
|
// Returns the resulting string.
|
|
//
|
|
// This function is optimized for constantly changing templates.
|
|
// Use Template.ExecuteFuncString for frozen templates.
|
|
func ExecuteFuncString(template, startTag, endTag string, f TagFunc) string {
|
|
tagsCount := bytes.Count(unsafeString2Bytes(template), unsafeString2Bytes(startTag))
|
|
if tagsCount == 0 {
|
|
return template
|
|
}
|
|
|
|
bb := byteBufferPool.Get()
|
|
if _, err := ExecuteFunc(template, startTag, endTag, bb, f); err != nil {
|
|
panic(fmt.Sprintf("unexpected error: %s", err))
|
|
}
|
|
s := string(bb.B)
|
|
bb.Reset()
|
|
byteBufferPool.Put(bb)
|
|
return s
|
|
}
|
|
|
|
var byteBufferPool bytebufferpool.Pool
|
|
|
|
// ExecuteString substitutes template tags (placeholders) with the corresponding
|
|
// values from the map m and returns the result.
|
|
//
|
|
// Substitution map m may contain values with the following types:
|
|
// * []byte - the fastest value type
|
|
// * string - convenient value type
|
|
// * TagFunc - flexible value type
|
|
//
|
|
// This function is optimized for constantly changing templates.
|
|
// Use Template.ExecuteString for frozen templates.
|
|
func ExecuteString(template, startTag, endTag string, m map[string]interface{}) string {
|
|
return ExecuteFuncString(template, startTag, endTag, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
|
|
}
|
|
|
|
// Template implements simple template engine, which can be used for fast
|
|
// tags' (aka placeholders) substitution.
|
|
type Template struct {
|
|
template string
|
|
startTag string
|
|
endTag string
|
|
|
|
texts [][]byte
|
|
tags []string
|
|
byteBufferPool bytebufferpool.Pool
|
|
}
|
|
|
|
// New parses the given template using the given startTag and endTag
|
|
// as tag start and tag end.
|
|
//
|
|
// The returned template can be executed by concurrently running goroutines
|
|
// using Execute* methods.
|
|
//
|
|
// New panics if the given template cannot be parsed. Use NewTemplate instead
|
|
// if template may contain errors.
|
|
func New(template, startTag, endTag string) *Template {
|
|
t, err := NewTemplate(template, startTag, endTag)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return t
|
|
}
|
|
|
|
// NewTemplate parses the given template using the given startTag and endTag
|
|
// as tag start and tag end.
|
|
//
|
|
// The returned template can be executed by concurrently running goroutines
|
|
// using Execute* methods.
|
|
func NewTemplate(template, startTag, endTag string) (*Template, error) {
|
|
var t Template
|
|
err := t.Reset(template, startTag, endTag)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &t, nil
|
|
}
|
|
|
|
// TagFunc can be used as a substitution value in the map passed to Execute*.
|
|
// Execute* functions pass tag (placeholder) name in 'tag' argument.
|
|
//
|
|
// TagFunc must be safe to call from concurrently running goroutines.
|
|
//
|
|
// TagFunc must write contents to w and return the number of bytes written.
|
|
type TagFunc func(w io.Writer, tag string) (int, error)
|
|
|
|
// Reset resets the template t to new one defined by
|
|
// template, startTag and endTag.
|
|
//
|
|
// Reset allows Template object re-use.
|
|
//
|
|
// Reset may be called only if no other goroutines call t methods at the moment.
|
|
func (t *Template) Reset(template, startTag, endTag string) error {
|
|
// Keep these vars in t, so GC won't collect them and won't break
|
|
// vars derived via unsafe*
|
|
t.template = template
|
|
t.startTag = startTag
|
|
t.endTag = endTag
|
|
t.texts = t.texts[:0]
|
|
t.tags = t.tags[:0]
|
|
|
|
if len(startTag) == 0 {
|
|
panic("startTag cannot be empty")
|
|
}
|
|
if len(endTag) == 0 {
|
|
panic("endTag cannot be empty")
|
|
}
|
|
|
|
s := unsafeString2Bytes(template)
|
|
a := unsafeString2Bytes(startTag)
|
|
b := unsafeString2Bytes(endTag)
|
|
|
|
tagsCount := bytes.Count(s, a)
|
|
if tagsCount == 0 {
|
|
return nil
|
|
}
|
|
|
|
if tagsCount+1 > cap(t.texts) {
|
|
t.texts = make([][]byte, 0, tagsCount+1)
|
|
}
|
|
if tagsCount > cap(t.tags) {
|
|
t.tags = make([]string, 0, tagsCount)
|
|
}
|
|
|
|
for {
|
|
n := bytes.Index(s, a)
|
|
if n < 0 {
|
|
t.texts = append(t.texts, s)
|
|
break
|
|
}
|
|
t.texts = append(t.texts, s[:n])
|
|
|
|
s = s[n+len(a):]
|
|
n = bytes.Index(s, b)
|
|
if n < 0 {
|
|
return fmt.Errorf("Cannot find end tag=%q in the template=%q starting from %q", endTag, template, s)
|
|
}
|
|
|
|
t.tags = append(t.tags, unsafeBytes2String(s[:n]))
|
|
s = s[n+len(b):]
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ExecuteFunc calls f on each template tag (placeholder) occurrence.
|
|
//
|
|
// Returns the number of bytes written to w.
|
|
//
|
|
// This function is optimized for frozen templates.
|
|
// Use ExecuteFunc for constantly changing templates.
|
|
func (t *Template) ExecuteFunc(w io.Writer, f TagFunc) (int64, error) {
|
|
var nn int64
|
|
|
|
n := len(t.texts) - 1
|
|
if n == -1 {
|
|
ni, err := w.Write(unsafeString2Bytes(t.template))
|
|
return int64(ni), err
|
|
}
|
|
|
|
for i := 0; i < n; i++ {
|
|
ni, err := w.Write(t.texts[i])
|
|
nn += int64(ni)
|
|
if err != nil {
|
|
return nn, err
|
|
}
|
|
|
|
ni, err = f(w, t.tags[i])
|
|
nn += int64(ni)
|
|
if err != nil {
|
|
return nn, err
|
|
}
|
|
}
|
|
ni, err := w.Write(t.texts[n])
|
|
nn += int64(ni)
|
|
return nn, err
|
|
}
|
|
|
|
// Execute substitutes template tags (placeholders) with the corresponding
|
|
// values from the map m and writes the result to the given writer w.
|
|
//
|
|
// Substitution map m may contain values with the following types:
|
|
// * []byte - the fastest value type
|
|
// * string - convenient value type
|
|
// * TagFunc - flexible value type
|
|
//
|
|
// Returns the number of bytes written to w.
|
|
func (t *Template) Execute(w io.Writer, m map[string]interface{}) (int64, error) {
|
|
return t.ExecuteFunc(w, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
|
|
}
|
|
|
|
// ExecuteFuncString calls f on each template tag (placeholder) occurrence
|
|
// and substitutes it with the data written to TagFunc's w.
|
|
//
|
|
// Returns the resulting string.
|
|
//
|
|
// This function is optimized for frozen templates.
|
|
// Use ExecuteFuncString for constantly changing templates.
|
|
func (t *Template) ExecuteFuncString(f TagFunc) string {
|
|
bb := t.byteBufferPool.Get()
|
|
if _, err := t.ExecuteFunc(bb, f); err != nil {
|
|
panic(fmt.Sprintf("unexpected error: %s", err))
|
|
}
|
|
s := string(bb.Bytes())
|
|
bb.Reset()
|
|
t.byteBufferPool.Put(bb)
|
|
return s
|
|
}
|
|
|
|
// ExecuteString substitutes template tags (placeholders) with the corresponding
|
|
// values from the map m and returns the result.
|
|
//
|
|
// Substitution map m may contain values with the following types:
|
|
// * []byte - the fastest value type
|
|
// * string - convenient value type
|
|
// * TagFunc - flexible value type
|
|
//
|
|
// This function is optimized for frozen templates.
|
|
// Use ExecuteString for constantly changing templates.
|
|
func (t *Template) ExecuteString(m map[string]interface{}) string {
|
|
return t.ExecuteFuncString(func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
|
|
}
|
|
|
|
func stdTagFunc(w io.Writer, tag string, m map[string]interface{}) (int, error) {
|
|
v := m[tag]
|
|
if v == nil {
|
|
return 0, nil
|
|
}
|
|
switch value := v.(type) {
|
|
case []byte:
|
|
return w.Write(value)
|
|
case string:
|
|
return w.Write([]byte(value))
|
|
case TagFunc:
|
|
return value(w, tag)
|
|
default:
|
|
panic(fmt.Sprintf("tag=%q contains unexpected value type=%#v. Expected []byte, string or TagFunc", tag, v))
|
|
}
|
|
}
|