-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcodec.go
More file actions
226 lines (199 loc) · 7.44 KB
/
Copy pathcodec.go
File metadata and controls
226 lines (199 loc) · 7.44 KB
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
package yin
import (
"encoding/json"
"errors"
"fmt"
"reflect"
)
// DeltaCodec serializes and deserializes concrete Delta values.
//
// Codecs live at the non-generic Delta seam: EncodeDelta accepts any Delta and
// may reject deltas it does not support, while DecodeDelta returns a concrete
// Delta that can be applied to a compatible Document.
type DeltaCodec interface {
EncodeDelta(Delta) ([]byte, error)
DecodeDelta([]byte) (Delta, error)
}
var (
// ErrUnsupportedDelta reports that a codec cannot encode the supplied Delta.
ErrUnsupportedDelta = errors.New("yin: unsupported delta")
// ErrMalformedDelta reports that encoded delta bytes are not valid for a codec.
ErrMalformedDelta = errors.New("yin: malformed delta")
)
const lwwRegisterDeltaJSONFormatVersion = 1
// JSONLWWRegisterDeltaCodec is a JSON codec for LWWRegister[T] deltas.
type JSONLWWRegisterDeltaCodec[T any] struct{}
var _ DeltaCodec = JSONLWWRegisterDeltaCodec[struct{}]{}
// NewJSONLWWRegisterDeltaCodec constructs a JSON codec for LWWRegister[T]
// deltas.
func NewJSONLWWRegisterDeltaCodec[T any]() JSONLWWRegisterDeltaCodec[T] {
return JSONLWWRegisterDeltaCodec[T]{}
}
// EncodeDelta encodes an lwwRegisterDelta[T] as JSON.
func (JSONLWWRegisterDeltaCodec[T]) EncodeDelta(d Delta) ([]byte, error) {
if deltaIsNil(d) {
return nil, fmt.Errorf("%w: nil delta", ErrUnsupportedDelta)
}
var delta lwwRegisterDelta[T]
switch typed := d.(type) {
case lwwRegisterDelta[T]:
delta = typed
case *lwwRegisterDelta[T]:
delta = *typed
default:
kind := d.Kind()
if kind != lwwRegisterDeltaKind {
return nil, fmt.Errorf("%w: delta kind %q", ErrUnsupportedDelta, kind)
}
return nil, fmt.Errorf("%w: delta type %T", ErrUnsupportedDelta, d)
}
counter := delta.timestamp.Counter()
writer := delta.timestamp.ReplicaID()
if counter == 0 {
return nil, fmt.Errorf("%w: lww register delta has zero counter", ErrUnsupportedDelta)
}
if writer == "" {
return nil, fmt.Errorf("%w: lww register delta has empty writer", ErrUnsupportedDelta)
}
wire := lwwRegisterDeltaJSON[T]{
FormatVersion: lwwRegisterDeltaJSONFormatVersion,
Kind: lwwRegisterDeltaKind,
Version: encodeVersionVector(delta.Version()),
Value: delta.value,
Counter: counter,
Writer: string(writer),
}
encoded, err := json.Marshal(wire)
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrUnsupportedDelta, err)
}
return encoded, nil
}
// DecodeDelta decodes an lwwRegisterDelta[T] from JSON.
func (JSONLWWRegisterDeltaCodec[T]) DecodeDelta(data []byte) (Delta, error) {
var envelope lwwRegisterDeltaJSONEnvelope
if err := json.Unmarshal(data, &envelope); err != nil {
return nil, fmt.Errorf("%w: %v", ErrMalformedDelta, err)
}
if err := validateJSONFormatVersion(envelope.FormatVersion, envelope.FormatVersion != nil, lwwRegisterDeltaJSONFormatVersion); err != nil {
return nil, fmt.Errorf("%w: LWWRegister delta JSON: %v", ErrMalformedDelta, err)
}
if envelope.Kind == nil {
return nil, fmt.Errorf("%w: missing kind", ErrMalformedDelta)
}
if *envelope.Kind != lwwRegisterDeltaKind {
return nil, fmt.Errorf("%w: delta kind %q", ErrMalformedDelta, *envelope.Kind)
}
if envelope.Version == nil {
return nil, fmt.Errorf("%w: missing version", ErrMalformedDelta)
}
if envelope.Value == nil {
return nil, fmt.Errorf("%w: missing value", ErrMalformedDelta)
}
var value T
if err := json.Unmarshal(envelope.Value, &value); err != nil {
return nil, fmt.Errorf("%w: invalid value: %v", ErrMalformedDelta, err)
}
if envelope.Counter == nil {
return nil, fmt.Errorf("%w: missing counter", ErrMalformedDelta)
}
if *envelope.Counter == 0 {
return nil, fmt.Errorf("%w: zero counter", ErrMalformedDelta)
}
if envelope.Writer == nil {
return nil, fmt.Errorf("%w: missing writer", ErrMalformedDelta)
}
if *envelope.Writer == "" {
return nil, fmt.Errorf("%w: empty writer", ErrMalformedDelta)
}
if len(*envelope.Version) != 1 || (*envelope.Version)[*envelope.Writer] != *envelope.Counter {
return nil, fmt.Errorf("%w: version does not match counter and writer", ErrMalformedDelta)
}
return lwwRegisterDelta[T]{
value: value,
timestamp: NewTimestamp(*envelope.Counter, ReplicaID(*envelope.Writer)),
}, nil
}
type lwwRegisterDeltaJSON[T any] struct {
FormatVersion int `json:"formatVersion"`
Kind string `json:"kind"`
Version map[string]uint64 `json:"version"`
Value T `json:"value"`
Counter uint64 `json:"counter"`
Writer string `json:"writer"`
}
// lwwRegisterDeltaJSONEnvelope mirrors lwwRegisterDeltaJSON with pointer
// fields so decoding can distinguish missing fields from zero values. Value
// stays raw so an explicit JSON null still decodes into T.
type lwwRegisterDeltaJSONEnvelope struct {
FormatVersion json.RawMessage `json:"formatVersion"`
Kind *string `json:"kind"`
Version *map[string]uint64 `json:"version"`
Value json.RawMessage `json:"value"`
Counter *uint64 `json:"counter"`
Writer *string `json:"writer"`
}
func encodeVersionVector(version VersionVector) map[string]uint64 {
counters := version.Counters()
encoded := make(map[string]uint64, len(counters))
for replica, counter := range counters {
encoded[string(replica)] = counter
}
return encoded
}
// JSONLWWMapDeltaCodec is a JSON codec for LWWMap[V] deltas.
type JSONLWWMapDeltaCodec[V any] struct{}
var _ DeltaCodec = JSONLWWMapDeltaCodec[struct{}]{}
// NewJSONLWWMapDeltaCodec constructs a JSON codec for LWWMap[V] deltas.
func NewJSONLWWMapDeltaCodec[V any]() JSONLWWMapDeltaCodec[V] {
return JSONLWWMapDeltaCodec[V]{}
}
// EncodeDelta encodes a supported LWWMap[V] delta as JSON.
func (JSONLWWMapDeltaCodec[V]) EncodeDelta(d Delta) ([]byte, error) {
if deltaIsNil(d) {
return nil, fmt.Errorf("%w: nil delta", ErrUnsupportedDelta)
}
var concrete lwwMapDelta[V]
switch delta := d.(type) {
case lwwMapDelta[V]:
concrete = delta
case *lwwMapDelta[V]:
concrete = *delta
default:
kind := d.Kind()
if kind != lwwMapDeltaKind {
return nil, fmt.Errorf("%w: unsupported delta kind %q for JSON encoding", ErrUnsupportedDelta, kind)
}
return nil, fmt.Errorf("%w: delta type %T is incompatible with LWWMap JSON codec", ErrUnsupportedDelta, d)
}
encoded, err := marshalLWWMapJSON(lwwMapDeltaJSONFormatVersion, lwwMapDeltaKind, concrete.records, concrete.version, "LWWMap delta JSON")
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrUnsupportedDelta, err)
}
return encoded, nil
}
// DecodeDelta decodes an LWWMap[V] delta from JSON.
//
// The value type V must match the map value type that will receive the decoded
// delta via ApplyDelta. Unsupported kinds, structurally malformed payloads, and
// value payloads that cannot decode into V are wrapped with ErrMalformedDelta
// instead of being converted into no-op core deltas.
func (JSONLWWMapDeltaCodec[V]) DecodeDelta(data []byte) (Delta, error) {
records, version, err := decodeLWWMapJSON[V](data, lwwMapDeltaJSONFormatVersion, lwwMapDeltaKind, "LWWMap delta JSON")
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrMalformedDelta, err)
}
return lwwMapDelta[V]{records: records, version: version}, nil
}
func deltaIsNil(delta Delta) bool {
if delta == nil {
return true
}
value := reflect.ValueOf(delta)
switch value.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
return value.IsNil()
default:
return false
}
}