@declaro/core 2.0.0-beta.8 → 2.0.0-y.0
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/dist/app/app-context.d.ts +8 -0
- package/dist/app/app-lifecycle.d.ts +4 -0
- package/dist/app/app.d.ts +22 -0
- package/dist/app/index.d.ts +3 -20
- package/dist/auth/permission-validator.d.ts +34 -0
- package/dist/auth/permission-validator.test.d.ts +1 -0
- package/dist/context/context.d.ts +88 -13
- package/dist/context/legacy-context.test.d.ts +1 -0
- package/dist/errors/errors.d.ts +36 -0
- package/dist/events/event-manager.d.ts +11 -6
- package/dist/http/headers.d.ts +4 -0
- package/dist/http/headers.spec.d.ts +1 -0
- package/dist/http/request-context.d.ts +12 -0
- package/dist/http/request-context.spec.d.ts +1 -0
- package/dist/http/request.d.ts +8 -0
- package/dist/http/request.spec.d.ts +1 -0
- package/dist/http/url.d.ts +8 -0
- package/dist/http/url.spec.d.ts +1 -0
- package/dist/index.d.ts +9 -3
- package/dist/pkg.cjs +30 -2
- package/dist/pkg.mjs +56461 -207
- package/dist/schema/application.d.ts +83 -0
- package/dist/schema/application.test.d.ts +1 -0
- package/dist/schema/define-model.d.ts +7 -4
- package/dist/schema/index.d.ts +7 -0
- package/dist/schema/labels.d.ts +13 -0
- package/dist/schema/labels.test.d.ts +1 -0
- package/dist/schema/module.d.ts +7 -0
- package/dist/schema/module.test.d.ts +1 -0
- package/dist/schema/properties.d.ts +19 -0
- package/dist/schema/response.d.ts +31 -0
- package/dist/schema/response.test.d.ts +1 -0
- package/dist/schema/transform-model.d.ts +1 -1
- package/dist/schema/types.d.ts +81 -15
- package/dist/schema/types.test.d.ts +1 -0
- package/dist/typescript/constant-manipulation/snake-case.d.ts +22 -0
- package/dist/typescript/index.d.ts +1 -0
- package/dist/typescript/objects.d.ts +6 -0
- package/package.json +8 -3
- package/src/app/app-context.ts +14 -0
- package/src/app/app-lifecycle.ts +14 -0
- package/src/app/app.ts +45 -0
- package/src/app/index.ts +3 -34
- package/src/auth/permission-validator.test.ts +209 -0
- package/src/auth/permission-validator.ts +135 -0
- package/src/context/context.test.ts +585 -94
- package/src/context/context.ts +348 -32
- package/src/context/legacy-context.test.ts +141 -0
- package/src/errors/errors.ts +73 -0
- package/src/events/event-manager.spec.ts +54 -8
- package/src/events/event-manager.ts +40 -24
- package/src/http/headers.spec.ts +48 -0
- package/src/http/headers.ts +16 -0
- package/src/http/request-context.spec.ts +39 -0
- package/src/http/request-context.ts +43 -0
- package/src/http/request.spec.ts +52 -0
- package/src/http/request.ts +22 -0
- package/src/http/url.spec.ts +87 -0
- package/src/http/url.ts +48 -0
- package/src/index.ts +9 -3
- package/src/schema/application.test.ts +286 -0
- package/src/schema/application.ts +150 -0
- package/src/schema/define-model.test.ts +48 -2
- package/src/schema/define-model.ts +40 -9
- package/src/schema/index.ts +7 -0
- package/src/schema/labels.test.ts +60 -0
- package/src/schema/labels.ts +30 -0
- package/src/schema/module.test.ts +39 -0
- package/src/schema/module.ts +6 -0
- package/src/schema/properties.ts +40 -0
- package/src/schema/response.test.ts +101 -0
- package/src/schema/response.ts +93 -0
- package/src/schema/transform-model.ts +1 -1
- package/src/schema/types.test.ts +28 -0
- package/src/schema/types.ts +135 -15
- package/src/typescript/constant-manipulation/snake-case.md +496 -0
- package/src/typescript/constant-manipulation/snake-case.ts +76 -0
- package/src/typescript/index.ts +1 -0
- package/src/typescript/objects.ts +8 -5
- package/tsconfig.json +4 -1
- package/dist/context/index.d.ts +0 -3
- package/dist/interfaces/IDatastoreProvider.d.ts +0 -16
- package/dist/interfaces/IStore.d.ts +0 -4
- package/dist/interfaces/index.d.ts +0 -2
- package/dist/server/index.d.ts +0 -2
- package/src/context/index.ts +0 -3
- package/src/interfaces/IDatastoreProvider.ts +0 -23
- package/src/interfaces/IStore.ts +0 -4
- package/src/interfaces/index.ts +0 -2
- package/src/server/index.ts +0 -3
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { PermissionError, PermissionRuleType, PermissionValidator } from './permission-validator'
|
|
3
|
+
|
|
4
|
+
describe('Permission Builder', () => {
|
|
5
|
+
it('should create a builder instance statically or with a constructor', () => {
|
|
6
|
+
const viaStatic = PermissionValidator.create()
|
|
7
|
+
const viaConstructor = new PermissionValidator()
|
|
8
|
+
|
|
9
|
+
expect(viaStatic).toBeInstanceOf(PermissionValidator)
|
|
10
|
+
expect(viaConstructor).toBeInstanceOf(PermissionValidator)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('should be able to add validation rules', () => {
|
|
14
|
+
const validator = PermissionValidator.create().addRule({
|
|
15
|
+
type: PermissionRuleType.ALL_OF,
|
|
16
|
+
permissions: ['some', 'permissions'],
|
|
17
|
+
errorMessage: 'Tisk tisk',
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
expect(validator).toBeInstanceOf(PermissionValidator)
|
|
21
|
+
expect(validator.rules.length).toBe(1)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('should be able to validate rules', () => {
|
|
25
|
+
const validator = PermissionValidator.create().addRule({
|
|
26
|
+
type: PermissionRuleType.ALL_OF,
|
|
27
|
+
permissions: ['some', 'permissions'],
|
|
28
|
+
errorMessage: 'Tisk tisk',
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const validResult = validator.validate(['look', 'we', 'have', 'some', 'permissions', 'and', 'some', 'more'])
|
|
32
|
+
|
|
33
|
+
expect(validResult).toBe(true)
|
|
34
|
+
expect(() => validator.validate(['we', 'are', 'not', 'authorized'])).toThrowError('Tisk tisk')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should be able to safe validate rules, so they do not throw permission errors', () => {
|
|
38
|
+
const validator = PermissionValidator.create().addRule({
|
|
39
|
+
type: PermissionRuleType.ALL_OF,
|
|
40
|
+
permissions: ['some', 'permissions'],
|
|
41
|
+
errorMessage: 'Tisk tisk',
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const validResult = validator.safeValidate(['look', 'we', 'have', 'some', 'permissions', 'and', 'some', 'more'])
|
|
45
|
+
|
|
46
|
+
expect(validResult).to.deep.equal({
|
|
47
|
+
valid: true,
|
|
48
|
+
errorMessage: '',
|
|
49
|
+
errors: [],
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
expect(() => validator.safeValidate(['we', 'are', 'not', 'authorized'])).not.toThrowError()
|
|
53
|
+
const result = validator.safeValidate(['we', 'are', 'not', 'authorized'])
|
|
54
|
+
expect(result).to.deep.equal({
|
|
55
|
+
valid: false,
|
|
56
|
+
errorMessage: 'Tisk tisk',
|
|
57
|
+
errors: [new PermissionError('Tisk tisk', validator.rules[0], ['we', 'are', 'not', 'authorized'])],
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('should support requiring all of a list of permissions', () => {
|
|
62
|
+
const validator = PermissionValidator.create().allOf(['permissions', 'you', 'need'])
|
|
63
|
+
|
|
64
|
+
const validResult = validator.validate(['i', 'have', 'the', 'permissions', 'you', 'need'])
|
|
65
|
+
|
|
66
|
+
expect(validResult).toBe(true)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('should support requiring none of a list of permissions', () => {
|
|
70
|
+
const validator = PermissionValidator.create().noneOf(['bad', 'things'], 'NOPE')
|
|
71
|
+
|
|
72
|
+
const validResult = validator.validate(['i', 'have', 'the', 'permissions', 'you', 'need'])
|
|
73
|
+
|
|
74
|
+
expect(validResult).toBe(true)
|
|
75
|
+
|
|
76
|
+
expect(() => validator.validate(['i', 'have', 'bad', 'stuff'])).toThrowError('NOPE')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('should support requiring some of a list of permissions', () => {
|
|
80
|
+
const validator = PermissionValidator.create().someOf(['some', 'optional', 'permissions'], 'You had one job')
|
|
81
|
+
|
|
82
|
+
const validResult = validator.validate(['i', 'have', 'the', 'permissions', 'you', 'need'])
|
|
83
|
+
|
|
84
|
+
expect(validResult).toBe(true)
|
|
85
|
+
|
|
86
|
+
expect(() => validator.validate(['i', 'have', 'bad', 'stuff'])).toThrowError('You had one job')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('should be able to mix and match permissions', () => {
|
|
90
|
+
const validator = PermissionValidator.create()
|
|
91
|
+
.allOf(['luke', 'leia', 'han', 'chewie'], 'Where are my heroes?')
|
|
92
|
+
.someOf(['obi-wan', 'yoda'], 'Requires 1 master jedi')
|
|
93
|
+
.noneOf(['darth-vader', 'storm-trooper', 'emperor'], 'No sith allowed')
|
|
94
|
+
|
|
95
|
+
const validResult = validator.validate(['leia', 'han', 'yoda', 'chewie', 'c3po', 'r2d2', 'han', 'luke'])
|
|
96
|
+
|
|
97
|
+
expect(validResult).toBe(true)
|
|
98
|
+
|
|
99
|
+
expect(() =>
|
|
100
|
+
validator.validate(['leia', 'han', 'yoda', 'chewie', 'c3po', 'r2d2', 'han', 'luke', 'storm-trooper']),
|
|
101
|
+
).toThrowError('No sith allowed')
|
|
102
|
+
|
|
103
|
+
expect(() => {
|
|
104
|
+
validator.validate(['leia', 'han', 'yoda', 'chewie', 'c3po', 'r2d2', 'han'])
|
|
105
|
+
}).toThrowError('Where are my heroes?')
|
|
106
|
+
|
|
107
|
+
expect(() => {
|
|
108
|
+
validator.validate(['leia', 'han', 'chewie', 'c3po', 'r2d2', 'han', 'luke'])
|
|
109
|
+
}).toThrowError('Requires 1 master jedi')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('should validate multiple permission validators at once', () => {
|
|
113
|
+
const validator = PermissionValidator.create()
|
|
114
|
+
.allOf(['luke', 'leia', 'han', 'chewie'], 'Where are my heroes?')
|
|
115
|
+
.someOf(['obi-wan', 'yoda'], 'Requires 1 master jedi')
|
|
116
|
+
|
|
117
|
+
const validator2 = PermissionValidator.create().noneOf(
|
|
118
|
+
['darth-vader', 'storm-trooper', 'emperor'],
|
|
119
|
+
'No sith allowed',
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
const combinedValidator = PermissionValidator.create().extend(validator, validator2)
|
|
123
|
+
|
|
124
|
+
const validResult = combinedValidator.validate(['leia', 'han', 'yoda', 'chewie', 'c3po', 'r2d2', 'han', 'luke'])
|
|
125
|
+
|
|
126
|
+
expect(validResult).toBe(true)
|
|
127
|
+
|
|
128
|
+
expect(() =>
|
|
129
|
+
combinedValidator.validate([
|
|
130
|
+
'leia',
|
|
131
|
+
'han',
|
|
132
|
+
'yoda',
|
|
133
|
+
'chewie',
|
|
134
|
+
'c3po',
|
|
135
|
+
'r2d2',
|
|
136
|
+
'han',
|
|
137
|
+
'luke',
|
|
138
|
+
'storm-trooper',
|
|
139
|
+
]),
|
|
140
|
+
).toThrowError('No sith allowed')
|
|
141
|
+
|
|
142
|
+
expect(() => {
|
|
143
|
+
combinedValidator.validate(['leia', 'han', 'yoda', 'chewie', 'c3po', 'r2d2', 'han'])
|
|
144
|
+
}).toThrowError('Where are my heroes?')
|
|
145
|
+
|
|
146
|
+
expect(() => {
|
|
147
|
+
combinedValidator.validate(['leia', 'han', 'chewie', 'c3po', 'r2d2', 'han', 'luke'])
|
|
148
|
+
}).toThrowError('Requires 1 master jedi')
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('Should be able to use validators interchangably with rules', () => {
|
|
152
|
+
const heros = PermissionValidator.create().allOf(['luke', 'leia', 'han', 'chewie'])
|
|
153
|
+
|
|
154
|
+
const jedi = PermissionValidator.create().someOf(['obi-wan', 'yoda'])
|
|
155
|
+
|
|
156
|
+
const sith = PermissionValidator.create().someOf(['darth-vadar', 'emperor', 'darth-maul'])
|
|
157
|
+
|
|
158
|
+
const lightSide = PermissionValidator.create().someOf([heros, jedi], 'The light side must be heroes or jedi')
|
|
159
|
+
const darkSide = PermissionValidator.create().someOf(['palpatine', sith], 'Only dark side allowed')
|
|
160
|
+
const lightOnly = PermissionValidator.create()
|
|
161
|
+
.someOf([lightSide], 'Light side only')
|
|
162
|
+
.noneOf([darkSide], 'No dark side allowed')
|
|
163
|
+
|
|
164
|
+
expect(lightOnly.validate(['luke', 'leia', 'yoda'])).toBe(true)
|
|
165
|
+
expect(() => lightOnly.validate(['luke', 'leia', 'yoda', 'darth-vadar'])).toThrowError('No dark side allowed')
|
|
166
|
+
expect(() => lightOnly.validate(['luke', 'leia', 'yoda', 'darth-vadar', 'darth-maul'])).toThrowError(
|
|
167
|
+
'No dark side allowed',
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
const dumbCharacters = PermissionValidator.create().someOf(['jar-jar-binx'], 'Oh George, what have you done?')
|
|
171
|
+
|
|
172
|
+
const coolCharacters = PermissionValidator.create()
|
|
173
|
+
.someOf([lightSide, darkSide], 'Must contain Star Wars characters')
|
|
174
|
+
.noneOf([dumbCharacters], 'Oh George, what have you done?')
|
|
175
|
+
|
|
176
|
+
expect(coolCharacters.validate(['luke', 'leia', 'han', 'chewie', 'yoda'])).toBe(true)
|
|
177
|
+
expect(coolCharacters.validate(['luke', 'leia', 'han', 'chewie', 'yoda', 'darth-vadar'])).toBe(true)
|
|
178
|
+
expect(coolCharacters.validate(['darth-vadar', 'darth-maul'])).toBe(true)
|
|
179
|
+
expect(() => coolCharacters.validate(['luke', 'leia', 'han', 'chewie', 'yoda', 'jar-jar-binx'])).toThrowError(
|
|
180
|
+
'Oh George, what have you done?',
|
|
181
|
+
)
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it('should not error when passed undefined instead of a permission array to validate', () => {
|
|
185
|
+
const validator = PermissionValidator.create().someOf(['luke', 'leia', 'han', 'chewie'], 'Where are my heroes?')
|
|
186
|
+
expect(() => validator.safeValidate(undefined)).not.toThrowError()
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('should support wildcards', () => {
|
|
190
|
+
const validator = PermissionValidator.create().allOf(['*'])
|
|
191
|
+
|
|
192
|
+
expect(validator.safeValidate(['anything']).valid).toBe(true)
|
|
193
|
+
expect(validator.safeValidate(['everything']).valid).toBe(true)
|
|
194
|
+
expect(validator.safeValidate(['nothing']).valid).toBe(true)
|
|
195
|
+
expect(validator.safeValidate(['something']).valid).toBe(true)
|
|
196
|
+
|
|
197
|
+
const validator2 = PermissionValidator.create().allOf(['namespace::resource.action:*'])
|
|
198
|
+
|
|
199
|
+
expect(validator2.safeValidate(['namespace::resource.action:read']).valid).toBe(true)
|
|
200
|
+
expect(validator2.safeValidate(['namespace::resource.action:write']).valid).toBe(true)
|
|
201
|
+
expect(validator2.safeValidate(['namespace::resource.action:delete']).valid).toBe(true)
|
|
202
|
+
|
|
203
|
+
const validator3 = PermissionValidator.create().allOf(['namespace::resource.*:*'])
|
|
204
|
+
|
|
205
|
+
expect(validator3.safeValidate(['namespace::resource.action:read']).valid).toBe(true)
|
|
206
|
+
expect(validator3.safeValidate(['namespace::resource.action1:write']).valid).toBe(true)
|
|
207
|
+
expect(validator3.safeValidate(['namespace::resource.action2:delete']).valid).toBe(true)
|
|
208
|
+
})
|
|
209
|
+
})
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { minimatch } from 'minimatch'
|
|
2
|
+
|
|
3
|
+
export enum PermissionRuleType {
|
|
4
|
+
ALL_OF = 'ALL_OF',
|
|
5
|
+
NONE_OF = 'NONE_OF',
|
|
6
|
+
SOME_OF = 'SOME_OF',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type RulePermission = string | PermissionValidator
|
|
10
|
+
|
|
11
|
+
export type PermissionRule = {
|
|
12
|
+
type: PermissionRuleType
|
|
13
|
+
permissions: RulePermission[]
|
|
14
|
+
errorMessage: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class PermissionError extends Error {
|
|
18
|
+
constructor(message: string, public rule: PermissionRule, public permissions: RulePermission[]) {
|
|
19
|
+
super(message ?? 'Permission Error')
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type PermissionValidationResults = {
|
|
24
|
+
valid: boolean
|
|
25
|
+
errorMessage: string
|
|
26
|
+
errors: PermissionError[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class PermissionValidator {
|
|
30
|
+
readonly rules: PermissionRule[] = []
|
|
31
|
+
static create() {
|
|
32
|
+
return new PermissionValidator()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
addRule(...rule: PermissionRule[]) {
|
|
36
|
+
this.rules.push(...rule)
|
|
37
|
+
return this
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
extend(...validators: PermissionValidator[]) {
|
|
41
|
+
validators.forEach((validator) => {
|
|
42
|
+
this.rules.push(...validator.rules)
|
|
43
|
+
})
|
|
44
|
+
return this
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
allOf(permissions: RulePermission[], errorMessage?: string) {
|
|
48
|
+
this.addRule({
|
|
49
|
+
type: PermissionRuleType.ALL_OF,
|
|
50
|
+
permissions,
|
|
51
|
+
errorMessage,
|
|
52
|
+
})
|
|
53
|
+
return this
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
noneOf(permissions: RulePermission[], errorMessage?: string) {
|
|
57
|
+
this.addRule({
|
|
58
|
+
type: PermissionRuleType.NONE_OF,
|
|
59
|
+
permissions,
|
|
60
|
+
errorMessage,
|
|
61
|
+
})
|
|
62
|
+
return this
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
someOf(permissions: RulePermission[], errorMessage?: string) {
|
|
66
|
+
this.addRule({
|
|
67
|
+
type: PermissionRuleType.SOME_OF,
|
|
68
|
+
permissions,
|
|
69
|
+
errorMessage,
|
|
70
|
+
})
|
|
71
|
+
return this
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private validateRule(rule: PermissionRule, permissions: string[]) {
|
|
75
|
+
if (rule.type === PermissionRuleType.ALL_OF) {
|
|
76
|
+
return rule.permissions.every((permission) => this.hasPermission(permission, permissions))
|
|
77
|
+
} else if (rule.type === PermissionRuleType.NONE_OF) {
|
|
78
|
+
return !rule.permissions.some((permission) => this.hasPermission(permission, permissions))
|
|
79
|
+
} else if (rule.type === PermissionRuleType.SOME_OF) {
|
|
80
|
+
return rule.permissions.some((permission) => this.hasPermission(permission, permissions))
|
|
81
|
+
} else {
|
|
82
|
+
throw new Error(`Invalid permission rule type ${rule.type}`)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private hasPermission(permission: RulePermission, permissions: string[]) {
|
|
87
|
+
if (typeof permission === 'string') {
|
|
88
|
+
const matches = minimatch.match(permissions, permission)
|
|
89
|
+
return matches.length > 0
|
|
90
|
+
// return permissions.some((p) => minimatch(p, permission))
|
|
91
|
+
} else {
|
|
92
|
+
return permission.safeValidate(permissions).valid
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
validate(permissions: string[] = []) {
|
|
97
|
+
return this.rules.reduce((status, rule) => {
|
|
98
|
+
const valid = this.validateRule(rule, permissions)
|
|
99
|
+
|
|
100
|
+
if (!valid) {
|
|
101
|
+
throw new PermissionError(rule.errorMessage, rule, permissions)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return status && valid
|
|
105
|
+
}, true)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
safeValidate(permissions: string[] = []): PermissionValidationResults {
|
|
109
|
+
const errors = this.rules
|
|
110
|
+
.map((rule) => {
|
|
111
|
+
const valid = this.validateRule(rule, permissions)
|
|
112
|
+
|
|
113
|
+
if (!valid) {
|
|
114
|
+
return new PermissionError(rule.errorMessage, rule, permissions)
|
|
115
|
+
} else {
|
|
116
|
+
return undefined
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
.filter((item) => !!item)
|
|
120
|
+
|
|
121
|
+
if (errors.length > 0) {
|
|
122
|
+
return {
|
|
123
|
+
valid: false,
|
|
124
|
+
errorMessage: errors[0].message,
|
|
125
|
+
errors,
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
return {
|
|
129
|
+
valid: true,
|
|
130
|
+
errorMessage: '',
|
|
131
|
+
errors: [],
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|