Skip to content

Commit

Permalink
Fixed rjsf-team#1596 by adapting the fix from rjsf-team#2002 into rjs…
Browse files Browse the repository at this point in the history
…f v5

- Added a new `mergeValidationData()` method in `@rjsf/utils` to handle the appending of errors onto the end of the validationData from an additional error schema
  - Added this to the `schema` directory `index.ts` along with exposing it on the `SchemaUtils` type and implementation
  - Also fixed the type of `toErrorList()` in `ValidatorType` to change from `fieldName: string` to `fieldPath: string[]`
  - Added reusable `mergeValidationDataTest.ts`, calling it in the utils
- Update the `@rjsf/validator-ajv6` to pick up the breaking change from rjsf-team#2002 around `AJV6Validator.toErrorList()`
  - Also modified the `validateFormData()` function to return the result of `mergeValidationData()` when the user has a custom validator
  - Updated tests for the new structure of the `toErrorList()` data
  - Also updated the `schema.tests` to add the new `mergeValidationDataTest()`
- Updated `Form` to use the `mergeValidationData()` function in the few places where `extraErrors` was being merged into the schema validation
  - Updated tests for the new structor of the `toErrorList()` data
- Updated the `CHANGELOG.md` to describe this fix
- Updated the `5.x upgrade guide.md` to describe all the new utility functions added and describe util.js and validator.js breaking changes
- Updated the `validation.md` documentation for the `ErrorListTemplate` change along with making the `RJSFValidationError` interface describe the optional properties
  • Loading branch information
