Skip to content

Commit

Permalink
Merge pull request #212 from rebuy-de/go-view
Browse files Browse the repository at this point in the history
add new interface for HTML viewer
  • Loading branch information
svenwltr committed Aug 29, 2024
2 parents 29d7b6a + c1f7098 commit c5b37bc
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 2 deletions.
21 changes: 19 additions & 2 deletions migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,24 @@
This file contains a list of tasks that are either required or at least
strongly recommended to align projects using this SDK.

## 2024-07-19 Replace cdnmirror with yarn
## M0003 2024-08-16 Change viewer interfaces of webutil

### Reasoning

The previous interface is a bit awkward to use with dependency injection together with splitting the HTTP handlers into
multiple structs. Additionally there were cases where we wanted to use the `webutil.Response` type for convenience, but
did not actually need any HTML rendering.

Therefore the interfaces are changed this way:
* The new `webuitil.WrapView` function replaces the old `webuitl.ViewHandler.Wrap` function and does not require any
template definitions.
* All `webutil.Response` functions, that do not need templates, are pure functions now (ie not attached to a type).
* The handler interface gets reduced to `func(*http.Request) Response`, so it does not contain the view parameter
anymore. When using HTML, it is required to attach the `webutil.GoTemplateViewer` to the struct that implements the
handler.


## M0002 2024-07-19 Replace cdnmirror with yarn

### Reasoning

Expand Down Expand Up @@ -231,7 +248,7 @@ The URL follows this pattern: `https://unpkg.com/{package}@{version}/{import}`,

There is not guarantee that this work, tho.

## 2024-06-14 Remove all uses of `github.com/pkg/errors`
## M0001 2024-06-14 Remove all uses of `github.com/pkg/errors`

### Reasoning

Expand Down
10 changes: 10 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: M0003 Use GoTemplateViewer and View* functions instead.
type ViewHandler struct {
FS fs.FS
FuncMaps []TemplateFuncMap
}

// Deprecated: M0003 Use GoTemplateViewer and View* functions instead.
func NewViewHandler(fs fs.FS, fms ...TemplateFuncMap) *ViewHandler {
v := &ViewHandler{
FS: fs,
Expand All @@ -28,12 +30,14 @@ func NewViewHandler(fs fs.FS, fms ...TemplateFuncMap) *ViewHandler {

type ResponseHandlerFunc func(*View, *http.Request) Response

// Deprecated: M0003 Use GoTemplateViewer and View* functions instead.
func (h *ViewHandler) Wrap(fn ResponseHandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
fn(&View{handler: h}, r)(w, r)
}
}

// Deprecated: M0003 Use GoTemplateViewer and View* functions instead.
func (h *ViewHandler) Render(filename string, r *http.Request, d interface{}) (*bytes.Buffer, error) {
t := template.New(filename)

Expand Down Expand Up @@ -70,10 +74,12 @@ func SimpleTemplateFuncMaps(fm template.FuncMap) TemplateFuncMap {

type Response = http.HandlerFunc

// Deprecated: M0003 Use GoTemplateViewer and View* functions instead.
type View struct {
handler *ViewHandler
}

// Deprecated: M0003 Use GoTemplateViewer and View* functions instead.
func (v *View) Error(status int, err error) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := logrus.
Expand All @@ -91,17 +97,20 @@ func (v *View) Error(status int, err error) http.HandlerFunc {
}
}

// Deprecated: M0003 Use GoTemplateViewer and View* functions instead.
func (v *View) Errorf(status int, text string, a ...interface{}) http.HandlerFunc {
return v.Error(status, fmt.Errorf(text, a...))
}

// Deprecated: M0003 Use GoTemplateViewer and View* functions instead.
func (v *View) Redirect(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)
}
}

// Deprecated: M0003 Use GoTemplateViewer and View* functions instead.
func (v *View) JSON(status int, data any) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
buf := new(bytes.Buffer)
Expand All @@ -120,6 +129,7 @@ func (v *View) JSON(status int, data any) http.HandlerFunc {
}
}

// Deprecated: M0003 Use GoTemplateViewer and View* functions instead.
func (v *View) HTML(status int, filename string, data any) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
buf, err := v.handler.Render(filename, r, data)
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...)
}
}
70 changes: 70 additions & 0 deletions pkg/webutil/viewgo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package webutil

import (
"bytes"
"html/template"
"io/fs"
"net/http"

"github.com/sirupsen/logrus"
)

type GoTemplateViewer struct {
fs fs.FS
funcMaps []TemplateFuncMap
}

func NewGoTemplateViewer(fs fs.FS, fms ...TemplateFuncMap) *GoTemplateViewer {
return &GoTemplateViewer{
fs: fs,
funcMaps: fms,
}
}

func (v *GoTemplateViewer) prepare(filename string, r *http.Request) (*template.Template, error) {
t := template.New(filename)

for _, fm := range v.funcMaps {
t = t.Funcs(fm(r))
}

return t.ParseFS(v.fs, "*")
}

func (v *GoTemplateViewer) HTML(status int, filename string, data any) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")

t, err := v.prepare(filename, r)
if err != nil {
ViewError(http.StatusInternalServerError, err)(w, r)
return
}

w.WriteHeader(status)

err = t.Execute(w, data)
if err != nil {
// It is possible that we already sent the header, but we try again anyways.
w.WriteHeader(http.StatusInternalServerError)

// We do not send the actual error to the client, since we don't know what we already sent.
logrus.Errorf("failed to render: %v", err.Error())
}
}
}

func (v *GoTemplateViewer) Render(filename string, r *http.Request, data any) (*bytes.Buffer, error) {
t, err := v.prepare(filename, r)
if err != nil {
return nil, err
}

buf := new(bytes.Buffer)
err = t.Execute(buf, data)
if err != nil {
return nil, err
}

return buf, nil
}

0 comments on commit c5b37bc

Please sign in to comment.