Skip to content

Commit

Permalink
toPathSchema iterative
Browse files Browse the repository at this point in the history
  • Loading branch information
igorbrasileiro committed Sep 5, 2024
1 parent 8502b90 commit 9018ef1
Showing 1 changed file with 150 additions and 91 deletions.
241 changes: 150 additions & 91 deletions packages/utils/src/schema/toPathSchema.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
import get from 'lodash/get';
import isEqual from 'lodash/isEqual';
import set from 'lodash/set';
import get from "lodash/get";
import isEqual from "lodash/isEqual";
import set from "lodash/set";

import {
ADDITIONAL_PROPERTIES_KEY,
ALL_OF_KEY,
ANY_OF_KEY,
ADDITIONAL_PROPERTIES_KEY,
DEPENDENCIES_KEY,
ITEMS_KEY,
NAME_KEY,
ONE_OF_KEY,
PROPERTIES_KEY,
REF_KEY,
RJSF_ADDITIONAL_PROPERTIES_FLAG,
} from '../constants';
import getDiscriminatorFieldFromSchema from '../getDiscriminatorFieldFromSchema';
import { FormContextType, GenericObjectType, PathSchema, RJSFSchema, StrictRJSFSchema, ValidatorType } from '../types';
import getClosestMatchingOption from './getClosestMatchingOption';
import retrieveSchema from './retrieveSchema';
} from "../constants";
import getDiscriminatorFieldFromSchema from "../getDiscriminatorFieldFromSchema";
import {
FormContextType,
GenericObjectType,
PathSchema,
RJSFSchema,
StrictRJSFSchema,
ValidatorType,
} from "../types";
import getClosestMatchingOption from "./getClosestMatchingOption";
import retrieveSchema from "./retrieveSchema";

