-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(mvc): treat errors in an idiomatic way
- Loading branch information
1 parent
45a9baf
commit 1723288
Showing
11 changed files
with
169 additions
and
158 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package mvc | ||
|
||
import ( | ||
"context" | ||
) | ||
|
||
// Controller for mvc. | ||
type Controller[Model any] func(ctx context.Context) (View, *Model, error) |
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 |
---|---|---|
@@ -1,131 +1,15 @@ | ||
package mvc | ||
|
||
import ( | ||
"context" | ||
"html/template" | ||
"io/fs" | ||
"net/http" | ||
|
||
"github.com/alexfalkowski/go-service/meta" | ||
nh "github.com/alexfalkowski/go-service/net/http" | ||
hc "github.com/alexfalkowski/go-service/net/http/context" | ||
"github.com/alexfalkowski/go-service/net/http/status" | ||
"github.com/go-sprout/sprout/sprigin" | ||
"go.uber.org/fx" | ||
) | ||
|
||
type ( | ||
// ViewsParams for mvc. | ||
ViewsParams struct { | ||
fx.In | ||
|
||
FS fs.FS `optional:"true"` | ||
Patterns Patterns `optional:"true"` | ||
} | ||
|
||
// Patterns to render views. | ||
Patterns []string | ||
) | ||
|
||
// IsValid verifies the params are present. | ||
func (p ViewsParams) IsValid() bool { | ||
return p.FS != nil && len(p.Patterns) != 0 | ||
} | ||
|
||
// NewView from fs with patterns. | ||
func NewViews(params ViewsParams) *Views { | ||
var tpl *template.Template | ||
|
||
if params.IsValid() { | ||
tpl = template.Must(template.New("").Funcs(sprigin.FuncMap()).ParseFS(params.FS, params.Patterns...)) | ||
} | ||
|
||
return &Views{template: tpl, fs: params.FS} | ||
} | ||
|
||
// View for mvc. | ||
type Views struct { | ||
template *template.Template | ||
fs fs.FS | ||
} | ||
|
||
// IsValid verifies that ut has an fs and template. | ||
func (v *Views) IsValid() bool { | ||
return v.template != nil && v.fs != nil | ||
} | ||
|
||
// NewRouter for mvc. | ||
func NewRouter(mux *http.ServeMux, views *Views) *Router { | ||
return &Router{mux: mux, views: views} | ||
} | ||
|
||
type ( | ||
// Router for mvc. | ||
Router struct { | ||
mux *http.ServeMux | ||
views *Views | ||
} | ||
|
||
// View to render. | ||
View string | ||
|
||
// Model for mvc. | ||
Model any | ||
|
||
// Controller for mvc. | ||
Controller func(ctx context.Context) (View, Model) | ||
var ( | ||
mux *http.ServeMux | ||
views *Views | ||
) | ||
|
||
// Route the path with controller for mvc. | ||
func (r *Router) Route(path string, controller Controller) bool { | ||
if !r.views.IsValid() { | ||
return false | ||
} | ||
|
||
handler := func(res http.ResponseWriter, req *http.Request) { | ||
res.Header().Set("Content-Type", "text/html; charset=utf-8") | ||
|
||
ctx := req.Context() | ||
ctx = hc.WithRequest(ctx, req) | ||
ctx = hc.WithResponse(ctx, res) | ||
|
||
view, model := controller(ctx) | ||
|
||
if err, ok := model.(error); ok { | ||
meta.WithAttribute(ctx, "mvcModelError", meta.Error(err)) | ||
res.WriteHeader(status.Code(err)) | ||
} | ||
|
||
if err := r.views.template.ExecuteTemplate(res, string(view), model); err != nil { | ||
meta.WithAttribute(ctx, "mvcViewError", meta.Error(err)) | ||
res.WriteHeader(status.Code(err)) | ||
} | ||
} | ||
|
||
r.mux.HandleFunc(path, handler) | ||
|
||
return true | ||
} | ||
|
||
// Static file name to be served via path. | ||
func (r *Router) Static(path, name string) bool { | ||
if !r.views.IsValid() { | ||
return false | ||
} | ||
|
||
handler := func(res http.ResponseWriter, req *http.Request) { | ||
ctx := req.Context() | ||
|
||
bytes, err := fs.ReadFile(r.views.fs, name) | ||
if err != nil { | ||
meta.WithAttribute(ctx, "mvcStaticError", meta.Error(err)) | ||
res.WriteHeader(status.Code(err)) | ||
} | ||
|
||
nh.WriteResponse(ctx, res, bytes) | ||
} | ||
|
||
r.mux.HandleFunc(path, handler) | ||
|
||
return true | ||
// Register for mvc. | ||
func Register(mu *http.ServeMux, vi *Views) { | ||
mux, views = mu, vi | ||
} |
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,63 @@ | ||
package mvc | ||
|
||
import ( | ||
"io/fs" | ||
"net/http" | ||
|
||
"github.com/alexfalkowski/go-service/meta" | ||
nh "github.com/alexfalkowski/go-service/net/http" | ||
hc "github.com/alexfalkowski/go-service/net/http/context" | ||
"github.com/alexfalkowski/go-service/net/http/status" | ||
) | ||
|
||
// Route the path with controller for mvc. | ||
func Route[Model any](path string, controller Controller[Model]) bool { | ||
if !views.IsValid() { | ||
return false | ||
} | ||
|
||
handler := func(res http.ResponseWriter, req *http.Request) { | ||
res.Header().Set("Content-Type", "text/html; charset=utf-8") | ||
|
||
ctx := req.Context() | ||
ctx = hc.WithRequest(ctx, req) | ||
ctx = hc.WithResponse(ctx, res) | ||
|
||
view, model, err := controller(ctx) | ||
if err != nil { | ||
meta.WithAttribute(ctx, "mvcModelError", meta.Error(err)) | ||
res.WriteHeader(status.Code(err)) | ||
|
||
view.Render(ctx, res, err) | ||
} else { | ||
view.Render(ctx, res, model) | ||
} | ||
} | ||
|
||
mux.HandleFunc(path, handler) | ||
|
||
return true | ||
} | ||
|
||
// Static file name to be served via path. | ||
func Static(path, name string) bool { | ||
if !views.IsValid() { | ||
return false | ||
} | ||
|
||
handler := func(res http.ResponseWriter, req *http.Request) { | ||
ctx := req.Context() | ||
|
||
bytes, err := fs.ReadFile(views.fs, name) | ||
if err != nil { | ||
meta.WithAttribute(ctx, "mvcStaticError", meta.Error(err)) | ||
res.WriteHeader(status.Code(err)) | ||
} | ||
|
||
nh.WriteResponse(ctx, res, bytes) | ||
} | ||
|
||
mux.HandleFunc(path, handler) | ||
|
||
return true | ||
} |
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,64 @@ | ||
package mvc | ||
|
||
import ( | ||
"context" | ||
"html/template" | ||
"io/fs" | ||
"net/http" | ||
|
||
"github.com/alexfalkowski/go-service/meta" | ||
"github.com/alexfalkowski/go-service/net/http/status" | ||
"github.com/go-sprout/sprout/sprigin" | ||
"go.uber.org/fx" | ||
) | ||
|
||
type ( | ||
// ViewsParams for mvc. | ||
ViewsParams struct { | ||
fx.In | ||
|
||
FS fs.FS `optional:"true"` | ||
Patterns Patterns `optional:"true"` | ||
} | ||
|
||
// Patterns to render views. | ||
Patterns []string | ||
) | ||
|
||
// IsValid verifies the params are present. | ||
func (p ViewsParams) IsValid() bool { | ||
return p.FS != nil && len(p.Patterns) != 0 | ||
} | ||
|
||
// NewView from fs with patterns. | ||
func NewViews(params ViewsParams) *Views { | ||
var tpl *template.Template | ||
|
||
if params.IsValid() { | ||
tpl = template.Must(template.New("").Funcs(sprigin.FuncMap()).ParseFS(params.FS, params.Patterns...)) | ||
} | ||
|
||
return &Views{template: tpl, fs: params.FS} | ||
} | ||
|
||
// View for mvc. | ||
type Views struct { | ||
template *template.Template | ||
fs fs.FS | ||
} | ||
|
||
// IsValid verifies that ut has an fs and template. | ||
func (v *Views) IsValid() bool { | ||
return v.template != nil && v.fs != nil | ||
} | ||
|
||
// View to render. | ||
type View string | ||
|
||
// Render the view. | ||
func (v View) Render(ctx context.Context, res http.ResponseWriter, model any) { | ||
if err := views.template.ExecuteTemplate(res, string(v), model); err != nil { | ||
meta.WithAttribute(ctx, "mvcViewError", meta.Error(err)) | ||
res.WriteHeader(status.Code(err)) | ||
} | ||
} |
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
Oops, something went wrong.