-
Notifications
You must be signed in to change notification settings - Fork 1
/
hrt.go
133 lines (107 loc) · 3.58 KB
/
hrt.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// Package hrt implements a type-safe HTTP router. It aids in creating a uniform
// API interface while making it easier to create API handlers.
package hrt
import (
"context"
"net/http"
"reflect"
)
type ctxKey uint8
const (
routerOptsCtxKey ctxKey = iota
requestCtxKey
)
// RequestFromContext returns the request from the Handler's context.
func RequestFromContext(ctx context.Context) *http.Request {
return ctx.Value(requestCtxKey).(*http.Request)
}
// Opts contains options for the router.
type Opts struct {
Encoder Encoder
ErrorWriter ErrorWriter
}
// DefaultOpts is the default options for the router.
var DefaultOpts = Opts{
Encoder: DefaultEncoder,
ErrorWriter: JSONErrorWriter("error"),
}
// OptsFromContext returns the options from the Handler's context. DefaultOpts
// is returned if no options are found.
func OptsFromContext(ctx context.Context) Opts {
opts, ok := ctx.Value(routerOptsCtxKey).(Opts)
if ok {
return opts
}
return DefaultOpts
}
// WithOpts returns a new context with the given options.
func WithOpts(ctx context.Context, opts Opts) context.Context {
return context.WithValue(ctx, routerOptsCtxKey, opts)
}
// Use creates a middleware that injects itself into each request's context.
func Use(opts Opts) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := WithOpts(r.Context(), opts)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
// None indicates that the request has no body or the request does not return
// anything.
type None struct{}
// Empty is a value of None.
var Empty = None{}
// Handler describes a generic handler that takes in a type and returns a
// response.
type Handler[RequestT, ResponseT any] func(ctx context.Context, req RequestT) (ResponseT, error)
// Wrap wraps a handler into a http.Handler. It exists because Go's type
// inference doesn't work well with the Handler type.
func Wrap[RequestT, ResponseT any](f func(ctx context.Context, req RequestT) (ResponseT, error)) http.HandlerFunc {
return Handler[RequestT, ResponseT](f).ServeHTTP
}
// ServeHTTP implements the http.Handler interface.
func (h Handler[RequestT, ResponseT]) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var req RequestT
// Context cycle! Let's go!!
ctx := context.WithValue(r.Context(), requestCtxKey, r)
opts := OptsFromContext(ctx)
req, err := decodeRequest[RequestT](r, opts)
if err != nil {
opts.ErrorWriter.WriteError(w, WrapHTTPError(http.StatusBadRequest, err))
return
}
resp, err := h(ctx, req)
if err != nil {
opts.ErrorWriter.WriteError(w, err)
return
}
if _, ok := any(resp).(None); !ok {
if err := opts.Encoder.Encode(w, resp); err != nil {
opts.ErrorWriter.WriteError(w, WrapHTTPError(http.StatusInternalServerError, err))
return
}
}
}
func decodeRequest[RequestT any](r *http.Request, opts Opts) (RequestT, error) {
var req RequestT
if _, ok := any(req).(None); ok {
return req, nil
}
if reflect.TypeFor[RequestT]().Kind() == reflect.Ptr {
// RequestT is a pointer type, so we need to allocate a new instance.
v := reflect.New(reflect.TypeFor[RequestT]().Elem()).Interface()
if err := opts.Encoder.Decode(r, v); err != nil {
return req, err
}
// Return the value as-is, since it's already a pointer.
return v.(RequestT), nil
}
// RequestT is a value type, so we need to allocate a new pointer instance
// and dereference it afterwards.
v := reflect.New(reflect.TypeFor[RequestT]()).Interface()
if err := opts.Encoder.Decode(r, v); err != nil {
return req, err
}
return *v.(*RequestT), nil
}