UNPKG

3.82 kBPlain TextView Raw
1// Copyright IBM Corp. and LoopBack contributors 2017,2020. All Rights Reserved.
2// Node module: @loopback/rest
3// This file is licensed under the MIT License.
4// License text available at https://opensource.org/licenses/MIT
5
6import {
7 isReferenceObject,
8 OperationObject,
9 ParameterObject,
10 REQUEST_BODY_INDEX,
11 SchemasObject,
12} from '@loopback/openapi-v3';
13import debugFactory from 'debug';
14import {RequestBody, RequestBodyParser} from './body-parsers';
15import {coerceParameter} from './coercion/coerce-parameter';
16import {RestHttpErrors} from './rest-http-error';
17import {ResolvedRoute} from './router';
18import {
19 OperationArgs,
20 PathParameterValues,
21 Request,
22 ValidationOptions,
23} from './types';
24import {DEFAULT_AJV_VALIDATION_OPTIONS} from './validation/ajv-factory.provider';
25import {validateRequestBody} from './validation/request-body.validator';
26const debug = debugFactory('loopback:rest:parser');
27
28/**
29 * Parses the request to derive arguments to be passed in for the Application
30 * controller method
31 *
32 * @param request - Incoming HTTP request
33 * @param route - Resolved Route
34 */
35export async function parseOperationArgs(
36 request: Request,
37 route: ResolvedRoute,
38 requestBodyParser: RequestBodyParser = new RequestBodyParser(),
39 options: ValidationOptions = DEFAULT_AJV_VALIDATION_OPTIONS,
40): Promise<OperationArgs> {
41 debug('Parsing operation arguments for route %s', route.describe());
42 const operationSpec = route.spec;
43 const pathParams = route.pathParams;
44 const body = await requestBodyParser.loadRequestBodyIfNeeded(
45 operationSpec,
46 request,
47 );
48 return buildOperationArguments(
49 operationSpec,
50 request,
51 pathParams,
52 body,
53 route.schemas,
54 options,
55 );
56}
57
58async function buildOperationArguments(
59 operationSpec: OperationObject,
60 request: Request,
61 pathParams: PathParameterValues,
62 body: RequestBody,
63 globalSchemas: SchemasObject,
64 options: ValidationOptions = DEFAULT_AJV_VALIDATION_OPTIONS,
65): Promise<OperationArgs> {
66 let requestBodyIndex = -1;
67 if (operationSpec.requestBody) {
68 // the type of `operationSpec.requestBody` could be `RequestBodyObject`
69 // or `ReferenceObject`, resolving a `$ref` value is not supported yet.
70 if (isReferenceObject(operationSpec.requestBody)) {
71 throw new Error('$ref requestBody is not supported yet.');
72 }
73 const i = operationSpec.requestBody[REQUEST_BODY_INDEX];
74 requestBodyIndex = i ?? 0;
75 }
76
77 const paramArgs: OperationArgs = [];
78
79 for (const paramSpec of operationSpec.parameters ?? []) {
80 if (isReferenceObject(paramSpec)) {
81 // TODO(bajtos) implement $ref parameters
82 // See https://github.com/loopbackio/loopback-next/issues/435
83 throw new Error('$ref parameters are not supported yet.');
84 }
85 const spec = paramSpec as ParameterObject;
86 const rawValue = getParamFromRequest(spec, request, pathParams);
87 const coercedValue = await coerceParameter(rawValue, spec, options);
88 paramArgs.push(coercedValue);
89 }
90
91 debug('Validating request body - value %j', body);
92 await validateRequestBody(
93 body,
94 operationSpec.requestBody,
95 globalSchemas,
96 options,
97 );
98
99 if (requestBodyIndex >= 0) {
100 paramArgs.splice(requestBodyIndex, 0, body.value);
101 }
102 return paramArgs;
103}
104
105function getParamFromRequest(
106 spec: ParameterObject,
107 request: Request,
108 pathParams: PathParameterValues,
109) {
110 switch (spec.in) {
111 case 'query':
112 return request.query[spec.name];
113 case 'path':
114 return pathParams[spec.name];
115 case 'header':
116 // @jannyhou TBD: check edge cases
117 return request.headers[spec.name.toLowerCase()];
118 // TODO(jannyhou) to support `cookie`,
119 // see issue https://github.com/loopbackio/loopback-next/issues/997
120 default:
121 throw RestHttpErrors.invalidParamLocation(spec.in);
122 }
123}