/** An internal helper that generates an `PathSchema` object for the `schema`, recursively with protection against
* infinite recursion
Expand All @@ -30,103 +37,151 @@ import retrieveSchema from './retrieveSchema';
* @param [_recurseList=[]] - The list of retrieved schemas currently being recursed, used to prevent infinite recursion
* @returns - The `PathSchema` object for the `schema`
*/
function toPathSchemaInternal<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
function toPathSchemaInternal<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F extends FormContextType = any,
>(
validator: ValidatorType<T, S, F>,
schema: S,
name: string,
rootSchema?: S,
formData?: T,
_recurseList: S[] = []
_recurseList: S[] = [],
): PathSchema<T> {
if (REF_KEY in schema || DEPENDENCIES_KEY in schema || ALL_OF_KEY in schema) {
const _schema = retrieveSchema<T, S, F>(validator, schema, rootSchema, formData);
const sameSchemaIndex = _recurseList.findIndex((item) => isEqual(item, _schema));
if (sameSchemaIndex === -1) {
return toPathSchemaInternal<T, S, F>(
const firstState = {
schema,
name,
formData,
_recurseList,
pathSchema: {} as PathSchema<T>,
};
const stack = [firstState];

while (stack.length > 0) {
const { schema, formData, _recurseList, pathSchema, name } = stack.pop()!;

if (
REF_KEY in schema || DEPENDENCIES_KEY in schema || ALL_OF_KEY in schema
) {
const _schema = retrieveSchema<T, S, F>(
validator,
_schema,
name,
schema,
rootSchema,
formData,
_recurseList.concat(_schema)
);
const sameSchemaIndex = _recurseList.findIndex((item) =>
isEqual(item, _schema)
);
if (sameSchemaIndex === -1) {
stack.push({
schema: _schema,
name,
formData,
_recurseList: _recurseList.concat(_schema),
pathSchema,
});
continue;
}
}
}

let pathSchema: PathSchema<T> = {
[NAME_KEY]: name.replace(/^\./, ''),
} as PathSchema<T>;
pathSchema[NAME_KEY] = name.replace(/^\./, "");

if (ONE_OF_KEY in schema || ANY_OF_KEY in schema) {
const xxxOf: S[] = ONE_OF_KEY in schema ? (schema.oneOf as S[]) : (schema.anyOf as S[]);
const discriminator = getDiscriminatorFieldFromSchema<S>(schema);
const index = getClosestMatchingOption<T, S, F>(validator, rootSchema!, formData, xxxOf, 0, discriminator);
const _schema: S = xxxOf![index] as S;
pathSchema = {
...pathSchema,
...toPathSchemaInternal<T, S, F>(validator, _schema, name, rootSchema, formData, _recurseList),
};
}
if (ONE_OF_KEY in schema || ANY_OF_KEY in schema) {
const xxxOf: S[] = ONE_OF_KEY in schema
? (schema.oneOf as S[])
: (schema.anyOf as S[]);
const discriminator = getDiscriminatorFieldFromSchema<S>(schema);
const index = getClosestMatchingOption<T, S, F>(
validator,
rootSchema!,
formData,
xxxOf,
0,
discriminator,
);
const _schema: S = xxxOf![index] as S;
stack.push({
schema: _schema,
name,
formData,
_recurseList,
pathSchema,
});
}

if (ADDITIONAL_PROPERTIES_KEY in schema && schema[ADDITIONAL_PROPERTIES_KEY] !== false) {
set(pathSchema, RJSF_ADDITIONAL_PROPERTIES_FLAG, true);
}
if (
ADDITIONAL_PROPERTIES_KEY in schema &&
schema[ADDITIONAL_PROPERTIES_KEY] !== false
) {
set(pathSchema, RJSF_ADDITIONAL_PROPERTIES_FLAG, true);
}

if (ITEMS_KEY in schema && Array.isArray(formData)) {
const { items: schemaItems, additionalItems: schemaAdditionalItems } = schema;
if (ITEMS_KEY in schema && Array.isArray(formData)) {
const { items: schemaItems, additionalItems: schemaAdditionalItems } =
schema;

if (Array.isArray(schemaItems)) {
formData.forEach((element, i: number) => {
if (schemaItems[i]) {
(pathSchema as PathSchema<T[]>)[i] = toPathSchemaInternal<T, S, F>(
validator,
schemaItems[i] as S,
`${name}.${i}`,
rootSchema,
element,
_recurseList
);
} else if (schemaAdditionalItems) {
(pathSchema as PathSchema<T[]>)[i] = toPathSchemaInternal<T, S, F>(
validator,
schemaAdditionalItems as S,
`${name}.${i}`,
rootSchema,
element,
_recurseList
);
} else {
console.warn(`Unable to generate path schema for "${name}.${i}". No schema defined for it`);
}
});
} else {
formData.forEach((element, i: number) => {
(pathSchema as PathSchema<T[]>)[i] = toPathSchemaInternal<T, S, F>(
validator,
schemaItems as S,
`${name}.${i}`,
rootSchema,
element,
_recurseList
);
});
}
} else if (PROPERTIES_KEY in schema) {
for (const property in schema.properties) {
const field = get(schema, [PROPERTIES_KEY, property]);
(pathSchema as PathSchema<GenericObjectType>)[property] = toPathSchemaInternal<T, S, F>(
validator,
field,
`${name}.${property}`,
rootSchema,
// It's possible that formData is not an object -- this can happen if an
// array item has just been added, but not populated with data yet
get(formData, [property]),
_recurseList
);
if (Array.isArray(schemaItems)) {
formData.forEach((element, i: number) => {
if (schemaItems[i]) {
const newPathSchema = {} as PathSchema<T>;
(pathSchema as PathSchema<T[]>)[i] = newPathSchema;
stack.push({
schema: schemaItems[i] as S,
name: `${name}.${i}`,
formData: element,
_recurseList,
pathSchema: newPathSchema,
});
} else if (schemaAdditionalItems) {
const newPathSchema = {} as PathSchema<T>;
(pathSchema as PathSchema<T[]>)[i] = newPathSchema;
stack.push({
schema: schemaAdditionalItems as S,
name: `${name}.${i}`,
formData: element,
_recurseList,
pathSchema: newPathSchema,
});
} else {
console.warn(
`Unable to generate path schema for "${name}.${i}". No schema defined for it`,
);
}
});
} else {
formData.forEach((element, i: number) => {
const newPathSchema = {} as PathSchema<T>;
(pathSchema as PathSchema<T[]>)[i] = newPathSchema;
stack.push({
schema: schemaItems as S,
name: `${name}.${i}`,
formData: element,
_recurseList,
pathSchema: newPathSchema,
});
});
}
} else if (PROPERTIES_KEY in schema) {
for (const property in schema.properties) {
const field = get(schema, [PROPERTIES_KEY, property]);

const newPathSchema = {} as PathSchema<T>;
(pathSchema as PathSchema<GenericObjectType>)[property] = newPathSchema;
stack.push({
schema: field,
name: `${name}.${property}`,
// It's possible that formData is not an object -- this can happen if an
// array item has just been added, but not populated with data yet
formData: get(formData, [property]),
_recurseList,
pathSchema: newPathSchema,
});
}
}
}
return pathSchema;

return firstState.pathSchema;
}

/** Generates an `PathSchema` object for the `schema`, recursively
Expand All @@ -138,12 +193,16 @@ function toPathSchemaInternal<T = any, S extends StrictRJSFSchema = RJSFSchema,
* @param [formData] - The current formData, if any, to assist retrieving a schema
* @returns - The `PathSchema` object for the `schema`
*/
export default function toPathSchema<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
export default function toPathSchema<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F extends FormContextType = any,
>(
validator: ValidatorType<T, S, F>,
schema: S,
name = '',
name = "",
rootSchema?: S,
formData?: T
formData?: T,
): PathSchema<T> {
return toPathSchemaInternal(validator, schema, name, rootSchema, formData);
}

0 comments on commit 9018ef1

Please sign in to comment.