@rjsf/utils 5.13.0 → 5.13.2

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.
@@ -1,4 +1,5 @@
1
1
  import get from 'lodash/get';
2
+ import isEqual from 'lodash/isEqual';
2
3
  import set from 'lodash/set';
3
4
  import times from 'lodash/times';
4
5
  import transform from 'lodash/transform';
@@ -51,6 +52,7 @@ export default function retrieveSchema<
51
52
  * @param rootSchema - The root schema that will be forwarded to all the APIs
52
53
  * @param expandAllBranches - Flag, if true, will return all possible branches of conditions, any/oneOf and
53
54
  * dependencies as a list of schemas
55
+ * @param recurseList - The list of recursive references already processed
54
56
  * @param [formData] - The current formData to assist retrieving a schema
55
57
  * @returns - A list of schemas with the appropriate conditions resolved, possibly with all branches expanded
56
58
  */
@@ -59,6 +61,7 @@ export function resolveCondition<T = any, S extends StrictRJSFSchema = RJSFSchem
59
61
  schema: S,
60
62
  rootSchema: S,
61
63
  expandAllBranches: boolean,
64
+ recurseList: string[],
62
65
  formData?: T
63
66
  ): S[] {
64
67
  const { if: expression, then, else: otherwise, ...resolvedSchemaLessConditional } = schema;
@@ -69,19 +72,26 @@ export function resolveCondition<T = any, S extends StrictRJSFSchema = RJSFSchem
69
72
  if (expandAllBranches) {
70
73
  if (then && typeof then !== 'boolean') {
71
74
  schemas = schemas.concat(
72
- retrieveSchemaInternal<T, S, F>(validator, then as S, rootSchema, formData, expandAllBranches)
75
+ retrieveSchemaInternal<T, S, F>(validator, then as S, rootSchema, formData, expandAllBranches, recurseList)
73
76
  );
74
77
  }
75
78
  if (otherwise && typeof otherwise !== 'boolean') {
76
79
  schemas = schemas.concat(
77
- retrieveSchemaInternal<T, S, F>(validator, otherwise as S, rootSchema, formData, expandAllBranches)
80
+ retrieveSchemaInternal<T, S, F>(validator, otherwise as S, rootSchema, formData, expandAllBranches, recurseList)
78
81
  );
79
82
  }
80
83
  } else {
81
84
  const conditionalSchema = conditionValue ? then : otherwise;
82
85
  if (conditionalSchema && typeof conditionalSchema !== 'boolean') {
83
86
  schemas = schemas.concat(
84
- retrieveSchemaInternal<T, S, F>(validator, conditionalSchema as S, rootSchema, formData, expandAllBranches)
87
+ retrieveSchemaInternal<T, S, F>(
88
+ validator,
89
+ conditionalSchema as S,
90
+ rootSchema,
91
+ formData,
92
+ expandAllBranches,
93
+ recurseList
94
+ )
85
95
  );
86
96
  }
87
97
  }
@@ -89,7 +99,7 @@ export function resolveCondition<T = any, S extends StrictRJSFSchema = RJSFSchem
89
99
  resolvedSchemas = schemas.map((s) => mergeSchemas(resolvedSchemaLessConditional, s) as S);
90
100
  }
91
101
  return resolvedSchemas.flatMap((s) =>
92
- retrieveSchemaInternal<T, S, F>(validator, s, rootSchema, formData, expandAllBranches)
102
+ retrieveSchemaInternal<T, S, F>(validator, s, rootSchema, formData, expandAllBranches, recurseList)
93
103
  );
94
104
  }
95
105
 
@@ -133,6 +143,7 @@ export function getAllPermutationsOfXxxOf<S extends StrictRJSFSchema = RJSFSchem
133
143
  * @param rootSchema - The root schema that will be forwarded to all the APIs
134
144
  * @param expandAllBranches - Flag, if true, will return all possible branches of conditions, any/oneOf and dependencies
