-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathcontext.go
401 lines (336 loc) · 10.5 KB
/
context.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
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
package fire
import (
"context"
"encoding/json"
"io"
"net/http"
"reflect"
"github.com/256dpi/jsonapi/v2"
"github.com/256dpi/xo"
"go.mongodb.org/mongo-driver/bson"
"github.com/256dpi/fire/coal"
"github.com/256dpi/fire/stick"
)
// An Operation indicates the purpose of a yield to a callback in the processing
// flow of an API request by a controller. These operations may occur multiple
// times during a single request.
type Operation int
// All the available operations.
const (
// List operation will be used to authorize the loading of multiple
// resources from a collection.
//
// Note: This operation is also used to load related resources.
List Operation = 1 << iota
// Find operation will be used to authorize the loading of a specific
// resource from a collection.
//
// Note: This operation is also used to load a specific related resource.
Find
// Create operation will be used to authorize and validate the creation
// of a new resource in a collection.
Create
// Update operation will be used to authorize the loading and validate
// the updating of a specific resource in a collection.
//
// Note: Updates can include attributes, relationships or both.
Update
// Delete operation will be used to authorize the loading and validate
// the deletion of a specific resource in a collection.
Delete
// CollectionAction operation will be used to authorize the execution
// of a callback for a collection action.
CollectionAction
// ResourceAction operation will be used to authorize the execution of a
// callback for a resource action.
ResourceAction
)
// Read will return true when the operations only reads data.
func (o Operation) Read() bool {
return o == List || o == Find
}
// Write will return true when the operation writes data.
func (o Operation) Write() bool {
return o == Create || o == Update || o == Delete
}
// Action will return true when the operation is a collection or resource action.
func (o Operation) Action() bool {
return o == CollectionAction || o == ResourceAction
}
// String returns the name of the operation.
func (o Operation) String() string {
switch o {
case List:
return "List"
case Find:
return "Find"
case Create:
return "Create"
case Update:
return "Update"
case Delete:
return "Delete"
case CollectionAction:
return "CollectionAction"
case ResourceAction:
return "ResourceAction"
}
return ""
}
// Context carries the state of a request and allows callbacks to influence the
// processing of a request.
type Context struct {
// The context that is cancelled when the timeout has been exceeded or the
// underlying connection transport has been closed. It may also carry the
// database session if a transaction is used.
//
// Values: lungo.ISessionContext?, trace.Span, *xo.Tracer
context.Context
// The custom data map.
Data stick.Map
// The current callback stage.
Stage Stage
// The deferred callbacks.
Defers map[Stage][]*Callback
// The current operation in process.
//
// Usage: Read only
// Availability: Authorizers
Operation Operation
// The query that will be used during a List, Find, Update, Delete or
// ResourceAction operation to select a list of models or a specific model.
//
// On Find, Update and Delete operations, the "_id" key is preset to the
// resource ID, while on forwarded List operations the relationship filter
// is preset.
//
// Usage: Read only
// Availability: Authorizers
// Operations: !Create, !CollectionAction
Selector bson.M
// The filters that will be used during a List, Find, Update, Delete or
// ResourceAction operation to further filter the selection of a list of
// models or a specific model.
//
// On List operations, attribute and relationship filters are preset.
//
// Usage: Append only
// Availability: Authorizers
// Operations: !Create, !CollectionAction
Filters []bson.M
// The sorting that will be used during List.
//
// Usage: No Restriction
// Availability: Authorizers
// Operations: List
Sorting []string
// Only the whitelisted readable fields are exposed to the client as
// attributes and relationships. Additionally, only readable fields can
// be used for filtering and sorting.
//
// Usage: Reduce only
// Availability: Authorizers
// Operations: !Delete, !ResourceAction, !CollectionAction
ReadableFields []string
// Used instead of ReadableFields if set. Allows specifying readable fields
// on a per model basis. The model is nil during the initial load of fields
// during a List operation. Returned list must be a subset of ReadableFields.
GetReadableFields func(coal.Model) []string
// Only the whitelisted writable fields can be altered by requests.
//
// Usage: Reduce only
// Availability: Authorizers
// Operations: Create, Update
WritableFields []string
// Used instead of WritableFields if set. Allows specifying writable fields
// on a per model basis. The model is zero when getting the writable fields
// during a Create operation. Returned list must be a subset of WritableFields.
GetWritableFields func(coal.Model) []string
// Only the whitelisted readable properties are exposed to the client as
// attributes.
//
// Usage: Reduce only
// Availability: Authorizers
// Operations: !Delete, !ResourceActon, !CollectionAction
ReadableProperties []string
// Used instead of ReadableProperties if set. Allows specifying readable
// properties on a per model basis. Returned list must be a subset of
// ReadableProperties.
GetReadableProperties func(coal.Model) []string
// The filters that will be applied when loading has one and has many
// relationships.
//
// Usage: Append only
// Availability: Authorizers.
// Operations: !Create, !CollectionAction
RelationshipFilters map[string][]bson.M
// The model that will be created, updated, deleted or is requested by a
// resource action.
//
// Usage: Modify only
// Availability: Validators
// Operations: Create, Update, Delete, ResourceAction
Model coal.Model
// The models that will be returned for a List operation.
//
// Usage: Modify only
// Availability: Decorators
// Operations: List
Models []coal.Model
// The original model that is being updated. Can be used to lookup up
// original values of changed fields.
//
// Usage: Ready only
// Availability: Validators
// Operations: Update
Original coal.Model
// The model from the which the related resources are loaded.
//
// Usage: Read only
// Availability: Authorizers
// Operations: List?, Find?
Parent coal.Model
// The document that has been received by the client.
//
// Usage: Read only
// Availability: Authorizers
// Operations: !List, !Find, !CollectionAction, !ResourceAction
Request *jsonapi.Document
// The document that will be written to the client.
//
// Usage: Modify only
// Availability: Notifiers
// Operations: !CollectionAction, !ResourceAction
Response *jsonapi.Document
// The status code that will be written to the client.
//
// Usage: Modify only
// Availability: Notifiers
// Operations: !CollectionAction, !ResourceAction
ResponseCode int
// The store that is used to retrieve and persist the model.
//
// Usage: Read only
Store *coal.Store
// The underlying JSON-API request.
//
// Usage: Read only
JSONAPIRequest *jsonapi.Request
// The underlying HTTP request.
//
// Note: The path is not updated when a controller forwards a request to
// a related controller.
//
// Usage: Read only
HTTPRequest *http.Request
// The underlying HTTP response writer. The response writer should only be
// used during collection or resource actions to write a custom response.
//
// Usage: Read only
ResponseWriter http.ResponseWriter
// The controller that is managing the request.
//
// Usage: Read only
Controller *Controller
// The group that received the request.
//
// Usage: Read only
Group *Group
// The current tracer.
//
// Usage: Read only
Tracer *xo.Tracer
}
// With will run the provided function with the specified context temporarily
// set on the context. This is especially useful together with transactions.
func (c *Context) With(ctx context.Context, fn func() error) error {
// retain current context and tracer
oc := c.Context
ot := c.Tracer
// swap tracer and context
if c.Tracer != nil {
c.Tracer, c.Context = xo.NewTracer(ctx)
} else {
c.Context = ctx
}
// ensure switchback
defer func() {
c.Context = oc
c.Tracer = ot
}()
// yield
return fn()
}
// Defer will defer the provided handler and run it after all controller
// callbacks have been run at the specified stages.
func (c *Context) Defer(cb *Callback) {
// check stage
if cb.Stage <= c.Stage {
panic("fire: invalid stage")
}
// ensure map
if c.Defers == nil {
c.Defers = map[Stage][]*Callback{}
}
// add callback for all stages
for _, stage := range cb.Stage.Split() {
c.Defers[stage] = append(c.Defers[stage], cb)
}
}
// Query returns the composite query of Selector and Filter.
func (c *Context) Query() bson.M {
// prepare sub queries
var subQueries []bson.M
// add selector if present
if len(c.Selector) > 0 {
subQueries = append(subQueries, c.Selector)
}
// add filters
subQueries = append(subQueries, c.Filters...)
// return empty query if no sub queries are present
if len(subQueries) == 0 {
return bson.M{}
}
// otherwise, return $and query
return bson.M{"$and": subQueries}
}
// Modified will return whether the specified field has been changed. During an
// update operation the modification is checked against the original model. For
// all other operations, the field is checked against its zero value.
func (c *Context) Modified(field string) bool {
// determine old value
var oldValue interface{}
if c.Original != nil {
oldValue = stick.MustGet(c.Original, field)
} else {
oldValue = reflect.Zero(coal.GetMeta(c.Model).Fields[field].Type).Interface()
}
// get new value
newValue := stick.MustGet(c.Model, field)
return !reflect.DeepEqual(newValue, oldValue)
}
// Parse will decode a custom JSON body to the specified value.
func (c *Context) Parse(value interface{}) error {
// unmarshal json
err := json.NewDecoder(c.HTTPRequest.Body).Decode(value)
if err == io.EOF {
return xo.SF("incomplete request body")
} else if err != nil {
return xo.W(err)
}
return nil
}
// Respond will encode the provided value as JSON and write it to the client.
func (c *Context) Respond(value interface{}) error {
// encode response
bytes, err := json.Marshal(value)
if err != nil {
return xo.W(err)
}
// write token
_, err = c.ResponseWriter.Write(bytes)
if err != nil {
return xo.W(err)
}
return nil
}