Skip to content

Commit

Permalink
fix: Implement precompiled validator support in @rjsf/validator-ajv8 (#…
Browse files Browse the repository at this point in the history
…3652)

* fix: Implement precompiled validator support in @rjsf/validator-ajv8
Fixes #3543 by implementing support for precompiled validators
- In `@rjsf/validator-ajv8` added support for precompiled validators as follows:
  - Added a new `compileSchemaValidators()` API function used to generate the precompiled validators for a schema to an output file
  - Updated the documentation for the `customizeValidator()` API function
  - Added a new `AJV8PrecompiledValidator` implementation of the `ValidatorType` interface
  - Refactored a large piece of the raw validation error processing from the `AJV8Validator` into a new `processRawValidationErrors()` function also used by the `AJV8PrecompiledValidator`
  - Added a new `usePrecompiledValidator()` API function that is similar to `customizeValidator()` but returning a precompiled validator-based `ValidatorType` interface implementation
  - Added some new types to the `types.ts` file in support of precompiled validators
  - Updated the main `index.ts` file to export the new types and API functions
  - Added 100% unit test coverage of the new feature
    - This included implementing a node function to precompile the `superSchema.json` file found in the `test/harness` directory
  - Added `ignorePatterns` to the `.eslintrc` file to ignore the precompiled schema files
- Updated the `validation.md` documentation for the new precompiled validator functionality
- Added a new `validator-ajv8.md` documentation file to the `api-reference` directory and the `sidebar.js`
- Updated the `CHANGELOG.md` file accordingly

* - Responded to self-review feedback

* - Replaced usage of `<` with `&lt;` to match the similar line later in the file

* - Fixed the peerDependencies in the `fluent-ui` package-lock.json and improved the `CHANGELOG.md` comments related to #3546

* - Responded to reviewer feedback
  • Loading branch information
heath-freenome authored May 11, 2023
1 parent c527317 commit 91bcc94
Show file tree
Hide file tree
Showing 28 changed files with 1,283 additions and 175 deletions.
13 changes: 10 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,28 @@ should change the heading of the (upcoming) version to include a major version b
## @rjsf/fluent-ui

