UNPKG

8.5 kBJavaScriptView Raw
1"use strict";
2// Copyright IBM Corp. and LoopBack contributors 2018,2019. All Rights Reserved.
3// Node module: @loopback/rest
4// This file is licensed under the MIT License.
5// License text available at https://opensource.org/licenses/MIT
6Object.defineProperty(exports, "__esModule", { value: true });
7exports.coerceParameter = void 0;
8const tslib_1 = require("tslib");
9const openapi_v3_1 = require("@loopback/openapi-v3");
10const debug_1 = tslib_1.__importDefault(require("debug"));
11const __1 = require("../");
12const parse_json_1 = require("../parse-json");
13const ajv_factory_provider_1 = require("../validation/ajv-factory.provider");
14const utils_1 = require("./utils");
15const validator_1 = require("./validator");
16const isRFC3339 = require('validator/lib/isRFC3339');
17const debug = (0, debug_1.default)('loopback:rest:coercion');
18/**
19 * Coerce the http raw data to a JavaScript type data of a parameter
20 * according to its OpenAPI schema specification.
21 *
22 * @param data - The raw data get from http request
23 * @param schema - The parameter's schema defined in OpenAPI specification
24 * @param options - The ajv validation options
25 */
26async function coerceParameter(data, spec, options) {
27 const schema = extractSchemaFromSpec(spec);
28 if (!schema || (0, openapi_v3_1.isReferenceObject)(schema)) {
29 debug('The parameter with schema %s is not coerced since schema' +
30 'dereference is not supported yet.', schema);
31 return data;
32 }
33 const OAIType = (0, utils_1.getOAIPrimitiveType)(schema.type, schema.format);
34 const validator = new validator_1.Validator({ parameterSpec: spec });
35 validator.validateParamBeforeCoercion(data);
36 if (data === undefined)
37 return data;
38 // eslint-disable-next-line @typescript-eslint/no-explicit-any
39 let result = data;
40 switch (OAIType) {
41 case 'byte':
42 result = coerceBuffer(data, spec);
43 break;
44 case 'date':
45 result = coerceDatetime(data, spec, { dateOnly: true });
46 break;
47 case 'date-time':
48 result = coerceDatetime(data, spec);
49 break;
50 case 'float':
51 case 'double':
52 case 'number':
53 result = coerceNumber(data, spec);
54 break;
55 case 'long':
56 result = coerceInteger(data, spec, { isLong: true });
57 break;
58 case 'integer':
59 result = coerceInteger(data, spec);
60 break;
61 case 'boolean':
62 result = coerceBoolean(data, spec);
63 break;
64 case 'object':
65 result = await coerceObject(data, spec, options);
66 break;
67 case 'string':
68 case 'password':
69 result = coerceString(data, spec);
70 break;
71 case 'array':
72 result = coerceArray(data, spec);
73 break;
74 }
75 if (result != null) {
76 // For date/date-time/byte, we need to pass the raw string value to AJV
77 if (OAIType === 'date' || OAIType === 'date-time' || OAIType === 'byte') {
78 await validateParam(spec, data, options);
79 return result;
80 }
81 result = await validateParam(spec, result, options);
82 }
83 return result;
84}
85exports.coerceParameter = coerceParameter;
86function coerceString(data, spec) {
87 if (typeof data !== 'string')
88 throw __1.RestHttpErrors.invalidData(data, spec.name);
89 debug('data of type string is coerced to %s', data);
90 return data;
91}
92function coerceBuffer(data, spec) {
93 if (typeof data === 'object')
94 throw __1.RestHttpErrors.invalidData(data, spec.name);
95 return Buffer.from(data, 'base64');
96}
97function coerceDatetime(data, spec, options) {
98 if (typeof data === 'object' || (0, utils_1.isEmpty)(data))
99 throw __1.RestHttpErrors.invalidData(data, spec.name);
100 if (options === null || options === void 0 ? void 0 : options.dateOnly) {
101 if (!(0, utils_1.matchDateFormat)(data))
102 throw __1.RestHttpErrors.invalidData(data, spec.name);
103 }
104 else {
105 if (!isRFC3339(data))
106 throw __1.RestHttpErrors.invalidData(data, spec.name);
107 }
108 const coercedDate = new Date(data);
109 if (!(0, utils_1.isValidDateTime)(coercedDate))
110 throw __1.RestHttpErrors.invalidData(data, spec.name);
111 return coercedDate;
112}
113function coerceNumber(data, spec) {
114 if (typeof data === 'object' || (0, utils_1.isEmpty)(data))
115 throw __1.RestHttpErrors.invalidData(data, spec.name);
116 const coercedNum = Number(data);
117 if (isNaN(coercedNum))
118 throw __1.RestHttpErrors.invalidData(data, spec.name);
119 debug('data of type number is coerced to %s', coercedNum);
120 return coercedNum;
121}
122function coerceInteger(data, spec, options) {
123 if (typeof data === 'object' || (0, utils_1.isEmpty)(data))
124 throw __1.RestHttpErrors.invalidData(data, spec.name);
125 const coercedInt = Number(data);
126 if (isNaN(coercedInt))
127 throw __1.RestHttpErrors.invalidData(data, spec.name);
128 if (options === null || options === void 0 ? void 0 : options.isLong) {
129 if (!Number.isInteger(coercedInt))
130 throw __1.RestHttpErrors.invalidData(data, spec.name);
131 }
132 else {
133 if (!Number.isSafeInteger(coercedInt))
134 throw __1.RestHttpErrors.invalidData(data, spec.name);
135 }
136 debug('data of type integer is coerced to %s', coercedInt);
137 return coercedInt;
138}
139function coerceBoolean(data, spec) {
140 if (typeof data === 'object' || (0, utils_1.isEmpty)(data))
141 throw __1.RestHttpErrors.invalidData(data, spec.name);
142 if ((0, utils_1.isTrue)(data))
143 return true;
144 if ((0, utils_1.isFalse)(data))
145 return false;
146 throw __1.RestHttpErrors.invalidData(data, spec.name);
147}
148async function coerceObject(input, spec, options) {
149 const data = parseJsonIfNeeded(input, spec, options);
150 if (data == null) {
151 // Skip any further checks and coercions, nothing we can do with `undefined`
152 return data;
153 }
154 if (typeof data !== 'object' || Array.isArray(data))
155 throw __1.RestHttpErrors.invalidData(input, spec.name);
156 return data;
157}
158function coerceArray(data, spec) {
159 if (spec.in === 'query') {
160 if (data == null || Array.isArray(data))
161 return data;
162 return [data];
163 }
164 return data;
165}
166function validateParam(spec,
167// eslint-disable-next-line @typescript-eslint/no-explicit-any
168data, options = ajv_factory_provider_1.DEFAULT_AJV_VALIDATION_OPTIONS) {
169 const schema = extractSchemaFromSpec(spec);
170 if (schema) {
171 // Apply coercion based on properties defined by spec.schema
172 return (0, __1.validateValueAgainstSchema)(data, schema, {}, { ...options, coerceTypes: true, source: 'parameter', name: spec.name });
173 }
174 return data;
175}
176/**
177 * Extract the schema from an OpenAPI parameter specification. If the root level
178 * one not found, search from media type 'application/json'.
179 *
180 * @param spec The parameter specification
181 */
182function extractSchemaFromSpec(spec) {
183 var _a, _b;
184 let schema = spec.schema;
185 // If a query parameter is a url encoded Json object,
186 // the schema is defined under content['application/json']
187 if (!schema && spec.in === 'query') {
188 schema = (_b = (_a = spec.content) === null || _a === void 0 ? void 0 : _a['application/json']) === null || _b === void 0 ? void 0 : _b.schema;
189 }
190 return schema;
191}
192function parseJsonIfNeeded(data, spec, options) {
193 if (typeof data !== 'string')
194 return data;
195 if (spec.in !== 'query' || (spec.in === 'query' && !spec.content)) {
196 debug('Skipping JSON.parse, argument %s is not a url encoded json object query parameter (since content field is missing in parameter schema)', spec.name);
197 return data;
198 }
199 if (data === '') {
200 debug('Converted empty string to object value `undefined`');
201 return undefined;
202 }
203 try {
204 const result = (0, parse_json_1.parseJson)(data, (0, parse_json_1.sanitizeJsonParse)(undefined, options === null || options === void 0 ? void 0 : options.prohibitedKeys));
205 debug('Parsed parameter %s as %j', spec.name, result);
206 return result;
207 }
208 catch (err) {
209 debug('Cannot parse %s value %j as JSON: %s', spec.name, data, err.message);
210 throw __1.RestHttpErrors.invalidData(data, spec.name, {
211 details: {
212 syntaxError: err.message,
213 },
214 });
215 }
216}
217//# sourceMappingURL=coerce-parameter.js.map
\No newline at end of file