mirror of
https://github.com/tarampampam/error-pages.git
synced 2024-08-30 18:22:40 +00:00
wip: 🔕 temporary commit
This commit is contained in:
parent
c1eaee0287
commit
65fc5ecc7f
@ -2,12 +2,12 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
|
||||||
"go.uber.org/automaxprocs/maxprocs"
|
"go.uber.org/automaxprocs/maxprocs"
|
||||||
|
|
||||||
"gh.tarampamp.am/error-pages/internal/cli"
|
"gh.tarampamp.am/error-pages/internal/cli"
|
||||||
@ -19,7 +19,7 @@ func main() {
|
|||||||
_, _ = maxprocs.Set(maxprocs.Min(1), maxprocs.Logger(func(_ string, _ ...any) {}))
|
_, _ = maxprocs.Set(maxprocs.Min(1), maxprocs.Logger(func(_ string, _ ...any) {}))
|
||||||
|
|
||||||
if err := run(); err != nil {
|
if err := run(); err != nil {
|
||||||
_, _ = color.New(color.FgHiRed, color.Bold).Fprintln(os.Stderr, err.Error())
|
_, _ = fmt.Fprintln(os.Stderr, err.Error())
|
||||||
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
33
internal/template/props.go
Normal file
33
internal/template/props.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package template
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
//nolint:lll
|
||||||
|
type Props struct {
|
||||||
|
Code string `token:"code"` // http status code
|
||||||
|
Message string `token:"message"` // status message
|
||||||
|
Description string `token:"description"` // status description
|
||||||
|
OriginalURI string `token:"original_uri"` // (ingress-nginx) URI that caused the error
|
||||||
|
Namespace string `token:"namespace"` // (ingress-nginx) namespace where the backend Service is located
|
||||||
|
IngressName string `token:"ingress_name"` // (ingress-nginx) name of the Ingress where the backend is defined
|
||||||
|
ServiceName string `token:"service_name"` // (ingress-nginx) name of the Service backing the backend
|
||||||
|
ServicePort string `token:"service_port"` // (ingress-nginx) port number of the Service backing the backend
|
||||||
|
RequestID string `token:"request_id"` // (ingress-nginx) unique ID that identifies the request - same as for backend service
|
||||||
|
ForwardedFor string `token:"forwarded_for"` // the value of the `X-Forwarded-For` header
|
||||||
|
Host string `token:"host"` // the value of the `Host` header
|
||||||
|
ShowRequestDetails bool `token:"show_details"` // (config) show request details?
|
||||||
|
L10nDisabled bool `token:"l10n_disabled"` // (config) disable localization feature?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values convert the Props struct into a map where each key is a token associated with its corresponding value.
|
||||||
|
func (p Props) Values() map[string]any {
|
||||||
|
var result = make(map[string]any, reflect.ValueOf(p).NumField())
|
||||||
|
|
||||||
|
for i, v := 0, reflect.ValueOf(p); i < v.NumField(); i++ {
|
||||||
|
if token, tagExists := v.Type().Field(i).Tag.Lookup("token"); tagExists {
|
||||||
|
result[token] = v.Field(i).Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
42
internal/template/props_test.go
Normal file
42
internal/template/props_test.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package template_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"gh.tarampamp.am/error-pages/internal/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProps_Values(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
assert.Equal(t, template.Props{
|
||||||
|
Code: "a",
|
||||||
|
Message: "b",
|
||||||
|
Description: "c",
|
||||||
|
OriginalURI: "d",
|
||||||
|
Namespace: "e",
|
||||||
|
IngressName: "f",
|
||||||
|
ServiceName: "g",
|
||||||
|
ServicePort: "h",
|
||||||
|
RequestID: "i",
|
||||||
|
ForwardedFor: "j",
|
||||||
|
L10nDisabled: true,
|
||||||
|
ShowRequestDetails: false,
|
||||||
|
}.Values(), map[string]any{
|
||||||
|
"code": "a",
|
||||||
|
"message": "b",
|
||||||
|
"description": "c",
|
||||||
|
"original_uri": "d",
|
||||||
|
"namespace": "e",
|
||||||
|
"ingress_name": "f",
|
||||||
|
"service_name": "g",
|
||||||
|
"service_port": "h",
|
||||||
|
"request_id": "i",
|
||||||
|
"forwarded_for": "j",
|
||||||
|
"host": "", // empty because it's not set
|
||||||
|
"l10n_disabled": true,
|
||||||
|
"show_details": false,
|
||||||
|
})
|
||||||
|
}
|
130
internal/template/template.go
Normal file
130
internal/template/template.go
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gh.tarampamp.am/error-pages/internal/appmeta"
|
||||||
|
)
|
||||||
|
|
||||||
|
var builtInFunctions = template.FuncMap{ //nolint:gochecknoglobals
|
||||||
|
// current time:
|
||||||
|
// `{{ now.Unix }}` // `1631610000`
|
||||||
|
// `{{ now.Hour }}:{{ now.Minute }}:{{ now.Second }}` // `15:4:5`
|
||||||
|
"now": time.Now,
|
||||||
|
|
||||||
|
// current hostname:
|
||||||
|
// `{{ hostname }}` // `localhost`
|
||||||
|
"hostname": func() string { h, _ := os.Hostname(); return h }, //nolint:nlreturn
|
||||||
|
|
||||||
|
// json-serialized value (safe to use with any type):
|
||||||
|
// `{{ json "test" }}` // `"test"`
|
||||||
|
// `{{ json 42 }}` // `42`
|
||||||
|
"json": func(v any) string { b, _ := json.Marshal(v); return string(b) }, //nolint:nlreturn,errchkjson
|
||||||
|
|
||||||
|
// cast any type to int, or return 0 if it's not possible:
|
||||||
|
// `{{ int "42" }}` // `42`
|
||||||
|
// `{{ int 42 }}` // `42`
|
||||||
|
// `{{ int 3.14 }}` // `3`
|
||||||
|
// `{{ int "test" }}` // `0`
|
||||||
|
// `{{ int "42test" }}` // `0`
|
||||||
|
"int": func(v any) int { // cast any type to int, or return 0 if it's not possible
|
||||||
|
switch v := v.(type) {
|
||||||
|
case string:
|
||||||
|
if i, err := strconv.Atoi(strings.TrimSpace(v)); err == nil {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
case int:
|
||||||
|
return v
|
||||||
|
case int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
||||||
|
if i, err := strconv.Atoi(fmt.Sprintf("%d", v)); err == nil { // not effective, but safe
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
case float32, float64:
|
||||||
|
if i, err := strconv.ParseFloat(fmt.Sprintf("%f", v), 32); err == nil { // not effective, but safe
|
||||||
|
return int(i)
|
||||||
|
}
|
||||||
|
case fmt.Stringer:
|
||||||
|
if i, err := strconv.Atoi(v.String()); err == nil {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
|
||||||
|
// current application version:
|
||||||
|
// `{{ version }}` // `1.0.0`
|
||||||
|
"version": appmeta.Version,
|
||||||
|
|
||||||
|
// counts the number of non-overlapping instances of substr in s:
|
||||||
|
// `{{ strCount "test" "t" }}` // `2`
|
||||||
|
"strCount": strings.Count,
|
||||||
|
|
||||||
|
// reports whether substr is within s:
|
||||||
|
// `{{ strContains "test" "es" }}` // `true`
|
||||||
|
// `{{ strContains "test" "ez" }}` // `false`
|
||||||
|
"strContains": strings.Contains,
|
||||||
|
|
||||||
|
// returns a slice of the string s, with all leading and trailing white space removed:
|
||||||
|
// `{{ strTrimSpace " test " }}` // `test`
|
||||||
|
"strTrimSpace": strings.TrimSpace,
|
||||||
|
|
||||||
|
// returns s without the provided leading prefix string:
|
||||||
|
// `{{ strTrimPrefix "test" "te" }}` // `st`
|
||||||
|
"strTrimPrefix": strings.TrimPrefix,
|
||||||
|
|
||||||
|
// returns s without the provided trailing suffix string:
|
||||||
|
// `{{ strTrimSuffix "test" "st" }}` // `te`
|
||||||
|
"strTrimSuffix": strings.TrimSuffix,
|
||||||
|
|
||||||
|
// returns a copy of the string s with all non-overlapping instances of old replaced by new:
|
||||||
|
// `{{ strReplace "test" "t" "z" }}` // `zesz`
|
||||||
|
"strReplace": strings.ReplaceAll,
|
||||||
|
|
||||||
|
// returns the index of the first instance of substr in s, or -1 if substr is not present in s:
|
||||||
|
// `{{ strIndex "barfoobaz" "foo" }}` // `3`
|
||||||
|
"strIndex": strings.Index,
|
||||||
|
|
||||||
|
// splits the string s around each instance of one or more consecutive white space characters:
|
||||||
|
// `{{ strFields "foo bar baz" }}` // `[foo bar baz]`
|
||||||
|
"strFields": strings.Fields,
|
||||||
|
|
||||||
|
// retrieves the value of the environment variable named by the key:
|
||||||
|
// `{{ env "SHELL" }}` // `/bin/bash`
|
||||||
|
"env": os.Getenv,
|
||||||
|
}
|
||||||
|
|
||||||
|
func Render(content string, props Props) (string, error) {
|
||||||
|
var fns = maps.Clone(builtInFunctions)
|
||||||
|
|
||||||
|
maps.Copy(fns, template.FuncMap{ // add custom functions
|
||||||
|
"hide_details": func() bool { return !props.ShowRequestDetails }, // inverted logic
|
||||||
|
"l10n_enabled": func() bool { return !props.L10nDisabled }, // inverted logic
|
||||||
|
})
|
||||||
|
|
||||||
|
// allow the direct access to the properties tokens, e.g. `{{ service_port | json }}`
|
||||||
|
// instead of `{{ .service_port | json }}`
|
||||||
|
for k, v := range props.Values() {
|
||||||
|
fns[k] = func() any { return v }
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, tErr := template.New("template").Funcs(fns).Parse(content)
|
||||||
|
if tErr != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse template: %w", tErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf strings.Builder
|
||||||
|
|
||||||
|
if err := tmpl.Execute(&buf, props); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
231
internal/template/template_test.go
Normal file
231
internal/template/template_test.go
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
package template_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"gh.tarampamp.am/error-pages/internal/appmeta"
|
||||||
|
"gh.tarampamp.am/error-pages/internal/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRender_BuiltInFunction(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var hostname, hErr = os.Hostname()
|
||||||
|
|
||||||
|
require.NoError(t, hErr)
|
||||||
|
|
||||||
|
for name, tt := range map[string]struct {
|
||||||
|
giveTemplate string
|
||||||
|
wantResult string
|
||||||
|
wantErrMsg string
|
||||||
|
}{
|
||||||
|
"now (unix)": {
|
||||||
|
giveTemplate: `{{ now.Unix }}`,
|
||||||
|
wantResult: strconv.Itoa(int(time.Now().Unix())),
|
||||||
|
},
|
||||||
|
"now (time)": {
|
||||||
|
giveTemplate: `{{ now.Hour }}:{{ now.Minute }}:{{ now.Second }}`,
|
||||||
|
wantResult: time.Now().Format("15:4:5"),
|
||||||
|
},
|
||||||
|
"hostname": {giveTemplate: `{{ hostname }}`, wantResult: hostname},
|
||||||
|
"json (string)": {giveTemplate: `{{ json "test" }}`, wantResult: `"test"`},
|
||||||
|
"json (int)": {giveTemplate: `{{ json 42 }}`, wantResult: `42`},
|
||||||
|
"json (func result)": {giveTemplate: `{{ json hostname }}`, wantResult: `"` + hostname + `"`},
|
||||||
|
"int (string)": {giveTemplate: `{{ int "42" }}`, wantResult: `42`},
|
||||||
|
"int (int)": {giveTemplate: `{{ int 42 }}`, wantResult: `42`},
|
||||||
|
"int (float)": {giveTemplate: `{{ int 3.14 }}`, wantResult: `3`},
|
||||||
|
"int (wrong string)": {giveTemplate: `{{ int "test" }}`, wantResult: `0`},
|
||||||
|
"int (string with numbers)": {giveTemplate: `{{ int "42test" }}`, wantResult: `0`},
|
||||||
|
"version": {giveTemplate: `{{ version }}`, wantResult: appmeta.Version()},
|
||||||
|
"strCount": {giveTemplate: `{{ strCount "test" "t" }}`, wantResult: `2`},
|
||||||
|
"strContains (true)": {giveTemplate: `{{ strContains "test" "es" }}`, wantResult: `true`},
|
||||||
|
"strContains (false)": {giveTemplate: `{{ strContains "test" "ez" }}`, wantResult: `false`},
|
||||||
|
"strTrimSpace": {giveTemplate: `{{ strTrimSpace " test " }}`, wantResult: `test`},
|
||||||
|
"strTrimPrefix": {giveTemplate: `{{ strTrimPrefix "test" "te" }}`, wantResult: `st`},
|
||||||
|
"strTrimSuffix": {giveTemplate: `{{ strTrimSuffix "test" "st" }}`, wantResult: `te`},
|
||||||
|
"strReplace": {giveTemplate: `{{ strReplace "test" "t" "z" }}`, wantResult: `zesz`},
|
||||||
|
"strIndex": {giveTemplate: `{{ strIndex "barfoobaz" "foo" }}`, wantResult: `3`},
|
||||||
|
"strFields": {giveTemplate: `{{ strFields "foo bar baz" }}`, wantResult: `[foo bar baz]`},
|
||||||
|
"env (ok)": {giveTemplate: `{{ env "TEST_ENV_VAR" }}`, wantResult: "unit-test"},
|
||||||
|
"env (not found)": {giveTemplate: `{{ env "NOT_FOUND_ENV_VAR" }}`, wantResult: ""},
|
||||||
|
} {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
require.NoError(t, os.Setenv("TEST_ENV_VAR", "unit-test"))
|
||||||
|
|
||||||
|
defer func() { require.NoError(t, os.Unsetenv("TEST_ENV_VAR")) }()
|
||||||
|
|
||||||
|
var result, err = template.Render(tt.giveTemplate, template.Props{})
|
||||||
|
|
||||||
|
if tt.wantErrMsg != "" {
|
||||||
|
assert.ErrorContains(t, err, tt.wantErrMsg)
|
||||||
|
assert.Empty(t, result)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.wantResult, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRender(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for name, tt := range map[string]struct {
|
||||||
|
giveTemplate string
|
||||||
|
giveProps template.Props
|
||||||
|
wantResult string
|
||||||
|
wantErrMsg string
|
||||||
|
}{
|
||||||
|
"common case": {
|
||||||
|
giveTemplate: "{{code}}: {{ message }} {{description}}",
|
||||||
|
giveProps: template.Props{Code: "404", Message: "Not found", Description: "Blah"},
|
||||||
|
wantResult: "404: Not found Blah",
|
||||||
|
},
|
||||||
|
"html markup": {
|
||||||
|
giveTemplate: "<!-- comment --><html><body>{{code}}: {{ message }} {{description}}</body></html>",
|
||||||
|
giveProps: template.Props{Code: "201", Message: "lorem ipsum"},
|
||||||
|
wantResult: "<!-- comment --><html><body>201: lorem ipsum </body></html>",
|
||||||
|
},
|
||||||
|
"with line breakers": {
|
||||||
|
giveTemplate: "\t {{code}}: {{ message }} {{description}}\n",
|
||||||
|
giveProps: template.Props{},
|
||||||
|
wantResult: "\t : \n",
|
||||||
|
},
|
||||||
|
"golang template": {
|
||||||
|
giveTemplate: "\t {{code}} {{ .Code }}{{ if .Message }} Yeah {{end}}",
|
||||||
|
giveProps: template.Props{Code: "201", Message: "lorem ipsum"},
|
||||||
|
wantResult: "\t 201 201 Yeah ",
|
||||||
|
},
|
||||||
|
|
||||||
|
"json common case": {
|
||||||
|
giveTemplate: `{"code": {{code | json}}, "message": {"here":[ {{ message | json }} ]}, "desc": "{{description}}"}`,
|
||||||
|
giveProps: template.Props{Code: `404'"{`, Message: "Not found\t\r\n"},
|
||||||
|
wantResult: `{"code": "404'\"{", "message": {"here":[ "Not found\t\r\n" ]}, "desc": ""}`,
|
||||||
|
},
|
||||||
|
"json golang template": {
|
||||||
|
giveTemplate: `{"code": "{{code}}", "message": {"here":[ "{{ if .Message }} Yeah {{end}}" ]}}`,
|
||||||
|
giveProps: template.Props{Code: "201", Message: "lorem ipsum"},
|
||||||
|
wantResult: `{"code": "201", "message": {"here":[ " Yeah " ]}}`,
|
||||||
|
},
|
||||||
|
|
||||||
|
"fn l10n_enabled": {
|
||||||
|
giveTemplate: "{{ if l10n_enabled }}Y{{ else }}N{{ end }}",
|
||||||
|
giveProps: template.Props{L10nDisabled: true},
|
||||||
|
wantResult: "N",
|
||||||
|
},
|
||||||
|
"fn l10n_disabled": {
|
||||||
|
giveTemplate: "{{ if l10n_disabled }}Y{{ else }}N{{ end }}",
|
||||||
|
giveProps: template.Props{L10nDisabled: true},
|
||||||
|
wantResult: "Y",
|
||||||
|
},
|
||||||
|
|
||||||
|
"complete example with every property and function": {
|
||||||
|
giveProps: template.Props{
|
||||||
|
Code: "404",
|
||||||
|
Message: "Not found",
|
||||||
|
Description: "Blah",
|
||||||
|
OriginalURI: "/test",
|
||||||
|
Namespace: "default",
|
||||||
|
IngressName: "test-ingress",
|
||||||
|
ServiceName: "test-service",
|
||||||
|
ServicePort: "80",
|
||||||
|
RequestID: "123456",
|
||||||
|
ForwardedFor: "123.123.123.123:321",
|
||||||
|
Host: "test-host",
|
||||||
|
ShowRequestDetails: true,
|
||||||
|
L10nDisabled: false,
|
||||||
|
},
|
||||||
|
giveTemplate: `
|
||||||
|
== Props as functions ==
|
||||||
|
code: {{code}}
|
||||||
|
message: {{message}}
|
||||||
|
description: {{description}}
|
||||||
|
original_uri: {{original_uri}}
|
||||||
|
namespace: {{namespace}}
|
||||||
|
ingress_name: {{ingress_name}}
|
||||||
|
service_name: {{service_name}}
|
||||||
|
service_port: {{service_port}}
|
||||||
|
request_id: {{request_id}}
|
||||||
|
forwarded_for: {{forwarded_for}}
|
||||||
|
host: {{host}}
|
||||||
|
show_details: {{show_details}}
|
||||||
|
l10n_disabled: {{l10n_disabled}}
|
||||||
|
|
||||||
|
== Props as properties ==
|
||||||
|
.Code: {{ .Code }}
|
||||||
|
.Message: {{ .Message }}
|
||||||
|
.Description: {{ .Description }}
|
||||||
|
.OriginalURI: {{ .OriginalURI }}
|
||||||
|
.Namespace: {{ .Namespace }}
|
||||||
|
.IngressName: {{ .IngressName }}
|
||||||
|
.ServiceName: {{ .ServiceName }}
|
||||||
|
.ServicePort: {{ .ServicePort }}
|
||||||
|
.RequestID: {{ .RequestID }}
|
||||||
|
.ForwardedFor: {{ .ForwardedFor }}
|
||||||
|
.Host: {{ .Host }}
|
||||||
|
.ShowRequestDetails: {{ .ShowRequestDetails }}
|
||||||
|
.L10nDisabled: {{ .L10nDisabled }}
|
||||||
|
|
||||||
|
== Custom functions ==
|
||||||
|
hide_details: {{ hide_details }}
|
||||||
|
l10n_enabled: {{ l10n_enabled }}
|
||||||
|
`,
|
||||||
|
wantResult: `
|
||||||
|
== Props as functions ==
|
||||||
|
code: 404
|
||||||
|
message: Not found
|
||||||
|
description: Blah
|
||||||
|
original_uri: /test
|
||||||
|
namespace: default
|
||||||
|
ingress_name: test-ingress
|
||||||
|
service_name: test-service
|
||||||
|
service_port: 80
|
||||||
|
request_id: 123456
|
||||||
|
forwarded_for: 123.123.123.123:321
|
||||||
|
host: test-host
|
||||||
|
show_details: true
|
||||||
|
l10n_disabled: false
|
||||||
|
|
||||||
|
== Props as properties ==
|
||||||
|
.Code: 404
|
||||||
|
.Message: Not found
|
||||||
|
.Description: Blah
|
||||||
|
.OriginalURI: /test
|
||||||
|
.Namespace: default
|
||||||
|
.IngressName: test-ingress
|
||||||
|
.ServiceName: test-service
|
||||||
|
.ServicePort: 80
|
||||||
|
.RequestID: 123456
|
||||||
|
.ForwardedFor: 123.123.123.123:321
|
||||||
|
.Host: test-host
|
||||||
|
.ShowRequestDetails: true
|
||||||
|
.L10nDisabled: false
|
||||||
|
|
||||||
|
== Custom functions ==
|
||||||
|
hide_details: false
|
||||||
|
l10n_enabled: true
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
"wrong template": {giveTemplate: `{{ foo() }}`, wantErrMsg: `function "foo" not defined`},
|
||||||
|
"wrong template #2": {giveTemplate: `{{ fo`, wantErrMsg: "failed to parse template"},
|
||||||
|
} {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
var result, err = template.Render(tt.giveTemplate, tt.giveProps)
|
||||||
|
|
||||||
|
if tt.wantErrMsg != "" {
|
||||||
|
assert.ErrorContains(t, err, tt.wantErrMsg)
|
||||||
|
assert.Empty(t, result)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.wantResult, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user