- Added support for `additionalProperties` to fluent-ui theme, fixing [#2777](https://github.com/rjsf-team/react-jsonschema-form/issues/2777).
- Upgraded to `8.x` version of `@fluentui/react`, fixing [#3463](https://github.com/rjsf-team/react-jsonschema-form/issues/3463)
- Upgraded to `8.x` version of `@fluentui/react` maintaining backwards compatibility to version 7, fixing [#3463](https://github.com/rjsf-team/react-jsonschema-form/issues/3463)

## @rjsf/utils

- Added two new APIs `getDiscriminatorFieldFromSchema()` (a refactor of code from `MultiSchemaField`) and `hashForSchema()`
- Updated `getDefaultFormState()` and `toPathSchema()` to use `getDiscriminatorFieldFromSchema()` to provide a discriminator field to `getClosestMatchingOption()` calls.
- Refactored the `retrieveSchema()` internal API functions to support implementing an internal `schemaParser()` API for use in precompiling schemas, in support of [#3543](https://github.com/rjsf-team/react-jsonschema-form/issues/3543)
- Fixed `toPathSchema()` to handle `properties` in an object along with `anyOf`/`oneOf`, fixing [#3628](https://github.com/rjsf-team/react-jsonschema-form/issues/3628) and [#1628](https://github.com/rjsf-team/react-jsonschema-form/issues/1628)
- Refactored optional parameters for `computeDefaults()` into destructured props object to reduce clutter when only specifying later of the optional argument ([3604](https://github.com/rjsf-team/react-jsonschema-form/pull/3604))
- Refactored optional parameters for `computeDefaults()` into destructured props object to reduce clutter when only specifying later of the optional argument, fixing [#3602](https://github.com/rjsf-team/react-jsonschema-form/issues/3602)
- Fixed `computeDefaults()` to handle `$ref` in an object along with `anyOf`/`oneOf`, fixing [#3633](https://github.com/rjsf-team/react-jsonschema-form/issues/3633)

## @rjsf/validator-ajv8

- Added two new APIs `compileSchemaValidators()` and `createPrecompiledValidator()` implemented to support using precompiled validators build with AJV 8, fixing [#3543](https://github.com/rjsf-team/react-jsonschema-form/issues/3543)

## Dev / docs / playground

- Added documentation to `custom-templates` describing how to extend the `BaseInputTemplate`
- Added **minItems behavior for array field** live setting ([3604](https://github.com/rjsf-team/react-jsonschema-form/pull/3604))
- Added **minItems behavior for array field** live setting, fixing [#3602](https://github.com/rjsf-team/react-jsonschema-form/issues/3602)
- Upgraded playground to `8.x` version of `@fluentui/react`, fixing [#3463](https://github.com/rjsf-team/react-jsonschema-form/issues/3463)
- Added documentation to `validation` describing the new precompiled validators feature
- Added new `validator-ajv8.md` documentation to the `api-reference` directory as well as putting it into the `sidebar.js`

# 5.6.2

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@
- [Ant Design](https://github.com/rjsf-team/react-jsonschema-form/tree/main/packages/antd)
- [Bootstrap 3](https://github.com/rjsf-team/react-jsonschema-form/tree/main/packages/core)
- [Bootstrap 4](https://github.com/rjsf-team/react-jsonschema-form/tree/main/packages/bootstrap-4)
- [Chakra UI](https://github.com/rjsf-team/react-jsonschema-form/tree/main/packages/chakra-ui)
- [Fluent UI](https://github.com/rjsf-team/react-jsonschema-form/tree/main/packages/fluent-ui)
- [Material UI 4](https://github.com/rjsf-team/react-jsonschema-form/tree/main/packages/material-ui)
- [Material UI 5](https://github.com/rjsf-team/react-jsonschema-form/tree/main/packages/mui)
- [Semantic UI](https://github.com/rjsf-team/react-jsonschema-form/tree/main/packages/semantic-ui)
- [Chakra UI](https://github.com/rjsf-team/react-jsonschema-form/tree/main/packages/chakra-ui)

## Documentation

Expand Down
57 changes: 57 additions & 0 deletions packages/docs/docs/api-reference/validator-ajv8.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# @rjsf/validator-ajv8 APIs

In RJSF version 5, the original, embedded AJV 6 validator implementation from earlier versions was extracted into its own package, `@rjsf/validator-ajv6`, which was immediately deprecated since AJV 6 is no longer getting maintenance updates.
A new `@rjsf/validator-ajv8` package was added that uses the AJV 8 package, including adding support for using precompiled validators.
Below are the exported API functions that are provided by this package.
See the [Validation documentation](../usage/validation.md) for examples of using these APIs.

## Types

There are a few Typescript types that are exported by `@rjsf/validator-ajv8` in support of the APIs.

These types can be found on GitHub [here](https://github.com/rjsf-team/react-jsonschema-form/blob/main/packages/validator-ajv8/src/types.ts).

## APIs

### customizeValidator<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>()

Creates and returns a customized implementation of the `ValidatorType` with the given customization `options` if provided.
If a `localizer` is provided, it is used to translate the messages generated by the underlying AJV validation.

#### Parameters

- [options={}]: CustomValidatorOptionsType - The optional map of `CustomValidatorOptionsType` options that are used to create the `ValidatorType` instance
- [localizer]: Localizer | undefined - If provided, is used to localize a list of Ajv `ErrorObject`s after running the form validation using AJV

#### Returns

- ValidatorType&lt;T, S, F>: The custom validator implementation resulting from the set of parameters provided

### compileSchemaValidators&lt;S extends StrictRJSFSchema = RJSFSchema>()

The function used to compile a schema into an output file in the form that allows it to be used as a precompiled validator.
The main reasons for using a precompiled validator is reducing code size, improving validation speed and, most importantly, avoiding dynamic code compilation when prohibited by a browser's Content Security Policy.
For more information about AJV code compilation see: https://ajv.js.org/standalone.html

#### Parameters

- schema: S - The schema to be compiled into a set of precompiled validators functions
- output: string - The name of the file into which the precompiled validator functions will be generated
- [options={}]: CustomValidatorOptionsType - The set of `CustomValidatorOptionsType` information used to alter the AJV validator used for compiling the schema. They are the same options that are passed to the `customizeValidator()` function in order to modify the behavior of the regular AJV-based validator.

### createPrecompiledValidator<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>()

Creates and returns a `ValidatorType` interface that is implemented with a precompiled validator.
If a `localizer` is provided, it is used to translate the messages generated by the underlying AJV validation.

> NOTE: The `validateFns` parameter is an object obtained by importing from a precompiled validation file created via the `compileSchemaValidators()` function.
#### Parameters

- validateFns: ValidatorFunctions&lt;T> - The map of the validation functions that are created by the `compileSchemaValidators()` function
- rootSchema: S - The root schema that was used with the `compileSchemaValidators()` function
- [localizer]: Localizer | undefined - If provided, is used to localize a list of Ajv `ErrorObject`s after running the form validation using AJV

#### Returns

- ValidatorType&lt;T, S, F>: The precompiled validator implementation resulting from the set of parameters provided
76 changes: 76 additions & 0 deletions packages/docs/docs/usage/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,82 @@ If you depend on having specifically formatted messages, then using this validat

It is also possible for you to provide your own implementation if you desire, as long as it fulfills the `ValidatorType` interface specified in `@rjsf/utils`.

## API documentation

The documentation for the APIs associated with the AJV 8 validator package can be found [here](../api-reference/validator-ajv8.md)

## Precompiled validators

In 5.7.0, support for precompiled validators was added to the `@rjsf/validator-ajv8` package.
The main use case for this is to overcome issues with `unsafe-eval` warnings from the browser caused by strict Content Security Policy settings.
See the [Standalone Validation Code](https://ajv.js.org/standalone.html) section of the AJV documentation for more details about precompiled validators.

Due to how RJSF uses the AJV validator in determining `anyOf/oneOf` selections and how it resolves dependencies, if-then-else and references ($ref) in schemas via the `retrieveSchema()` utility method, RJSF provides its own schema compilation API built on-top-of the one provided by AJV 8.
If you are wanting to use a precompiled validator, two steps are required:

1. Precompiling the schema into a set of validator functions
2. Providing those precompiled validator functions to a `ValidatorType` implementation in the `Form`

### Schema precompilation

The first step in the process is to compile a schema into a set of validator functions that are saved into a commonJS-based Javascript file.
The `@rjsf/validator-ajv8` package exports the `compileSchemaValidators()` function that does this.
It is expected that this function will be used in a manner similar to the following:

```cjs
const { compileSchemaValidators } = require('@rjsf/validator-ajv8');
const yourSchema = require('path_to/yourSchema'); // If your schema is a js file

compileSchemaValidators(yourSchema, 'path_to/yourCompiledSchema.js');
```

If you are currently using the `customizeValidator()` function to provide `additionalMetaSchemas`, `customFormats`, `ajvOptionsOverrides` and/or `ajvFormatOptions` then you can pass those in as the optional third parameter to the `compileSchemaValidators()` function in a manner similar to:

```cjs
const { compileSchemaValidators } = require('@rjsf/validator-ajv8');
const yourSchema = require('path_to/yourSchema.json'); // If your schema is a json file

const options = {
additionalMetaSchemas: [require('ajv/lib/refs/json-schema-draft-06.json')],
customFormats: { 'phone-us': /\(?\d{3}\)?[\s-]?\d{3}[\s-]?\d{4}$/, 'area-code': /\d{3}/ },
ajvOptionsOverrides: {
$data: true,
verbose: true,
},
ajvFormatOptions: {
mode: 'fast',
},
};

compileSchemaValidators(yourSchema, 'path_to/yourCompiledSchema.js', options);
```

It is highly recommended to create a `compileYourSchema.js` file (or what ever name you want) with code similar to what is shown above and then, using node, run the code as follows:

```
node compileYourSchema.js
```

> NOTE: You must have your schema provided within a file that can be parsed and turned into the set of precompiled validator functions.
### Using the precompiled validator

After you have completed step 1 having generated your precompiled schema functions into the `yourCompiledSchema.js` output file (or whatever you called it), then you need to create a `ValidatorType` implementation from it to use in the `Form`.
The `@rjsf/validator-ajv8` package exports the `createPrecompiledValidator()` function for this.
Here is an example of how to use your precompiled validator with your `Form`:

```tsx
import { createPrecompiledValidator, ValidatorFunctions } from '@rjsf/validator-ajv8';
import Form from '@rjsf/core'; // Or whatever theme you use

import yourSchema from 'path_to/yourSchema'; // This needs to be the same file that was precompiled
import * as precompiledValidator from 'path_to/yourCompiledSchema';

const validator = createPrecompiledValidator(precompiledValidator as ValidatorFunctions);

render(<Form schema={schema} validator={validator} />, document.getElementById('app'));
```

## Live validation

By default, form data are only validated when the form is submitted or when a new `formData` prop is passed to the `Form` component.
Expand Down
1 change: 1 addition & 0 deletions packages/docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const sidebars = {
'api-reference/themes/chakra-ui/uiSchema',
'api-reference/form-props',
'api-reference/utility-functions',
'api-reference/validator-ajv8'
],
},
{
Expand Down
2 changes: 1 addition & 1 deletion packages/fluent-ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/validator-ajv8/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Ignore the generated test schema files
test/harness/superSchema*.js
3 changes: 2 additions & 1 deletion packages/validator-ajv8/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"cs-format": "prettier \"{src,test}/**/*.ts?(x)\" --write",
"lint": "eslint src test",
"precommit": "lint-staged",
"test": "dts test",
"compileSchemas":"node test/harness/compileTestSchema.js",
"test": "npm run compileSchemas && dts test",
"bump-packages": "npm update --save --lockfile-version 2"
},
"lint-staged": {
Expand Down
40 changes: 40 additions & 0 deletions packages/validator-ajv8/src/compileSchemaValidators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import fs from 'fs';
import standaloneCode from 'ajv/dist/standalone';
import { RJSFSchema, StrictRJSFSchema, schemaParser } from '@rjsf/utils';

import createAjvInstance from './createAjvInstance';
import { CustomValidatorOptionsType } from './types';

/** The function used to compile a schema into an output file in the form that allows it to be used as a precompiled
* validator. The main reasons for using a precompiled validator is reducing code size, improving validation speed and,
* most importantly, avoiding dynamic code compilation when prohibited by a browser's Content Security Policy. For more
* information about AJV code compilation see: https://ajv.js.org/standalone.html
*
* @param schema - The schema to be compiled into a set of precompiled validators functions
* @param output - The name of the file into which the precompiled validator functions will be generated
* @param [options={}] - The set of `CustomValidatorOptionsType` information used to alter the AJV validator used for
* compiling the schema. They are the same options that are passed to the `customizeValidator()` function in
* order to modify the behavior of the regular AJV-based validator.
*/
export default function compileSchemaValidators<S extends StrictRJSFSchema = RJSFSchema>(
schema: S,
output: string,
options: CustomValidatorOptionsType = {}
) {
console.log('parsing the schema');
const schemaMaps = schemaParser(schema);
const schemas = Object.values(schemaMaps);

const { additionalMetaSchemas, customFormats, ajvOptionsOverrides = {}, ajvFormatOptions, AjvClass } = options;
// Allow users to turn off the `lines: true` feature in their own overrides, but NOT the `source: true`
const compileOptions = {
...ajvOptionsOverrides,
code: { lines: true, ...ajvOptionsOverrides.code, source: true },
schemas,
};
const ajv = createAjvInstance(additionalMetaSchemas, customFormats, compileOptions, ajvFormatOptions, AjvClass);

const moduleCode = standaloneCode(ajv);
console.log(`writing ${output}`);
fs.writeFileSync(output, moduleCode);
}
23 changes: 23 additions & 0 deletions packages/validator-ajv8/src/createPrecompiledValidator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { FormContextType, RJSFSchema, StrictRJSFSchema, ValidatorType } from '@rjsf/utils';

import { Localizer, ValidatorFunctions } from './types';
import AJV8PrecompiledValidator from './precompiledValidator';

/** Creates and returns a `ValidatorType` interface that is implemented with a precompiled validator. If a `localizer`
* is provided, it is used to translate the messages generated by the underlying AJV validation.
*
* NOTE: The `validateFns` parameter is an object obtained by importing from a precompiled validation file created via
* the `compileSchemaValidators()` function.
*
* @param validateFns - The map of the validation functions that are created by the `compileSchemaValidators()` function
* @param rootSchema - The root schema that was used with the `compileSchemaValidators()` function
* @param [localizer] - If provided, is used to localize a list of Ajv `ErrorObject`s
* @returns - The precompiled validator implementation resulting from the set of parameters provided
*/
export default function createPrecompiledValidator<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F extends FormContextType = any
>(validateFns: ValidatorFunctions<T>, rootSchema: S, localizer?: Localizer): ValidatorType<T, S, F> {
return new AJV8PrecompiledValidator<T, S, F>(validateFns, rootSchema, localizer);
}
4 changes: 3 additions & 1 deletion packages/validator-ajv8/src/customizeValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import { CustomValidatorOptionsType, Localizer } from './types';
import AJV8Validator from './validator';

/** Creates and returns a customized implementation of the `ValidatorType` with the given customization `options` if
* provided.
* provided. If a `localizer` is provided, it is used to translate the messages generated by the underlying AJV
* validation.
*
* @param [options={}] - The `CustomValidatorOptionsType` options that are used to create the `ValidatorType` instance
* @param [localizer] - If provided, is used to localize a list of Ajv `ErrorObject`s
* @returns - The custom validator implementation resulting from the set of parameters provided
*/
export default function customizeValidator<
T = any,
Expand Down
4 changes: 3 additions & 1 deletion packages/validator-ajv8/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import customizeValidator from './customizeValidator';
import compileSchemaValidators from './compileSchemaValidators';
import createPrecompiledValidator from './createPrecompiledValidator';

export { customizeValidator };
export { customizeValidator, compileSchemaValidators, createPrecompiledValidator };
export * from './types';

export default customizeValidator();
Loading

0 comments on commit 91bcc94

Please sign in to comment.