Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Implement precompiled validator support in @rjsf/validator-ajv8 #3652

Conversation

heath-freenome
Copy link
Member

Reasons for making this change

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
  • 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

Checklist

  • I'm updating documentation
  • I'm adding or updating code
    • I've added and/or updated tests. I've run npm run test:update to update snapshots, if needed.
    • I've updated docs if needed
    • I've updated the changelog with a description of the PR
  • I'm adding a new feature
    • I've updated the playground with an example use of the feature

@heath-freenome heath-freenome force-pushed the fix-3543-by-implementing-precompiled-validators branch from 4f94b29 to f80ed1b Compare May 10, 2023 02:46
@heath-freenome heath-freenome force-pushed the fix-3543-by-implementing-precompiled-validators branch from f80ed1b to eb7a423 Compare May 10, 2023 14:53
Fixes rjsf-team#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
@heath-freenome heath-freenome force-pushed the fix-3543-by-implementing-precompiled-validators branch from f1353a8 to 31b69af Compare May 10, 2023 23:52
@nickgros
Copy link
Contributor

Just leaving a note to cross-reference with #1961 and follow-up later...while this isn't a silver bullet for that issue, I'm curious to see how much better the pre-compiled validator performs when using schemas with high conditional complexity.

* @returns - The precompiled validator function associated with this schema
*/
getValidator(schema: S) {
const key = get(schema, ID_KEY, hashForSchema(schema));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need to compute the hash if we have an $id. This is not a big deal if the hash is not an expensive computation.

- 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.

### usePrecompiledValidator<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use... makes this look like a React hook, so I would suggest a slightly different name, like

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

Comment on lines 29 to 34
export interface CompiledValidateFunction<T = any> extends Omit<ValidateFunction<T>, 'schema' | 'schemaEnv'> {
/** This is literally copied from the `ValidateFunction` type definition from which it extends because it seems to get
* lost as part of the Omit<>.
*/
(this: Ajv | any, data: any, dataCxt?: DataValidationCxt): boolean;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tbh I don't really know how a function definition is supposed to work with Omit, but it makes sense to do this, since it also has properties you want to drop.

* @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 usePrecompiledValidator<
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned before, prefer a different name so this doesn't look like a React hook.

Comment on lines +2 to +9
* In order to keep things in sync, it may be necessary to run this after making changes in the schemaParser world in
* `@rjsf/utils` OR if an AJV update is installed. To run this, simply do the following, starting in the root directory
* of the `@rjsf/validator-ajv8` directory:
*
* - cd test/harness
* - node compileTestSchema.js
*
* Then add the two updated `superSchema.js` and `superSchemaOptions.js` files to your PR
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm...we'd have no idea if something breaks unless we're disciplined about following these steps. Is it burdensome to just run this file and recompile the validators before we run the tests?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And if so, we can .gitignore the compiled files, of course

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, good idea, let me see how that goes

const compileSchemaValidators = require('../../dist').compileSchemaValidators;
const superSchema = require('./superSchema.json');

// NOTE these are the same as the CUSTOM_OPTIONS in `testData.ts`, keep them in sync
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't we just import this from testData?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TS file is using imports for types which don't work in a CJS world that this file is running in.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha, so this is easier than using ts-node or something like that. That's fine.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic set of tests

describe('passing validatorFns and rootSchema to usePrecompiledValidator', () => {
let custom: any;
beforeAll(() => {
(AJV8PrecompiledValidator as unknown as jest.Mock).mockClear();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can probably use jest.mocked() instead of this sequence of casts - https://jestjs.io/docs/mock-function-api/#jestmockedsource-options

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, I just learned something new

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just learned about it recently 🙂

@@ -32,10 +25,10 @@ describe('AJV8Validator', () => {
builder.resetAllErrors();
});
describe('default options', () => {
// Use the TestValidator to access the `withIdRefPrefix` function
let validator: TestValidator;
// Use the AJV8Validator to access the `withIdRefPrefix` function
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to remove this comment (and the other 3 times it appears in this file)? Seems like the refactor made this easy to clean up and we're not even accessing withIdRefPrefix in this file

Suggested change
// Use the AJV8Validator to access the `withIdRefPrefix` function

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

@heath-freenome heath-freenome requested a review from nickgros May 11, 2023 19:01
Copy link
Contributor

@nickgros nickgros left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Excited to see this released!

@heath-freenome heath-freenome merged commit 91bcc94 into rjsf-team:main May 11, 2023
@heath-freenome heath-freenome deleted the fix-3543-by-implementing-precompiled-validators branch May 11, 2023 22:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Content Security Policy (CSP) reporting unsafe eval usage inside Chrome Extension V3
2 participants