135
145
  * as a list of schemas
146
+ * @param recurseList - The list of recursive references already processed
136
147
  * @param [formData] - The current formData, if any, to assist retrieving a schema
137
148
  * @returns - The list of schemas having its references, dependencies and allOf schemas resolved
138
149
  */
@@ -141,20 +152,45 @@ export function resolveSchema<T = any, S extends StrictRJSFSchema = RJSFSchema,
141
152
  schema: S,
142
153
  rootSchema: S,
143
154
  expandAllBranches: boolean,
155
+ recurseList: string[],
144
156
  formData?: T
145
157
  ): S[] {
146
- if (REF_KEY in schema) {
147
- return resolveReference<T, S, F>(validator, schema, rootSchema, expandAllBranches, formData);
158
+ const updatedSchemas = resolveReference<T, S, F>(
159
+ validator,
160
+ schema,
161
+ rootSchema,
162
+ expandAllBranches,
163
+ recurseList,
164
+ formData
165
+ );
166
+ if (updatedSchemas.length > 1 || updatedSchemas[0] !== schema) {
167
+ // return the updatedSchemas array if it has either multiple schemas within it
168
+ // OR the first schema is not the same as the original schema
169
+ return updatedSchemas;
148
170
  }
149
171
  if (DEPENDENCIES_KEY in schema) {
150
- const resolvedSchemas = resolveDependencies<T, S, F>(validator, schema, rootSchema, expandAllBranches, formData);
172
+ const resolvedSchemas = resolveDependencies<T, S, F>(
173
+ validator,
174
+ schema,
175
+ rootSchema,
176
+ expandAllBranches,
177
+ recurseList,
178
+ formData
179
+ );
151
180
  return resolvedSchemas.flatMap((s) => {
152
- return retrieveSchemaInternal<T, S, F>(validator, s, rootSchema, formData, expandAllBranches);
181
+ return retrieveSchemaInternal<T, S, F>(validator, s, rootSchema, formData, expandAllBranches, recurseList);
153
182
  });
154
183
  }
155
184
  if (ALL_OF_KEY in schema && Array.isArray(schema.allOf)) {
156
185
  const allOfSchemaElements: S[][] = schema.allOf.map((allOfSubschema) =>
157
- retrieveSchemaInternal<T, S, F>(validator, allOfSubschema as S, rootSchema, formData, expandAllBranches)
186
+ retrieveSchemaInternal<T, S, F>(
187
+ validator,
188
+ allOfSubschema as S,
189
+ rootSchema,
190
+ formData,
191
+ expandAllBranches,
192
+ recurseList
193
+ )
158
194
  );
159
195
  const allPermutations = getAllPermutationsOfXxxOf<S>(allOfSchemaElements);
160
196
  return allPermutations.map((permutation) => ({ ...schema, allOf: permutation }));
@@ -163,14 +199,16 @@ export function resolveSchema<T = any, S extends StrictRJSFSchema = RJSFSchema,
163
199
  return [schema];
164
200
  }
165
201
 
166
- /** Resolves references within a schema and then returns the `retrieveSchemaInternal()` of the resolved schema. Passes
167
- * the `expandAllBranches` flag down to the `retrieveSchemaInternal()` helper call.
202
+ /** Resolves all references within a schema and then returns the `retrieveSchemaInternal()` if the resolved schema is
203
+ * actually different than the original. Passes the `expandAllBranches` flag down to the `retrieveSchemaInternal()`
204
+ * helper call.
168
205
  *
169
206
  * @param validator - An implementation of the `ValidatorType` interface that will be forwarded to all the APIs
170
207
  * @param schema - The schema for which resolving a reference is desired
171
208
  * @param rootSchema - The root schema that will be forwarded to all the APIs
172
209
  * @param expandAllBranches - Flag, if true, will return all possible branches of conditions, any/oneOf and dependencies
173
210
  * as a list of schemas
211
+ * @param recurseList - The list of recursive references already processed
174
212
  * @param [formData] - The current formData, if any, to assist retrieving a schema
175
213
  * @returns - The list schemas retrieved after having all references resolved
176
214
  */
@@ -179,33 +217,48 @@ export function resolveReference<T = any, S extends StrictRJSFSchema = RJSFSchem
179
217
  schema: S,
180
218
  rootSchema: S,
181
219
  expandAllBranches: boolean,
220
+ recurseList: string[],
182
221
  formData?: T
183
222
  ): S[] {
184
- // Drop the $ref property of the source schema.
185
- const { $ref, ...localSchema } = schema;
186
- // Retrieve the referenced schema definition.
187
- const refSchema = findSchemaDefinition<S>($ref, rootSchema);
188
- // Update referenced schema definition with local schema properties.
189
- return retrieveSchemaInternal<T, S, F>(
190
- validator,
191
- { ...refSchema, ...localSchema },
192
- rootSchema,
193
- formData,
194
- expandAllBranches
195
- );
223
+ const updatedSchema = resolveAllReferences<S>(schema, rootSchema, recurseList);
224
+ if (updatedSchema !== schema) {
225
+ // Only call this if the schema was actually changed by the `resolveAllReferences()` function
226
+ return retrieveSchemaInternal<T, S, F>(
227
+ validator,
228
+ updatedSchema,
229
+ rootSchema,
230
+ formData,
231
+ expandAllBranches,
232
+ recurseList
233
+ );
234
+ }
235
+ return [schema];
196
236
  }
197
237
 
198
- /** Resolves all references within a schema's properties and array items.
238
+ /** Resolves all references within the schema itself as well as any of its properties and array items.
199
239
  *
200
240
  * @param schema - The schema for which resolving all references is desired
201
241
  * @param rootSchema - The root schema that will be forwarded to all the APIs
202
- * @returns - given schema will all references resolved
242
+ * @param recurseList - List of $refs already resolved to prevent recursion
243
+ * @returns - given schema will all references resolved or the original schema if no internal `$refs` were resolved
203
244
  */
204
- export function resolveAllReferences<S extends StrictRJSFSchema = RJSFSchema>(schema: S, rootSchema: S): S {
245
+ export function resolveAllReferences<S extends StrictRJSFSchema = RJSFSchema>(
246
+ schema: S,
247
+ rootSchema: S,
248
+ recurseList: string[]
249
+ ): S {
250
+ if (!isObject(schema)) {
251
+ return schema;
252
+ }
205
253
  let resolvedSchema: S = schema;
206
254
  // resolve top level ref
207
255
  if (REF_KEY in resolvedSchema) {
208
256
  const { $ref, ...localSchema } = resolvedSchema;
257
+ // Check for a recursive reference and stop the loop
258
+ if (recurseList.includes($ref!)) {
259
+ return resolvedSchema;
260
+ }
261
+ recurseList.push($ref!);
209
262
  // Retrieve the referenced schema definition.
210
263
  const refSchema = findSchemaDefinition<S>($ref, rootSchema);
211
264
  resolvedSchema = { ...refSchema, ...localSchema };
@@ -215,7 +268,7 @@ export function resolveAllReferences<S extends StrictRJSFSchema = RJSFSchema>(sc
215
268
  const updatedProps = transform(
216
269
  resolvedSchema[PROPERTIES_KEY]!,
217
270
  (result, value, key: string) => {
218
- result[key] = resolveAllReferences(value as S, rootSchema);
271
+ result[key] = resolveAllReferences(value as S, rootSchema, recurseList);
219
272
  },
220
273
  {} as RJSFSchema
221
274
  );
@@ -227,10 +280,13 @@ export function resolveAllReferences<S extends StrictRJSFSchema = RJSFSchema>(sc
227
280
  !Array.isArray(resolvedSchema.items) &&
228
281
  typeof resolvedSchema.items !== 'boolean'
229
282
  ) {
230
- resolvedSchema = { ...resolvedSchema, items: resolveAllReferences(resolvedSchema.items as S, rootSchema) };
283
+ resolvedSchema = {
284
+ ...resolvedSchema,
285
+ items: resolveAllReferences(resolvedSchema.items as S, rootSchema, recurseList),
286
+ };
231
287
  }
232
288
 
233
- return resolvedSchema;
289
+ return isEqual(schema, resolvedSchema) ? schema : resolvedSchema;
234
290
  }
235
291
 
236
292
  /** Creates new 'properties' items for each key in the `formData`
@@ -303,6 +359,7 @@ export function stubExistingAdditionalProperties<
303
359
  * @param [rawFormData] - The current formData, if any, to assist retrieving a schema
304
360
  * @param [expandAllBranches=false] - Flag, if true, will return all possible branches of conditions, any/oneOf and
305
361
  * dependencies as a list of schemas
362
+ * @param [recurseList=[]] - The optional, list of recursive references already processed
306
363
  * @returns - The schema(s) resulting from having its conditions, additional properties, references and dependencies
307
364
  * resolved. Multiple schemas may be returned if `expandAllBranches` is true.
308
365
  */
@@ -310,15 +367,36 @@ export function retrieveSchemaInternal<
310
367
  T = any,
311
368
  S extends StrictRJSFSchema = RJSFSchema,
312
369
  F extends FormContextType = any
313
- >(validator: ValidatorType<T, S, F>, schema: S, rootSchema: S, rawFormData?: T, expandAllBranches = false): S[] {
370
+ >(
371
+ validator: ValidatorType<T, S, F>,
372
+ schema: S,
373
+ rootSchema: S,
374
+ rawFormData?: T,
375
+ expandAllBranches = false,
376
+ recurseList: string[] = []
377
+ ): S[] {
314
378
  if (!isObject(schema)) {
315
379
  return [{} as S];
316
380
  }
317
- const resolvedSchemas = resolveSchema<T, S, F>(validator, schema, rootSchema, expandAllBranches, rawFormData);
381
+ const resolvedSchemas = resolveSchema<T, S, F>(
382
+ validator,
383
+ schema,
384
+ rootSchema,
385
+ expandAllBranches,
386
+ recurseList,
387
+ rawFormData
388
+ );
318
389
  return resolvedSchemas.flatMap((s: S) => {
319
390
  let resolvedSchema = s;
320
391
  if (IF_KEY in resolvedSchema) {
321
- return resolveCondition<T, S, F>(validator, resolvedSchema, rootSchema, expandAllBranches, rawFormData as T);
392
+ return resolveCondition<T, S, F>(
393
+ validator,
394
+ resolvedSchema,
395
+ rootSchema,
396
+ expandAllBranches,
397
+ recurseList,
398
+ rawFormData as T
399
+ );
322
400
  }
323
401
  if (ALL_OF_KEY in resolvedSchema) {
324
402
  // resolve allOf schemas
@@ -375,7 +453,9 @@ export function resolveAnyOrOneOfSchemas<
375
453
  const formData = rawFormData === undefined && expandAllBranches ? ({} as T) : rawFormData;
376
454
  const discriminator = getDiscriminatorFieldFromSchema<S>(schema);
377
455
  anyOrOneOf = anyOrOneOf.map((s) => {
378
- return resolveAllReferences(s, rootSchema);
456
+ // Due to anyOf/oneOf possibly using the same $ref we always pass a fresh recurse list array so that each option
457
+ // can resolve recursive references independently
458
+ return resolveAllReferences(s, rootSchema, []);
379
459
  });
380
460
  // Call this to trigger the set of isValid() calls that the schema parser will need
381
461
  const option = getFirstMatchingOption<T, S, F>(validator, formData, anyOrOneOf, rootSchema, discriminator);
@@ -395,6 +475,7 @@ export function resolveAnyOrOneOfSchemas<
395
475
  * @param rootSchema - The root schema that will be forwarded to all the APIs
396
476
  * @param expandAllBranches - Flag, if true, will return all possible branches of conditions, any/oneOf and dependencies
397
477
  * as a list of schemas
478
+ * @param recurseList - The list of recursive references already processed
398
479
  * @param [formData] - The current formData, if any, to assist retrieving a schema
399
480
  * @returns - The list of schemas with their dependencies resolved
400
481
  */
@@ -403,6 +484,7 @@ export function resolveDependencies<T = any, S extends StrictRJSFSchema = RJSFSc
403
484
  schema: S,
404
485
  rootSchema: S,
405
486
  expandAllBranches: boolean,
487
+ recurseList: string[],
406
488
  formData?: T
407
489
  ): S[] {
408
490
  // Drop the dependencies from the source schema.
@@ -415,7 +497,15 @@ export function resolveDependencies<T = any, S extends StrictRJSFSchema = RJSFSc
415
497
  formData
416
498
  );
417
499
  return resolvedSchemas.flatMap((resolvedSchema) =>
418
- processDependencies<T, S, F>(validator, dependencies, resolvedSchema, rootSchema, expandAllBranches, formData)
500
+ processDependencies<T, S, F>(
501
+ validator,
502
+ dependencies,
503
+ resolvedSchema,
504
+ rootSchema,
505
+ expandAllBranches,
506
+ recurseList,
507
+ formData
508
+ )
419
509
  );
420
510
  }
421
511
 
@@ -428,6 +518,7 @@ export function resolveDependencies<T = any, S extends StrictRJSFSchema = RJSFSc
428
518
  * @param rootSchema - The root schema that will be forwarded to all the APIs
429
519
  * @param expandAllBranches - Flag, if true, will return all possible branches of conditions, any/oneOf and dependencies
430
520
  * as a list of schemas
521
+ * @param recurseList - The list of recursive references already processed
431
522
  * @param [formData] - The current formData, if any, to assist retrieving a schema
432
523
  * @returns - The schema with the `dependencies` resolved into it
433
524
  */
@@ -437,6 +528,7 @@ export function processDependencies<T = any, S extends StrictRJSFSchema = RJSFSc
437
528
  resolvedSchema: S,
438
529
  rootSchema: S,
439
530
  expandAllBranches: boolean,
531
+ recurseList: string[],
440
532
  formData?: T
441
533
  ): S[] {
442
534
  let schemas = [resolvedSchema];
@@ -464,11 +556,20 @@ export function processDependencies<T = any, S extends StrictRJSFSchema = RJSFSc
464
556
  dependencyKey,
465
557
  dependencyValue as S,
466
558
  expandAllBranches,
559
+ recurseList,
467
560
  formData
468
561
  );
469
562
  }
470
563
  return schemas.flatMap((schema) =>
471
- processDependencies<T, S, F>(validator, remainingDependencies, schema, rootSchema, expandAllBranches, formData)
564
+ processDependencies<T, S, F>(
565
+ validator,
566
+ remainingDependencies,
567
+ schema,
568
+ rootSchema,
569
+ expandAllBranches,
570
+ recurseList,
571
+ formData
572
+ )
472
573
  );
473
574
  }
474
575
  return schemas;
@@ -503,6 +604,7 @@ export function withDependentProperties<S extends StrictRJSFSchema = RJSFSchema>
503
604
  * @param dependencyValue - The potentially dependent schema
504
605
  * @param expandAllBranches - Flag, if true, will return all possible branches of conditions, any/oneOf and dependencies
505
606
  * as a list of schemas
607
+ * @param recurseList - The list of recursive references already processed
506
608
  * @param [formData]- The current formData to assist retrieving a schema
507
609
  * @returns - The list of schemas with the dependent schema resolved into them
508
610
  */
@@ -513,6 +615,7 @@ export function withDependentSchema<T = any, S extends StrictRJSFSchema = RJSFSc
513
615
  dependencyKey: string,
514
616
  dependencyValue: S,
515
617
  expandAllBranches: boolean,
618
+ recurseList: string[],
516
619
  formData?: T
517
620
  ): S[] {
518
621
  const dependentSchemas = retrieveSchemaInternal<T, S, F>(
@@ -520,7 +623,8 @@ export function withDependentSchema<T = any, S extends StrictRJSFSchema = RJSFSc
520
623
  dependencyValue,
521
624
  rootSchema,
522
625
  formData,
523
- expandAllBranches
626
+ expandAllBranches,
627
+ recurseList
524
628
  );
525
629
  return dependentSchemas.flatMap((dependent) => {
526
630
  const { oneOf, ...dependentSchema } = dependent;
@@ -534,7 +638,7 @@ export function withDependentSchema<T = any, S extends StrictRJSFSchema = RJSFSc
534
638
  if (typeof subschema === 'boolean' || !(REF_KEY in subschema)) {
535
639
  return [subschema as S];
536
640
  }
537
- return resolveReference<T, S, F>(validator, subschema as S, rootSchema, expandAllBranches, formData);
641
+ return resolveReference<T, S, F>(validator, subschema as S, rootSchema, expandAllBranches, recurseList, formData);
538
642
  });
539
643
  const allPermutations = getAllPermutationsOfXxxOf(resolvedOneOfs);
540
644
  return allPermutations.flatMap((resolvedOneOf) =>
@@ -545,6 +649,7 @@ export function withDependentSchema<T = any, S extends StrictRJSFSchema = RJSFSc
545
649
  dependencyKey,
546
650
  resolvedOneOf,
547
651
  expandAllBranches,
652
+ recurseList,
548
653
  formData
549
654
  )
550
655
  );
@@ -562,6 +667,7 @@ export function withDependentSchema<T = any, S extends StrictRJSFSchema = RJSFSc
562
667
  * @param oneOf - The list of schemas representing the oneOf options
563
668
  * @param expandAllBranches - Flag, if true, will return all possible branches of conditions, any/oneOf and dependencies
564
669
  * as a list of schemas
670
+ * @param recurseList - The list of recursive references already processed
565
671
  * @param [formData] - The current formData to assist retrieving a schema
566
672
  * @returns - Either an array containing the best matching option or all options if `expandAllBranches` is true
567
673
  */
@@ -576,6 +682,7 @@ export function withExactlyOneSubschema<
576
682
  dependencyKey: string,
577
683
  oneOf: S['oneOf'],
578
684
  expandAllBranches: boolean,
685
+ recurseList: string[],
579
686
  formData?: T
580
687
  ): S[] {
581
688
  const validSubschemas = oneOf!.filter((subschema) => {
@@ -608,7 +715,8 @@ export function withExactlyOneSubschema<
608
715
  dependentSchema,
609
716
  rootSchema,
610
717
  formData,
611
- expandAllBranches
718
+ expandAllBranches,
719
+ recurseList
612
720
  );
613
721
  return schemas.map((s) => mergeSchemas(schema, s) as S);
614
722
  });
package/src/types.ts CHANGED
@@ -440,7 +440,7 @@ export type FieldTemplateProps<T = any, S extends StrictRJSFSchema = RJSFSchema,
440
440
  /** The formData for this field */
441
441
  formData?: T;
442
442
  /** The value change event handler; Can be called with a new value to change the value for this field */
443
- onChange: FieldProps['onChange'];
443
+ onChange: FieldProps<T, S, F>['onChange'];
444
444
  /** The key change event handler; Called when the key associated with a field is changed for an additionalProperty */
445
445
  onKeyChange: (value: string) => () => void;
446
446
  /** The property drop/removal event handler; Called when a field is removed in an additionalProperty context */