generated from dogmatiq/template-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
handler.go
198 lines (175 loc) · 5.24 KB
/
handler.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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package configkit
import (
"fmt"
"reflect"
"github.com/dogmatiq/configkit/internal/validation"
"github.com/dogmatiq/dogma"
"github.com/dogmatiq/enginekit/message"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
// Handler is a specialization of the Entity interface for message handlers.
type Handler interface {
Entity
// HandlerType returns the type of handler.
HandlerType() HandlerType
// IsDisabled returns true if the handler is disabled.
IsDisabled() bool
}
// RichHandler is a specialization of the Handler interface that exposes
// information about the Go types used to implement the Dogma application.
type RichHandler interface {
RichEntity
// HandlerType returns the type of handler.
HandlerType() HandlerType
// IsDisabled returns true if the handler is disabled.
IsDisabled() bool
}
// IsHandlerEqual compares two handlers for equality.
//
// It returns true if both handlers:
//
// 1. have the same identity
// 2. produce and consume the same message types
// 3. are implemented using the same Go types
//
// Point 3. refers to the type used to implement the dogma.Aggregate,
// dogma.Process, dogma.Integration or dogma.Projection interface (not the type
// used to implement the configkit.Handler interface).
//
// This definition of equality relies on the fact that no single Go type can
// implement more than one Dogma handler interface because they all contain
// a Configure() method with different signatures.
func IsHandlerEqual(a, b Handler) bool {
return a.Identity() == b.Identity() &&
a.TypeName() == b.TypeName() &&
a.HandlerType() == b.HandlerType() &&
a.IsDisabled() == b.IsDisabled() &&
a.MessageNames().IsEqual(b.MessageNames())
}
func configureRoutes[T dogma.Route](
types *EntityMessages[message.Type],
routes []T,
handlerIdent Identity,
handlerType reflect.Type,
) {
if *types == nil {
*types = EntityMessages[message.Type]{}
}
for _, route := range routes {
switch route := any(route).(type) {
case dogma.HandlesCommandRoute:
configureConsumerRoute(*types, route.Type, "HandlesCommand", handlerIdent, handlerType)
case dogma.RecordsEventRoute:
configureProducerRoute(*types, route.Type, "RecordsEvent", handlerIdent, handlerType)
case dogma.HandlesEventRoute:
configureConsumerRoute(*types, route.Type, "HandlesEvent", handlerIdent, handlerType)
case dogma.ExecutesCommandRoute:
configureProducerRoute(*types, route.Type, "ExecutesCommand", handlerIdent, handlerType)
case dogma.SchedulesTimeoutRoute:
configureConsumerRoute(*types, route.Type, "SchedulesTimeout", handlerIdent, handlerType)
configureProducerRoute(*types, route.Type, "SchedulesTimeout", handlerIdent, handlerType)
default:
panic(fmt.Sprintf("unsupported route type: %T", route))
}
}
}
func configureConsumerRoute(
types EntityMessages[message.Type],
messageType reflect.Type,
routeFunc string,
handlerIdent Identity,
handlerType reflect.Type,
) {
types.Update(
message.TypeFromReflect(messageType),
func(t message.Type, em *EntityMessage) {
if em.IsConsumed {
validation.Panicf(
"%s is configured with multiple %s() routes for %s, should these refer to different message types?",
handlerDisplayName(handlerIdent, handlerType),
routeFunc,
t,
)
}
em.Kind = t.Kind()
em.IsConsumed = true
},
)
}
func configureProducerRoute(
types EntityMessages[message.Type],
messageType reflect.Type,
routeFunc string,
handlerIdent Identity,
handlerType reflect.Type,
) {
types.Update(
message.TypeFromReflect(messageType),
func(t message.Type, em *EntityMessage) {
if em.IsProduced {
validation.Panicf(
"%s is configured with multiple %s() routes for %s, should these refer to different message types?",
handlerDisplayName(handlerIdent, handlerType),
routeFunc,
t,
)
}
em.Kind = t.Kind()
em.IsProduced = true
},
)
}
func handlerDisplayName(
handlerIdent Identity,
handlerType reflect.Type,
) string {
s := handlerType.String()
if !handlerIdent.IsZero() {
s += " (" + handlerIdent.Name + ")"
}
return s
}
// mustHaveConsumerRoute panics if the handler is not configured to handle any
// messages of the given kind.
func mustHaveConsumerRoute(
types *EntityMessages[message.Type],
kind message.Kind,
handlerIdent Identity,
handlerType reflect.Type,
) {
for _, k := range types.Consumed() {
if k == kind {
return
}
}
validation.Panicf(
`%s is not configured to handle any %ss, at least one Handles%s() route must be added within Configure()`,
handlerDisplayName(handlerIdent, handlerType),
kind,
cases.Title(language.English).String(kind.String()),
)
}
// mustHaveProducerRoute panics if the handler is not configured to produce any
// messages of the given kind.
func mustHaveProducerRoute(
types *EntityMessages[message.Type],
kind message.Kind,
handlerIdent Identity,
handlerType reflect.Type,
) {
for _, k := range types.Produced() {
if k == kind {
return
}
}
verb := message.MapByKind(kind, "execute", "record", "schedule")
routeFunc := message.MapByKind(kind, "ExecutesCommand", "RecordsEvent", "SchedulesTimeout")
validation.Panicf(
`%s is not configured to %s any %ss, at least one %s() route must be added within Configure()`,
handlerDisplayName(handlerIdent, handlerType),
verb,
kind,
routeFunc,
)
}