-
Notifications
You must be signed in to change notification settings - Fork 1
/
dispatcher.go
89 lines (82 loc) · 2.09 KB
/
dispatcher.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
package josh
import (
"bytes"
"context"
"encoding/json"
"io"
"log/slog"
)
type Dispatcher struct {
handlers map[string]func(context.Context, json.RawMessage) Resp
}
func NewDispatcher() Dispatcher {
return Dispatcher{
handlers: make(map[string]func(context.Context, json.RawMessage) Resp),
}
}
func Register[R any](d *Dispatcher, t string, h func(context.Context, R) Resp) {
if d.handlers == nil {
panic("josh.Dispatcher must be constructed using josh.NewDispatcher")
}
_, exists := d.handlers[t]
if exists {
panic("the Dispatcher already contains handler for the given type")
}
d.handlers[t] = func(ctx context.Context, raw json.RawMessage) Resp {
decoder := json.NewDecoder(bytes.NewBuffer(raw))
decoder.DisallowUnknownFields()
var req R
err := decoder.Decode(&req)
if err != nil {
// TODO: better error message
return BadRequest(Error{
Title: "Invalid JSON request",
Detail: err.Error(),
})
}
return h(ctx, req)
}
}
func (d *Dispatcher) Read(ctx context.Context, r io.Reader) Resp {
envelope := struct {
Data *Data[json.RawMessage] `json:"data"`
}{}
decoder := json.NewDecoder(r)
decoder.DisallowUnknownFields()
err := decoder.Decode(&envelope)
if err != nil {
// TODO: better error message
return BadRequest(Error{
Title: "Invalid JSON request",
Detail: err.Error(),
})
}
if envelope.Data == nil {
return BadRequest(Error{
Title: "JSON request misses the data field",
})
}
if envelope.Data.ID != "" {
return BadRequest(Error{
Title: "request cannot contain id",
})
}
h, found := d.handlers[envelope.Data.Type]
if !found {
return BadRequest(Error{
Title: "Unsupported request type",
})
}
ctx = patchLogger(ctx, envelope.Data.Type)
return h(ctx, envelope.Data.Attributes)
}
// If the context has a logger, add the request-type into the log extras.
func patchLogger(ctx context.Context, t string) context.Context {
raw := ctx.Value(ctxKey[*slog.Logger]{})
if raw == nil {
return ctx
}
logger := raw.(*slog.Logger)
logger = logger.With("request-type", t)
return context.WithValue(ctx, ctxKey[*slog.Logger]{}, logger)
}