-
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
0 parents
commit 77b0cbc
Showing
5 changed files
with
282 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package sender | ||
|
||
import ( | ||
"context" | ||
"io" | ||
"net/http" | ||
) | ||
|
||
type ( | ||
Options struct { | ||
ErrorMap func(error) int | ||
} | ||
|
||
JSONOptions struct { | ||
Options | ||
} | ||
|
||
Controller struct { | ||
errorMap func(error) int | ||
handlers map[string]func(w http.ResponseWriter, r *http.Request) | ||
} | ||
) | ||
|
||
func NewController( | ||
errorMap func(error) int, | ||
) *Controller { | ||
return &Controller{ | ||
errorMap: errorMap, | ||
} | ||
} | ||
|
||
func (c *Controller) Init(mux *http.ServeMux) { | ||
for route, handler := range c.handlers { | ||
mux.HandleFunc(route, handler) | ||
} | ||
} | ||
|
||
func (c *Controller) initHandlers() { | ||
if c.handlers == nil { | ||
c.handlers = make(map[string]func(w http.ResponseWriter, r *http.Request)) | ||
} | ||
} | ||
|
||
func (c *Controller) Handler(route string, handler func(context.Context, Sender)) { | ||
c.initHandlers() | ||
c.handlers[route] = Handler(handler) | ||
} | ||
|
||
func (c *Controller) JsonHandler(route string, handler func(context.Context, Sender) (any, error)) { | ||
c.initHandlers() | ||
c.handlers[route] = JsonHandler(c.errorMap, handler) | ||
} | ||
|
||
func (c *Controller) StreamHandler(route string, handler func(context.Context, Sender) (io.Reader, error)) { | ||
c.initHandlers() | ||
c.handlers[route] = StreamHandler(c.errorMap, handler) | ||
} | ||
|
||
func (c *Controller) TemplateHandler(route string, template *Template, handler func(context.Context, Sender) (any, error)) { | ||
c.initHandlers() | ||
c.handlers[route] = TemplateHandler(c.errorMap, template, handler) | ||
} |
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,3 @@ | ||
module github.com/cardboardrobots/sender | ||
|
||
go 1.23.1 |
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,84 @@ | ||
package sender | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"runtime/debug" | ||
) | ||
|
||
func Handler(handler func(context.Context, Sender)) func(w http.ResponseWriter, r *http.Request) { | ||
return func(w http.ResponseWriter, r *http.Request) { | ||
s := NewSender(w, r) | ||
defer func() { | ||
if result := recover(); result != nil { | ||
stack := debug.Stack() | ||
s.SendStatus(http.StatusInternalServerError) | ||
_ = s.SendJson(panicData{ | ||
Panic: fmt.Sprint(result), | ||
Stack: string(stack), | ||
}) | ||
} | ||
}() | ||
handler(r.Context(), s) | ||
} | ||
} | ||
|
||
func BasicHandler(errorMap func(error) int, handler func(context.Context, Sender) error) func(w http.ResponseWriter, r *http.Request) { | ||
return Handler(func(ctx context.Context, s Sender) { | ||
err := handler(ctx, s) | ||
if err != nil { | ||
s.SendError(errorMap(err), err) | ||
} | ||
}) | ||
} | ||
|
||
func JsonHandler[T any](errorMap func(error) int, handler func(context.Context, Sender) (T, error)) func(w http.ResponseWriter, r *http.Request) { | ||
return Handler(func(ctx context.Context, s Sender) { | ||
result, err := handler(ctx, s) | ||
if err == nil { | ||
err1 := s.SendJson(result) | ||
if err1 != nil { | ||
err = errors.Join(err, err1) | ||
} else { | ||
return | ||
} | ||
} | ||
|
||
s.SendError(errorMap(err), err) | ||
}) | ||
} | ||
|
||
func StreamHandler(errorMap func(error) int, handler func(context.Context, Sender) (io.Reader, error)) func(w http.ResponseWriter, r *http.Request) { | ||
return Handler(func(ctx context.Context, s Sender) { | ||
result, err := handler(ctx, s) | ||
if err == nil { | ||
_, err1 := s.SendReader(result) | ||
if err1 != nil { | ||
err = errors.Join(err, err1) | ||
} else { | ||
return | ||
} | ||
} | ||
|
||
s.SendError(errorMap(err), err) | ||
}) | ||
} | ||
|
||
func TemplateHandler[T any](errorMap func(error) int, template *Template, handler func(context.Context, Sender) (T, error)) func(w http.ResponseWriter, r *http.Request) { | ||
return Handler(func(ctx context.Context, s Sender) { | ||
result, err := handler(ctx, s) | ||
if err == nil { | ||
err1 := template.ExecuteTemplate(s.response, result) | ||
if err1 != nil { | ||
err = errors.Join(err, err1) | ||
} else { | ||
return | ||
} | ||
} | ||
|
||
s.SendError(errorMap(err), 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package sender | ||
|
||
import ( | ||
"encoding/json" | ||
"io" | ||
"net/http" | ||
) | ||
|
||
const ( | ||
HeaderContentType = "Content-Type" | ||
|
||
ContentTypeApplicationJSON = "application/json" | ||
ContentTypeTextHTML = "text/html; charset=utf-8" | ||
) | ||
|
||
type ( | ||
Sender struct { | ||
response http.ResponseWriter | ||
request *http.Request | ||
} | ||
|
||
errorData struct { | ||
Error string `json:"error"` | ||
} | ||
|
||
panicData struct { | ||
Panic string `json:"panic"` | ||
Stack string `json:"stack"` | ||
} | ||
) | ||
|
||
func NewSender(w http.ResponseWriter, r *http.Request) Sender { | ||
return Sender{ | ||
response: w, | ||
request: r, | ||
} | ||
} | ||
|
||
func (s Sender) Response() http.ResponseWriter { return s.response } | ||
func (s Sender) Request() *http.Request { return s.request } | ||
|
||
func (s Sender) SendStatus(statusCode int) { | ||
s.response.WriteHeader(statusCode) | ||
} | ||
|
||
func (s Sender) SetContentTypeJSON() { | ||
s.response.Header().Set(HeaderContentType, ContentTypeApplicationJSON) | ||
} | ||
|
||
func (s Sender) SetContentTypeHTML() { | ||
s.response.Header().Set(HeaderContentType, ContentTypeTextHTML) | ||
} | ||
|
||
func (s Sender) SendReader(r io.Reader) (int64, error) { | ||
return io.Copy(s.response, r) | ||
} | ||
|
||
func (s Sender) SendBytes(data []byte) error { | ||
_, err := s.response.Write(data) | ||
if err != nil { | ||
return s.SendError(http.StatusInternalServerError, err) | ||
} | ||
|
||
return err | ||
} | ||
|
||
func (s Sender) SendJson(value any) error { | ||
s.SetContentTypeJSON() | ||
|
||
err := json.NewEncoder(s.response).Encode(value) | ||
if err != nil { | ||
return s.SendError(http.StatusInternalServerError, err) | ||
} | ||
|
||
return err | ||
} | ||
|
||
func (s Sender) SendError(statusCode int, err error) error { | ||
s.SetContentTypeJSON() | ||
|
||
s.SendStatus(statusCode) | ||
return json.NewEncoder(s.response).Encode(errorData{ | ||
Error: err.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 |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package sender | ||
|
||
import ( | ||
"html/template" | ||
"io" | ||
"io/fs" | ||
) | ||
|
||
type Template struct { | ||
debug bool | ||
fs fs.FS | ||
patterns []string | ||
name string | ||
template *template.Template | ||
} | ||
|
||
func NewTemplate(fs fs.FS, patterns ...string) *Template { | ||
return &Template{ | ||
fs: fs, | ||
patterns: patterns, | ||
name: patterns[len(patterns)-1], | ||
template: refreshTemplate(fs, patterns...), | ||
} | ||
} | ||
|
||
func (t *Template) EnableDebug() { | ||
t.debug = true | ||
} | ||
|
||
func (t *Template) DisableDebug() { | ||
t.debug = false | ||
} | ||
|
||
func (t *Template) Template() *template.Template { | ||
if t.debug { | ||
return refreshTemplate(t.fs, t.patterns...) | ||
} | ||
|
||
return t.template | ||
} | ||
|
||
func (t *Template) ExecuteTemplate(wr io.Writer, data any) error { | ||
return t.Template().ExecuteTemplate(wr, t.name, data) | ||
} | ||
|
||
func refreshTemplate(fs fs.FS, patterns ...string) *template.Template { | ||
return template.Must(template.ParseFS(fs, patterns...)) | ||
} |