heath-freenome committed Aug 26, 2022
1 parent e3edba7 commit 1fa566c
Show file tree
Hide file tree
Showing 19 changed files with 260 additions and 80 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,19 @@ should change the heading of the (upcoming) version to include a major version b
## @rjsf/utils
- clear errors on formData change when liveOmit=true when "additionalProperties: false" [issue 1507](https://github.com/rjsf-team/react-jsonschema-form/issues/1507) (https://github.com/rjsf-team/react-jsonschema-form/pull/2631)

## @rjsf/validator-ajv6
- A **BREAKING CHANGE** to `toErrorList()` was made so that it takes `fieldPath: string[]` rather than `fieldName='root'` as part of the fix (https://github.com/rjsf-team/react-jsonschema-form/issues/1596)
- The returned `errors` also now adds `property` from the `fieldPath` along with the proper path from the `property` to the `stack` message, making it consistent with the AJV errors
- In addition, the extra information provided by AJV is no longer stripped from the `errors` when merged with custom validation, fixing (https://github.com/rjsf-team/react-jsonschema-form/issues/1596).

## @rjsf/core
- **BREAKING CHANGE** Fix overriding core submit button className (https://github.com/rjsf-team/react-jsonschema-form/issues/2979)
- Fix `ui:field` with anyOf or oneOf no longer rendered twice (#2890)
- **BREAKING CHANGE** Fixed `anyOf` and `oneOf` getting incorrect, potentially duplicate ids when combined with array (https://github.com/rjsf-team/react-jsonschema-form/issues/2197)
- `formContext` is now passed properly to `SchemaField`, fixes (https://github.com/rjsf-team/react-jsonschema-form/issues/2394, https://github.com/rjsf-team/react-jsonschema-form/issues/2274)
- Added `ui:duplicateKeySuffixSeparator` to customize how duplicate object keys are renamed when using `additionalProperties`.
- The `extraErrors` are now appended onto the end of the schema validation-based `errors` information that is returned via the `onErrors()` callback when submit fails, rather than being merged into the hierarchy.
- In addition, the extra information provided by AJV is no longer stripped from the `errors` during the merge process, fixing (https://github.com/rjsf-team/react-jsonschema-form/issues/1596).

## @rjsf/antd
- Fix esm build to use `@rollup/plugin-replace` to replace `antd/lib` and `rc-picker/lib` with `antd/es` and `rc-picker/es` respectively, fixing (https://github.com/rjsf-team/react-jsonschema-form/issues/2962)
Expand Down
3 changes: 2 additions & 1 deletion docs/5.x upgrade guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ Unfortunately, there is required work pending to properly support React 18, so u
### New packages

There are three new packages added in RJSF version 5:
- `@rjsf/utils`: All of the [utility functions](https://react-jsonschema-form.readthedocs.io/en/stable/api-reference/utiltity-functions) previously imported from `@rjsf/core/utils` as well as the Typescript types for RJSV version 5.
- `@rjsf/utils`: All of the [utility functions](https://react-jsonschema-form.readthedocs.io/en/stable/api-reference/utiltity-functions) previously imported from `@rjsf/core/utils` as well as the Typescript types for RJSF version 5.
- The following new utility functions were added: `createSchemaUtils()`, `getInputProps()`, `mergeValidationData()` and `processSelectValue()`
- `@rjsf/validator-ajv6`: The [ajv](https://github.com/ajv-validator/ajv)-v6-based validator refactored out of `@rjsf/[email protected]`, that implements the `ValidatorType` interface defined in `@rjsf/utils`.
- `@rjsf/mui`: Previously `@rjsf/material-ui/v5`, now provided as its own theme.

Expand Down
32 changes: 22 additions & 10 deletions docs/api-reference/utility-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ Converts a UTC date string into a local Date format
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<T> - An implementation of the `ValidatorType` interface that will be used when necessary
- theSchema: RJSFSchema - The schema for which the default state is desired
- [formData]: T - The current formData, if any, onto which to provide any missing defaults
- [rootSchema]: RJSFSchema - The root schema, used to primarily to look up `$ref`s
Expand All @@ -398,7 +398,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<T> - An implementation of the `ValidatorType` interface that will be used when necessary
- schema: RJSFSchema - The schema for which the display label flag is desired
- [uiSchema={}]: UiSchema<T, F> - The UI schema from which to derive potentially displayable information
- [rootSchema]: RJSFSchema - The root schema, used to primarily to look up `$ref`s
Expand All @@ -410,7 +410,7 @@ Determines whether the combination of `schema` and `uiSchema` properties indicat
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<T> - 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: RJSFSchema[] - The list of options to find a matching options from
- rootSchema: RJSFSchema - The root schema, used to primarily to look up `$ref`s
Expand All @@ -422,7 +422,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<T> - An implementation of the `ValidatorType` interface that will be used when necessary
- schema: RJSFSchema - The schema for which check for array of files flag is desired
- [uiSchema={}]: UiSchema<T, F> - The UI schema from which to check the widget
- [rootSchema]: RJSFSchema - The root schema, used to primarily to look up `$ref`s
Expand All @@ -434,7 +434,7 @@ Checks to see if the `schema` and `uiSchema` combination represents an array of
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<T> - An implementation of the `ValidatorType` interface that will be used when necessary
- schema: RJSFSchema - The schema for which check for a multi-select flag is desired
- [rootSchema]: RJSFSchema - The root schema, used to primarily to look up `$ref`s

Expand All @@ -445,20 +445,32 @@ Checks to see if the `schema` combination represents a multi-select
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<T> - An implementation of the `ValidatorType` interface that will be used when necessary
- theSchema: RJSFSchema - The schema for which check for a select flag is desired
- [rootSchema]: RJSFSchema - The root schema, used to primarily to look up `$ref`s

#### Returns
- boolean: True if schema contains a select, otherwise false

### mergeValidationData<T=any>()
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<T> - An implementation of the `ValidatorType` interface that will be used to convert an ErrorSchema to a list of errors
- validationData: ValidationData<T> - The current `ValidationData` into which to merge the additional errors
- [additionalErrorSchema]: ErrorSchema<T> - The additional set of errors in an `ErrorSchema`

#### Returns
- ValidationData<T>: The `validationData` with the additional errors from `additionalErrorSchema` merged into it, if provided.

### retrieveSchema<T = any>()
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<T> - An implementation of the `ValidatorType` interface that will be forwarded to all the APIs
- schema: RJSFSchema - The schema for which retrieving a schema is desired
- [rootSchema={}]: RJSFSchema - The root schema that will be forwarded to all the APIs
- [rawFormData]: T - The current formData, if any, to assist retrieving a schema
Expand All @@ -470,7 +482,7 @@ potentially recursive resolution.
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<T> - An implementation of the `ValidatorType` interface that will be used when necessary
- schema: RJSFSchema - The schema for which the `IdSchema` is desired
- [id]: string | null - The base id for the schema
- [rootSchema]: RJSFSchema - The root schema, used to primarily to look up `$ref`s
Expand All @@ -485,7 +497,7 @@ Generates an `IdSchema` object for the `schema`, recursively
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<T> - An implementation of the `ValidatorType` interface that will be used when necessary
- schema: RJSFSchema - The schema for which the `PathSchema` is desired
- [name='']: string - The base name for the schema
- [rootSchema]: RJSFSchema - The root schema, used to primarily to look up `$ref`s
Expand All @@ -501,7 +513,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<T> - an implementation of the `ValidatorType` interface that will be forwarded to all the APIs
- rootSchema: RJSFSchema - The root schema that will be forwarded to all the APIs

#### Returns
Expand Down
15 changes: 7 additions & 8 deletions docs/usage/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,19 +123,19 @@ render((
Each element in the `errors` list passed to `transformErrors` is a `RJSFValidationError` interface (in `@rjsf/utils`) and has the following properties:

- `name`: name of the error, for example, "required" or "minLength"
- `message`: message, for example, "is a required property" or "should NOT be shorter than 3 characters"
- `params`: an object with the error params returned by ajv ([see doc](https://github.com/ajv-validator/ajv/tree/6a671057ea6aae690b5967ee26a0ddf8452c6297#error-parameters) for more info).
- `property`: a string in Javascript property accessor notation to the data path of the field with the error. For example, `.name` or `['first-name']`.
- `schemaPath`: JSON pointer to the schema of the keyword that failed validation. For example, `#/fields/firstName/required`. (Note: this may sometimes be wrong due to a [bug in ajv](https://github.com/ajv-validator/ajv/issues/512)).
- `name`: optional name of the error, for example, "required" or "minLength"
- `message`: optional message, for example, "is a required property" or "should NOT be shorter than 3 characters"
- `params`: optional object with the error params returned by ajv ([see doc](https://github.com/ajv-validator/ajv/tree/6a671057ea6aae690b5967ee26a0ddf8452c6297#error-parameters) for more info).
- `property`: optional string in Javascript property accessor notation to the data path of the field with the error. For example, `.name` or `.first-name`.
- `schemaPath`: optional JSON pointer to the schema of the keyword that failed validation. For example, `#/fields/firstName/required`. (Note: this may sometimes be wrong due to a [bug in ajv](https://github.com/ajv-validator/ajv/issues/512)).
- `stack`: full error name, for example ".name is a required property".

## Error List Display

To take control over how the form errors are displayed, you can define an *error list template* for your form.
This list is the form global error list that appears at the top of your forms.

An error list template is basically a React stateless component being passed errors as props so you can render them as you like:
An error list template is basically a React stateless component being passed errors as props, so you can render them as you like:

```tsx
import validator from "@rjsf/validator-ajv6";
Expand Down Expand Up @@ -167,7 +167,7 @@ render((
showErrorList={true}
formData={""}
liveValidate
ErrorList={ErrorListTemplate} />
templates: {{ ErrorListTemplate }} />
), document.getElementById("app"));
```

Expand All @@ -181,7 +181,6 @@ The following props are passed to `ErrorList` as defined by the `ErrorListProps`
- `uiSchema`: The uiSchema that was passed to `Form`.
- `formContext`: The `formContext` object that you passed to `Form`.


## The case of empty strings

When a text input is empty, the field in form data is set to `undefined`. String fields that use `enum` and a `select` widget will have an empty option at the top of the options list that when selected will result in the field being `undefined`.
Expand Down
36 changes: 18 additions & 18 deletions packages/core/src/components/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -334,12 +334,12 @@ export default class Form<T = any, F = any> extends Component<
errorSchema = currentErrors.errorSchema;
}
if (props.extraErrors) {
errorSchema = mergeObjects(
errorSchema,
props.extraErrors,
true
) as ErrorSchema<T>;
errors = schemaUtils.getValidator().toErrorList(errorSchema);
const merged = schemaUtils.mergeValidationData(
{ errorSchema, errors },
props.extraErrors
);
errorSchema = merged.errorSchema;
errors = merged.errors;
}
const idSchema = schemaUtils.toIdSchema(
retrievedSchema,
Expand Down Expand Up @@ -536,12 +536,12 @@ export default class Form<T = any, F = any> extends Component<
const schemaValidationErrors = errors;
const schemaValidationErrorSchema = errorSchema;
if (extraErrors) {
errorSchema = mergeObjects(
errorSchema,
extraErrors,
true
) as ErrorSchema<T>;
errors = schemaUtils.getValidator().toErrorList(errorSchema);
const merged = schemaUtils.mergeValidationData(
schemaValidation,
extraErrors
);
errorSchema = merged.errorSchema;
errors = merged.errors;
}
state = {
formData: newFormData,
Expand Down Expand Up @@ -633,12 +633,12 @@ export default class Form<T = any, F = any> extends Component<
const schemaValidationErrorSchema = errorSchema;
if (errors.length > 0) {
if (extraErrors) {
errorSchema = mergeObjects(
errorSchema,
extraErrors,
true
) as ErrorSchema<T>;
errors = schemaUtils.getValidator().toErrorList(errorSchema);
const merged = schemaUtils.mergeValidationData(
schemaValidation,
extraErrors
);
errorSchema = merged.errorSchema;
errors = merged.errors;
}
this.setState(
{
Expand Down
19 changes: 13 additions & 6 deletions packages/core/test/validate_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ describe("Validation", () => {

submitForm(node);
sinon.assert.calledWithMatch(onError.lastCall, [
{ stack: "root: Invalid" },
{ property: ".", stack: ".: Invalid" },
]);
});

Expand All @@ -160,7 +160,7 @@ describe("Validation", () => {

sinon.assert.calledWithMatch(onChange.lastCall, {
errorSchema: { __errors: ["Invalid"] },
errors: [{ stack: "root: Invalid" }],
errors: [{ property: ".", stack: ".: Invalid" }],
formData: "1234",
});
});
Expand Down Expand Up @@ -242,8 +242,15 @@ describe("Validation", () => {
});
submitForm(node);
sinon.assert.calledWithMatch(onError.lastCall, [
{ stack: "pass2: should NOT be shorter than 3 characters" },
{ stack: "pass2: Passwords don't match" },
{
message: "should NOT be shorter than 3 characters",
name: "minLength",
params: { limit: 3 },
property: ".pass2",
schemaPath: "#/properties/pass2/minLength",
stack: ".pass2 should NOT be shorter than 3 characters",
},
{ property: ".pass2", stack: ".pass2: Passwords don't match" },
]);
});

Expand Down Expand Up @@ -281,7 +288,7 @@ describe("Validation", () => {

submitForm(node);
sinon.assert.calledWithMatch(onError.lastCall, [
{ stack: "pass2: Passwords don't match" },
{ property: ".0.pass2", stack: ".0.pass2: Passwords don't match" },
]);
});

Expand Down Expand Up @@ -309,7 +316,7 @@ describe("Validation", () => {
});
submitForm(node);
sinon.assert.calledWithMatch(onError.lastCall, [
{ stack: "root: Forbidden value: bbb" },
{ property: ".", stack: ".: Forbidden value: bbb" },
]);
});
});
Expand Down
23 changes: 23 additions & 0 deletions packages/utils/src/createSchemaUtils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import deepEquals from "./deepEquals";
import {
ErrorSchema,
IdSchema,
PathSchema,
RJSFSchema,
SchemaUtilsType,
UiSchema,
ValidationData,
ValidatorType,
} from "./types";
import {
Expand All @@ -14,6 +16,7 @@ import {
isFilesArray,
isMultiSelect,
isSelect,
mergeValidationData,
retrieveSchema,
toIdSchema,
toPathSchema,
Expand Down Expand Up @@ -152,6 +155,26 @@ class SchemaUtils<T = any> implements SchemaUtilsType<T> {
return isSelect<T>(this.validator, schema, this.rootSchema);
}

/** 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
* `getValidator().toErrorList()` onto the `errors` in the `validationData`. If no `additionalErrorSchema` is passed,
* then `validationData` is returned.
*
* @param validationData - The current `ValidationData` into which to merge the additional errors
* @param [additionalErrorSchema] - The additional set of errors
* @returns - The `validationData` with the additional errors from `additionalErrorSchema` merged into it, if provided.
*/
mergeValidationData(
validationData: ValidationData<T>,
additionalErrorSchema?: ErrorSchema<T>
): ValidationData<T> {
return mergeValidationData<T>(
this.validator,
validationData,
additionalErrorSchema
);
}

/** Retrieves an expanded schema that has had all of its conditions, additional properties, references and
* dependencies resolved and merged into the `schema` given a `rawFormData` that is used to do the potentially
* recursive resolution.
Expand Down
2 changes: 2 additions & 0 deletions packages/utils/src/schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import getMatchingOption from "./getMatchingOption";
import isFilesArray from "./isFilesArray";
import isMultiSelect from "./isMultiSelect";
import isSelect from "./isSelect";
import mergeValidationData from "./mergeValidationData";
import retrieveSchema from "./retrieveSchema";
import toIdSchema from "./toIdSchema";
import toPathSchema from "./toPathSchema";
Expand All @@ -15,6 +16,7 @@ export {
isFilesArray,
isMultiSelect,
isSelect,
mergeValidationData,
retrieveSchema,
toIdSchema,
toPathSchema,
Expand Down
Loading

0 comments on commit 1fa566c

Please sign in to comment.