-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcentra.go
172 lines (144 loc) · 4.64 KB
/
centra.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
171
172
// Copyright 2024 Oscar Pernia
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package centra
import (
"context"
"errors"
"net/http"
"strconv"
"sync"
)
type handlerStruct struct {
err error
handler ErrorHandlerFunc
}
// Multiplexer error handler, multiplexes a call to [Error] to the registered error handler,
// if error is not found, then a call to the registered UnknownHandler is made.
type Mux struct {
handlersStack []handlerStruct
mu sync.RWMutex
}
// Returns a new Mux with UnknownHandler set to DefaultUnknownError.
func NewMux() *Mux {
return &Mux{
handlersStack: []handlerStruct{
{
err: nil,
handler: DefaultUnknownHandler,
},
},
}
}
// Function type to handle errors
type ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
type keyContext struct{}
// Middleware handler, compatible with Chi router, changes the request's context and adds
// the error handlers to it.
func (m *Mux) Handler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r = r.WithContext(context.WithValue(r.Context(), keyContext{}, m))
next.ServeHTTP(w, r)
})
}
// Sets handler to handle err when a call to Error(w, r, errOrWrappedErr) is made in the context
// of a http request.
func (m *Mux) Handle(err error, handler ErrorHandlerFunc) {
if err == nil {
panic("centra: err must not be nil")
}
if handler == nil {
panic("centra: handler must not be nil")
}
m.mu.Lock()
defer m.mu.Unlock()
if len(m.handlersStack) == 0 {
panic("centra: Mux has not been initialized correctly, please call NewMux()")
}
m.handlersStack = append(m.handlersStack, handlerStruct{
err: err,
handler: handler,
})
}
// Sets handler to handle unknown errors when a call to Error(w, r, err) doesn't find a registered
// error handler for err.
func (m *Mux) UnknownHandler(handler ErrorHandlerFunc) {
if handler == nil {
panic("centra: handler must not be nil")
}
m.mu.Lock()
defer m.mu.Unlock()
if len(m.handlersStack) == 0 {
panic("centra: Mux has not been initialized correctly, please call NewMux()")
}
m.handlersStack[0] = handlerStruct{
err: nil,
handler: handler,
}
}
// Returns the registered UnknownHandler, if [Mux.UnknownHandler] has not been called yet,
// by default it is [DefaultUnknownHandler]
func (m *Mux) GetUnknownHandler() ErrorHandlerFunc {
m.mu.RLock()
defer m.mu.RUnlock()
if len(m.handlersStack) == 0 {
panic("centra: Mux has not been initialized correctly, please call NewMux()")
}
return m.handlersStack[0].handler
}
// Error search for registered error handlers to handle err, if no error handler is found, then
// it calls the registered UnknownHandler
func Error(w http.ResponseWriter, r *http.Request, err error) {
mux := getMux(r)
if mux == nil {
// TODO: panic or DefaultUnknownHandler?
//
// For now we are panicking, since this should be a invalid state for the library,
// and calling Default may not be desired behaviour.
panic("centra: Mux has not been initialized, cannot call Error() for this request")
}
mux.mu.RLock()
defer mux.mu.RUnlock()
if len(mux.handlersStack) == 0 {
panic("centra: Mux has not been initialized, cannot call Error() for this request")
}
if err == nil {
// as a special case, if err is nil, call unknown handler
mux.handlersStack[0].handler(w, r, err)
return
}
for i := len(mux.handlersStack) - 1; i >= 1; i-- {
h := mux.handlersStack[i]
if errors.Is(err, h.err) {
h.handler(w, r, err)
return
}
}
// if err is not registered, then call unknown error handler
mux.handlersStack[0].handler(w, r, err)
}
// Default error handler for unknown errors
//
// Writes string "<h1>Internal Server Error</h1>" to w, sets Content-Type to "text/html"
// and writes status code 500
func DefaultUnknownHandler(w http.ResponseWriter, r *http.Request, err error) {
response := "<h1>Internal Server Error</h1>"
w.Header().Set("Content-Type", "text/html")
w.Header().Set("Content-Length", strconv.Itoa(len(response)))
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(response))
}
func getMux(r *http.Request) *Mux {
m, _ := r.Context().Value(keyContext{}).(*Mux)
return m
}