4
4
// License text available at https://opensource.org/licenses/MIT
5
5
6
6
import * as assert from 'assert' ;
7
+ import * as _ from 'lodash' ;
8
+
7
9
import { Reflector } from '@loopback/context' ;
8
10
import {
9
11
OperationObject ,
@@ -21,6 +23,17 @@ const API_SPEC_KEY = 'rest:api-spec';
21
23
22
24
// tslint:disable:no-any
23
25
26
+ function cloneDeep < T > ( val : T ) : T {
27
+ if ( val === undefined ) {
28
+ return { } as T ;
29
+ }
30
+ return _ . cloneDeepWith ( val , v => {
31
+ // Do not clone functions
32
+ if ( typeof v === 'function' ) return v ;
33
+ return undefined ;
34
+ } ) ;
35
+ }
36
+
24
37
export interface ControllerSpec {
25
38
/**
26
39
* The base path on which the Controller API is served.
@@ -73,6 +86,20 @@ interface RestEndpoint {
73
86
target : any ;
74
87
}
75
88
89
+ function getEndpoints (
90
+ target : any ,
91
+ ) : { [ property : string ] : Partial < RestEndpoint > } {
92
+ let endpoints = Reflector . getOwnMetadata ( ENDPOINTS_KEY , target ) ;
93
+ if ( ! endpoints ) {
94
+ // Clone the endpoints so that subclasses won't mutate the metadata
95
+ // in the base class
96
+ const baseEndpoints = Reflector . getMetadata ( ENDPOINTS_KEY , target ) ;
97
+ endpoints = cloneDeep ( baseEndpoints ) ;
98
+ Reflector . defineMetadata ( ENDPOINTS_KEY , endpoints , target ) ;
99
+ }
100
+ return endpoints ;
101
+ }
102
+
76
103
/**
77
104
* Build the api spec from class and method level decorations
78
105
* @param constructor Controller class
@@ -86,33 +113,39 @@ function resolveControllerSpec(
86
113
87
114
if ( spec ) {
88
115
debug ( ' using class-level spec defined via @api()' , spec ) ;
89
- spec = Object . assign ( { } , spec ) ;
116
+ spec = cloneDeep ( spec ) ;
90
117
} else {
91
118
spec = { paths : { } } ;
92
119
}
93
120
94
- const endpoints =
95
- Reflector . getMetadata ( ENDPOINTS_KEY , constructor . prototype ) || { } ;
121
+ const endpoints = getEndpoints ( constructor . prototype ) ;
96
122
97
123
for ( const op in endpoints ) {
98
124
const endpoint = endpoints [ op ] ;
99
- const className =
100
- endpoint . target . constructor . name ||
101
- constructor . name ||
102
- '<AnonymousClass>' ;
103
- const fullMethodName = `${ className } .${ op } ` ;
104
-
105
- const { verb, path} = endpoint ;
106
- const endpointName = `${ fullMethodName } (${ verb } ${ path } )` ;
125
+ const verb = endpoint . verb ! ;
126
+ const path = endpoint . path ! ;
127
+
128
+ let endpointName = '' ;
129
+ if ( debug . enabled ) {
130
+ const className =
131
+ endpoint . target . constructor . name ||
132
+ constructor . name ||
133
+ '<AnonymousClass>' ;
134
+ const fullMethodName = `${ className } .${ op } ` ;
135
+ endpointName = `${ fullMethodName } (${ verb } ${ path } )` ;
136
+ }
107
137
108
138
let operationSpec = endpoint . spec ;
109
139
if ( ! operationSpec ) {
110
140
// The operation was defined via @operation (verb, path) with no spec
111
141
operationSpec = {
112
142
responses : { } ,
113
143
} ;
144
+ endpoint . spec = operationSpec ;
114
145
}
115
146
147
+ operationSpec [ 'x-operation-name' ] = op ;
148
+
116
149
if ( ! spec . paths [ path ] ) {
117
150
spec . paths [ path ] = { } ;
118
151
}
@@ -123,9 +156,7 @@ function resolveControllerSpec(
123
156
}
124
157
125
158
debug ( ` adding ${ endpointName } ` , operationSpec ) ;
126
- spec . paths [ path ] [ verb ] = Object . assign ( { } , operationSpec , {
127
- 'x-operation-name' : op ,
128
- } ) ;
159
+ spec . paths [ path ] [ verb ] = operationSpec ;
129
160
}
130
161
return spec ;
131
162
}
@@ -135,7 +166,7 @@ function resolveControllerSpec(
135
166
* @param constructor Controller class
136
167
*/
137
168
export function getControllerSpec ( constructor : Function ) : ControllerSpec {
138
- let spec = Reflector . getMetadata ( API_SPEC_KEY , constructor ) ;
169
+ let spec = Reflector . getOwnMetadata ( API_SPEC_KEY , constructor ) ;
139
170
if ( ! spec ) {
140
171
spec = resolveControllerSpec ( constructor , spec ) ;
141
172
Reflector . defineMetadata ( API_SPEC_KEY , spec , constructor ) ;
@@ -222,14 +253,9 @@ export function operation(verb: string, path: string, spec?: OperationObject) {
222
253
'@operation decorator can be applied to methods only' ,
223
254
) ;
224
255
225
- let endpoints = Object . assign (
226
- { } ,
227
- Reflector . getMetadata ( ENDPOINTS_KEY , target ) ,
228
- ) ;
229
- Reflector . defineMetadata ( ENDPOINTS_KEY , endpoints , target ) ;
230
-
231
- let endpoint : Partial < RestEndpoint > = endpoints [ propertyKey ] ;
232
- if ( ! endpoint ) {
256
+ const endpoints = getEndpoints ( target ) ;
257
+ let endpoint = endpoints [ propertyKey ] ;
258
+ if ( ! endpoint || endpoint . target !== target ) {
233
259
// Add the new endpoint metadata for the method
234
260
endpoint = { verb, path, spec, target} ;
235
261
endpoints [ propertyKey ] = endpoint ;
@@ -305,16 +331,13 @@ export function param(paramSpec: ParameterObject) {
305
331
'@param decorator can be applied to methods only' ,
306
332
) ;
307
333
308
- let endpoints = Object . assign (
309
- { } ,
310
- Reflector . getMetadata ( ENDPOINTS_KEY , target ) ,
311
- ) ;
312
- Reflector . defineMetadata ( ENDPOINTS_KEY , endpoints , target ) ;
313
-
314
- let endpoint : Partial < RestEndpoint > = endpoints [ propertyKey ] ;
315
- if ( ! endpoint ) {
334
+ const endpoints = getEndpoints ( target ) ;
335
+ let endpoint = endpoints [ propertyKey ] ;
336
+ if ( ! endpoint || endpoint . target !== target ) {
337
+ const baseEndpoint = endpoint ;
316
338
// Add the new endpoint metadata for the method
317
- endpoint = { target} ;
339
+ endpoint = cloneDeep ( baseEndpoint ) ;
340
+ endpoint . target = target ;
318
341
endpoints [ propertyKey ] = endpoint ;
319
342
}
320
343
0 commit comments