@carecard/validate 3.1.26 → 3.1.27
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/.husky/pre-commit
CHANGED
|
@@ -16,6 +16,8 @@ const MAX_NESTING_DEPTH = 5;
|
|
|
16
16
|
* adversarial inputs.
|
|
17
17
|
*/
|
|
18
18
|
const MAX_KEYS_PER_CALL = 5000;
|
|
19
|
+
const DEFAULT_FLATTEN_KEY_STYLE = 'path';
|
|
20
|
+
const VALID_FLATTEN_KEY_STYLES = new Set(['path', 'leaf']);
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
23
|
* Returns true if the segment contains a mix of snake_case (underscore) and
|
|
@@ -161,6 +163,35 @@ function flattenObject(obj, prefix = '', out = {}) {
|
|
|
161
163
|
return out;
|
|
162
164
|
}
|
|
163
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Recursively flattens a nested plain object using only each leaf property
|
|
168
|
+
* name as the output key.
|
|
169
|
+
*
|
|
170
|
+
* Example: `{ a: { b: { c: 1, d: 2 } } }` => `{ c: 1, d: 2 }`.
|
|
171
|
+
* If duplicate leaf keys exist at different nesting levels, the higher-level
|
|
172
|
+
* leaf wins. If duplicate leaf keys exist at the same depth, the first
|
|
173
|
+
* traversal wins.
|
|
174
|
+
*
|
|
175
|
+
* @param {Object} obj
|
|
176
|
+
* @param {Object} [out]
|
|
177
|
+
* @param {Object} [depthByKey]
|
|
178
|
+
* @param {number} [depth]
|
|
179
|
+
* @returns {Object}
|
|
180
|
+
*/
|
|
181
|
+
function flattenObjectByLeafKey(obj, out = {}, depthByKey = {}, depth = 1) {
|
|
182
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
183
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)) {
|
|
184
|
+
flattenObjectByLeafKey(value, out, depthByKey, depth + 1);
|
|
185
|
+
} else {
|
|
186
|
+
if (!Object.prototype.hasOwnProperty.call(out, key) || depth < depthByKey[key]) {
|
|
187
|
+
out[key] = value;
|
|
188
|
+
depthByKey[key] = depth;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return out;
|
|
193
|
+
}
|
|
194
|
+
|
|
164
195
|
/**
|
|
165
196
|
* Validates and transforms whitelisted properties from an input object.
|
|
166
197
|
*
|
|
@@ -181,8 +212,11 @@ function flattenObject(obj, prefix = '', out = {}) {
|
|
|
181
212
|
* element passes validation, and the returned value is an array of the
|
|
182
213
|
* validated elements (in the same order).
|
|
183
214
|
* 5. Optionally converts all keys (including nested) to snake_case.
|
|
184
|
-
* 6. Optionally flattens the result
|
|
185
|
-
*
|
|
215
|
+
* 6. Optionally flattens the result (`flattenOutput`). Flattened keys use
|
|
216
|
+
* full dot paths by default (`flattenKeyStyle: 'path'`) or direct leaf
|
|
217
|
+
* names when requested (`flattenKeyStyle: 'leaf'`). For duplicate leaf
|
|
218
|
+
* keys in leaf mode, the shallower value wins; ties keep the first value
|
|
219
|
+
* encountered. Applied after snake_case conversion.
|
|
186
220
|
*
|
|
187
221
|
* @param {Object} inputObject - The input object (e.g., req.body / req.params).
|
|
188
222
|
* @param {Array<string>} [requiredProperties=[]] - Leaf paths that MUST be present and valid.
|
|
@@ -190,17 +224,26 @@ function flattenObject(obj, prefix = '', out = {}) {
|
|
|
190
224
|
* @param {Array<string>} [options.optionalProperties=[]] - Leaf paths allowed but not required.
|
|
191
225
|
* @param {boolean} [options.convertToSnakeCase=false] - Whether to convert keys to snake_case.
|
|
192
226
|
* @param {boolean} [options.flattenOutput=false] - Whether to flatten the result so that
|
|
193
|
-
* every leaf is a top-level key
|
|
227
|
+
* every leaf is a top-level key, with no nested objects in the output.
|
|
228
|
+
* @param {'path'|'leaf'} [options.flattenKeyStyle='path'] - Flattened key naming strategy
|
|
229
|
+
* when `flattenOutput` is true. `path` uses dot-joined paths; `leaf` uses leaf names.
|
|
194
230
|
* @returns {Promise<Object>} Resolves with the validated (and possibly transformed) object.
|
|
195
231
|
*/
|
|
196
232
|
function validateWhitelistProperties(
|
|
197
233
|
inputObject,
|
|
198
234
|
requiredProperties = [],
|
|
199
|
-
options = { optionalProperties: [], convertToSnakeCase: false, flattenOutput: false },
|
|
235
|
+
options = { optionalProperties: [], convertToSnakeCase: false, flattenOutput: false, flattenKeyStyle: DEFAULT_FLATTEN_KEY_STYLE },
|
|
200
236
|
) {
|
|
201
237
|
const optionalProperties = (options && options.optionalProperties) || [];
|
|
202
238
|
const convertToSnakeCase = !!(options && options.convertToSnakeCase);
|
|
203
239
|
const flattenOutput = !!(options && options.flattenOutput);
|
|
240
|
+
const flattenKeyStyle = options && options.flattenKeyStyle !== undefined ? options.flattenKeyStyle : DEFAULT_FLATTEN_KEY_STYLE;
|
|
241
|
+
|
|
242
|
+
if (!VALID_FLATTEN_KEY_STYLES.has(flattenKeyStyle)) {
|
|
243
|
+
throwBadInputError({
|
|
244
|
+
userMessage: `Invalid flattenKeyStyle: ${String(flattenKeyStyle)}. Expected "path" or "leaf"`,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
204
247
|
|
|
205
248
|
// Cap the total number of paths to validate per call.
|
|
206
249
|
const totalKeys = (requiredProperties ? requiredProperties.length : 0) + optionalProperties.length;
|
|
@@ -271,9 +314,9 @@ function validateWhitelistProperties(
|
|
|
271
314
|
validatedObject = keysToSnakeCase(validatedObject);
|
|
272
315
|
}
|
|
273
316
|
|
|
274
|
-
// 6. Optional flattening
|
|
317
|
+
// 6. Optional flattening.
|
|
275
318
|
if (flattenOutput) {
|
|
276
|
-
validatedObject = flattenObject(validatedObject);
|
|
319
|
+
validatedObject = flattenKeyStyle === 'leaf' ? flattenObjectByLeafKey(validatedObject) : flattenObject(validatedObject);
|
|
277
320
|
}
|
|
278
321
|
|
|
279
322
|
return Promise.resolve(validatedObject);
|