-
Notifications
You must be signed in to change notification settings - Fork 7
/
normify.go
312 lines (270 loc) · 8.17 KB
/
normify.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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC
// SPDX-License-Identifier: Apache-2.0
package wrp
import (
"errors"
"strconv"
"time"
"github.com/google/uuid"
)
var (
ErrInvalidMessageType = errors.New("invalid message type")
ErrInvalidPartnerID = errors.New("invalid partner ID")
ErrInvalidSource = errors.New("invalid source locator")
ErrInvalidDest = errors.New("invalid destination locator")
ErrInvalidString = errors.New("invalid UTF-8 string")
)
// Normifier applies a series of normalizing options to a WRP message.
type Normifier struct {
opts []NormifierOption
}
// NormifierOption is a functional option for normalizing a WRP message.
type NormifierOption interface {
// normify applies the option to the given message.
normify(*Message) error
}
// optionFunc is an adapter to allow the use of ordinary functions as
// normalizing options.
type optionFunc func(*Message) error
var _ NormifierOption = optionFunc(nil)
func (f optionFunc) normify(m *Message) error {
return f(m)
}
// New creates a new Correctifier with the given options.
func NewNormifier(opts ...NormifierOption) *Normifier {
return &Normifier{
opts: opts,
}
}
// Normify applies the normalizing and validating options to the message. It
// returns an error if any of the options fail.
func (n *Normifier) Normify(m *Message) error {
for _, opt := range n.opts {
if opt != nil {
if err := opt.normify(m); err != nil {
return err
}
}
}
return nil
}
// errorOption returns an option that always returns the given error.
func errorOption(err error) NormifierOption {
return optionFunc(func(*Message) error {
return err
})
}
// options returns a new option that applies all of the given options in order.
func options(opts ...NormifierOption) NormifierOption {
return optionFunc(func(m *Message) error {
for _, opt := range opts {
if opt != nil {
if err := opt.normify(m); err != nil {
return err
}
}
}
return nil
})
}
// -- Normalizers --------------------------------------------------------------
// ReplaceAnySelfLocator replaces any `self:` based locator with the scheme and
// authority of the given locator. If the given locator is not valid, the
// option returns an error.
func ReplaceAnySelfLocator(me string) NormifierOption {
return options(
ReplaceSourceSelfLocator(me),
ReplaceDestinationSelfLocator(me),
)
}
// ReplaceSourceSelfLocator replaces a `self:` based source locator with the
// scheme and authority of the given locator. If the given locator is not valid,
// the option returns an error.
func ReplaceSourceSelfLocator(me string) NormifierOption {
full, err := ParseLocator(me)
if err != nil {
return errorOption(err)
}
return optionFunc(func(m *Message) error {
src, err := ParseLocator(m.Source)
if err != nil {
return err
}
if src.Scheme == "self" {
src.Scheme = full.Scheme
src.Authority = full.Authority
m.Source = src.String()
}
return nil
})
}
// ReplaceDestinationSelfLocator replaces the destination of the message with the
// given locator if the destination is a `self:` based locator. If the given
// locator is not valid, the option returns an error.
func ReplaceDestinationSelfLocator(me string) NormifierOption {
full, err := ParseLocator(me)
if err != nil {
return errorOption(err)
}
return optionFunc(func(m *Message) error {
dst, err := ParseLocator(m.Destination)
if err != nil {
return err
}
if dst.Scheme == "self" {
dst.Scheme = full.Scheme
dst.Authority = full.Authority
m.Destination = dst.String()
}
return nil
})
}
// EnsureTransactionUUID ensures that the message has a transaction UUID. If
// the message does not have a transaction UUID, a new one is generated and
// added to the message.
func EnsureTransactionUUID() NormifierOption {
return optionFunc(func(m *Message) error {
if m.TransactionUUID == "" {
id, err := uuid.NewRandom()
if err != nil {
return err
}
m.TransactionUUID = id.String()
}
return nil
})
}
// EnsurePartnerID ensures that the message includes the given partner ID in
// the list. If not present, the partner ID is added to the list.
func EnsurePartnerID(partnerID string) NormifierOption {
return optionFunc(func(m *Message) error {
if m.PartnerIDs == nil {
m.PartnerIDs = make([]string, 0, 1)
}
for _, id := range m.PartnerIDs {
if id == partnerID {
return nil
}
}
m.PartnerIDs = append(m.PartnerIDs, partnerID)
return nil
})
}
// SetPartnerID ensures that the message has only the given partner ID. This
// will always set the partner ID, replacing any existing partner IDs.
func SetPartnerID(partnerID string) NormifierOption {
return optionFunc(func(m *Message) error {
m.PartnerIDs = []string{partnerID}
return nil
})
}
// SetSessionID ensures that the message has the given session ID. This will
// always set the session ID, replacing any existing session ID
func SetSessionID(sessionID string) NormifierOption {
return optionFunc(func(m *Message) error {
m.SessionID = sessionID
return nil
})
}
// ClampQualityOfService clamps a wrp message's qos value between 0 and 99.
func ClampQualityOfService() NormifierOption {
return optionFunc(func(m *Message) error {
if m.QualityOfService < 0 {
m.QualityOfService = 0
} else if m.QualityOfService > 99 {
m.QualityOfService = 99
}
return nil
})
}
// EnsureMetadataString ensures that the message has the given string metadata.
// This will always set the metadata.
func EnsureMetadataString(key, value string) NormifierOption {
return optionFunc(func(m *Message) error {
if m.Metadata == nil {
m.Metadata = make(map[string]string)
}
m.Metadata[key] = value
return nil
})
}
// EnsureMetadataTime ensures that the message has the given time metadata.
// This will always set the metadata. The time is formatted using RFC3339.
func EnsureMetadataTime(key string, t time.Time) NormifierOption {
return EnsureMetadataString(key, t.Format(time.RFC3339))
}
// EnsureMetadataInt64 ensures that the message has the given integer metadata.
// This will always set the metadata. The integer is converted to a string
// using base 10.
func EnsureMetadataInt64(key string, i int64) NormifierOption {
return EnsureMetadataString(key, strconv.FormatInt(i, 10))
}
// -- Validators ---------------------------------------------------------------
// ValidateSource ensures that the source locator is valid.
func ValidateSource() NormifierOption {
return optionFunc(func(m *Message) error {
if _, err := ParseLocator(m.Source); err != nil {
return errors.Join(err, ErrInvalidSource)
}
return nil
})
}
// ValidateDestination ensures that the destination locator is valid.
func ValidateDestination() NormifierOption {
return optionFunc(func(m *Message) error {
if _, err := ParseLocator(m.Destination); err != nil {
return errors.Join(err, ErrInvalidDest)
}
return nil
})
}
// ValidateMessageType ensures that the message type is valid.
func ValidateMessageType() NormifierOption {
return optionFunc(func(m *Message) error {
if m.Type <= Invalid1MessageType || m.Type >= LastMessageType {
return ErrInvalidMessageType
}
return nil
})
}
// ValidateOnlyUTF8Strings ensures that all string fields in the message are
// valid UTF-8.
func ValidateOnlyUTF8Strings() NormifierOption {
return optionFunc(func(m *Message) error {
if err := UTF8(m); err != nil {
return errors.Join(err, ErrInvalidString)
}
return nil
})
}
// ValidateIsPartner ensures that the message has the given partner ID.
func ValidateIsPartner(partner string) NormifierOption {
return optionFunc(func(m *Message) error {
list := m.TrimmedPartnerIDs()
if len(list) != 1 || list[0] != partner {
return ErrInvalidPartnerID
}
return nil
})
}
// ValidateHasPartner ensures that the message has one of the given partner
// IDs.
func ValidateHasPartner(partners ...string) NormifierOption {
trimmed := make([]string, 0, len(partners))
for _, p := range partners {
if p != "" {
trimmed = append(trimmed, p)
}
}
return optionFunc(func(m *Message) error {
list := m.TrimmedPartnerIDs()
for _, p := range trimmed {
for _, id := range list {
if id == p {
return nil
}
}
}
return ErrInvalidPartnerID
})
}