@loopback/rest 6.0.0 → 7.0.1
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/CHANGELOG.md +73 -0
- package/dist/body-parsers/body-parser.js +3 -0
- package/dist/body-parsers/body-parser.js.map +1 -1
- package/dist/coercion/coerce-parameter.d.ts +1 -1
- package/dist/coercion/coerce-parameter.js +36 -13
- package/dist/coercion/coerce-parameter.js.map +1 -1
- package/dist/coercion/utils.d.ts +1 -1
- package/dist/http-handler.js +5 -0
- package/dist/http-handler.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/providers/find-route.provider.d.ts +0 -2
- package/dist/providers/find-route.provider.js +11 -8
- package/dist/providers/find-route.provider.js.map +1 -1
- package/dist/providers/invoke-method.provider.d.ts +0 -2
- package/dist/providers/invoke-method.provider.js +21 -10
- package/dist/providers/invoke-method.provider.js.map +1 -1
- package/dist/providers/log-error.provider.js +7 -2
- package/dist/providers/log-error.provider.js.map +1 -1
- package/dist/providers/parse-params.provider.d.ts +0 -2
- package/dist/providers/parse-params.provider.js +16 -8
- package/dist/providers/parse-params.provider.js.map +1 -1
- package/dist/providers/reject.provider.d.ts +1 -1
- package/dist/providers/reject.provider.js +2 -1
- package/dist/providers/reject.provider.js.map +1 -1
- package/dist/providers/send.provider.d.ts +1 -4
- package/dist/providers/send.provider.js +11 -13
- package/dist/providers/send.provider.js.map +1 -1
- package/dist/request-context.js.map +1 -1
- package/dist/rest-http-error.d.ts +3 -1
- package/dist/rest-http-error.js +3 -2
- package/dist/rest-http-error.js.map +1 -1
- package/dist/rest.application.js +2 -2
- package/dist/rest.application.js.map +1 -1
- package/dist/rest.component.js +0 -2
- package/dist/rest.component.js.map +1 -1
- package/dist/rest.server.d.ts +3 -2
- package/dist/rest.server.js +14 -7
- package/dist/rest.server.js.map +1 -1
- package/dist/router/base-route.js +3 -3
- package/dist/router/base-route.js.map +1 -1
- package/dist/router/controller-route.js +1 -1
- package/dist/router/controller-route.js.map +1 -1
- package/dist/router/handler-route.js +1 -1
- package/dist/router/handler-route.js.map +1 -1
- package/dist/router/redirect-route.js +1 -1
- package/dist/router/redirect-route.js.map +1 -1
- package/dist/sequence.d.ts +3 -2
- package/dist/sequence.js +30 -7
- package/dist/sequence.js.map +1 -1
- package/dist/spec-enhancers/consolidate.spec-enhancer.js +1 -1
- package/dist/spec-enhancers/consolidate.spec-enhancer.js.map +1 -1
- package/dist/spec-enhancers/info.spec-enhancer.js +13 -5
- package/dist/spec-enhancers/info.spec-enhancer.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/validation/ajv-factory.provider.js +5 -3
- package/dist/validation/ajv-factory.provider.js.map +1 -1
- package/dist/validation/openapi-formats.d.ts +26 -0
- package/dist/validation/openapi-formats.js +85 -0
- package/dist/validation/openapi-formats.js.map +1 -0
- package/dist/validation/request-body.validator.d.ts +4 -4
- package/dist/validation/request-body.validator.js +14 -19
- package/dist/validation/request-body.validator.js.map +1 -1
- package/package.json +24 -21
- package/src/body-parsers/body-parser.ts +3 -0
- package/src/coercion/coerce-parameter.ts +44 -20
- package/src/http-handler.ts +6 -0
- package/src/providers/find-route.provider.ts +24 -8
- package/src/providers/invoke-method.provider.ts +28 -10
- package/src/providers/log-error.provider.ts +2 -1
- package/src/providers/parse-params.provider.ts +28 -8
- package/src/providers/reject.provider.ts +4 -3
- package/src/providers/send.provider.ts +9 -12
- package/src/request-context.ts +2 -1
- package/src/rest-http-error.ts +6 -2
- package/src/rest.application.ts +2 -2
- package/src/rest.component.ts +0 -2
- package/src/rest.server.ts +17 -8
- package/src/router/base-route.ts +3 -3
- package/src/router/controller-route.ts +1 -1
- package/src/router/handler-route.ts +3 -1
- package/src/router/redirect-route.ts +1 -1
- package/src/sequence.ts +45 -3
- package/src/spec-enhancers/consolidate.spec-enhancer.ts +2 -2
- package/src/spec-enhancers/info.spec-enhancer.ts +15 -6
- package/src/types.ts +5 -0
- package/src/validation/ajv-factory.provider.ts +8 -4
- package/src/validation/openapi-formats.ts +92 -0
- package/src/validation/request-body.validator.ts +26 -25
|
@@ -3,14 +3,23 @@
|
|
|
3
3
|
// This file is licensed under the MIT License.
|
|
4
4
|
// License text available at https://opensource.org/licenses/MIT
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
BindingScope,
|
|
8
|
+
Context,
|
|
9
|
+
inject,
|
|
10
|
+
injectable,
|
|
11
|
+
Provider,
|
|
12
|
+
} from '@loopback/core';
|
|
7
13
|
import {asMiddleware, Middleware} from '@loopback/express';
|
|
14
|
+
import debugFactory from 'debug';
|
|
8
15
|
import {HttpHandler} from '../http-handler';
|
|
9
16
|
import {RestBindings, RestTags} from '../keys';
|
|
10
17
|
import {ResolvedRoute} from '../router';
|
|
11
18
|
import {RestMiddlewareGroups} from '../sequence';
|
|
12
19
|
import {FindRoute, Request} from '../types';
|
|
13
20
|
|
|
21
|
+
const debug = debugFactory('loopback:rest:find-route');
|
|
22
|
+
|
|
14
23
|
export class FindRouteProvider implements Provider<FindRoute> {
|
|
15
24
|
constructor(
|
|
16
25
|
@inject(RestBindings.Http.CONTEXT) protected context: Context,
|
|
@@ -23,26 +32,33 @@ export class FindRouteProvider implements Provider<FindRoute> {
|
|
|
23
32
|
|
|
24
33
|
action(request: Request): ResolvedRoute {
|
|
25
34
|
const found = this.handler.findRoute(request);
|
|
35
|
+
debug('Route found for %s %s', request.method, request.originalUrl, found);
|
|
26
36
|
found.updateBindings(this.context);
|
|
27
37
|
return found;
|
|
28
38
|
}
|
|
29
39
|
}
|
|
30
40
|
|
|
31
|
-
@
|
|
41
|
+
@injectable(
|
|
32
42
|
asMiddleware({
|
|
33
43
|
group: RestMiddlewareGroups.FIND_ROUTE,
|
|
34
44
|
chain: RestTags.REST_MIDDLEWARE_CHAIN,
|
|
35
45
|
}),
|
|
46
|
+
{scope: BindingScope.SINGLETON},
|
|
36
47
|
)
|
|
37
48
|
export class FindRouteMiddlewareProvider implements Provider<Middleware> {
|
|
38
|
-
constructor(
|
|
39
|
-
@inject(RestBindings.SequenceActions.FIND_ROUTE)
|
|
40
|
-
protected findRoute: FindRoute,
|
|
41
|
-
) {}
|
|
42
|
-
|
|
43
49
|
value(): Middleware {
|
|
44
50
|
return async (ctx, next) => {
|
|
45
|
-
const
|
|
51
|
+
const request = ctx.request;
|
|
52
|
+
debug('Finding route for %s %s', request.method, request.originalUrl);
|
|
53
|
+
const handler = await ctx.get(RestBindings.HANDLER);
|
|
54
|
+
const route = handler.findRoute(request);
|
|
55
|
+
debug(
|
|
56
|
+
'Route found for %s %s',
|
|
57
|
+
request.method,
|
|
58
|
+
request.originalUrl,
|
|
59
|
+
route,
|
|
60
|
+
);
|
|
61
|
+
route.updateBindings(ctx);
|
|
46
62
|
ctx.bind(RestBindings.Operation.ROUTE).to(route);
|
|
47
63
|
return next();
|
|
48
64
|
};
|
|
@@ -3,13 +3,22 @@
|
|
|
3
3
|
// This file is licensed under the MIT License.
|
|
4
4
|
// License text available at https://opensource.org/licenses/MIT
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
BindingScope,
|
|
8
|
+
Context,
|
|
9
|
+
inject,
|
|
10
|
+
injectable,
|
|
11
|
+
Provider,
|
|
12
|
+
} from '@loopback/core';
|
|
7
13
|
import {asMiddleware, Middleware} from '@loopback/express';
|
|
14
|
+
import debugFactory from 'debug';
|
|
8
15
|
import {RestBindings, RestTags} from '../keys';
|
|
9
16
|
import {RouteEntry} from '../router';
|
|
10
17
|
import {RestMiddlewareGroups} from '../sequence';
|
|
11
18
|
import {InvokeMethod, OperationArgs, OperationRetval} from '../types';
|
|
12
19
|
|
|
20
|
+
const debug = debugFactory('loopback:rest:invoke-method');
|
|
21
|
+
|
|
13
22
|
export class InvokeMethodProvider implements Provider<InvokeMethod> {
|
|
14
23
|
constructor(@inject(RestBindings.Http.CONTEXT) protected context: Context) {}
|
|
15
24
|
|
|
@@ -22,28 +31,37 @@ export class InvokeMethodProvider implements Provider<InvokeMethod> {
|
|
|
22
31
|
}
|
|
23
32
|
}
|
|
24
33
|
|
|
25
|
-
@
|
|
34
|
+
@injectable(
|
|
26
35
|
asMiddleware({
|
|
27
36
|
group: RestMiddlewareGroups.INVOKE_METHOD,
|
|
28
37
|
upstreamGroups: RestMiddlewareGroups.PARSE_PARAMS,
|
|
29
38
|
chain: RestTags.REST_MIDDLEWARE_CHAIN,
|
|
30
39
|
}),
|
|
40
|
+
{scope: BindingScope.SINGLETON},
|
|
31
41
|
)
|
|
32
42
|
export class InvokeMethodMiddlewareProvider implements Provider<Middleware> {
|
|
33
|
-
constructor(
|
|
34
|
-
@inject(RestBindings.SequenceActions.INVOKE_METHOD)
|
|
35
|
-
protected invokeMethod: InvokeMethod,
|
|
36
|
-
) {}
|
|
37
|
-
|
|
38
43
|
value(): Middleware {
|
|
39
44
|
return async (ctx, next) => {
|
|
40
45
|
const route: RouteEntry = await ctx.get(RestBindings.Operation.ROUTE);
|
|
41
46
|
const params: OperationArgs = await ctx.get(
|
|
42
47
|
RestBindings.Operation.PARAMS,
|
|
43
48
|
);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
if (debug.enabled) {
|
|
50
|
+
debug('Invoking method %s with', route.describe(), params);
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const retVal = await route.invokeHandler(ctx, params);
|
|
54
|
+
ctx.bind(RestBindings.Operation.RETURN_VALUE).to(retVal);
|
|
55
|
+
if (debug.enabled) {
|
|
56
|
+
debug('Return value from %s', route.describe(), retVal);
|
|
57
|
+
}
|
|
58
|
+
return retVal;
|
|
59
|
+
} catch (err) {
|
|
60
|
+
if (debug.enabled) {
|
|
61
|
+
debug('Error thrown from %s', route.describe(), err);
|
|
62
|
+
}
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
47
65
|
};
|
|
48
66
|
}
|
|
49
67
|
}
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
// This file is licensed under the MIT License.
|
|
4
4
|
// License text available at https://opensource.org/licenses/MIT
|
|
5
5
|
|
|
6
|
-
import {Provider} from '@loopback/core';
|
|
6
|
+
import {BindingScope, injectable, Provider} from '@loopback/core';
|
|
7
7
|
import {LogError, Request} from '../types';
|
|
8
8
|
|
|
9
|
+
@injectable({scope: BindingScope.SINGLETON})
|
|
9
10
|
export class LogErrorProvider implements Provider<LogError> {
|
|
10
11
|
value(): LogError {
|
|
11
12
|
return (err, statusCode, req) => this.action(err, statusCode, req);
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
// This file is licensed under the MIT License.
|
|
4
4
|
// License text available at https://opensource.org/licenses/MIT
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {BindingScope, inject, injectable, Provider} from '@loopback/core';
|
|
7
7
|
import {asMiddleware, Middleware} from '@loopback/express';
|
|
8
|
+
import debugFactory from 'debug';
|
|
8
9
|
import {RequestBodyParser} from '../body-parsers';
|
|
9
10
|
import {RestBindings, RestTags} from '../keys';
|
|
10
11
|
import {parseOperationArgs} from '../parser';
|
|
@@ -12,6 +13,9 @@ import {ResolvedRoute} from '../router';
|
|
|
12
13
|
import {RestMiddlewareGroups} from '../sequence';
|
|
13
14
|
import {AjvFactory, ParseParams, Request, ValidationOptions} from '../types';
|
|
14
15
|
import {DEFAULT_AJV_VALIDATION_OPTIONS} from '../validation/ajv-factory.provider';
|
|
16
|
+
|
|
17
|
+
const debug = debugFactory('loopback:rest:parse-param');
|
|
18
|
+
|
|
15
19
|
/**
|
|
16
20
|
* Provides the function for parsing args in requests at runtime.
|
|
17
21
|
*
|
|
@@ -39,24 +43,40 @@ export class ParseParamsProvider implements Provider<ParseParams> {
|
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
@
|
|
46
|
+
@injectable(
|
|
43
47
|
asMiddleware({
|
|
44
48
|
group: RestMiddlewareGroups.PARSE_PARAMS,
|
|
45
49
|
upstreamGroups: RestMiddlewareGroups.FIND_ROUTE,
|
|
46
50
|
chain: RestTags.REST_MIDDLEWARE_CHAIN,
|
|
47
51
|
}),
|
|
52
|
+
{scope: BindingScope.SINGLETON},
|
|
48
53
|
)
|
|
49
54
|
export class ParseParamsMiddlewareProvider implements Provider<Middleware> {
|
|
50
|
-
constructor(
|
|
51
|
-
@inject(RestBindings.SequenceActions.PARSE_PARAMS)
|
|
52
|
-
protected parseParams: ParseParams,
|
|
53
|
-
) {}
|
|
54
|
-
|
|
55
55
|
value(): Middleware {
|
|
56
56
|
return async (ctx, next) => {
|
|
57
|
+
const requestBodyParser = await ctx.get(RestBindings.REQUEST_BODY_PARSER);
|
|
58
|
+
const validationOptions: ValidationOptions =
|
|
59
|
+
(await ctx.get(
|
|
60
|
+
RestBindings.REQUEST_BODY_PARSER_OPTIONS.deepProperty('validation'),
|
|
61
|
+
{optional: true},
|
|
62
|
+
)) ?? DEFAULT_AJV_VALIDATION_OPTIONS;
|
|
63
|
+
const ajvFactory = await ctx.get(RestBindings.AJV_FACTORY, {
|
|
64
|
+
optional: true,
|
|
65
|
+
});
|
|
66
|
+
|
|
57
67
|
const route: ResolvedRoute = await ctx.get(RestBindings.Operation.ROUTE);
|
|
58
|
-
|
|
68
|
+
debug('Parsing parameters for %s %s', route.verb, route.path);
|
|
69
|
+
const params = await parseOperationArgs(
|
|
70
|
+
ctx.request,
|
|
71
|
+
route,
|
|
72
|
+
requestBodyParser,
|
|
73
|
+
{
|
|
74
|
+
ajvFactory: ajvFactory,
|
|
75
|
+
...validationOptions,
|
|
76
|
+
},
|
|
77
|
+
);
|
|
59
78
|
ctx.bind(RestBindings.Operation.PARAMS).to(params);
|
|
79
|
+
debug('Parameters', params);
|
|
60
80
|
return next();
|
|
61
81
|
};
|
|
62
82
|
}
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
// This file is licensed under the MIT License.
|
|
4
4
|
// License text available at https://opensource.org/licenses/MIT
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {inject, Provider} from '@loopback/core';
|
|
6
|
+
import {BindingScope, inject, injectable, Provider} from '@loopback/core';
|
|
8
7
|
import {HttpError} from 'http-errors';
|
|
8
|
+
import {ErrorWriterOptions, writeErrorToResponse} from 'strong-error-handler';
|
|
9
9
|
import {RestBindings} from '../keys';
|
|
10
|
-
import {
|
|
10
|
+
import {HandlerContext, LogError, Reject} from '../types';
|
|
11
11
|
|
|
12
12
|
// TODO(bajtos) Make this mapping configurable at RestServer level,
|
|
13
13
|
// allow apps and extensions to contribute additional mappings.
|
|
@@ -15,6 +15,7 @@ const codeToStatusCodeMap: {[key: string]: number} = {
|
|
|
15
15
|
ENTITY_NOT_FOUND: 404,
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
@injectable({scope: BindingScope.SINGLETON})
|
|
18
19
|
export class RejectProvider implements Provider<Reject> {
|
|
19
20
|
constructor(
|
|
20
21
|
@inject(RestBindings.SequenceActions.LOG_ERROR)
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
// This file is licensed under the MIT License.
|
|
4
4
|
// License text available at https://opensource.org/licenses/MIT
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {BindingScope, injectable, Provider} from '@loopback/core';
|
|
7
7
|
import {asMiddleware, Middleware} from '@loopback/express';
|
|
8
8
|
import {RestBindings, RestTags} from '../keys';
|
|
9
9
|
import {RestMiddlewareGroups} from '../sequence';
|
|
10
|
-
import {
|
|
10
|
+
import {Send} from '../types';
|
|
11
11
|
import {writeResultToResponse} from '../writer';
|
|
12
12
|
/**
|
|
13
13
|
* Provides the function that populates the response object with
|
|
@@ -16,13 +16,14 @@ import {writeResultToResponse} from '../writer';
|
|
|
16
16
|
* @returns The handler function that will populate the
|
|
17
17
|
* response with operation results.
|
|
18
18
|
*/
|
|
19
|
+
@injectable({scope: BindingScope.SINGLETON})
|
|
19
20
|
export class SendProvider implements Provider<Send> {
|
|
20
21
|
value() {
|
|
21
22
|
return writeResultToResponse;
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
@
|
|
26
|
+
@injectable(
|
|
26
27
|
asMiddleware({
|
|
27
28
|
group: RestMiddlewareGroups.SEND_RESPONSE,
|
|
28
29
|
downstreamGroups: [
|
|
@@ -31,17 +32,13 @@ export class SendProvider implements Provider<Send> {
|
|
|
31
32
|
],
|
|
32
33
|
chain: RestTags.REST_MIDDLEWARE_CHAIN,
|
|
33
34
|
}),
|
|
35
|
+
{scope: BindingScope.SINGLETON},
|
|
34
36
|
)
|
|
35
37
|
export class SendResponseMiddlewareProvider implements Provider<Middleware> {
|
|
36
|
-
constructor(
|
|
37
|
-
@inject(RestBindings.SequenceActions.SEND)
|
|
38
|
-
protected send: Send,
|
|
39
|
-
@inject(RestBindings.SequenceActions.REJECT)
|
|
40
|
-
protected reject: Reject,
|
|
41
|
-
) {}
|
|
42
|
-
|
|
43
38
|
value(): Middleware {
|
|
44
39
|
return async (ctx, next) => {
|
|
40
|
+
const send = await ctx.get(RestBindings.SequenceActions.SEND);
|
|
41
|
+
const reject = await ctx.get(RestBindings.SequenceActions.REJECT);
|
|
45
42
|
try {
|
|
46
43
|
/**
|
|
47
44
|
* Invoke downstream middleware to produce the result
|
|
@@ -50,12 +47,12 @@ export class SendResponseMiddlewareProvider implements Provider<Middleware> {
|
|
|
50
47
|
/**
|
|
51
48
|
* Write the result to HTTP response
|
|
52
49
|
*/
|
|
53
|
-
|
|
50
|
+
send(ctx.response, result);
|
|
54
51
|
} catch (err) {
|
|
55
52
|
/**
|
|
56
53
|
* Write the error to HTTP response
|
|
57
54
|
*/
|
|
58
|
-
|
|
55
|
+
reject(ctx, err);
|
|
59
56
|
}
|
|
60
57
|
};
|
|
61
58
|
}
|
package/src/request-context.ts
CHANGED
|
@@ -17,7 +17,8 @@ import {RestServerResolvedConfig} from './rest.server';
|
|
|
17
17
|
* A per-request Context combining an IoC container with handler context
|
|
18
18
|
* (request, response, etc.).
|
|
19
19
|
*/
|
|
20
|
-
export class RequestContext
|
|
20
|
+
export class RequestContext
|
|
21
|
+
extends MiddlewareContext
|
|
21
22
|
implements HandlerContext {
|
|
22
23
|
/**
|
|
23
24
|
* Get the protocol used by the client to make the request.
|
package/src/rest-http-error.ts
CHANGED
|
@@ -11,7 +11,7 @@ export namespace RestHttpErrors {
|
|
|
11
11
|
name: string,
|
|
12
12
|
extraProperties?: Props,
|
|
13
13
|
): HttpErrors.HttpError & Props {
|
|
14
|
-
const msg = `Invalid data ${JSON.stringify(data)} for parameter ${name}
|
|
14
|
+
const msg = `Invalid data ${JSON.stringify(data)} for parameter "${name}".`;
|
|
15
15
|
return Object.assign(
|
|
16
16
|
new HttpErrors.BadRequest(msg),
|
|
17
17
|
{
|
|
@@ -51,11 +51,15 @@ export namespace RestHttpErrors {
|
|
|
51
51
|
|
|
52
52
|
export const INVALID_REQUEST_BODY_MESSAGE =
|
|
53
53
|
'The request body is invalid. See error object `details` property for more info.';
|
|
54
|
-
|
|
54
|
+
|
|
55
|
+
export function invalidRequestBody(
|
|
56
|
+
details: ValidationErrorDetails[],
|
|
57
|
+
): HttpErrors.HttpError & {details: ValidationErrorDetails[]} {
|
|
55
58
|
return Object.assign(
|
|
56
59
|
new HttpErrors.UnprocessableEntity(INVALID_REQUEST_BODY_MESSAGE),
|
|
57
60
|
{
|
|
58
61
|
code: 'VALIDATION_FAILED',
|
|
62
|
+
details,
|
|
59
63
|
},
|
|
60
64
|
);
|
|
61
65
|
}
|
package/src/rest.application.ts
CHANGED
|
@@ -102,7 +102,7 @@ export class RestApplication extends Application implements HttpServerLike {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
sequence(sequence: Constructor<SequenceHandler>): Binding {
|
|
105
|
-
return this.
|
|
105
|
+
return this.restServer.sequence(sequence);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
handler(handlerFn: SequenceFunction) {
|
|
@@ -368,7 +368,7 @@ export class RestApplication extends Application implements HttpServerLike {
|
|
|
368
368
|
* @returns Binding for the api spec
|
|
369
369
|
*/
|
|
370
370
|
api(spec: OpenApiSpec): Binding {
|
|
371
|
-
return this.bind(RestBindings.API_SPEC).to(spec);
|
|
371
|
+
return this.restServer.bind(RestBindings.API_SPEC).to(spec);
|
|
372
372
|
}
|
|
373
373
|
|
|
374
374
|
/**
|
package/src/rest.component.ts
CHANGED
|
@@ -43,7 +43,6 @@ import {
|
|
|
43
43
|
RestServer,
|
|
44
44
|
RestServerConfig,
|
|
45
45
|
} from './rest.server';
|
|
46
|
-
import {MiddlewareSequence} from './sequence';
|
|
47
46
|
import {ConsolidationEnhancer} from './spec-enhancers/consolidate.spec-enhancer';
|
|
48
47
|
import {InfoSpecEnhancer} from './spec-enhancers/info.spec-enhancer';
|
|
49
48
|
import {AjvFactoryProvider} from './validation/ajv-factory.provider';
|
|
@@ -120,7 +119,6 @@ export class RestComponent implements Component {
|
|
|
120
119
|
).tag({[CoreTags.EXTENSION_POINT]: RestTags.REST_MIDDLEWARE_CHAIN});
|
|
121
120
|
app.add(invokeMiddlewareServiceBinding);
|
|
122
121
|
|
|
123
|
-
app.bind(RestBindings.SEQUENCE).toClass(MiddlewareSequence);
|
|
124
122
|
const apiSpec = createEmptyApiSpec();
|
|
125
123
|
// Merge the OpenAPI `servers` spec from the config into the empty one
|
|
126
124
|
if (config?.openApiSpec?.servers) {
|
package/src/rest.server.ts
CHANGED
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
import {assignRouterSpec} from './router/router-spec';
|
|
65
65
|
import {
|
|
66
66
|
DefaultSequence,
|
|
67
|
+
MiddlewareSequence,
|
|
67
68
|
RestMiddlewareGroups,
|
|
68
69
|
SequenceFunction,
|
|
69
70
|
SequenceHandler,
|
|
@@ -110,7 +111,8 @@ const SequenceActions = RestBindings.SequenceActions;
|
|
|
110
111
|
* const server = await app.get('servers.foo');
|
|
111
112
|
* ```
|
|
112
113
|
*/
|
|
113
|
-
export class RestServer
|
|
114
|
+
export class RestServer
|
|
115
|
+
extends BaseMiddlewareRegistry
|
|
114
116
|
implements Server, HttpServerLike {
|
|
115
117
|
/**
|
|
116
118
|
* Handle incoming HTTP(S) request by invoking the corresponding
|
|
@@ -169,6 +171,10 @@ export class RestServer extends BaseMiddlewareRegistry
|
|
|
169
171
|
return this._httpServer ? this._httpServer.listening : false;
|
|
170
172
|
}
|
|
171
173
|
|
|
174
|
+
get httpServer(): HttpServer | undefined {
|
|
175
|
+
return this._httpServer;
|
|
176
|
+
}
|
|
177
|
+
|
|
172
178
|
/**
|
|
173
179
|
* The base url for the server, including the basePath if set. For example,
|
|
174
180
|
* the value will be 'http://localhost:3000/api' if `basePath` is set to
|
|
@@ -222,6 +228,8 @@ export class RestServer extends BaseMiddlewareRegistry
|
|
|
222
228
|
|
|
223
229
|
if (config.sequence) {
|
|
224
230
|
this.sequence(config.sequence);
|
|
231
|
+
} else {
|
|
232
|
+
this.sequence(MiddlewareSequence);
|
|
225
233
|
}
|
|
226
234
|
|
|
227
235
|
if (config.router) {
|
|
@@ -889,10 +897,14 @@ export class RestServer extends BaseMiddlewareRegistry
|
|
|
889
897
|
* }
|
|
890
898
|
* ```
|
|
891
899
|
*
|
|
892
|
-
* @param
|
|
900
|
+
* @param sequenceClass - The sequence class to invoke for each incoming request.
|
|
893
901
|
*/
|
|
894
|
-
public sequence(
|
|
895
|
-
|
|
902
|
+
public sequence(sequenceClass: Constructor<SequenceHandler>) {
|
|
903
|
+
const sequenceBinding = createBindingFromClass(sequenceClass, {
|
|
904
|
+
key: RestBindings.SEQUENCE,
|
|
905
|
+
});
|
|
906
|
+
this.add(sequenceBinding);
|
|
907
|
+
return sequenceBinding;
|
|
896
908
|
}
|
|
897
909
|
|
|
898
910
|
/**
|
|
@@ -971,10 +983,7 @@ export class RestServer extends BaseMiddlewareRegistry
|
|
|
971
983
|
return;
|
|
972
984
|
}
|
|
973
985
|
|
|
974
|
-
const serverOptions = {};
|
|
975
|
-
if (protocol === 'https') Object.assign(serverOptions, httpsOptions);
|
|
976
|
-
Object.assign(serverOptions, {port, host, protocol, path});
|
|
977
|
-
|
|
986
|
+
const serverOptions = {...httpsOptions, port, host, protocol, path};
|
|
978
987
|
this._httpServer = new HttpServer(this.requestHandler, serverOptions);
|
|
979
988
|
|
|
980
989
|
await this._httpServer.start();
|
package/src/router/base-route.ts
CHANGED
|
@@ -36,11 +36,11 @@ export abstract class BaseRoute implements RouteEntry {
|
|
|
36
36
|
): Promise<OperationRetval>;
|
|
37
37
|
|
|
38
38
|
describe(): string {
|
|
39
|
-
return
|
|
39
|
+
return `${this.verb} ${this.path}`;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
toString() {
|
|
43
|
-
return `${this.constructor.name} - ${this.
|
|
43
|
+
return `${this.constructor.name} - ${this.describe()}`;
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -48,6 +48,6 @@ export class RouteSource implements InvocationSource<RouteEntry> {
|
|
|
48
48
|
type = 'route';
|
|
49
49
|
constructor(readonly value: RouteEntry) {}
|
|
50
50
|
toString() {
|
|
51
|
-
return
|
|
51
|
+
return this.value.toString();
|
|
52
52
|
}
|
|
53
53
|
}
|
|
@@ -101,7 +101,7 @@ export class ControllerRoute<T> extends BaseRoute {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
describe(): string {
|
|
104
|
-
return `${this._controllerName}.${this._methodName}`;
|
|
104
|
+
return `${super.describe()} => ${this._controllerName}.${this._methodName}`;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
updateBindings(requestContext: Context) {
|
|
@@ -20,7 +20,9 @@ export class Route extends BaseRoute {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
describe(): string {
|
|
23
|
-
return
|
|
23
|
+
return `${super.describe()} => ${
|
|
24
|
+
this._handler.name || this._handler.toString()
|
|
25
|
+
}`;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
updateBindings(requestContext: Context) {
|
|
@@ -42,7 +42,7 @@ export class RedirectRoute implements RouteEntry, ResolvedRoute {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
describe(): string {
|
|
45
|
-
return `
|
|
45
|
+
return `Redirect: "${this.sourcePath}" => "${this.targetLocation}"`;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/**
|
package/src/sequence.ts
CHANGED
|
@@ -3,11 +3,19 @@
|
|
|
3
3
|
// This file is licensed under the MIT License.
|
|
4
4
|
// License text available at https://opensource.org/licenses/MIT
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
BindingScope,
|
|
8
|
+
config,
|
|
9
|
+
Context,
|
|
10
|
+
inject,
|
|
11
|
+
injectable,
|
|
12
|
+
ValueOrPromise,
|
|
13
|
+
} from '@loopback/core';
|
|
7
14
|
import {
|
|
8
15
|
InvokeMiddleware,
|
|
9
16
|
InvokeMiddlewareOptions,
|
|
10
17
|
MiddlewareGroups,
|
|
18
|
+
MiddlewareView,
|
|
11
19
|
} from '@loopback/express';
|
|
12
20
|
import debugFactory from 'debug';
|
|
13
21
|
import {RestBindings, RestTags} from './keys';
|
|
@@ -177,7 +185,10 @@ export namespace RestMiddlewareGroups {
|
|
|
177
185
|
/**
|
|
178
186
|
* A sequence implementation using middleware chains
|
|
179
187
|
*/
|
|
188
|
+
@injectable({scope: BindingScope.SINGLETON})
|
|
180
189
|
export class MiddlewareSequence implements SequenceHandler {
|
|
190
|
+
private middlewareView: MiddlewareView;
|
|
191
|
+
|
|
181
192
|
static defaultOptions: InvokeMiddlewareOptions = {
|
|
182
193
|
chain: RestTags.REST_MIDDLEWARE_CHAIN,
|
|
183
194
|
orderedGroups: [
|
|
@@ -199,6 +210,26 @@ export class MiddlewareSequence implements SequenceHandler {
|
|
|
199
210
|
|
|
200
211
|
RestMiddlewareGroups.INVOKE_METHOD,
|
|
201
212
|
],
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Reports an error if there are middleware groups are unreachable as they
|
|
216
|
+
* are ordered after the `invokeMethod` group.
|
|
217
|
+
*/
|
|
218
|
+
validate: groups => {
|
|
219
|
+
const index = groups.indexOf(RestMiddlewareGroups.INVOKE_METHOD);
|
|
220
|
+
if (index !== -1) {
|
|
221
|
+
const unreachableGroups = groups.slice(index + 1);
|
|
222
|
+
if (unreachableGroups.length > 0) {
|
|
223
|
+
throw new Error(
|
|
224
|
+
`Middleware groups "${unreachableGroups.join(
|
|
225
|
+
',',
|
|
226
|
+
)}" are not invoked as they are ordered after "${
|
|
227
|
+
RestMiddlewareGroups.INVOKE_METHOD
|
|
228
|
+
}"`,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
},
|
|
202
233
|
};
|
|
203
234
|
|
|
204
235
|
/**
|
|
@@ -208,11 +239,17 @@ export class MiddlewareSequence implements SequenceHandler {
|
|
|
208
239
|
* To be injected via RestBindings.INVOKE_MIDDLEWARE_SERVICE.
|
|
209
240
|
*/
|
|
210
241
|
constructor(
|
|
242
|
+
@inject.context()
|
|
243
|
+
context: Context,
|
|
244
|
+
|
|
211
245
|
@inject(RestBindings.INVOKE_MIDDLEWARE_SERVICE)
|
|
212
246
|
readonly invokeMiddleware: InvokeMiddleware,
|
|
213
247
|
@config()
|
|
214
248
|
readonly options: InvokeMiddlewareOptions = MiddlewareSequence.defaultOptions,
|
|
215
|
-
) {
|
|
249
|
+
) {
|
|
250
|
+
this.middlewareView = new MiddlewareView(context, options);
|
|
251
|
+
debug('Discovered middleware', this.middlewareView.middlewareBindingKeys);
|
|
252
|
+
}
|
|
216
253
|
|
|
217
254
|
/**
|
|
218
255
|
* Runs the default sequence. Given a handler context (request and response),
|
|
@@ -246,6 +283,11 @@ export class MiddlewareSequence implements SequenceHandler {
|
|
|
246
283
|
this.options.chain,
|
|
247
284
|
this.options.orderedGroups,
|
|
248
285
|
);
|
|
249
|
-
|
|
286
|
+
const options: InvokeMiddlewareOptions = {
|
|
287
|
+
middlewareList: this.middlewareView.middlewareBindingKeys,
|
|
288
|
+
validate: MiddlewareSequence.defaultOptions.validate,
|
|
289
|
+
...this.options,
|
|
290
|
+
};
|
|
291
|
+
await this.invokeMiddleware(context, options);
|
|
250
292
|
}
|
|
251
293
|
}
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
ApplicationConfig,
|
|
8
|
-
bind,
|
|
9
8
|
BindingScope,
|
|
10
9
|
CoreBindings,
|
|
11
10
|
inject,
|
|
11
|
+
injectable,
|
|
12
12
|
} from '@loopback/core';
|
|
13
13
|
import {
|
|
14
14
|
asSpecEnhancer,
|
|
@@ -58,7 +58,7 @@ const debug = debugFactory('loopback:openapi:spec-enhancer:consolidate');
|
|
|
58
58
|
* When comparing schemas to avoid naming collisions, the description field
|
|
59
59
|
* is ignored.
|
|
60
60
|
*/
|
|
61
|
-
@
|
|
61
|
+
@injectable(asSpecEnhancer, {scope: BindingScope.SINGLETON})
|
|
62
62
|
export class ConsolidationEnhancer implements OASEnhancer {
|
|
63
63
|
name = 'consolidate';
|
|
64
64
|
disabled: boolean;
|
|
@@ -5,16 +5,17 @@
|
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
ApplicationMetadata,
|
|
8
|
-
bind,
|
|
9
8
|
BindingScope,
|
|
10
9
|
CoreBindings,
|
|
11
10
|
inject,
|
|
11
|
+
injectable,
|
|
12
12
|
JSONObject,
|
|
13
13
|
JSONValue,
|
|
14
14
|
} from '@loopback/core';
|
|
15
15
|
import {
|
|
16
16
|
asSpecEnhancer,
|
|
17
17
|
ContactObject,
|
|
18
|
+
DEFAULT_OPENAPI_SPEC_INFO,
|
|
18
19
|
mergeOpenAPISpec,
|
|
19
20
|
OASEnhancer,
|
|
20
21
|
OpenApiSpec,
|
|
@@ -27,7 +28,7 @@ const debug = debugFactory('loopback:openapi:spec-enhancer:info');
|
|
|
27
28
|
* An OpenAPI spec enhancer to populate `info` with application metadata
|
|
28
29
|
* (package.json).
|
|
29
30
|
*/
|
|
30
|
-
@
|
|
31
|
+
@injectable(asSpecEnhancer, {scope: BindingScope.SINGLETON})
|
|
31
32
|
export class InfoSpecEnhancer implements OASEnhancer {
|
|
32
33
|
name = 'info';
|
|
33
34
|
|
|
@@ -44,12 +45,20 @@ export class InfoSpecEnhancer implements OASEnhancer {
|
|
|
44
45
|
const contact: ContactObject = InfoSpecEnhancer.parseAuthor(
|
|
45
46
|
this.pkg.author,
|
|
46
47
|
);
|
|
48
|
+
// Only override `info` if the `spec.info` is not customized
|
|
49
|
+
const overrideInfo =
|
|
50
|
+
spec.info.title === DEFAULT_OPENAPI_SPEC_INFO.title &&
|
|
51
|
+
spec.info.version === DEFAULT_OPENAPI_SPEC_INFO.version;
|
|
47
52
|
const patchSpec = {
|
|
48
53
|
info: {
|
|
49
|
-
title: this.pkg.name,
|
|
50
|
-
description:
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
title: overrideInfo ? this.pkg.name : spec.info.title ?? this.pkg.name,
|
|
55
|
+
description: overrideInfo
|
|
56
|
+
? this.pkg.description
|
|
57
|
+
: spec.info.description ?? this.pkg.description,
|
|
58
|
+
version: overrideInfo
|
|
59
|
+
? this.pkg.version
|
|
60
|
+
: spec.info.version ?? this.pkg.version,
|
|
61
|
+
contact: overrideInfo ? contact : spec.info.contact ?? contact,
|
|
53
62
|
},
|
|
54
63
|
};
|
|
55
64
|
debug('Enhancing OpenAPI spec with %j', patchSpec);
|
package/src/types.ts
CHANGED
|
@@ -117,6 +117,11 @@ export interface ValueValidationOptions extends ValidationOptions {
|
|
|
117
117
|
* 'query', 'cookie', etc...
|
|
118
118
|
*/
|
|
119
119
|
source?: string;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Parameter name, as provided in `ParameterObject#name` property.
|
|
123
|
+
*/
|
|
124
|
+
name?: string;
|
|
120
125
|
}
|
|
121
126
|
|
|
122
127
|
/**
|