From e4067bbe83e561064d96841fab2407dd936aa95d Mon Sep 17 00:00:00 2001 From: Heath C <51679588+heath-freenome@users.noreply.github.com> Date: Mon, 9 Jan 2023 09:42:43 -0800 Subject: [PATCH] fix: Fixed #3170 by passing `uiSchema` into the `validateFormData()` call (#3358) * fix: Fixed 3170 by passing `uiSchema` into the `validateFormData()` call Fixes #3170 by updating types and implementations to support passing `uiSchema` to the `validateFormData()` call so that it can be forwarded to `customValidate()` and `transformErrors()` - In `@rjsf/utils`, updated the `ValidatorType` to take the `F` generic so that `validateFormData()` can be passed `uiSchema?: UiSchema` - This required updating all of the schema-based functions to also take the `F` generic and properly adding generics to all the function calls - Also, the `CustomValidator` and `ErrorTransformer` types were updated to take all the generics needed to add the `uiSchema?: UiSchema` parameter to the functions - In `@rjsf/validator-ajv6`, updated the `customizeValidator` and `AJV6Validator` implementations to add the `S` and `F` generics - Updated `validateFormData()` to accept a new optional `uiSchema` parameter that is passed to `transformErrors()` and `customValidate()` - In `@rjsf/validator-ajv8`, updated the `customizeValidator` and `AJV8Validator` implementations to add the `F` generic - Updated `validateFormData()` to accept a new optional `uiSchema` parameter that is passed to `transformErrors()` and `customValidate()` - In `@rjsf/core`, updated the `ValidatorType`, `CustomValidator` and `ErrorTransformer` types to add the appropriate missing generics - Also passed `uiSchema` to the `validateFormData()` call - Updated the `utility-functions.md` file to add the new generics - Updated the `typescript.md` file to switch to using `customizeValidator()` with the generics to get the `validator` - Updated the `CHANGELOG.md` file accordingly * - Add test cases to validate the proper passing of `uiSchema` --- CHANGELOG.md | 11 ++ docs/advanced-customization/typescript.md | 23 ++- docs/api-reference/utility-functions.md | 40 ++--- packages/core/src/components/Form.tsx | 15 +- packages/utils/src/createSchemaUtils.ts | 26 +-- .../utils/src/schema/getDefaultFormState.ts | 44 +++-- packages/utils/src/schema/getDisplayLabel.ts | 6 +- .../utils/src/schema/getMatchingOption.ts | 12 +- packages/utils/src/schema/isFilesArray.ts | 4 +- packages/utils/src/schema/isMultiSelect.ts | 14 +- packages/utils/src/schema/isSelect.ts | 14 +- .../utils/src/schema/mergeValidationData.ts | 6 +- packages/utils/src/schema/retrieveSchema.ts | 104 +++++++---- packages/utils/src/schema/toIdSchema.ts | 14 +- packages/utils/src/schema/toPathSchema.ts | 22 ++- packages/utils/src/types.ts | 42 +++-- .../validator-ajv6/src/customizeValidator.ts | 17 +- packages/validator-ajv6/src/validator.ts | 27 ++- .../validator-ajv6/test/validator.test.ts | 64 +++++-- .../validator-ajv8/src/customizeValidator.ts | 14 +- packages/validator-ajv8/src/validator.ts | 22 ++- .../validator-ajv8/test/validator.test.ts | 169 ++++++++++++------ 22 files changed, 470 insertions(+), 240 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eea1e8c1dc..16b0c74d79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ should change the heading of the (upcoming) version to include a major version b - Updated `ArrayField` to pass the new `totalItems` and `canAdd` props to the `ArrayFieldItemTemplate` instances, fixing [#3315](https://github.com/rjsf-team/react-jsonschema-form/issues/3315) - Also refactored the near duplicate logic for `onAddClick` and `onAddIndexClick` into a new `_handleAddClick()` function, fixing [#3316](https://github.com/rjsf-team/react-jsonschema-form/issues/3316) - Fix passing of generic types to a few helper methods, partially fixing [#3072](https://github.com/rjsf-team/react-jsonschema-form/issues/3072) +- Updated the types for `ValidatorType`, `CustomValidator` and `ErrorTransformer` to add the new generics, as well as passing `uiSchema` to the `validateFormData()` call, partially fixing [#3170](https://github.com/rjsf-team/react-jsonschema-form/issues/3170) ## @rjsf/fluent-ui - Updated the usage of the `ButtonTemplates` to pass the new required `registry` prop, filtering it out in the actual implementations before spreading props, fixing - [#3314](https://github.com/rjsf-team/react-jsonschema-form/issues/3314) @@ -66,11 +67,21 @@ should change the heading of the (upcoming) version to include a major version b ## @rjsf/utils - Updated the `SubmitButtonProps` and `IconButtonProps` to add required `registry` prop, fixing - [#3314](https://github.com/rjsf-team/react-jsonschema-form/issues/3314) - Updated the `ArrayFieldTemplateItemType` to add the new `totalItems` and `canAdd` props, fixing [#3315](https://github.com/rjsf-team/react-jsonschema-form/issues/3315) +- Updated the `CustomValidator` and `ErrorTransformer` types to take the full set of `T`, `S`, `F` generics in order to accept a new optional `uiSchema` property, partially fixing [#3170](https://github.com/rjsf-team/react-jsonschema-form/issues/3170) +- Updated the `ValidatorType` to add the `F` generic to allow the `validateFormData()` function to take a new optional `uiSchema` parameter, partially fixing [#3170](https://github.com/rjsf-team/react-jsonschema-form/issues/3170) + - Updated many of the schema-based utility functions to take the additional generics as well to fulfill the `ValidatorType` interface change + +## @rjsf/validator-ajv6 +- Updated the `customizeValidator` and `AJV6Validator` implementations to add the `S` and `F` generics, so that `validateFormData()` can accept a new optional `uiSchema` parameter that is passed to `transformErrors()` and `customValidate()`, partially fixing [#3170](https://github.com/rjsf-team/react-jsonschema-form/issues/3170) + +## @rjsf/validator-ajv8 +- Updated the `customizeValidator` and `AJV8Validator` implementations to add the `F` generic, so that `validateFormData()` can accept a new optional `uiSchema` parameter that is passed to `transformErrors()` and `customValidate()`, partially fixing [#3170](https://github.com/rjsf-team/react-jsonschema-form/issues/3170) ## Dev / docs / playground - Fixed the documentation for `ArrayFieldItemTemplate`, `SubmitButtonProps` and `IconButtonProps` as part of the fix for [#3314](https://github.com/rjsf-team/react-jsonschema-form/issues/3314) and [#3315](https://github.com/rjsf-team/react-jsonschema-form/issues/3315) - Updated the documentation in `form-props.md` for `children`, fixing [#3322](https://github.com/rjsf-team/react-jsonschema-form/issues/3322) - Added new `typescript.md` documentation to `Advanced Customization` describing how to use custom generics as part of the fix for [#3072](https://github.com/rjsf-team/react-jsonschema-form/issues/3072) +- Updated the documentation in `utilty-functions.md` to add the new `F` generic to all the places which needed them # 5.0.0-beta-15 diff --git a/docs/advanced-customization/typescript.md b/docs/advanced-customization/typescript.md index bdbca8849f..fedd652681 100644 --- a/docs/advanced-customization/typescript.md +++ b/docs/advanced-customization/typescript.md @@ -47,7 +47,7 @@ If you are working with a simple, unchanging JSON Schema and you have defined a ```tsx import { RJSFSchema } from "@rjsf/utils"; -import { validator } from "@rjsf/validator-ajv8"; +import { customizeValidator } from "@rjsf/validator-ajv8"; import { Form } from "@rjsf/core"; interface FormData { @@ -65,6 +65,8 @@ const schema: RJSFSchema = { const formData: FormData = {}; +const validator = customizeValidator(); + render(( schema={schema} validator={validator} formData={formData} /> ), document.getElementById("app")); @@ -78,7 +80,7 @@ If you are using something like the [Ajv utility types for schemas](https://ajv. ```tsx import { JSONSchemaType } from "ajv"; import { RJSFSchema } from "@rjsf/utils"; -import { validator } from "@rjsf/validator-ajv8"; +import { customizeValidator } from "@rjsf/validator-ajv8"; import { Form } from "@rjsf/core"; interface FormData { @@ -96,11 +98,14 @@ const schema: MySchema = { } }; +const validator = customizeValidator(); + render(( schema={schema} validator={validator} /> ), document.getElementById("app")); // Alternatively since you have the type, you could also use this +// const validator = customizeValidator(); // render(( // schema={schema} validator={validator} /> //), document.getElementById("app")); @@ -115,7 +120,7 @@ If you have a type for this data, you can override this generic as follows: ```tsx import { RJSFSchema } from "@rjsf/utils"; -import { validator } from "@rjsf/validator-ajv8"; +import { customizeValidator } from "@rjsf/validator-ajv8"; import { Form } from "@rjsf/core"; interface FormContext { @@ -136,6 +141,8 @@ const formContext: FormContext = { } }; +const validator = customizeValidator(); + render(( schema={schema} validator={validator} formContext={formContext} /> ), document.getElementById("app")); @@ -147,7 +154,7 @@ Using the `withTheme()` function is just as easy: ```tsx import { RJSFSchema } from "@rjsf/utils"; -import { validator } from "@rjsf/validator-ajv8"; +import { customizeValidator } from "@rjsf/validator-ajv8"; import { withTheme, ThemeProps } from '@rjsf/core'; interface FormData { @@ -173,6 +180,8 @@ const theme: ThemeProps = { widgets: {test: () const ThemedForm = withTheme(theme); +const validator = customizeValidator(); + const Demo = () => ( ); @@ -190,7 +199,7 @@ If you are doing something like the following to create a new theme based on `@r import React from "react"; import { WidgetProps } from "@rjsf/utils"; import { ThemeProps, withTheme } from "@rjsf/core"; -import { validator } from "@rjsf/validator-ajv8"; +import validator from "@rjsf/validator-ajv8"; import { Theme } from "@rjsf/mui"; const OldBaseInputTemplate = Theme.templates.BaseInputTemplate; @@ -221,7 +230,7 @@ Then you would use the new `generateTheme()` and `generateForm()` functions as f import React from "react"; import { WidgetProps } from "@rjsf/utils"; import { ThemeProps, withTheme } from "@rjsf/core"; -import { validator } from "@rjsf/validator-ajv8"; +import { customizeValidator } from "@rjsf/validator-ajv8"; import { generateTheme } from "@rjsf/mui"; interface FormData { @@ -262,6 +271,8 @@ const myTheme: ThemeProps = { const ThemedForm = withTheme(myTheme); +const validator = customizeValidator(); + // You could also do since they are effectively the same: // const ThemedForm = generateForm(myTheme); diff --git a/docs/api-reference/utility-functions.md b/docs/api-reference/utility-functions.md index a9c5d9969c..65b3b4c76b 100644 --- a/docs/api-reference/utility-functions.md +++ b/docs/api-reference/utility-functions.md @@ -347,7 +347,7 @@ Extracts the range spec information `{ step?: number, min?: number, max?: number #### Returns - RangeSpecType: A range specification from the schema -### schemaRequiresTrueValue() +### schemaRequiresTrueValue\() Check to see if a `schema` specifies that a value must be true. This happens when: - `schema.const` is truthy - `schema.enum` == `[true]` @@ -407,11 +407,11 @@ Converts a UTC date string into a local Date format ## Validator-based utility functions -### getDefaultFormState() +### getDefaultFormState() Returns the superset of `formData` that includes the given set updated to include any missing fields that have computed to have defaults provided in the `schema`. #### Parameters -- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary +- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary - theSchema: S - The schema for which the default state is desired - [formData]: T - The current formData, if any, onto which to provide any missing defaults - [rootSchema]: S - The root schema, used to primarily to look up `$ref`s @@ -424,7 +424,7 @@ Returns the superset of `formData` that includes the given set updated to includ Determines whether the combination of `schema` and `uiSchema` properties indicates that the label for the `schema` should be displayed in a UI. #### Parameters -- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary +- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary - schema: S - The schema for which the display label flag is desired - [uiSchema={}]: UiSchema - The UI schema from which to derive potentially displayable information - [rootSchema]: S - The root schema, used to primarily to look up `$ref`s @@ -432,11 +432,11 @@ Determines whether the combination of `schema` and `uiSchema` properties indicat #### Returns - boolean: True if the label should be displayed or false if it should not -### getMatchingOption() +### getMatchingOption() Given the `formData` and list of `options`, attempts to find the index of the option that best matches the data. #### Parameters -- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary +- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary - formData: T | undefined - The current formData, if any, used to figure out a match - options: S[] - The list of options to find a matching options from - rootSchema: S - The root schema, used to primarily to look up `$ref`s @@ -448,7 +448,7 @@ Given the `formData` and list of `options`, attempts to find the index of the op Checks to see if the `schema` and `uiSchema` combination represents an array of files #### Parameters -- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary +- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary - schema: S - The schema for which check for array of files flag is desired - [uiSchema={}]: UiSchema - The UI schema from which to check the widget - [rootSchema]: S - The root schema, used to primarily to look up `$ref`s @@ -456,47 +456,47 @@ Checks to see if the `schema` and `uiSchema` combination represents an array of #### Returns - boolean: True if schema/uiSchema contains an array of files, otherwise false -### isMultiSelect() +### isMultiSelect() Checks to see if the `schema` combination represents a multi-select #### Parameters -- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary +- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary - schema: S - The schema for which check for a multi-select flag is desired - [rootSchema]: S - The root schema, used to primarily to look up `$ref`s #### Returns - boolean: True if schema contains a multi-select, otherwise false -### isSelect() +### isSelect() Checks to see if the `schema` combination represents a select #### Parameters -- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary +- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary - theSchema: S - The schema for which check for a select flag is desired - [rootSchema]: S - The root schema, used to primarily to look up `$ref`s #### Returns - boolean: True if schema contains a select, otherwise false -### mergeValidationData() +### mergeValidationData() Merges the errors in `additionalErrorSchema` into the existing `validationData` by combining the hierarchies in the two `ErrorSchema`s and then appending the error list from the `additionalErrorSchema` obtained by calling `validator.toErrorList()` onto the `errors` in the `validationData`. If no `additionalErrorSchema` is passed, then `validationData` is returned. #### Parameters -- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used to convert an ErrorSchema to a list of errors +- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used to convert an ErrorSchema to a list of errors - validationData: ValidationData - The current `ValidationData` into which to merge the additional errors - [additionalErrorSchema]: ErrorSchema - The additional set of errors in an `ErrorSchema` #### Returns - ValidationData: The `validationData` with the additional errors from `additionalErrorSchema` merged into it, if provided. -### retrieveSchema() +### retrieveSchema() Retrieves an expanded schema that has had all of its conditions, additional properties, references and dependencies resolved and merged into the `schema` given a `validator`, `rootSchema` and `rawFormData` that is used to do the potentially recursive resolution. #### Parameters -- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be forwarded to all the APIs +- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be forwarded to all the APIs - schema: S - The schema for which retrieving a schema is desired - [rootSchema={}]: S - The root schema that will be forwarded to all the APIs - [rawFormData]: T - The current formData, if any, to assist retrieving a schema @@ -504,11 +504,11 @@ potentially recursive resolution. #### Returns - RJSFSchema: The schema having its conditions, additional properties, references and dependencies resolved -### toIdSchema() +### toIdSchema() Generates an `IdSchema` object for the `schema`, recursively #### Parameters -- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary +- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary - schema: S - The schema for which the `IdSchema` is desired - [id]: string | null - The base id for the schema - [rootSchema]: S - The root schema, used to primarily to look up `$ref`s @@ -519,11 +519,11 @@ Generates an `IdSchema` object for the `schema`, recursively #### Returns - IDSchema: The `IdSchema` object for the `schema` -### toPathSchema() +### toPathSchema() Generates an `PathSchema` object for the `schema`, recursively #### Parameters -- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary +- validator: ValidatorType - An implementation of the `ValidatorType` interface that will be used when necessary - schema: S - The schema for which the `PathSchema` is desired - [name='']: string - The base name for the schema - [rootSchema]: S - The root schema, used to primarily to look up `$ref`s @@ -539,7 +539,7 @@ Creates a `SchemaUtilsType` interface that is based around the given `validator` The resulting interface implementation will forward the `validator` and `rootSchema` to all the wrapped APIs. #### Parameters -- validator: ValidatorType - an implementation of the `ValidatorType` interface that will be forwarded to all the APIs +- validator: ValidatorType - an implementation of the `ValidatorType` interface that will be forwarded to all the APIs - rootSchema: S - The root schema that will be forwarded to all the APIs #### Returns diff --git a/packages/core/src/components/Form.tsx b/packages/core/src/components/Form.tsx index 2682754f3d..a2b6965c33 100644 --- a/packages/core/src/components/Form.tsx +++ b/packages/core/src/components/Form.tsx @@ -43,7 +43,7 @@ export interface FormProps< /** The JSON schema object for the form */ schema: S; /** An implementation of the `ValidatorType` interface that is needed for form validation to work */ - validator: ValidatorType; + validator: ValidatorType; /** The optional children for the form, if provided, it will replace the default `SubmitButton` */ children?: React.ReactNode; /** The uiSchema for the form */ @@ -138,7 +138,7 @@ export interface FormProps< target?: string; // Errors and validation /** Formerly the `validate` prop; Takes a function that specifies custom validation rules for the form */ - customValidate?: CustomValidator; + customValidate?: CustomValidator; /** This prop allows passing in custom errors that are augmented with the existing JSON Schema errors on the form; it * can be used to implement asynchronous validation */ @@ -169,7 +169,7 @@ export interface FormProps< /** A function can be passed to this prop in order to make modifications to the default errors resulting from JSON * Schema validation */ - transformErrors?: ErrorTransformer; + transformErrors?: ErrorTransformer; // Private /** * _internalFormWrapper is currently used by the semantic-ui theme to provide a custom wrapper around `
` @@ -309,7 +309,7 @@ export default class Form< "liveValidate" in props ? props.liveValidate : this.props.liveValidate; const mustValidate = edit && !props.noValidate && liveValidate; const rootSchema = schema; - let schemaUtils: SchemaUtilsType = state.schemaUtils; + let schemaUtils: SchemaUtilsType = state.schemaUtils; if ( !schemaUtils || schemaUtils.doesSchemaUtilsDiffer(props.validator, rootSchema) @@ -407,12 +407,12 @@ export default class Form< validate( formData: T, schema = this.props.schema, - altSchemaUtils?: SchemaUtilsType + altSchemaUtils?: SchemaUtilsType ): ValidationData { const schemaUtils = altSchemaUtils ? altSchemaUtils : this.state.schemaUtils; - const { customValidate, transformErrors } = this.props; + const { customValidate, transformErrors, uiSchema } = this.props; const resolvedSchema = schemaUtils.retrieveSchema(schema, formData); return schemaUtils .getValidator() @@ -420,7 +420,8 @@ export default class Form< formData, resolvedSchema, customValidate, - transformErrors + transformErrors, + uiSchema ); } diff --git a/packages/utils/src/createSchemaUtils.ts b/packages/utils/src/createSchemaUtils.ts index a33d76caef..bf582255c0 100644 --- a/packages/utils/src/createSchemaUtils.ts +++ b/packages/utils/src/createSchemaUtils.ts @@ -33,17 +33,17 @@ class SchemaUtils< T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any -> implements SchemaUtilsType +> implements SchemaUtilsType { rootSchema: S; - validator: ValidatorType; + validator: ValidatorType; /** Constructs the `SchemaUtils` instance with the given `validator` and `rootSchema` stored as instance variables * * @param validator - An implementation of the `ValidatorType` interface that will be forwarded to all the APIs * @param rootSchema - The root schema that will be forwarded to all the APIs */ - constructor(validator: ValidatorType, rootSchema: S) { + constructor(validator: ValidatorType, rootSchema: S) { this.rootSchema = rootSchema; this.validator = validator; } @@ -65,7 +65,7 @@ class SchemaUtils< * @returns - True if the `SchemaUtilsType` differs from the given `validator` or `rootSchema` */ doesSchemaUtilsDiffer( - validator: ValidatorType, + validator: ValidatorType, rootSchema: S ): boolean { if (!validator || !rootSchema) { @@ -91,7 +91,7 @@ class SchemaUtils< formData?: T, includeUndefinedValues: boolean | "excludeObjectChildren" = false ): T | T[] | undefined { - return getDefaultFormState( + return getDefaultFormState( this.validator, schema, formData, @@ -123,7 +123,7 @@ class SchemaUtils< * @returns - The index of the matched option or 0 if none is available */ getMatchingOption(formData: T, options: S[]) { - return getMatchingOption( + return getMatchingOption( this.validator, formData, options, @@ -152,7 +152,7 @@ class SchemaUtils< * @returns - True if schema contains a multi-select, otherwise false */ isMultiSelect(schema: S) { - return isMultiSelect(this.validator, schema, this.rootSchema); + return isMultiSelect(this.validator, schema, this.rootSchema); } /** Checks to see if the `schema` combination represents a select @@ -161,7 +161,7 @@ class SchemaUtils< * @returns - True if schema contains a select, otherwise false */ isSelect(schema: S) { - return isSelect(this.validator, schema, this.rootSchema); + return isSelect(this.validator, schema, this.rootSchema); } /** Merges the errors in `additionalErrorSchema` into the existing `validationData` by combining the hierarchies in @@ -177,7 +177,7 @@ class SchemaUtils< validationData: ValidationData, additionalErrorSchema?: ErrorSchema ): ValidationData { - return mergeValidationData( + return mergeValidationData( this.validator, validationData, additionalErrorSchema @@ -193,7 +193,7 @@ class SchemaUtils< * @returns - The schema having its conditions, additional properties, references and dependencies resolved */ retrieveSchema(schema: S, rawFormData: T) { - return retrieveSchema( + return retrieveSchema( this.validator, schema, this.rootSchema, @@ -217,7 +217,7 @@ class SchemaUtils< idPrefix = "root", idSeparator = "_" ): IdSchema { - return toIdSchema( + return toIdSchema( this.validator, schema, id, @@ -236,7 +236,7 @@ class SchemaUtils< * @returns - The `PathSchema` object for the `schema` */ toPathSchema(schema: S, name?: string, formData?: T): PathSchema { - return toPathSchema( + return toPathSchema( this.validator, schema, name, @@ -257,6 +257,6 @@ export default function createSchemaUtils< T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any ->(validator: ValidatorType, rootSchema: S): SchemaUtilsType { +>(validator: ValidatorType, rootSchema: S): SchemaUtilsType { return new SchemaUtils(validator, rootSchema); } diff --git a/packages/utils/src/schema/getDefaultFormState.ts b/packages/utils/src/schema/getDefaultFormState.ts index be29fd8e2c..8be1438827 100644 --- a/packages/utils/src/schema/getDefaultFormState.ts +++ b/packages/utils/src/schema/getDefaultFormState.ts @@ -17,6 +17,7 @@ import isFixedItems from "../isFixedItems"; import mergeDefaultsWithFormData from "../mergeDefaultsWithFormData"; import mergeObjects from "../mergeObjects"; import { + FormContextType, GenericObjectType, RJSFSchema, StrictRJSFSchema, @@ -93,16 +94,17 @@ export function getInnerSchemaForArrayItem< */ export function computeDefaults< T = any, - S extends StrictRJSFSchema = RJSFSchema + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any >( - validator: ValidatorType, + validator: ValidatorType, rawSchema: S, parentDefaults?: T, rootSchema: S = {} as S, rawFormData?: T, includeUndefinedValues: boolean | "excludeObjectChildren" = false ): T | T[] | undefined { - const formData = isObject(rawFormData) ? rawFormData : {}; + const formData: T = (isObject(rawFormData) ? rawFormData : {}) as T; let schema: S = isObject(rawSchema) ? rawSchema : ({} as S); // Compute the defaults recursively: give highest priority to deepest nodes. let defaults: T | T[] | undefined = parentDefaults; @@ -118,7 +120,7 @@ export function computeDefaults< } else if (REF_KEY in schema) { // Use referenced schema defaults for this node. const refSchema = findSchemaDefinition(schema[REF_KEY]!, rootSchema); - return computeDefaults( + return computeDefaults( validator, refSchema, defaults, @@ -127,13 +129,13 @@ export function computeDefaults< includeUndefinedValues ); } else if (DEPENDENCIES_KEY in schema) { - const resolvedSchema = resolveDependencies( + const resolvedSchema = resolveDependencies( validator, schema, rootSchema, formData ); - return computeDefaults( + return computeDefaults( validator, resolvedSchema, defaults, @@ -154,7 +156,7 @@ export function computeDefaults< ) as T[]; } else if (ONE_OF_KEY in schema) { schema = schema.oneOf![ - getMatchingOption( + getMatchingOption( validator, isEmpty(formData) ? undefined : formData, schema.oneOf as S[], @@ -163,7 +165,7 @@ export function computeDefaults< ] as S; } else if (ANY_OF_KEY in schema) { schema = schema.anyOf![ - getMatchingOption( + getMatchingOption( validator, isEmpty(formData) ? undefined : formData, schema.anyOf as S[], @@ -177,14 +179,14 @@ export function computeDefaults< defaults = schema.default as unknown as T; } - switch (getSchemaType(schema)) { + switch (getSchemaType(schema)) { // We need to recur for object schema inner default values. case "object": return Object.keys(schema.properties || {}).reduce( (acc: GenericObjectType, key: string) => { // Compute the defaults for this node, with the parent defaults we might // have from a previous run: defaults[key]. - const computedDefault = computeDefaults( + const computedDefault = computeDefaults( validator, get(schema, [PROPERTIES_KEY, key]), get(defaults, [key]), @@ -219,7 +221,12 @@ export function computeDefaults< AdditionalItemsHandling.Fallback, idx ); - return computeDefaults(validator, schemaItem, item, rootSchema); + return computeDefaults( + validator, + schemaItem, + item, + rootSchema + ); }) as T[]; } @@ -227,7 +234,7 @@ export function computeDefaults< if (Array.isArray(rawFormData)) { const schemaItem: S = getInnerSchemaForArrayItem(schema); defaults = rawFormData.map((item: T, idx: number) => { - return computeDefaults( + return computeDefaults( validator, schemaItem, get(defaults, [idx]), @@ -237,7 +244,7 @@ export function computeDefaults< }) as T[]; } if (schema.minItems) { - if (!isMultiSelect(validator, schema, rootSchema)) { + if (!isMultiSelect(validator, schema, rootSchema)) { const defaultsLength = Array.isArray(defaults) ? defaults.length : 0; if (schema.minItems > defaultsLength) { const defaultEntries: T[] = (defaults || []) as T[]; @@ -250,7 +257,7 @@ export function computeDefaults< const fillerEntries: T[] = new Array( schema.minItems - defaultsLength ).fill( - computeDefaults( + computeDefaults( validator, fillerSchema, fillerDefault, @@ -281,9 +288,10 @@ export function computeDefaults< */ export default function getDefaultFormState< T = any, - S extends StrictRJSFSchema = RJSFSchema + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any >( - validator: ValidatorType, + validator: ValidatorType, theSchema: S, formData?: T, rootSchema?: S, @@ -292,13 +300,13 @@ export default function getDefaultFormState< if (!isObject(theSchema)) { throw new Error("Invalid schema: " + theSchema); } - const schema = retrieveSchema( + const schema = retrieveSchema( validator, theSchema, rootSchema, formData ); - const defaults = computeDefaults( + const defaults = computeDefaults( validator, schema, undefined, diff --git a/packages/utils/src/schema/getDisplayLabel.ts b/packages/utils/src/schema/getDisplayLabel.ts index ca9bfc6cc2..e3198f0467 100644 --- a/packages/utils/src/schema/getDisplayLabel.ts +++ b/packages/utils/src/schema/getDisplayLabel.ts @@ -26,7 +26,7 @@ export default function getDisplayLabel< S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any >( - validator: ValidatorType, + validator: ValidatorType, schema: S, uiSchema: UiSchema = {}, rootSchema?: S @@ -34,11 +34,11 @@ export default function getDisplayLabel< const uiOptions = getUiOptions(uiSchema); const { label = true } = uiOptions; let displayLabel = !!label; - const schemaType = getSchemaType(schema); + const schemaType = getSchemaType(schema); if (schemaType === "array") { displayLabel = - isMultiSelect(validator, schema, rootSchema) || + isMultiSelect(validator, schema, rootSchema) || isFilesArray(validator, schema, uiSchema, rootSchema) || isCustomWidget(uiSchema); } diff --git a/packages/utils/src/schema/getMatchingOption.ts b/packages/utils/src/schema/getMatchingOption.ts index 0c621bb2a3..78a2e6806a 100644 --- a/packages/utils/src/schema/getMatchingOption.ts +++ b/packages/utils/src/schema/getMatchingOption.ts @@ -1,4 +1,9 @@ -import { RJSFSchema, StrictRJSFSchema, ValidatorType } from "../types"; +import { + FormContextType, + RJSFSchema, + StrictRJSFSchema, + ValidatorType, +} from "../types"; /** Given the `formData` and list of `options`, attempts to find the index of the option that best matches the data. * @@ -10,9 +15,10 @@ import { RJSFSchema, StrictRJSFSchema, ValidatorType } from "../types"; */ export default function getMatchingOption< T = any, - S extends StrictRJSFSchema = RJSFSchema + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any >( - validator: ValidatorType, + validator: ValidatorType, formData: T | undefined, options: S[], rootSchema: S diff --git a/packages/utils/src/schema/isFilesArray.ts b/packages/utils/src/schema/isFilesArray.ts index 1fa06130ad..d260fa7d52 100644 --- a/packages/utils/src/schema/isFilesArray.ts +++ b/packages/utils/src/schema/isFilesArray.ts @@ -21,7 +21,7 @@ export default function isFilesArray< S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any >( - validator: ValidatorType, + validator: ValidatorType, schema: S, uiSchema: UiSchema = {}, rootSchema?: S @@ -30,7 +30,7 @@ export default function isFilesArray< return true; } if (schema.items) { - const itemsSchema = retrieveSchema( + const itemsSchema = retrieveSchema( validator, schema.items as S, rootSchema diff --git a/packages/utils/src/schema/isMultiSelect.ts b/packages/utils/src/schema/isMultiSelect.ts index b55d124e48..76343023ef 100644 --- a/packages/utils/src/schema/isMultiSelect.ts +++ b/packages/utils/src/schema/isMultiSelect.ts @@ -1,4 +1,9 @@ -import { RJSFSchema, StrictRJSFSchema, ValidatorType } from "../types"; +import { + FormContextType, + RJSFSchema, + StrictRJSFSchema, + ValidatorType, +} from "../types"; import isSelect from "./isSelect"; @@ -11,8 +16,9 @@ import isSelect from "./isSelect"; */ export default function isMultiSelect< T = any, - S extends StrictRJSFSchema = RJSFSchema ->(validator: ValidatorType, schema: S, rootSchema?: S) { + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(validator: ValidatorType, schema: S, rootSchema?: S) { if ( !schema.uniqueItems || !schema.items || @@ -20,5 +26,5 @@ export default function isMultiSelect< ) { return false; } - return isSelect(validator, schema.items as S, rootSchema); + return isSelect(validator, schema.items as S, rootSchema); } diff --git a/packages/utils/src/schema/isSelect.ts b/packages/utils/src/schema/isSelect.ts index 3c3fb60a66..3a3db62725 100644 --- a/packages/utils/src/schema/isSelect.ts +++ b/packages/utils/src/schema/isSelect.ts @@ -1,5 +1,10 @@ import isConstant from "../isConstant"; -import { RJSFSchema, StrictRJSFSchema, ValidatorType } from "../types"; +import { + FormContextType, + RJSFSchema, + StrictRJSFSchema, + ValidatorType, +} from "../types"; import retrieveSchema from "./retrieveSchema"; /** Checks to see if the `schema` combination represents a select @@ -11,9 +16,10 @@ import retrieveSchema from "./retrieveSchema"; */ export default function isSelect< T = any, - S extends StrictRJSFSchema = RJSFSchema ->(validator: ValidatorType, theSchema: S, rootSchema: S = {} as S) { - const schema = retrieveSchema( + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(validator: ValidatorType, theSchema: S, rootSchema: S = {} as S) { + const schema = retrieveSchema( validator, theSchema, rootSchema, diff --git a/packages/utils/src/schema/mergeValidationData.ts b/packages/utils/src/schema/mergeValidationData.ts index 188f161d4e..2ab632ea2b 100644 --- a/packages/utils/src/schema/mergeValidationData.ts +++ b/packages/utils/src/schema/mergeValidationData.ts @@ -3,6 +3,7 @@ import isEmpty from "lodash/isEmpty"; import mergeObjects from "../mergeObjects"; import { ErrorSchema, + FormContextType, RJSFSchema, StrictRJSFSchema, ValidationData, @@ -21,9 +22,10 @@ import { */ export default function mergeValidationData< T = any, - S extends StrictRJSFSchema = RJSFSchema + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any >( - validator: ValidatorType, + validator: ValidatorType, validationData: ValidationData, additionalErrorSchema?: ErrorSchema ): ValidationData { diff --git a/packages/utils/src/schema/retrieveSchema.ts b/packages/utils/src/schema/retrieveSchema.ts index 2da8c4a453..bbfd8650d1 100644 --- a/packages/utils/src/schema/retrieveSchema.ts +++ b/packages/utils/src/schema/retrieveSchema.ts @@ -16,6 +16,7 @@ import guessType from "../guessType"; import isObject from "../isObject"; import mergeSchemas from "../mergeSchemas"; import { + FormContextType, GenericObjectType, RJSFSchema, StrictRJSFSchema, @@ -34,8 +35,9 @@ import getMatchingOption from "./getMatchingOption"; */ export function resolveCondition< T = any, - S extends StrictRJSFSchema = RJSFSchema ->(validator: ValidatorType, schema: S, rootSchema: S, formData: T) { + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(validator: ValidatorType, schema: S, rootSchema: S, formData: T) { const { if: expression, then, @@ -56,13 +58,18 @@ export function resolveCondition< validator, mergeSchemas( resolvedSchemaLessConditional, - retrieveSchema(validator, conditionalSchema, rootSchema, formData) + retrieveSchema( + validator, + conditionalSchema as S, + rootSchema, + formData + ) ) as S, rootSchema, formData ); } - return retrieveSchema( + return retrieveSchema( validator, resolvedSchemaLessConditional as S, rootSchema, @@ -79,23 +86,27 @@ export function resolveCondition< * @param [formData] - The current formData, if any, to assist retrieving a schema * @returns - The schema having its references and dependencies resolved */ -export function resolveSchema( - validator: ValidatorType, +export function resolveSchema< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>( + validator: ValidatorType, schema: S, rootSchema: S = {} as S, formData?: T ): S { if (REF_KEY in schema) { - return resolveReference(validator, schema, rootSchema, formData); + return resolveReference(validator, schema, rootSchema, formData); } if (DEPENDENCIES_KEY in schema) { - const resolvedSchema = resolveDependencies( + const resolvedSchema = resolveDependencies( validator, schema, rootSchema, formData ); - return retrieveSchema( + return retrieveSchema( validator, resolvedSchema, rootSchema, @@ -106,7 +117,7 @@ export function resolveSchema( return { ...schema, allOf: schema.allOf!.map((allOfSubschema) => - retrieveSchema( + retrieveSchema( validator, allOfSubschema as S, rootSchema, @@ -129,14 +140,20 @@ export function resolveSchema( */ export function resolveReference< T = any, - S extends StrictRJSFSchema = RJSFSchema ->(validator: ValidatorType, schema: S, rootSchema: S, formData?: T): S { + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>( + validator: ValidatorType, + schema: S, + rootSchema: S, + formData?: T +): S { // Retrieve the referenced schema definition. const $refSchema = findSchemaDefinition(schema.$ref, rootSchema); // Drop the $ref property of the source schema. const { $ref, ...localSchema } = schema; // Update referenced schema definition with local schema properties. - return retrieveSchema( + return retrieveSchema( validator, { ...$refSchema, ...localSchema }, rootSchema, @@ -154,9 +171,10 @@ export function resolveReference< */ export function stubExistingAdditionalProperties< T = any, - S extends StrictRJSFSchema = RJSFSchema + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any >( - validator: ValidatorType, + validator: ValidatorType, theSchema: S, rootSchema?: S, aFormData?: T @@ -179,7 +197,7 @@ export function stubExistingAdditionalProperties< let additionalProperties: S["additionalProperties"] = {}; if (typeof schema.additionalProperties !== "boolean") { if (REF_KEY in schema.additionalProperties!) { - additionalProperties = retrieveSchema( + additionalProperties = retrieveSchema( validator, { $ref: get(schema.additionalProperties, [REF_KEY]) } as S, rootSchema, @@ -215,9 +233,10 @@ export function stubExistingAdditionalProperties< */ export default function retrieveSchema< T = any, - S extends StrictRJSFSchema = RJSFSchema + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any >( - validator: ValidatorType, + validator: ValidatorType, schema: S, rootSchema: S = {} as S, rawFormData?: T @@ -225,7 +244,7 @@ export default function retrieveSchema< if (!isObject(schema)) { return {} as S; } - let resolvedSchema = resolveSchema( + let resolvedSchema = resolveSchema( validator, schema, rootSchema, @@ -233,7 +252,7 @@ export default function retrieveSchema< ); if ("if" in schema) { - return resolveCondition( + return resolveCondition( validator, schema, rootSchema, @@ -258,7 +277,7 @@ export default function retrieveSchema< ADDITIONAL_PROPERTIES_KEY in resolvedSchema && resolvedSchema.additionalProperties !== false; if (hasAdditionalProperties) { - return stubExistingAdditionalProperties( + return stubExistingAdditionalProperties( validator, resolvedSchema, rootSchema, @@ -278,14 +297,20 @@ export default function retrieveSchema< */ export function resolveDependencies< T = any, - S extends StrictRJSFSchema = RJSFSchema ->(validator: ValidatorType, schema: S, rootSchema: S, formData?: T): S { + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>( + validator: ValidatorType, + schema: S, + rootSchema: S, + formData?: T +): S { // Drop the dependencies from the source schema. const { dependencies, ...remainingSchema } = schema; let resolvedSchema: S = remainingSchema as S; if (Array.isArray(resolvedSchema.oneOf)) { resolvedSchema = resolvedSchema.oneOf[ - getMatchingOption( + getMatchingOption( validator, formData, resolvedSchema.oneOf as S[], @@ -294,7 +319,7 @@ export function resolveDependencies< ] as S; } else if (Array.isArray(resolvedSchema.anyOf)) { resolvedSchema = resolvedSchema.anyOf[ - getMatchingOption( + getMatchingOption( validator, formData, resolvedSchema.anyOf as S[], @@ -302,7 +327,7 @@ export function resolveDependencies< ) ] as S; } - return processDependencies( + return processDependencies( validator, dependencies, resolvedSchema, @@ -322,9 +347,10 @@ export function resolveDependencies< */ export function processDependencies< T = any, - S extends StrictRJSFSchema = RJSFSchema + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any >( - validator: ValidatorType, + validator: ValidatorType, dependencies: S["dependencies"], resolvedSchema: S, rootSchema: S, @@ -346,9 +372,9 @@ export function processDependencies< dependencies as GenericObjectType ); if (Array.isArray(dependencyValue)) { - schema = withDependentProperties(schema, dependencyValue); + schema = withDependentProperties(schema, dependencyValue); } else if (isObject(dependencyValue)) { - schema = withDependentSchema( + schema = withDependentSchema( validator, schema, rootSchema, @@ -357,7 +383,7 @@ export function processDependencies< formData ); } - return processDependencies( + return processDependencies( validator, remainingDependencies, schema, @@ -398,16 +424,17 @@ export function withDependentProperties< */ export function withDependentSchema< T = any, - S extends StrictRJSFSchema = RJSFSchema + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any >( - validator: ValidatorType, + validator: ValidatorType, schema: S, rootSchema: S, dependencyKey: string, dependencyValue: S, formData?: T ) { - const { oneOf, ...dependentSchema } = retrieveSchema( + const { oneOf, ...dependentSchema } = retrieveSchema( validator, dependencyValue, rootSchema, @@ -423,14 +450,14 @@ export function withDependentSchema< if (typeof subschema === "boolean" || !(REF_KEY in subschema)) { return subschema; } - return resolveReference( + return resolveReference( validator, subschema as S, rootSchema, formData ); }); - return withExactlyOneSubschema( + return withExactlyOneSubschema( validator, schema, rootSchema, @@ -452,9 +479,10 @@ export function withDependentSchema< */ export function withExactlyOneSubschema< T = any, - S extends StrictRJSFSchema = RJSFSchema + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any >( - validator: ValidatorType, + validator: ValidatorType, schema: S, rootSchema: S, dependencyKey: string, diff --git a/packages/utils/src/schema/toIdSchema.ts b/packages/utils/src/schema/toIdSchema.ts index 13e9e7c9fb..4ee750756b 100644 --- a/packages/utils/src/schema/toIdSchema.ts +++ b/packages/utils/src/schema/toIdSchema.ts @@ -10,6 +10,7 @@ import { } from "../constants"; import isObject from "../isObject"; import { + FormContextType, IdSchema, RJSFSchema, StrictRJSFSchema, @@ -30,9 +31,10 @@ import retrieveSchema from "./retrieveSchema"; */ export default function toIdSchema< T = any, - S extends StrictRJSFSchema = RJSFSchema + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any >( - validator: ValidatorType, + validator: ValidatorType, schema: S, id?: string | null, rootSchema?: S, @@ -41,13 +43,13 @@ export default function toIdSchema< idSeparator = "_" ): IdSchema { if (REF_KEY in schema || DEPENDENCIES_KEY in schema || ALL_OF_KEY in schema) { - const _schema = retrieveSchema( + const _schema = retrieveSchema( validator, schema, rootSchema, formData ); - return toIdSchema( + return toIdSchema( validator, _schema, id, @@ -58,7 +60,7 @@ export default function toIdSchema< ); } if (ITEMS_KEY in schema && !get(schema, [ITEMS_KEY, REF_KEY])) { - return toIdSchema( + return toIdSchema( validator, get(schema, ITEMS_KEY) as S, id, @@ -74,7 +76,7 @@ export default function toIdSchema< for (const name in schema.properties) { const field = get(schema, [PROPERTIES_KEY, name]); const fieldId = idSchema[ID_KEY] + idSeparator + name; - idSchema[name] = toIdSchema( + idSchema[name] = toIdSchema( validator, isObject(field) ? field : {}, fieldId, diff --git a/packages/utils/src/schema/toPathSchema.ts b/packages/utils/src/schema/toPathSchema.ts index 84279fab8f..d0bce50273 100644 --- a/packages/utils/src/schema/toPathSchema.ts +++ b/packages/utils/src/schema/toPathSchema.ts @@ -12,6 +12,7 @@ import { RJSF_ADDITONAL_PROPERTIES_FLAG, } from "../constants"; import { + FormContextType, PathSchema, RJSFSchema, StrictRJSFSchema, @@ -30,22 +31,29 @@ import retrieveSchema from "./retrieveSchema"; */ export default function toPathSchema< T = any, - S extends StrictRJSFSchema = RJSFSchema + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any >( - validator: ValidatorType, + validator: ValidatorType, schema: S, name = "", rootSchema?: S, formData?: T ): PathSchema { if (REF_KEY in schema || DEPENDENCIES_KEY in schema || ALL_OF_KEY in schema) { - const _schema = retrieveSchema( + const _schema = retrieveSchema( validator, schema, rootSchema, formData ); - return toPathSchema(validator, _schema, name, rootSchema, formData); + return toPathSchema( + validator, + _schema, + name, + rootSchema, + formData + ); } const pathSchema: PathSchema = { @@ -61,9 +69,9 @@ export default function toPathSchema< if (ITEMS_KEY in schema && Array.isArray(formData)) { formData.forEach((element, i: number) => { - pathSchema[i] = toPathSchema( + pathSchema[i] = toPathSchema( validator, - schema.items as RJSFSchema, + schema.items as S, `${name}.${i}`, rootSchema, element @@ -72,7 +80,7 @@ export default function toPathSchema< } else if (PROPERTIES_KEY in schema) { for (const property in schema.properties) { const field = get(schema, [PROPERTIES_KEY, property]); - pathSchema[property] = toPathSchema( + pathSchema[property] = toPathSchema( validator, field, `${name}.${property}`, diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index 77402ac57d..d77a3b7cb3 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -856,19 +856,29 @@ export type UiSchema< "ui:options"?: UIOptionsType; }; -/** A `CustomValidator` function takes in a `formData` and `errors` object and returns the given `errors` object back, - * while potentially adding additional messages to the `errors` +/** A `CustomValidator` function takes in a `formData`, `errors` and `uiSchema` objects and returns the given `errors` + * object back, while potentially adding additional messages to the `errors` */ -export type CustomValidator = ( +export type CustomValidator< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +> = ( formData: T, - errors: FormValidation + errors: FormValidation, + uiSchema?: UiSchema ) => FormValidation; -/** An `ErrorTransformer` function will take in a list of `errors` and potentially return a transformation of those - * errors in what ever way it deems necessary +/** An `ErrorTransformer` function will take in a list of `errors` & a `uiSchema` and potentially return a + * transformation of those errors in what ever way it deems necessary */ -export type ErrorTransformer = ( - errors: RJSFValidationError[] +export type ErrorTransformer< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +> = ( + errors: RJSFValidationError[], + uiSchema?: UiSchema ) => RJSFValidationError[]; /** The type that describes the data that is returned from the `ValidatorType.validateFormData()` function */ @@ -884,7 +894,8 @@ export type ValidationData = { */ export interface ValidatorType< T = any, - S extends StrictRJSFSchema = RJSFSchema + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any > { /** This function processes the `formData` with an optional user contributed `customValidate` function, which receives * the form data and a `errorHandler` function that will be used to add custom validation errors for each field. Also @@ -895,12 +906,14 @@ export interface ValidatorType< * @param schema - The schema against which to validate the form data * @param [customValidate] - An optional function that is used to perform custom validation * @param [transformErrors] - An optional function that is used to transform errors after AJV validation + * @param [uiSchema] - An optional uiSchema that is passed to `transformErrors` and `customValidate` */ validateFormData( formData: T | undefined, schema: S, - customValidate?: CustomValidator, - transformErrors?: ErrorTransformer + customValidate?: CustomValidator, + transformErrors?: ErrorTransformer, + uiSchema?: UiSchema ): ValidationData; /** Converts an `errorSchema` into a list of `RJSFValidationErrors` * @@ -946,7 +959,7 @@ export interface SchemaUtilsType< * * @returns - The `ValidatorType` */ - getValidator(): ValidatorType; + getValidator(): ValidatorType; /** Determines whether either the `validator` and `rootSchema` differ from the ones associated with this instance of * the `SchemaUtilsType`. If either `validator` or `rootSchema` are falsy, then return false to prevent the creation * of a new `SchemaUtilsType` with incomplete properties. @@ -955,7 +968,10 @@ export interface SchemaUtilsType< * @param rootSchema - The root schema that will be compared against the current one * @returns - True if the `SchemaUtilsType` differs from the given `validator` or `rootSchema` */ - doesSchemaUtilsDiffer(validator: ValidatorType, rootSchema: S): boolean; + doesSchemaUtilsDiffer( + validator: ValidatorType, + rootSchema: S + ): boolean; /** Returns the superset of `formData` that includes the given set updated to include any missing fields that have * computed to have defaults provided in the `schema`. * diff --git a/packages/validator-ajv6/src/customizeValidator.ts b/packages/validator-ajv6/src/customizeValidator.ts index 01e2a828cb..d751a58499 100644 --- a/packages/validator-ajv6/src/customizeValidator.ts +++ b/packages/validator-ajv6/src/customizeValidator.ts @@ -1,4 +1,9 @@ -import { ValidatorType } from "@rjsf/utils"; +import { + FormContextType, + RJSFSchema, + StrictRJSFSchema, + ValidatorType, +} from "@rjsf/utils"; import { CustomValidatorOptionsType } from "./types"; import AJV6Validator from "./validator"; @@ -9,8 +14,10 @@ import AJV6Validator from "./validator"; * @param [options={}] - The `CustomValidatorOptionsType` options that are used to create the `ValidatorType` instance * @deprecated in favor of the `@rjsf/validator-ajv8 */ -export default function customizeValidator( - options: CustomValidatorOptionsType = {} -): ValidatorType { - return new AJV6Validator(options); +export default function customizeValidator< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(options: CustomValidatorOptionsType = {}): ValidatorType { + return new AJV6Validator(options); } diff --git a/packages/validator-ajv6/src/validator.ts b/packages/validator-ajv6/src/validator.ts index 43d6fa5aaa..962ac97cd9 100644 --- a/packages/validator-ajv6/src/validator.ts +++ b/packages/validator-ajv6/src/validator.ts @@ -6,10 +6,13 @@ import { ErrorSchemaBuilder, ErrorTransformer, FieldValidation, + FormContextType, FormValidation, GenericObjectType, RJSFSchema, RJSFValidationError, + StrictRJSFSchema, + UiSchema, ValidationData, ValidatorType, getDefaultFormState, @@ -28,8 +31,11 @@ const ROOT_SCHEMA_PREFIX = "__rjsf_rootSchema"; * * @deprecated in favor of the `@rjsf/validator-ajv8 */ -export default class AJV6Validator - implements ValidatorType +export default class AJV6Validator< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +> implements ValidatorType { /** The AJV instance to use for all validations * @@ -234,12 +240,14 @@ export default class AJV6Validator * @param schema - The schema against which to validate the form data * @param [customValidate] - An optional function that is used to perform custom validation * @param [transformErrors] - An optional function that is used to transform errors after AJV validation + * @param [uiSchema] - An optional uiSchema that is passed to `transformErrors` and `customValidate` */ validateFormData( formData: T | undefined, - schema: RJSFSchema, - customValidate?: CustomValidator, - transformErrors?: ErrorTransformer + schema: S, + customValidate?: CustomValidator, + transformErrors?: ErrorTransformer, + uiSchema?: UiSchema ): ValidationData { const rootSchema = schema; @@ -256,7 +264,7 @@ export default class AJV6Validator errors = [...errors, { stack: validationError!.message }]; } if (typeof transformErrors === "function") { - errors = transformErrors(errors); + errors = transformErrors(errors, uiSchema); } let errorSchema = this.toErrorSchema(errors); @@ -277,7 +285,7 @@ export default class AJV6Validator } // Include form data with undefined values, which is required for custom validation. - const newFormData = getDefaultFormState( + const newFormData = getDefaultFormState( this, schema, formData, @@ -287,10 +295,11 @@ export default class AJV6Validator const errorHandler = customValidate( newFormData, - this.createErrorHandler(newFormData) + this.createErrorHandler(newFormData), + uiSchema ); const userErrorSchema = this.unwrapErrorHandler(errorHandler); - return mergeValidationData( + return mergeValidationData( this, { errors, errorSchema }, userErrorSchema diff --git a/packages/validator-ajv6/test/validator.test.ts b/packages/validator-ajv6/test/validator.test.ts index 38ca1e9da2..63bb612dd9 100644 --- a/packages/validator-ajv6/test/validator.test.ts +++ b/packages/validator-ajv6/test/validator.test.ts @@ -4,6 +4,7 @@ import { FormValidation, RJSFSchema, RJSFValidationError, + UiSchema, ValidatorType, } from "@rjsf/utils"; @@ -317,6 +318,8 @@ describe("AJV6Validator", () => { describe("TransformErrors", () => { let errors: RJSFValidationError[]; let newErrorMessage: string; + let transformErrors: jest.Mock; + let uiSchema: UiSchema; beforeAll(() => { const schema: RJSFSchema = { type: "object", @@ -325,15 +328,19 @@ describe("AJV6Validator", () => { [illFormedKey]: { type: "string" }, }, }; + uiSchema = { + foo: { "ui:label": false }, + }; newErrorMessage = "Better error message"; - const transformErrors = (errors: RJSFValidationError[]) => { + transformErrors = jest.fn((errors: RJSFValidationError[]) => { return [Object.assign({}, errors[0], { message: newErrorMessage })]; - }; + }); const result = validator.validateFormData( { foo: 42, [illFormedKey]: 41 }, schema, undefined, - transformErrors + transformErrors, + uiSchema ); errors = result.errors; }); @@ -342,10 +349,30 @@ describe("AJV6Validator", () => { expect(errors).not.toHaveLength(0); expect(errors[0].message).toEqual(newErrorMessage); }); + it("transformErrors function was called with uiSchema", () => { + expect(transformErrors).toHaveBeenCalledWith( + expect.any(Array), + uiSchema + ); + }); }); describe("Custom validate function", () => { let errors: RJSFValidationError[]; let errorSchema: ErrorSchema; + let validate: jest.Mock; + let uiSchema: UiSchema; + beforeAll(() => { + uiSchema = { + foo: { "ui:label": false }, + }; + + validate = jest.fn((formData: any, errors: FormValidation) => { + if (formData.pass1 !== formData.pass2) { + errors.pass2!.addError("passwords don`t match."); + } + return errors; + }); + }); describe("formData is provided", () => { beforeAll(() => { const schema: RJSFSchema = { @@ -357,18 +384,13 @@ describe("AJV6Validator", () => { foo: { type: "array", items: { type: "string" } }, // Adding an array for test coverage }, }; - - const validate = (formData: any, errors: FormValidation) => { - if (formData.pass1 !== formData.pass2) { - errors.pass2!.addError("passwords don`t match."); - } - return errors; - }; const formData = { pass1: "a", pass2: "b", foo: ["a"] }; const result = validator.validateFormData( formData, schema, - validate + validate, + undefined, + uiSchema ); errors = result.errors; errorSchema = result.errorSchema; @@ -383,6 +405,13 @@ describe("AJV6Validator", () => { "passwords don`t match." ); }); + it("validate function was called with uiSchema", () => { + expect(validate).toHaveBeenCalledWith( + expect.any(Object), + expect.any(Object), + uiSchema + ); + }); }); describe("formData is missing data", () => { beforeAll(() => { @@ -393,12 +422,6 @@ describe("AJV6Validator", () => { pass2: { type: "string" }, }, }; - const validate = (formData: any, errors: FormValidation) => { - if (formData.pass1 !== formData.pass2) { - errors.pass2!.addError("passwords don`t match."); - } - return errors; - }; const formData = { pass1: "a" }; const result = validator.validateFormData( formData, @@ -418,6 +441,13 @@ describe("AJV6Validator", () => { "passwords don`t match." ); }); + it("validate function was called with undefined uiSchema", () => { + expect(validate).toHaveBeenCalledWith( + expect.any(Object), + expect.any(Object), + undefined + ); + }); }); }); describe("Data-Url validation", () => { diff --git a/packages/validator-ajv8/src/customizeValidator.ts b/packages/validator-ajv8/src/customizeValidator.ts index 0c575bc754..a330bf5ef0 100644 --- a/packages/validator-ajv8/src/customizeValidator.ts +++ b/packages/validator-ajv8/src/customizeValidator.ts @@ -1,4 +1,9 @@ -import { RJSFSchema, StrictRJSFSchema, ValidatorType } from "@rjsf/utils"; +import { + FormContextType, + RJSFSchema, + StrictRJSFSchema, + ValidatorType, +} from "@rjsf/utils"; import { CustomValidatorOptionsType, Localizer } from "./types"; import AJV8Validator from "./validator"; @@ -11,10 +16,11 @@ import AJV8Validator from "./validator"; */ export default function customizeValidator< T = any, - S extends StrictRJSFSchema = RJSFSchema + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any >( options: CustomValidatorOptionsType = {}, localizer?: Localizer -): ValidatorType { - return new AJV8Validator(options, localizer); +): ValidatorType { + return new AJV8Validator(options, localizer); } diff --git a/packages/validator-ajv8/src/validator.ts b/packages/validator-ajv8/src/validator.ts index d4038e3c12..6259280d91 100644 --- a/packages/validator-ajv8/src/validator.ts +++ b/packages/validator-ajv8/src/validator.ts @@ -9,6 +9,7 @@ import { ErrorSchemaBuilder, ErrorTransformer, FieldValidation, + FormContextType, FormValidation, GenericObjectType, getDefaultFormState, @@ -17,6 +18,7 @@ import { RJSFSchema, RJSFValidationError, StrictRJSFSchema, + UiSchema, ValidationData, ValidatorType, } from "@rjsf/utils"; @@ -30,8 +32,9 @@ const ROOT_SCHEMA_PREFIX = "__rjsf_rootSchema"; */ export default class AJV8Validator< T = any, - S extends StrictRJSFSchema = RJSFSchema -> implements ValidatorType + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +> implements ValidatorType { /** The AJV instance to use for all validations * @@ -274,12 +277,14 @@ export default class AJV8Validator< * @param schema - The schema against which to validate the form data * @param [customValidate] - An optional function that is used to perform custom validation * @param [transformErrors] - An optional function that is used to transform errors after AJV validation + * @param [uiSchema] - An optional uiSchema that is passed to `transformErrors` and `customValidate` */ validateFormData( formData: T | undefined, schema: S, - customValidate?: CustomValidator, - transformErrors?: ErrorTransformer + customValidate?: CustomValidator, + transformErrors?: ErrorTransformer, + uiSchema?: UiSchema ): ValidationData { const rawErrors = this.rawValidation(schema, formData); const { validationError: invalidSchemaError } = rawErrors; @@ -289,7 +294,7 @@ export default class AJV8Validator< errors = [...errors, { stack: invalidSchemaError!.message }]; } if (typeof transformErrors === "function") { - errors = transformErrors(errors); + errors = transformErrors(errors, uiSchema); } let errorSchema = this.toErrorSchema(errors); @@ -308,7 +313,7 @@ export default class AJV8Validator< } // Include form data with undefined values, which is required for custom validation. - const newFormData = getDefaultFormState( + const newFormData = getDefaultFormState( this, schema, formData, @@ -318,10 +323,11 @@ export default class AJV8Validator< const errorHandler = customValidate( newFormData, - this.createErrorHandler(newFormData) + this.createErrorHandler(newFormData), + uiSchema ); const userErrorSchema = this.unwrapErrorHandler(errorHandler); - return mergeValidationData( + return mergeValidationData( this, { errors, errorSchema }, userErrorSchema diff --git a/packages/validator-ajv8/test/validator.test.ts b/packages/validator-ajv8/test/validator.test.ts index b90f3b4bb1..95ba74e207 100644 --- a/packages/validator-ajv8/test/validator.test.ts +++ b/packages/validator-ajv8/test/validator.test.ts @@ -6,6 +6,7 @@ import { FormValidation, RJSFSchema, RJSFValidationError, + UiSchema, ValidatorType, } from "@rjsf/utils"; @@ -372,6 +373,8 @@ describe("AJV8Validator", () => { describe("TransformErrors", () => { let errors: RJSFValidationError[]; let newErrorMessage: string; + let transformErrors: jest.Mock; + let uiSchema: UiSchema; beforeAll(() => { const schema: RJSFSchema = { type: "object", @@ -380,15 +383,19 @@ describe("AJV8Validator", () => { [illFormedKey]: { type: "string" }, }, }; + uiSchema = { + foo: { "ui:label": false }, + }; newErrorMessage = "Better error message"; - const transformErrors = (errors: RJSFValidationError[]) => { + transformErrors = jest.fn((errors: RJSFValidationError[]) => { return [Object.assign({}, errors[0], { message: newErrorMessage })]; - }; + }); const result = validator.validateFormData( { foo: 42, [illFormedKey]: 41 }, schema, undefined, - transformErrors + transformErrors, + uiSchema ); errors = result.errors; }); @@ -397,10 +404,30 @@ describe("AJV8Validator", () => { expect(errors).not.toHaveLength(0); expect(errors[0].message).toEqual(newErrorMessage); }); + it("transformErrors function was called with uiSchema", () => { + expect(transformErrors).toHaveBeenCalledWith( + expect.any(Array), + uiSchema + ); + }); }); describe("Custom validate function", () => { let errors: RJSFValidationError[]; let errorSchema: ErrorSchema; + let validate: jest.Mock; + let uiSchema: UiSchema; + beforeAll(() => { + uiSchema = { + foo: { "ui:label": false }, + }; + + validate = jest.fn((formData: any, errors: FormValidation) => { + if (formData.pass1 !== formData.pass2) { + errors.pass2!.addError("passwords don`t match."); + } + return errors; + }); + }); describe("formData is provided", () => { beforeAll(() => { const schema: RJSFSchema = { @@ -412,18 +439,13 @@ describe("AJV8Validator", () => { foo: { type: "array", items: { type: "string" } }, // Adding an array for test coverage }, }; - - const validate = (formData: any, errors: FormValidation) => { - if (formData.pass1 !== formData.pass2) { - errors.pass2!.addError("passwords don`t match."); - } - return errors; - }; const formData = { pass1: "a", pass2: "b", foo: ["a"] }; const result = validator.validateFormData( formData, schema, - validate + validate, + undefined, + uiSchema ); errors = result.errors; errorSchema = result.errorSchema; @@ -438,6 +460,13 @@ describe("AJV8Validator", () => { "passwords don`t match." ); }); + it("validate function was called with uiSchema", () => { + expect(validate).toHaveBeenCalledWith( + expect.any(Object), + expect.any(Object), + uiSchema + ); + }); }); describe("formData is missing data", () => { beforeAll(() => { @@ -448,12 +477,6 @@ describe("AJV8Validator", () => { pass2: { type: "string" }, }, }; - const validate = (formData: any, errors: FormValidation) => { - if (formData.pass1 !== formData.pass2) { - errors.pass2!.addError("passwords don`t match."); - } - return errors; - }; const formData = { pass1: "a" }; const result = validator.validateFormData( formData, @@ -881,6 +904,8 @@ describe("AJV8Validator", () => { describe("TransformErrors", () => { let errors: RJSFValidationError[]; let newErrorMessage: string; + let transformErrors: jest.Mock; + let uiSchema: UiSchema; beforeAll(() => { const schema: RJSFSchema = { type: "object", @@ -889,15 +914,19 @@ describe("AJV8Validator", () => { [illFormedKey]: { type: "string" }, }, }; + uiSchema = { + foo: { "ui:label": false }, + }; newErrorMessage = "Better error message"; - const transformErrors = (errors: RJSFValidationError[]) => { + transformErrors = jest.fn((errors: RJSFValidationError[]) => { return [Object.assign({}, errors[0], { message: newErrorMessage })]; - }; + }); const result = validator.validateFormData( { foo: 42, [illFormedKey]: 41 }, schema, undefined, - transformErrors + transformErrors, + uiSchema ); errors = result.errors; }); @@ -906,10 +935,30 @@ describe("AJV8Validator", () => { expect(errors).not.toHaveLength(0); expect(errors[0].message).toEqual(newErrorMessage); }); + it("transformErrors function was called with uiSchema", () => { + expect(transformErrors).toHaveBeenCalledWith( + expect.any(Array), + uiSchema + ); + }); }); describe("Custom validate function", () => { let errors: RJSFValidationError[]; let errorSchema: ErrorSchema; + let validate: jest.Mock; + let uiSchema: UiSchema; + beforeAll(() => { + uiSchema = { + foo: { "ui:label": false }, + }; + + validate = jest.fn((formData: any, errors: FormValidation) => { + if (formData.pass1 !== formData.pass2) { + errors.pass2!.addError("passwords don`t match."); + } + return errors; + }); + }); describe("formData is provided", () => { beforeAll(() => { const schema: RJSFSchema = { @@ -921,18 +970,13 @@ describe("AJV8Validator", () => { foo: { type: "array", items: { type: "string" } }, // Adding an array for test coverage }, }; - - const validate = (formData: any, errors: FormValidation) => { - if (formData.pass1 !== formData.pass2) { - errors.pass2!.addError("passwords don`t match."); - } - return errors; - }; const formData = { pass1: "a", pass2: "b", foo: ["a"] }; const result = validator.validateFormData( formData, schema, - validate + validate, + undefined, + uiSchema ); errors = result.errors; errorSchema = result.errorSchema; @@ -947,6 +991,13 @@ describe("AJV8Validator", () => { "passwords don`t match." ); }); + it("validate function was called with uiSchema", () => { + expect(validate).toHaveBeenCalledWith( + expect.any(Object), + expect.any(Object), + uiSchema + ); + }); }); describe("formData is missing data", () => { beforeAll(() => { @@ -957,12 +1008,6 @@ describe("AJV8Validator", () => { pass2: { type: "string" }, }, }; - const validate = (formData: any, errors: FormValidation) => { - if (formData.pass1 !== formData.pass2) { - errors.pass2!.addError("passwords don`t match."); - } - return errors; - }; const formData = { pass1: "a" }; const result = validator.validateFormData( formData, @@ -1390,6 +1435,8 @@ describe("AJV8Validator", () => { describe("TransformErrors", () => { let errors: RJSFValidationError[]; let newErrorMessage: string; + let transformErrors: jest.Mock; + let uiSchema: UiSchema; beforeAll(() => { const schema: RJSFSchema = { type: "object", @@ -1398,15 +1445,19 @@ describe("AJV8Validator", () => { [illFormedKey]: { type: "string" }, }, }; + uiSchema = { + foo: { "ui:label": false }, + }; newErrorMessage = "Better error message"; - const transformErrors = (errors: RJSFValidationError[]) => { + transformErrors = jest.fn((errors: RJSFValidationError[]) => { return [Object.assign({}, errors[0], { message: newErrorMessage })]; - }; + }); const result = validator.validateFormData( { foo: 42, [illFormedKey]: 41 }, schema, undefined, - transformErrors + transformErrors, + uiSchema ); errors = result.errors; }); @@ -1415,10 +1466,30 @@ describe("AJV8Validator", () => { expect(errors).not.toHaveLength(0); expect(errors[0].message).toEqual(newErrorMessage); }); + it("transformErrors function was called with uiSchema", () => { + expect(transformErrors).toHaveBeenCalledWith( + expect.any(Array), + uiSchema + ); + }); }); describe("Custom validate function", () => { let errors: RJSFValidationError[]; let errorSchema: ErrorSchema; + let validate: jest.Mock; + let uiSchema: UiSchema; + beforeAll(() => { + uiSchema = { + foo: { "ui:label": false }, + }; + + validate = jest.fn((formData: any, errors: FormValidation) => { + if (formData.pass1 !== formData.pass2) { + errors.pass2!.addError("passwords don`t match."); + } + return errors; + }); + }); describe("formData is provided", () => { beforeAll(() => { const schema: RJSFSchema = { @@ -1430,18 +1501,13 @@ describe("AJV8Validator", () => { foo: { type: "array", items: { type: "string" } }, // Adding an array for test coverage }, }; - - const validate = (formData: any, errors: FormValidation) => { - if (formData.pass1 !== formData.pass2) { - errors.pass2!.addError("passwords don`t match."); - } - return errors; - }; const formData = { pass1: "a", pass2: "b", foo: ["a"] }; const result = validator.validateFormData( formData, schema, - validate + validate, + undefined, + uiSchema ); errors = result.errors; errorSchema = result.errorSchema; @@ -1456,6 +1522,13 @@ describe("AJV8Validator", () => { "passwords don`t match." ); }); + it("validate function was called with uiSchema", () => { + expect(validate).toHaveBeenCalledWith( + expect.any(Object), + expect.any(Object), + uiSchema + ); + }); }); describe("formData is missing data", () => { beforeAll(() => { @@ -1466,12 +1539,6 @@ describe("AJV8Validator", () => { pass2: { type: "string" }, }, }; - const validate = (formData: any, errors: FormValidation) => { - if (formData.pass1 !== formData.pass2) { - errors.pass2!.addError("passwords don`t match."); - } - return errors; - }; const formData = { pass1: "a" }; const result = validator.validateFormData( formData,