1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | Object.defineProperty(exports, "__esModule", { value: true });
|
7 | exports.coerceParameter = void 0;
|
8 | const tslib_1 = require("tslib");
|
9 | const openapi_v3_1 = require("@loopback/openapi-v3");
|
10 | const debug_1 = tslib_1.__importDefault(require("debug"));
|
11 | const __1 = require("../");
|
12 | const parse_json_1 = require("../parse-json");
|
13 | const ajv_factory_provider_1 = require("../validation/ajv-factory.provider");
|
14 | const utils_1 = require("./utils");
|
15 | const validator_1 = require("./validator");
|
16 | const isRFC3339 = require('validator/lib/isRFC3339');
|
17 | const debug = (0, debug_1.default)('loopback:rest:coercion');
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 | async 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 |
|
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 |
|
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 | }
|
85 | exports.coerceParameter = coerceParameter;
|
86 | function 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 | }
|
92 | function coerceBuffer(data, spec) {
|
93 | if (typeof data === 'object')
|
94 | throw __1.RestHttpErrors.invalidData(data, spec.name);
|
95 | return Buffer.from(data, 'base64');
|
96 | }
|
97 | function 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 | }
|
113 | function 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 | }
|
122 | function 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 | }
|
139 | function 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 | }
|
148 | async function coerceObject(input, spec, options) {
|
149 | const data = parseJsonIfNeeded(input, spec, options);
|
150 | if (data == null) {
|
151 |
|
152 | return data;
|
153 | }
|
154 | if (typeof data !== 'object' || Array.isArray(data))
|
155 | throw __1.RestHttpErrors.invalidData(input, spec.name);
|
156 | return data;
|
157 | }
|
158 | function 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 | }
|
166 | function validateParam(spec,
|
167 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
168 | data, options = ajv_factory_provider_1.DEFAULT_AJV_VALIDATION_OPTIONS) {
|
169 | const schema = extractSchemaFromSpec(spec);
|
170 | if (schema) {
|
171 |
|
172 | return (0, __1.validateValueAgainstSchema)(data, schema, {}, { ...options, coerceTypes: true, source: 'parameter', name: spec.name });
|
173 | }
|
174 | return data;
|
175 | }
|
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 | function extractSchemaFromSpec(spec) {
|
183 | var _a, _b;
|
184 | let schema = spec.schema;
|
185 |
|
186 |
|
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 | }
|
192 | function 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 |
|
\ | No newline at end of file |