Skip to content

Commit

Permalink
add jet template engine
Browse files Browse the repository at this point in the history
  • Loading branch information
svenwltr committed Jul 5, 2024
1 parent 7932278 commit 5aa3ba2
Show file tree
Hide file tree
Showing 8 changed files with 288 additions and 0 deletions.
2 changes: 2 additions & 0 deletions examples/full/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ require (
dario.cat/mergo v1.0.0 // indirect
github.com/AlekSi/pointer v1.2.0 // indirect
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect
github.com/CloudyKit/jet/v6 v6.2.0 // indirect
github.com/DataDog/appsec-internal-go v1.6.0 // indirect
github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 // indirect
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 // indirect
Expand Down
4 changes: 4 additions & 0 deletions examples/full/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME=
github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4=
github.com/DataDog/appsec-internal-go v1.6.0 h1:QHvPOv/O0s2fSI/BraZJNpRDAtdlrRm5APJFZNBxjAw=
github.com/DataDog/appsec-internal-go v1.6.0/go.mod h1:pEp8gjfNLtEOmz+iZqC8bXhu0h4k7NUsW/qiQb34k1U=
github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 h1:bUMSNsw1iofWiju9yc1f+kBd33E3hMJtq9GuU602Iy8=
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/rebuy-de/rebuy-go-sdk/v8
go 1.22.0

require (
github.com/CloudyKit/jet/v6 v6.2.0
github.com/afiskon/promtail-client v0.0.0-20190305142237-506f3f921e9c
github.com/alicebob/miniredis/v2 v2.31.1
github.com/aws/aws-sdk-go-v2 v1.25.0
Expand Down Expand Up @@ -48,6 +49,7 @@ require (
dario.cat/mergo v1.0.0 // indirect
github.com/AlekSi/pointer v1.2.0 // indirect
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect
github.com/DataDog/appsec-internal-go v1.6.0 // indirect
github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 // indirect
github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME=
github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4=
github.com/DataDog/appsec-internal-go v1.6.0 h1:QHvPOv/O0s2fSI/BraZJNpRDAtdlrRm5APJFZNBxjAw=
github.com/DataDog/appsec-internal-go v1.6.0/go.mod h1:pEp8gjfNLtEOmz+iZqC8bXhu0h4k7NUsW/qiQb34k1U=
github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 h1:bUMSNsw1iofWiju9yc1f+kBd33E3hMJtq9GuU602Iy8=
Expand Down
61 changes: 61 additions & 0 deletions migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,64 @@ strongly recommended to align projects using this SDK.

* Use the pattern `return fmt.Errorf("something happenend with %#v: %w", someID, err)`
* The stack trace feature gets lost. Therefore it is suggested to properly add error messages each time handling errors.


## 2024-07-05 Switch to Jet Templates

### Reasoning

The builtin template engine has some shortcommings like being able to pass multiple and optional parameters to a template fragment. For example this block definition is not possible to builtin template engine:

```
{{ block entityLink(label, id, tab, extraClasses) }}
{{ e := findEntity(.) }}
<a class="ry-nowrap ry-underline {{ extraClasses }}" href="/{{.}}{{if id}}/{{id}}{{end}}{{ if tab }}/{{tab}}{{end}}">
<span class="icon">
<i class="fa-solid {{ e.IconClasses }}"></i>
</span>
{{ if label }}
<span>{{ label }}</span>
{{ else if id }}
<span>{{ id }}</span>
{{ else }}
<span>{{ e.Plural }}</span>
{{ end }}
</a>
{{ end }}
```

### Hints

* The handler function signatures changed from `func(*webutil.View, *http.Request) webutil.Response` to `func(*http.Request) webutil.Response`.
* Wrap function changes from `webutil.ViewHandler.Wrap` to `webutil.Wrap`.
* Since the `View` interface is not part of the function signature, it needs to be added to the struct where the handler function is attached to.
* Non-HTML responses are now created with functions directly from the `webutil` package. For example `return webutilext.ViewError(http.StatusInternalServerError, err)` instead of `return v.Error(http.StatusBadRequest, fmt.Errorf(`unknown value for "until"`))`. where `v` was passed to the handler.

### Examples

Jet Set for Development:

```go
jet.NewSet(
jet.NewOSFileSystemLoader("./pkg/app/web/templates"),
jet.InDevelopmentMode(),
)
```

Jet Set for Production:

```go
jet.NewSet(
webutil.JetFSLoader{FS: templateFS},
)
```

Jet Viewer based on Jet Set:

```go
webutilext.NewJetViewer(
js,
webutil.JetVarOption("clusterName", cn),
webutil.JetVarOption("assetPath", "/assets/"+prefix),
)
```
130 changes: 130 additions & 0 deletions pkg/webutil/jetview.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package webutil

import (
"fmt"
"io"
"io/fs"
"net/http"
"reflect"
"strings"

"github.com/CloudyKit/jet/v6"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
)

type JetViewer struct {
views *jet.Set
}

type JetOption func(*jet.Set)

func JetFunctionOption(name string, fn any) JetOption {
return func(set *jet.Set) {
set.AddGlobal(name, fn)
}
}

func JetFunctionMapOption(funcs map[string]any) JetOption {
return func(set *jet.Set) {
for name, fn := range funcs {
set.AddGlobal(name, fn)
}
}
}

func JetVarOption(key string, value any) JetOption {
return func(set *jet.Set) {
set.AddGlobal(key, value)
}
}

func NewJetViewer(js *jet.Set, options ...JetOption) *JetViewer {
jv := &JetViewer{
views: js,
}

jv.views.AddGlobal("contains", strings.Contains)

jv.views.AddGlobalFunc("deref", func(a jet.Arguments) reflect.Value {
a.RequireNumOfArguments("pointer", 1, 1)
v := a.Get(0)
if v.Kind() == reflect.Ptr {
return v.Elem()
}

return v
})

jv.apply(options...)

return jv
}

func (j *JetViewer) apply(options ...JetOption) {
for _, option := range options {
option(j.views)
}
}

type JetViewerHTMLOption func(*jet.VarMap)

func WithVar(name string, value any) JetViewerHTMLOption {
return func(vars *jet.VarMap) {
vars.Set(name, value)
}
}

func WithVarf(name string, s string, a ...any) JetViewerHTMLOption {
return WithVar(name, fmt.Sprintf(s, a...))
}

func (j *JetViewer) HTML(status int, filename string, data any, opts ...JetViewerHTMLOption) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
span, ctx := tracer.StartSpanFromContext(
r.Context(), "render",
tracer.Tag(ext.ResourceName, filename),
tracer.Tag(ext.SpanKind, ext.SpanKindInternal),
)
r = r.WithContext(ctx)
defer span.Finish()

w.Header().Set("Content-Type", "text/html; charset=utf-8")
view, err := j.views.GetTemplate(filename)
if err != nil {
ViewError(http.StatusInternalServerError, err)(w, r)
return
}

vars := make(jet.VarMap)
vars.Set("currentURLPath", r.URL.Path)

for _, o := range opts {
o(&vars)
}

err = view.Execute(w, vars, data)
if err != nil {
ViewError(http.StatusInternalServerError, err)(w, r)
return
}
}
}

type JetFSLoader struct {
fs.FS
}

func (l JetFSLoader) Exists(path string) bool {
f, err := l.Open(path)
if err != nil {
return false
}
f.Close()
return true
}

func (l JetFSLoader) Open(path string) (io.ReadCloser, error) {
path = strings.TrimLeft(path, "/")
return l.FS.Open(path)
}
5 changes: 5 additions & 0 deletions pkg/webutil/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import (
"github.com/sirupsen/logrus"
)

// Deprecated: Use webutil.JetViewer
type ViewHandler struct {
FS fs.FS
FuncMaps []TemplateFuncMap
}

// Deprecated: Use webutil.JetViewer
func NewViewHandler(fs fs.FS, fms ...TemplateFuncMap) *ViewHandler {
v := &ViewHandler{
FS: fs,
Expand All @@ -26,6 +28,7 @@ func NewViewHandler(fs fs.FS, fms ...TemplateFuncMap) *ViewHandler {
return v
}

// Deprecated: Use webutil.JetViewer
type ResponseHandlerFunc func(*View, *http.Request) Response

func (h *ViewHandler) Wrap(fn ResponseHandlerFunc) http.HandlerFunc {
Expand All @@ -52,6 +55,7 @@ func (h *ViewHandler) Render(filename string, r *http.Request, d interface{}) (*
return buf, errors.Wrap(err, "executing template failed")
}

// Deprecated
type TemplateFuncMap func(*http.Request) template.FuncMap

func SimpleTemplateFuncMap(name string, fn interface{}) TemplateFuncMap {
Expand All @@ -62,6 +66,7 @@ func SimpleTemplateFuncMap(name string, fn interface{}) TemplateFuncMap {
}
}

// Deprecated
func SimpleTemplateFuncMaps(fm template.FuncMap) TemplateFuncMap {
return func(_ *http.Request) template.FuncMap {
return fm
Expand Down
80 changes: 80 additions & 0 deletions pkg/webutil/viewfuncs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package webutil

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"

"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)

func WrapView(fn func(*http.Request) Response) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
fn(r)(w, r)
}
}

func ViewError(status int, err error) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := logrus.
WithField("stacktrace", fmt.Sprintf("%+v", err)).
WithError(errors.WithStack(err))

if errors.Is(err, context.Canceled) {
l.Debugf("request cancelled: %s", err)

// The code is copied from nginx, where it means that the client
// closed the connection. It is necessary to alter the status code,
// because DataDog will report errors, if the code is >=500,
// regardless of the connection state.
status = 499
} else if status >= 500 {
l.Errorf("request failed: %s", err)
} else {
l.Warnf("request failed: %s", err)
}

w.WriteHeader(status)
fmt.Fprint(w, err.Error())
}
}

func ViewErrorf(status int, text string, a ...interface{}) http.HandlerFunc {
return ViewError(status, fmt.Errorf(text, a...))
}

func ViewRedirect(status int, location string, args ...interface{}) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
url := fmt.Sprintf(location, args...)
http.Redirect(w, r, url, status)
}
}

func ViewJSON(status int, data any) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
buf := new(bytes.Buffer)
enc := json.NewEncoder(buf)
enc.SetIndent("", " ")

err := enc.Encode(data)
if err != nil {
ViewError(http.StatusInternalServerError, err)(w, r)
return
}

w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
buf.WriteTo(w)
}
}

func ViewInlineHTML(status int, data string, a ...any) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(status)
fmt.Fprintf(w, data, a...)
}
}

0 comments on commit 5aa3ba2

Please sign in to comment.