|
1 | 1 | import get from 'lodash/get'; |
2 | 2 | import has from 'lodash/has'; |
3 | 3 | import isNumber from 'lodash/isNumber'; |
| 4 | +import isObject from 'lodash/isObject'; |
4 | 5 |
|
5 | | -import { PROPERTIES_KEY } from '../constants'; |
| 6 | +import { ANY_OF_KEY, ALL_OF_KEY, ONE_OF_KEY, PROPERTIES_KEY, REF_KEY } from '../constants'; |
6 | 7 | import getOptionMatchingSimpleDiscriminator from '../getOptionMatchingSimpleDiscriminator'; |
7 | 8 | import { FormContextType, RJSFSchema, StrictRJSFSchema, ValidatorType } from '../types'; |
8 | 9 |
|
| 10 | +/** Returns a copy of `schema` with properties that could cause deep recursive AJV |
| 11 | + * validation (oneOf, anyOf, allOf, $ref) replaced by `{}` (accept any value). |
| 12 | + * This prevents O(options^depth) validation work in schemas with cross-referencing definitions. |
| 13 | + */ |
| 14 | +function stripRecursiveProperties<S extends StrictRJSFSchema = RJSFSchema>(schema: S): S { |
| 15 | + const properties = schema[PROPERTIES_KEY]; |
| 16 | + if (!isObject(properties)) { |
| 17 | + return schema; |
| 18 | + } |
| 19 | + const shallow: Record<string, unknown> = {}; |
| 20 | + for (const [key, prop] of Object.entries(properties)) { |
| 21 | + if (typeof prop !== 'object' || prop === null) { |
| 22 | + shallow[key] = prop; |
| 23 | + } else if (ONE_OF_KEY in prop || ANY_OF_KEY in prop || ALL_OF_KEY in prop || REF_KEY in prop) { |
| 24 | + shallow[key] = {}; |
| 25 | + } else { |
| 26 | + shallow[key] = prop; |
| 27 | + } |
| 28 | + } |
| 29 | + return { ...schema, [PROPERTIES_KEY]: shallow }; |
| 30 | +} |
| 31 | + |
9 | 32 | /** Given the `formData` and list of `options`, attempts to find the index of the first option that matches the data. |
| 33 | + * Matching is shallow: properties containing `oneOf`, `anyOf`, `allOf`, or `$ref` are treated as unconstrained during |
| 34 | + * validation to avoid exponential AJV validation time with cross-referencing schemas. |
10 | 35 | * Always returns the first option if there is nothing that matches. |
11 | 36 | * |
12 | 37 | * @param validator - An implementation of the `ValidatorType` interface that will be used when necessary |
@@ -91,10 +116,10 @@ export default function getFirstMatchingOption< |
91 | 116 | // been filled in yet, which will mean that the schema is not valid |
92 | 117 | delete augmentedSchema.required; |
93 | 118 |
|
94 | | - if (validator.isValid(augmentedSchema, formData, rootSchema)) { |
| 119 | + if (validator.isValid(stripRecursiveProperties(augmentedSchema as S), formData, rootSchema)) { |
95 | 120 | return i; |
96 | 121 | } |
97 | | - } else if (validator.isValid(option, formData, rootSchema)) { |
| 122 | + } else if (validator.isValid(stripRecursiveProperties(option), formData, rootSchema)) { |
98 | 123 | return i; |
99 | 124 | } |
100 | 125 | } |
|
0 commit comments