@modernpoacher/halacious 7.0.3 → 7.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -17
- package/babel.config.cjs +44 -0
- package/package.json +9 -2
- package/src/index.d.mts +205 -20
- package/src/plugin.mjs +765 -671
- package/src/representation.mjs +282 -108
- package/src/utils/getFieldValueByPath.mjs +20 -0
- package/src/utils/getMediaType.mjs +15 -0
- package/src/utils/getTemplateContext.mjs +32 -0
- package/src/utils.mjs +382 -0
- package/views/namespaces.jade +11 -0
- package/views/rel.jade +7 -0
- package/index.d.mts +0 -7
package/src/representation.mjs
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import _ from 'lodash'
|
|
2
1
|
import * as hoek from '@hapi/hoek'
|
|
3
2
|
import URI from 'urijs'
|
|
4
3
|
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
getSelf,
|
|
6
|
+
getHref,
|
|
7
|
+
getPrefix
|
|
8
|
+
} from './utils.mjs'
|
|
9
|
+
|
|
10
|
+
const TEMPLATE = /{*}/
|
|
6
11
|
|
|
7
12
|
/**
|
|
8
13
|
* A HAL wrapper interface around an entity. Provides an api for adding new links and recursively embedding child
|
|
@@ -15,34 +20,104 @@ const templatedRE = /{*}/
|
|
|
15
20
|
* @constructor
|
|
16
21
|
*/
|
|
17
22
|
export class Representation {
|
|
18
|
-
constructor (factory, self, entity, root) {
|
|
19
|
-
|
|
23
|
+
constructor (factory, self, entity, root = this) {
|
|
24
|
+
const {
|
|
25
|
+
_halacious: halacious,
|
|
26
|
+
_request: request
|
|
27
|
+
} = factory
|
|
28
|
+
|
|
29
|
+
const {
|
|
30
|
+
href
|
|
31
|
+
} = self
|
|
32
|
+
|
|
33
|
+
const links = {
|
|
34
|
+
self
|
|
35
|
+
}
|
|
36
|
+
|
|
20
37
|
this.factory = factory
|
|
21
|
-
this.
|
|
22
|
-
this.
|
|
23
|
-
this.
|
|
24
|
-
this.
|
|
38
|
+
this.self = href
|
|
39
|
+
this.entity = entity
|
|
40
|
+
this._root = root
|
|
41
|
+
this._halacious = halacious
|
|
42
|
+
this._request = request
|
|
43
|
+
this._links = links
|
|
25
44
|
this._embedded = {}
|
|
26
45
|
this._namespaces = {}
|
|
27
|
-
this._props =
|
|
28
|
-
this._ignore =
|
|
29
|
-
|
|
46
|
+
this._props = new Map()
|
|
47
|
+
this._ignore = new Set()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getHalacious () {
|
|
51
|
+
return this._halacious
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getRequest () {
|
|
55
|
+
return this._request
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getRoot () {
|
|
59
|
+
return this._root
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getLinks () {
|
|
63
|
+
return this._links
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getEmbedded () {
|
|
67
|
+
return this._embedded
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getNamespaces () {
|
|
71
|
+
return this._namespaces
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getProps () {
|
|
75
|
+
return this._props
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getIgnore () {
|
|
79
|
+
return this._ignore
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
get halacious () {
|
|
83
|
+
return this._halacious
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
get request () {
|
|
87
|
+
return this._request
|
|
30
88
|
}
|
|
31
89
|
|
|
32
90
|
/**
|
|
33
91
|
* Adds a namespace to the 'curie' link collection. all curies in a response, top level or nested, should be declared
|
|
34
92
|
* in the top level _links collection. a reference '_root' is kept to the top level representation for this purpose
|
|
93
|
+
*
|
|
35
94
|
* @param namespace
|
|
36
95
|
*/
|
|
37
96
|
curie (namespace) {
|
|
38
|
-
if (namespace
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
97
|
+
if (namespace) {
|
|
98
|
+
const root = this.getRoot()
|
|
99
|
+
|
|
100
|
+
const namespaces = root.getNamespaces()
|
|
101
|
+
|
|
102
|
+
const prefix = getPrefix(namespace)
|
|
103
|
+
|
|
104
|
+
if (Reflect.has(namespaces, prefix)) return
|
|
105
|
+
|
|
106
|
+
Reflect.set(namespaces, prefix, namespace)
|
|
107
|
+
|
|
108
|
+
const links = root.getLinks()
|
|
109
|
+
|
|
110
|
+
const curies = Reflect.get(links, 'curies') ?? []
|
|
111
|
+
|
|
112
|
+
const request = this.getRequest()
|
|
113
|
+
|
|
114
|
+
const namespaceUrl = this.getHalacious().namespaceUrl(request, namespace)
|
|
115
|
+
|
|
116
|
+
Reflect.set(links, 'curies', curies.concat({
|
|
117
|
+
name: prefix,
|
|
118
|
+
href: `${namespaceUrl}/{rel}`,
|
|
44
119
|
templated: true
|
|
45
|
-
})
|
|
120
|
+
}))
|
|
46
121
|
}
|
|
47
122
|
}
|
|
48
123
|
|
|
@@ -53,121 +128,173 @@ export class Representation {
|
|
|
53
128
|
* @return {Representation}
|
|
54
129
|
*/
|
|
55
130
|
prop (name, value) {
|
|
56
|
-
this.
|
|
131
|
+
this.getProps()
|
|
132
|
+
.set(name, value)
|
|
133
|
+
|
|
57
134
|
return this
|
|
58
135
|
}
|
|
59
136
|
|
|
60
137
|
/**
|
|
61
|
-
* Merges an object's properties into the custom properties collection
|
|
138
|
+
* Merges an object's properties into the custom properties collection
|
|
139
|
+
*
|
|
62
140
|
* @param prop
|
|
63
141
|
*/
|
|
64
142
|
merge (prop) {
|
|
65
|
-
|
|
143
|
+
const props = this.getProps()
|
|
144
|
+
|
|
145
|
+
Object
|
|
146
|
+
.entries(prop)
|
|
147
|
+
.forEach(([name, value]) => {
|
|
148
|
+
props.set(name, value)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
return this
|
|
66
152
|
}
|
|
67
153
|
|
|
68
154
|
/**
|
|
69
155
|
* @param {...String || String[]} args properties to ignore
|
|
70
156
|
* @return {Representation}
|
|
71
157
|
*/
|
|
72
|
-
ignore (...args) {
|
|
73
|
-
const props =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
158
|
+
ignore (arg, ...args) {
|
|
159
|
+
const props = (
|
|
160
|
+
Array.isArray(arg)
|
|
161
|
+
? arg
|
|
162
|
+
: [arg].concat(args)
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
const ignore = this.getIgnore()
|
|
166
|
+
|
|
167
|
+
props
|
|
168
|
+
.filter(Boolean)
|
|
169
|
+
.forEach((prop) => {
|
|
170
|
+
ignore.add(prop)
|
|
171
|
+
})
|
|
172
|
+
|
|
77
173
|
return this
|
|
78
174
|
}
|
|
79
175
|
|
|
80
176
|
/**
|
|
81
|
-
* Prepares the representation for JSON serialization
|
|
177
|
+
* Prepares the representation for JSON serialization
|
|
178
|
+
*
|
|
82
179
|
* @return {{}}
|
|
83
180
|
*/
|
|
84
181
|
toJSON () {
|
|
85
182
|
// initialize the json entity
|
|
86
|
-
const
|
|
87
|
-
const self = this
|
|
183
|
+
const object = { _links: this.getLinks() }
|
|
88
184
|
|
|
89
185
|
// copy all target properties in the entity using JSON.stringify(). if the entity has a .toJSON() implementation,
|
|
90
186
|
// it will be called. properties on the ignore list will not be copied
|
|
91
|
-
const {
|
|
187
|
+
const {
|
|
188
|
+
entity
|
|
189
|
+
} = this
|
|
190
|
+
|
|
191
|
+
const ignore = this.getIgnore()
|
|
192
|
+
|
|
92
193
|
JSON.stringify(entity, (key, value) => {
|
|
93
194
|
if (!key) {
|
|
94
195
|
return value
|
|
95
196
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
197
|
+
|
|
198
|
+
if (!ignore.has(key)) Reflect.set(object, key, value)
|
|
99
199
|
})
|
|
100
200
|
|
|
201
|
+
const props = this.getProps()
|
|
202
|
+
|
|
101
203
|
// merge in any extra properties
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
204
|
+
Object.assign(object, Object.fromEntries(Array.from(props.entries()).filter(([key]) => !ignore.has(key))))
|
|
205
|
+
|
|
206
|
+
const embedded = this.getEmbedded()
|
|
207
|
+
|
|
208
|
+
const entries = Object.entries(embedded)
|
|
209
|
+
if (entries.length) {
|
|
210
|
+
object._embedded = (
|
|
211
|
+
entries
|
|
212
|
+
.reduce((accumulator, [entryKey, entryValue]) => {
|
|
213
|
+
let currentValue
|
|
214
|
+
|
|
215
|
+
if (entryValue instanceof Representation) {
|
|
216
|
+
currentValue = {}
|
|
217
|
+
} else {
|
|
218
|
+
if (Array.isArray(entryValue)) {
|
|
219
|
+
currentValue = []
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
JSON.stringify(entryValue, (key, value) => {
|
|
224
|
+
if (!key) {
|
|
225
|
+
return value
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!ignore.has(key)) Reflect.set(currentValue, key, value)
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
return (
|
|
232
|
+
Object.assign(accumulator, { [entryKey]: currentValue })
|
|
233
|
+
)
|
|
234
|
+
}, {})
|
|
235
|
+
)
|
|
124
236
|
}
|
|
125
237
|
|
|
126
|
-
return
|
|
238
|
+
return object
|
|
127
239
|
}
|
|
128
240
|
|
|
129
241
|
/**
|
|
130
242
|
* Creates a new link and adds it to the _links collection
|
|
243
|
+
*
|
|
131
244
|
* @param rel
|
|
132
245
|
* @param link
|
|
133
246
|
* @return {{} || []} the new link
|
|
134
247
|
*/
|
|
135
|
-
link (
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
248
|
+
link (relName, link) {
|
|
249
|
+
const halacious = this.getHalacious()
|
|
250
|
+
|
|
251
|
+
const rel = halacious.rel(relName)
|
|
252
|
+
const key = rel.qname()
|
|
253
|
+
|
|
254
|
+
const links = this.getLinks()
|
|
255
|
+
|
|
256
|
+
if (Array.isArray(link)) {
|
|
257
|
+
if (!Reflect.has(links, key)) Reflect.set(links, key, [])
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
link.map((href) => this.link(relName, href))
|
|
261
|
+
)
|
|
143
262
|
}
|
|
144
263
|
|
|
145
264
|
// adds the namespace to the top level curie list
|
|
146
265
|
this.curie(rel.namespace)
|
|
147
266
|
|
|
148
|
-
|
|
149
|
-
|
|
267
|
+
const href = getHref(getSelf(links)) ?? ''
|
|
268
|
+
|
|
269
|
+
link = halacious.link(link, href)
|
|
270
|
+
|
|
271
|
+
if (TEMPLATE.test(link.href)) link.templated = true
|
|
272
|
+
|
|
150
273
|
// e.g. 'mco:rel'
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
274
|
+
|
|
275
|
+
Reflect.set(links, key, (
|
|
276
|
+
Reflect.has(links, key)
|
|
277
|
+
? [].concat(Reflect.get(links, key), link)
|
|
278
|
+
: link
|
|
279
|
+
))
|
|
158
280
|
|
|
159
281
|
return link
|
|
160
282
|
}
|
|
161
283
|
|
|
162
284
|
/**
|
|
163
285
|
* Resolves a relative path against the representation's self href
|
|
286
|
+
*
|
|
164
287
|
* @param relativePath
|
|
165
288
|
* @return {*}
|
|
166
289
|
*/
|
|
167
290
|
resolve (relativePath) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
291
|
+
const href = getHref(getSelf(this.getLinks())) ?? ''
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
new URI(relativePath)
|
|
295
|
+
.absoluteTo(href.concat('/'))
|
|
296
|
+
.toString()
|
|
297
|
+
)
|
|
171
298
|
}
|
|
172
299
|
|
|
173
300
|
/**
|
|
@@ -178,7 +305,9 @@ export class Representation {
|
|
|
178
305
|
* @return {*}
|
|
179
306
|
*/
|
|
180
307
|
route (routeName, params) {
|
|
181
|
-
return
|
|
308
|
+
return (
|
|
309
|
+
this.getHalacious().route(routeName, params)
|
|
310
|
+
)
|
|
182
311
|
}
|
|
183
312
|
|
|
184
313
|
/**
|
|
@@ -188,45 +317,62 @@ export class Representation {
|
|
|
188
317
|
* @param {{} || []} entity an object to wrap
|
|
189
318
|
* @return {entity || []}
|
|
190
319
|
*/
|
|
191
|
-
embed (
|
|
192
|
-
const
|
|
193
|
-
|
|
320
|
+
embed (relName, self, entity) {
|
|
321
|
+
const halacious = this.getHalacious()
|
|
322
|
+
|
|
323
|
+
const rel = halacious.rel(relName)
|
|
324
|
+
const key = rel.qname()
|
|
194
325
|
|
|
195
326
|
this.curie(rel.namespace)
|
|
196
327
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
328
|
+
const embedded = this.getEmbedded()
|
|
329
|
+
|
|
330
|
+
if (Array.isArray(entity)) {
|
|
331
|
+
if (!Reflect.has(embedded, key)) Reflect.set(embedded, key, [])
|
|
332
|
+
|
|
333
|
+
return (
|
|
334
|
+
entity.map((entity) => this.embed(relName, self, entity))
|
|
335
|
+
)
|
|
201
336
|
}
|
|
202
337
|
|
|
203
|
-
|
|
338
|
+
const href = getHref(getSelf(this.getLinks())) ?? ''
|
|
204
339
|
|
|
205
|
-
|
|
340
|
+
self = halacious.link(self, href)
|
|
206
341
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
342
|
+
const root = this.getRoot()
|
|
343
|
+
|
|
344
|
+
const embed = this.factory.create(entity, self, root)
|
|
345
|
+
|
|
346
|
+
Reflect.set(embedded, key, (
|
|
347
|
+
Reflect.has(embedded, key)
|
|
348
|
+
? [].concat(Reflect.get(embedded, key), embed)
|
|
349
|
+
: embed
|
|
350
|
+
))
|
|
214
351
|
|
|
215
|
-
return
|
|
352
|
+
return embed
|
|
216
353
|
}
|
|
217
354
|
|
|
218
355
|
/**
|
|
219
|
-
* Convenience method for embedding an array of entities
|
|
356
|
+
* Convenience method for embedding an entity or array of entities
|
|
357
|
+
*
|
|
220
358
|
* @param rel
|
|
221
359
|
* @param self
|
|
222
|
-
* @param
|
|
360
|
+
* @param arg
|
|
223
361
|
* @return {Representation}
|
|
224
362
|
*/
|
|
225
|
-
embedCollection (rel, self,
|
|
226
|
-
entities =
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
363
|
+
embedCollection (rel, self, arg, ...args) {
|
|
364
|
+
const entities = (
|
|
365
|
+
Array.isArray(arg)
|
|
366
|
+
? arg
|
|
367
|
+
: [arg].concat(args)
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
entities
|
|
371
|
+
.filter(Boolean)
|
|
372
|
+
.forEach((entity) => {
|
|
373
|
+
this.embed(rel, hoek.clone(self), entity)
|
|
374
|
+
})
|
|
375
|
+
|
|
230
376
|
return this
|
|
231
377
|
}
|
|
232
378
|
|
|
@@ -236,12 +382,13 @@ export class Representation {
|
|
|
236
382
|
* @param callback
|
|
237
383
|
*/
|
|
238
384
|
configure (config, callback) {
|
|
239
|
-
this.
|
|
385
|
+
this.getHalacious().configureRepresentation(config, this, callback)
|
|
240
386
|
}
|
|
241
387
|
}
|
|
242
388
|
|
|
243
389
|
/**
|
|
244
|
-
* Responsible for creating all hal entities, top level or embedded, needed for a
|
|
390
|
+
* Responsible for creating all hal entities, top level or embedded, needed for a Hapi request
|
|
391
|
+
*
|
|
245
392
|
* @param halacious a reference to the plugin api
|
|
246
393
|
* @param request a hapi request object
|
|
247
394
|
* @constructor
|
|
@@ -252,6 +399,30 @@ export class RepresentationFactory {
|
|
|
252
399
|
this._request = request
|
|
253
400
|
}
|
|
254
401
|
|
|
402
|
+
getHalacious () {
|
|
403
|
+
return this._halacious
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
getRequest () {
|
|
407
|
+
return this._request
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
getRequestPath () {
|
|
411
|
+
return this.getRequest()?.path
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
get halacious () {
|
|
415
|
+
return this._halacious
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
get request () {
|
|
419
|
+
return this._request
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
get requestPath () {
|
|
423
|
+
return this.request?.path
|
|
424
|
+
}
|
|
425
|
+
|
|
255
426
|
/**
|
|
256
427
|
* Creates a new hal representation out of a javascript object
|
|
257
428
|
* @param {{}=} entity the entity to wrap with a representation. an empty object is created by default
|
|
@@ -260,11 +431,14 @@ export class RepresentationFactory {
|
|
|
260
431
|
* should be expanded into absolute urls
|
|
261
432
|
* @return {Representation}
|
|
262
433
|
*/
|
|
263
|
-
create (entity, self, root) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
434
|
+
create (entity = {}, self = this.getRequestPath(), root) {
|
|
435
|
+
const halacious = this.getHalacious()
|
|
436
|
+
|
|
437
|
+
const link = halacious.link(self)
|
|
438
|
+
|
|
439
|
+
return (
|
|
440
|
+
new Representation(this, link, entity, root)
|
|
441
|
+
)
|
|
268
442
|
}
|
|
269
443
|
}
|
|
270
444
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
function reduce (accumulator, fieldName) {
|
|
2
|
+
return (
|
|
3
|
+
Reflect.has(accumulator, fieldName)
|
|
4
|
+
? Reflect.get(accumulator, fieldName)
|
|
5
|
+
: accumulator
|
|
6
|
+
)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Previously `reach`
|
|
11
|
+
*/
|
|
12
|
+
export default function getFieldValueByPath (path = '', context = {}) {
|
|
13
|
+
const fieldNames = String(path ?? '').split('.')
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
fieldNames
|
|
17
|
+
.filter(Boolean)
|
|
18
|
+
.reduce(reduce, context)
|
|
19
|
+
)
|
|
20
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Negotiator from 'negotiator'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Selects the media type based on the request's Accept header and a ranked ordering of configured
|
|
5
|
+
* media types
|
|
6
|
+
*
|
|
7
|
+
* @param request
|
|
8
|
+
* @param mediaTypes
|
|
9
|
+
* @return {*}
|
|
10
|
+
*/
|
|
11
|
+
export default function getMediaType (request, mediaTypes) {
|
|
12
|
+
return (
|
|
13
|
+
new Negotiator(request).mediaType([].concat(mediaTypes))
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import getFieldValueByPath from './getFieldValueByPath.mjs'
|
|
2
|
+
|
|
3
|
+
const REG = /{([^{}]+)}|([^{}]+)/g
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Evaluates and flattens deep expressions (e.g. '/{foo.a.b}') into a single level context object: {'foo.a.b': value}
|
|
7
|
+
* so that it may be used by url-template library
|
|
8
|
+
*
|
|
9
|
+
* @param template
|
|
10
|
+
* @param context
|
|
11
|
+
* @return {{}}
|
|
12
|
+
*/
|
|
13
|
+
export default function getTemplateContext (template, context) {
|
|
14
|
+
const templateContext = {}
|
|
15
|
+
let matches
|
|
16
|
+
|
|
17
|
+
while ((matches = REG.exec(template))) {
|
|
18
|
+
if (Array.isArray(matches)) {
|
|
19
|
+
const [
|
|
20
|
+
, // input,
|
|
21
|
+
path
|
|
22
|
+
] = matches
|
|
23
|
+
|
|
24
|
+
if (path) {
|
|
25
|
+
const fieldValue = getFieldValueByPath(path, context)
|
|
26
|
+
if (fieldValue) Reflect.set(templateContext, path, fieldValue.toString())
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return templateContext
|
|
32
|
+
}
|