Skip to content

Commit

Permalink
Merge pull request #117 from rebuy-de/replace-mvp
Browse files Browse the repository at this point in the history
replace MVP with ViewWrapper
  • Loading branch information
svenwltr authored Apr 14, 2022
2 parents a7eedd1 + 3982699 commit 2137f6b
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 169 deletions.
41 changes: 30 additions & 11 deletions examples/full/cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"context"
"fmt"
"io/fs"
"net/http"
"time"
Expand All @@ -25,11 +26,6 @@ type Server struct {
}

func (s *Server) Run(ctxRoot context.Context) error {
// Prepare some interfaces to later use.
html := webutil.NewHTMLTemplateView(s.TemplateFS,
webutil.SimpleTemplateFuncMap("prettyTime", PrettyTimeTemplateFunction),
)

// Creating a new context, so we can have two stages for the graceful
// shutdown. First is to make pod unready (within the admin api) and the
// seconds is all the rest.
Expand All @@ -49,20 +45,28 @@ func (s *Server) Run(ctxRoot context.Context) error {
webutil.AdminAPIListenAndServe(ctxRoot, group, cancel)

// Other background processes use the main context.
s.setupHTTPServer(ctx, group, html)
s.setupHTTPServer(ctx, group)

return errors.WithStack(group.Wait())
}

func (s *Server) setupHTTPServer(ctx context.Context, group *errgroup.Group, html *webutil.HTMLTemplateView) {
func (s *Server) setupHTTPServer(ctx context.Context, group *errgroup.Group) {
// It is a good practice to init a new context logger for a new background
// process, so we can see what triggered a specific log message later.
ctx = logutil.Start(ctx, "http-server")

// Prepare some interfaces to later use.
vh := webutil.NewViewHandler(s.TemplateFS,
webutil.SimpleTemplateFuncMap("prettyTime", PrettyTimeTemplateFunction),
)

router := chi.NewRouter()
router.Use(middleware.Logger)

router.Get("/", webutil.Presenter(s.indexModel, html.View("index.html")))
router.Get("/", vh.Wrap(s.handleIndex))
router.Get("/json", vh.Wrap(s.handleJSON))
router.Get("/redirect", vh.Wrap(s.handleRedirect))
router.Get("/error", vh.Wrap(s.handleError))
router.Mount("/assets", http.StripPrefix("/assets", http.FileServer(http.FS(s.AssetFS))))

group.Go(func() error {
Expand All @@ -72,9 +76,24 @@ func (s *Server) setupHTTPServer(ctx context.Context, group *errgroup.Group, htm
})
}

func (s *Server) indexModel(r *http.Request) (interface{}, int, error) {
InstIndexRequest(r.Context(), r)
func (s *Server) timeModel() any {
return map[string]interface{}{
"now": time.Now(),
}, http.StatusOK, nil
}
}

func (s *Server) handleIndex(v *webutil.View, r *http.Request) webutil.Response {
return v.HTML(http.StatusOK, "index.html", s.timeModel())
}

func (s *Server) handleJSON(v *webutil.View, r *http.Request) webutil.Response {
return v.JSON(http.StatusOK, s.timeModel())
}

func (s *Server) handleRedirect(v *webutil.View, r *http.Request) webutil.Response {
return v.Redirect(http.StatusTemporaryRedirect, "/")
}

func (s *Server) handleError(v *webutil.View, r *http.Request) webutil.Response {
return v.Error(http.StatusBadRequest, fmt.Errorf("oh no"))
}
9 changes: 8 additions & 1 deletion examples/full/cmd/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
<script src="/assets/script.js"></script>
</head>
<body>
<p>Hello.</p>
<p>Hello @ {{ prettyTime .now }}</p>

<ul>
<li><a href="/error">/error</a></li>
<li><a href="/json">/json</a></li>
<li><a href="/redirect">/redirect</a></li>
</ul>

</body>
</html>
157 changes: 0 additions & 157 deletions pkg/webutil/mvp.go

This file was deleted.

130 changes: 130 additions & 0 deletions pkg/webutil/view.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package webutil

import (
"bytes"
"encoding/json"
"fmt"
"html/template"
"io/fs"
"net/http"

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

type ViewHandler struct {
FS fs.FS
FuncMaps []TemplateFuncMap
}

func NewViewHandler(fs fs.FS, fms ...TemplateFuncMap) *ViewHandler {
v := &ViewHandler{
FS: fs,
FuncMaps: fms,
}

return v
}

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

func (h *ViewHandler) Wrap(fn ResponseHandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
fn(&View{handler: h}, r)(w, r)
}
}

func (h *ViewHandler) Render(filename string, r *http.Request, d interface{}) (*bytes.Buffer, error) {
t := template.New(filename)

for _, fm := range h.FuncMaps {
t = t.Funcs(fm(r))
}

t, err := t.ParseFS(h.FS, "*")
if err != nil {
return nil, errors.Wrap(err, "parsing template failed")
}

buf := new(bytes.Buffer)
err = t.Execute(buf, d)

return buf, errors.Wrap(err, "executing template failed")
}

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

func SimpleTemplateFuncMap(name string, fn interface{}) TemplateFuncMap {
return func(_ *http.Request) template.FuncMap {
return template.FuncMap{
name: fn,
}
}
}

func SimpleTemplateFuncMaps(fm template.FuncMap) TemplateFuncMap {
return func(_ *http.Request) template.FuncMap {
return fm
}
}

type Response = http.HandlerFunc

type View struct {
handler *ViewHandler
}

func (v *View) Error(status int, err error) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
logrus.
WithField("stacktrace", fmt.Sprintf("%+v", err)).
WithError(errors.WithStack(err)).
Errorf("request failed: %s", err)

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

func (v *View) Errorf(status int, text string, a ...interface{}) http.HandlerFunc {
return v.Error(status, fmt.Errorf(text, a...))
}

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)
}
}

func (v *View) JSON(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 {
v.Error(http.StatusInternalServerError, err)(w, r)
return
}

w.WriteHeader(status)
w.Header().Set("Content-Type", "application/json")
buf.WriteTo(w)
}
}

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)
if err != nil {
v.Error(http.StatusInternalServerError, err)(w, r)
return
}

w.WriteHeader(status)
w.Header().Set("Content-Type", "text/html")
buf.WriteTo(w)
}
}

0 comments on commit 2137f6b

Please sign in to comment.