-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
288 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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...) | ||
} | ||
} |