-
-
Notifications
You must be signed in to change notification settings - Fork 13
/
load_free_entity.go
238 lines (193 loc) · 6.48 KB
/
load_free_entity.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
package core
import (
"errors"
"fmt"
"reflect"
"sync"
)
var (
loadFreeEntityFnRegistry = map[ /*pattern type*/ reflect.Type]LoadSelfManagedEntityFn{}
loadFreeEntityFnRegistryLock sync.Mutex
ErrNonUniqueLoadFreeEntityFnRegistration = errors.New("non unique loading function registration")
ErrNonUniqueGetSymbolicInitialFactoryRegistration = errors.New("non unique symbolic initial value factory registration")
ErrNoLoadFreeEntityFnRegistered = errors.New("no loading function registered for given type")
ErrLoadingRequireTransaction = errors.New("loading a value requires a transaction")
ErrTransactionsNotSupportedYet = errors.New("transactions not supported yet")
ErrFailedToLoadNonExistingValue = errors.New("failed to load non-existing value")
ErrInvalidInitialValue = errors.New("invalid initial value")
)
func init() {
resetLoadFreeEntityFnRegistry()
}
func resetLoadFreeEntityFnRegistry() {
loadFreeEntityFnRegistryLock.Lock()
clear(loadFreeEntityFnRegistry)
loadFreeEntityFnRegistryLock.Unlock()
RegisterLoadFreeEntityFn(OBJECT_PATTERN_TYPE, loadFreeObject)
}
type DataStore interface {
BaseURL() URL
GetSerialized(ctx *Context, key Path) (string, bool)
Has(ctx *Context, key Path) bool
SetSerialized(ctx *Context, key Path, serialized string)
InsertSerialized(ctx *Context, key Path, serialized string)
}
type FreeEntityLoadingParams struct {
Key Path
Storage DataStore
Pattern Pattern
InitialValue Serializable
AllowMissing bool //if true the loading function is allowed to return an empty/default value matching the pattern
Migration *FreeEntityMigrationArgs
}
func (a FreeEntityLoadingParams) IsDeletion(ctx *Context) bool {
if a.Migration == nil {
return false
}
for pathPattern := range a.Migration.MigrationHandlers.Deletions {
if pathPattern.Test(ctx, a.Key) {
return true
}
}
return false
}
type FreeEntityMigrationArgs struct {
NextPattern Pattern //can be nil
MigrationHandlers MigrationOpHandlers
}
// LoadSelfManagedEntityFn should load a self-managed entity and should call the corresponding migration handlers.
// In the case of a deletion (nil, nil) should be returned.
// If the entity changes due to a migration this function should call LoadSelfManagedEntityFn
// with the new value passed in .InitialValue.
type LoadSelfManagedEntityFn func(ctx *Context, args FreeEntityLoadingParams) (UrlHolder, error)
func RegisterLoadFreeEntityFn(patternType reflect.Type, fn LoadSelfManagedEntityFn) {
loadFreeEntityFnRegistryLock.Lock()
defer loadFreeEntityFnRegistryLock.Unlock()
_, ok := loadFreeEntityFnRegistry[patternType]
if ok {
panic(ErrNonUniqueLoadFreeEntityFnRegistration)
}
loadFreeEntityFnRegistry[patternType] = fn
}
func hasTypeLoadingFunction(pattern Pattern) bool {
loadFreeEntityFnRegistryLock.Lock()
defer loadFreeEntityFnRegistryLock.Unlock()
_, ok := loadFreeEntityFnRegistry[reflect.TypeOf(pattern)]
return ok
}
// See documentation of LoadFreeEntityFn.
func LoadFreeEntity(ctx *Context, args FreeEntityLoadingParams) (UrlHolder, error) {
loadFreeEntityFnRegistryLock.Lock()
patternType := reflect.TypeOf(args.Pattern)
fn, ok := loadFreeEntityFnRegistry[patternType]
loadFreeEntityFnRegistryLock.Unlock()
if !ok {
panic(ErrNoLoadFreeEntityFnRegistered)
}
if args.Key[len(args.Key)-1] == '/' {
return nil, errors.New("free entity's key should not end with '/'")
}
return fn(ctx, args)
}
func loadFreeObject(ctx *Context, args FreeEntityLoadingParams) (UrlHolder, error) {
path := args.Key
pattern := args.Pattern
storage := args.Storage
objectPattern := pattern.(*ObjectPattern)
object, ok := args.InitialValue.(*Object)
if !ok && args.InitialValue != nil {
return nil, fmt.Errorf("%w: an object is expected", ErrInvalidInitialValue)
}
if object == nil {
serialized, ok := storage.GetSerialized(ctx, path)
if !ok {
return nil, fmt.Errorf("%w: %s", ErrFailedToLoadNonExistingValue, path)
}
parsed, err := ParseJSONRepresentation(ctx, serialized, objectPattern)
if err != nil {
return nil, err
}
object, ok = parsed.(*Object)
if !ok {
return nil, fmt.Errorf("an object was expected")
}
} else { //initial value
_, hasURL := object.URL()
if hasURL {
return nil, errors.New("initial object should not have a URL")
}
}
_, hasURL := object.URL()
if !hasURL {
object.SetURLOnce(ctx, args.Storage.BaseURL().AppendAbsolutePath(path))
}
if args.InitialValue != nil {
storage.SetSerialized(ctx, path, GetJSONRepresentation(object, ctx, pattern))
if args.Migration != nil {
panic(ErrUnreachable)
}
}
//we perform the migration before adding mutation handlers for obvious reasons
if args.Migration != nil {
next, err := object.Migrate(ctx, args.Key, args.Migration)
if err != nil {
return nil, fmt.Errorf("migration failed: %w", err)
}
if args.IsDeletion(ctx) {
return nil, nil
}
nextObject, ok := next.(*Object)
if !ok || object != nextObject {
return LoadFreeEntity(ctx, FreeEntityLoadingParams{
Key: args.Key,
Storage: args.Storage,
Pattern: args.Migration.NextPattern,
InitialValue: next.(Serializable),
AllowMissing: false,
Migration: nil,
})
}
if args.Migration.NextPattern == nil {
return nil, fmt.Errorf("missing next pattern for %s", path)
}
pattern = args.Migration.NextPattern
updatedRepr := GetJSONRepresentation(object, ctx, pattern)
storage.SetSerialized(ctx, path, updatedRepr)
}
object.Share(ctx.GetClosestState())
//add mutation handlers
object.OnMutation(ctx, func(ctx *Context, mutation Mutation) (registerAgain bool) {
registerAgain = true
updatedRepr := GetJSONRepresentation(object, ctx, pattern)
storage.SetSerialized(ctx, path, updatedRepr)
return
}, MutationWatchingConfiguration{
Depth: DeepWatching,
})
return object, nil
}
type TestValueStorage struct {
BaseURL_ URL
Data map[Path]string
}
func (s *TestValueStorage) BaseURL() URL {
return s.BaseURL_
}
func (s *TestValueStorage) GetSerialized(ctx *Context, key Path) (string, bool) {
v, ok := s.Data[key]
return v, ok
}
func (s *TestValueStorage) Has(ctx *Context, key Path) bool {
_, ok := s.Data[key]
return ok
}
func (s *TestValueStorage) InsertSerialized(ctx *Context, key Path, serialized string) {
_, ok := s.Data[key]
if !ok {
panic(errors.New("already present"))
}
s.Data[key] = serialized
}
func (s *TestValueStorage) SetSerialized(ctx *Context, key Path, serialized string) {
s.Data[key] = serialized
}