-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathhrt.go
170 lines (139 loc) · 4.67 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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// 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.Handler {
return Handler[RequestT, ResponseT](f)
}
// 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
}
// HandlerIntrospection is a struct that contains information about a handler.
// This is primarily used for documentation.
type HandlerIntrospection struct {
// FuncType is the type of the function.
FuncType reflect.Type
// RequestType is the type of the request parameter.
RequestType reflect.Type
// ResponseType is the type of the response parameter.
ResponseType reflect.Type
}
// TryIntrospectingHandler checks if h is an hrt.Handler and returns its
// introspection if it is, otherwise it returns false.
func TryIntrospectingHandler(h http.Handler) (HandlerIntrospection, bool) {
type introspector interface {
Introspect() HandlerIntrospection
}
var _ introspector = Handler[None, None](nil)
if h, ok := h.(introspector); ok {
return h.Introspect(), true
}
return HandlerIntrospection{}, false
}
// Introspect returns information about the handler.
func (h Handler[RequestT, ResponseT]) Introspect() HandlerIntrospection {
var req RequestT
var resp ResponseT
return HandlerIntrospection{
FuncType: reflect.TypeOf(h),
RequestType: reflect.TypeOf(req),
ResponseType: reflect.TypeOf(resp),
}
}