Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
sjohnsonaz committed Jan 23, 2025
0 parents commit 77b0cbc
Show file tree
Hide file tree
Showing 5 changed files with 282 additions and 0 deletions.
62 changes: 62 additions & 0 deletions controller.go
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)
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/cardboardrobots/sender

go 1.23.1
84 changes: 84 additions & 0 deletions handler.go
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)
})
}
85 changes: 85 additions & 0 deletions sender.go
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(),
})
}
48 changes: 48 additions & 0 deletions template.go
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...))
}

0 comments on commit 77b0cbc

Please sign in to comment.