diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c6b50135a..ef9a0ef58 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} - name: update website if: ${{ github.event_name == 'push' && matrix.node-version == '14.x' }} - run: ./scripts/publish-gh-pages + run: ./scripts/publish-site env: GH_TOKEN_PUBLIC: ${{ secrets.GH_TOKEN_PUBLIC }} GIT_USER_EMAIL: ${{ secrets.GIT_USER_EMAIL }} diff --git a/.gitignore b/.gitignore index 8fc84d941..370ab2b27 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,9 @@ bundle/ package-lock.json -spec/_json/*.js \ No newline at end of file +spec/_json/*.js + +docs/README.md +docs/code_of_conduct.md +docs/contributing.md +docs/license.md diff --git a/.tonic_example.js b/.tonic_example.js index 2b0d6683e..7579d21c5 100644 --- a/.tonic_example.js +++ b/.tonic_example.js @@ -1,20 +1,20 @@ -var Ajv = require("ajv") -var ajv = new Ajv({allErrors: true}) +const Ajv = require("ajv").default +const ajv = new Ajv({allErrors: true}) -var schema = { +const schema = { properties: { foo: {type: "string"}, bar: {type: "number", maximum: 3}, }, } -var validate = ajv.compile(schema) +const validate = ajv.compile(schema) test({foo: "abc", bar: 2}) test({foo: 2, bar: 4}) function test(data) { - var valid = validate(data) + const valid = validate(data) if (valid) console.log("Valid!") else console.log("Invalid: " + ajv.errorsText(validate.errors)) } diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index b8026a2cf..b3ceffd86 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,76 +1,66 @@ +--- +permalink: /code_of_conduct +--- + # Contributor Covenant Code of Conduct -## Our Pledge +### Our Pledge -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. +We commit to creating and maintaining an open and welcoming environment. We, as contributors and maintainers, commit to building a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. -## Our Standards +### Our Standards -Examples of behavior that contributes to creating a positive environment -include: +**Behaviour that contributes to creating a positive environment include**: - Using welcoming and inclusive language -- Being respectful of differing viewpoints and experiences + - Consider when identity words like race or ethnicity matter + - Be conscious of language with discriminatory connotations (e.g. gendered, ableist, racialized phrases) + - Be open to being corrected if you make a mistake - it’s okay to mess up, what matters is your follow up - Gracefully accepting constructive criticism -- Focusing on what is best for the community - Showing empathy towards other community members +- Treat other community members and project team members with respect +- Report if you witness harassment or wrongdoing in our spaces -Examples of unacceptable behavior by participants include: +**Unacceptable behaviour by participants include**: -- The use of sexualized language or imagery and unwelcome sexual attention or - advances +- The use of sexualized language or imagery and unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment -- Publishing others' private information, such as a physical or electronic - address, without explicit permission -- Other conduct which could reasonably be considered inappropriate in a - professional setting +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +### Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behaviour and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behaviour. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +### Scope -## Our Responsibilities +The goal of this Code of Conduct is to set standards and expectations around how we interact within this community. It’s scope applies to all project participants and covers all interactions within the community associated with this project including, but not limited to, email communication, issue trackers, source code repositories, forums, and social media. -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. +Examples of representing a project or community include: -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. +- Using an official project e-mail address +- Posting via an official social media account +- Acting as an appointed representative at an online or offline event -## Scope +Representation of a project may be further defined and clarified by project maintainers. -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. +### Enforcement -## Enforcement +We will not tolerate abuse, harassment, or any other unacceptable behaviour made against community members, project maintainers, or members of our project team, either online or offline. -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at ajv.validator@gmail.com. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. +Violations of our Code of Conduct may be reported by contacting the project team at [ajv.validator@gmail.com](mailto:ajv.validator@gmail.com). The project team will review and investigate all complaints, to the best of our ability, and will respond in a way that it deems appropriate to the circumstances. -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. +Reports of violations will be investigated in a respectful, professional manner as promptly and confidentially as possible. We will have zero tolerance for intimidation or retaliation against anyone who raises a concern, makes a report or cooperates in an investigation around a violation of our code of conduct. Further details of specific enforcement policies may be posted separately. -## Attribution +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions or removal as determined by other members of the project's leadership. -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html +### Attribution -[homepage]: https://www.contributor-covenant.org +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, +available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html) For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq +[https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b105d2f8a..3231428c9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,47 +1,50 @@ -# Contributing - -Thank you for your help making Ajv better! Every contribution is appreciated. If you plan to implement a new feature or some other change please create an issue first, to make sure that your work is not lost. - -- [Documentation](#documentation) -- [Issues](#issues) - - [Bug reports](#bug-reports) - - [Security vulnerabilities](#security-vulnerabilities) - - [Change proposals](#changes) - - [Browser and compatibility issues](#compatibility) - - [Installation and dependency issues](#installation) - - [JSON Schema standard](#json-schema) - - [Ajv usage questions](#usage) -- [Code](#code) - - [Development](#development) - - [Pull requests](#pull-requests) - - [Contributions license](#contributions-license) +--- +permalink: /contributing +--- + +# Contributing guide + +Thank you for your help making Ajv better! Every contribution is appreciated. There are many areas where you can contribute. + +::: tip Please note +If you plan to implement a new feature or some other change please create an issue first, to make sure that your work is not lost. +::: + +[[toc]] ## Documentation -Ajv has a lot of features and maintaining documentation takes time. I appreciate the time you spend correcting or clarifying the documentation. +Ajv has a lot of features and maintaining documentation takes time. If anything is unclear, or could be explained better, we appreciate the time you spend correcting or clarifying it. + +There is a link in the bottom of each website page to quickly edit it. ## Issues -Before submitting the issue please search the existing issues and also review [Frequently Asked Questions](./docs/faq.md). +Before submitting the issue: + +- Search the existing issues +- Review [Frequently Asked Questions](./docs/faq.md). +- Provide all the relevant information, reducing both your schema and data to the smallest possible size when they still have the issue. -It is really important that you spend time to provide all the relevant information and to reduce both your schema and data to the smallest possible size when they still have the issue. Simplifying the issue also makes it more valuable for other users (in cases it turns out to be an incorrect usage rather than a bug). +We value simplicity - simplifying the example that shows the issue makes it more valuable for other users. This process helps us reduce situations where an error is occurring due to incorrect usage rather than a bug. -#### Bug reports +### Bug reports Please make sure to include the following information in the issue: -1. What version of Ajv are you using? Does the issue happen if you use the latest version? -2. Ajv options object (see https://github.com/ajv-validator/ajv/api.md#options). -3. JSON Schema and the data you are validating (please make it as small as possible to reproduce the issue). -4. Your code sample (please use `options`, `schema` and `data` as variables). -5. Validation result, data AFTER validation, error messages. -6. What results did you expect? +1. What version of Ajv are you using? +2. Does the issue happen if you use the latest version? +3. Ajv [options object](./docs/options) +4. Schema and the data you are validating (please make it as small as possible to reproduce the issue). +5. Your code sample (please use `options`, `schema` and `data` as variables). +6. Validation result, data AFTER validation, error messages. +7. What results did you expect? -Please include the link to the working code sample at Runkit.com (please clone https://runkit.com/esp/ajv-issue) - it will speed up investigation and fixing. +To speed up investigation and fixes, please include the link to the working code sample at runkit.com (please clone https://runkit.com/esp/ajv-issue). [Create bug report](https://github.com/ajv-validator/ajv/issues/new?template=bug-or-error-report.md). -#### Security vulnerabilities +### Security vulnerabilities To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). @@ -49,7 +52,7 @@ Tidelift will coordinate the fix and disclosure. Please do NOT report security vulnerabilities via GitHub issues. -#### Change proposals +### Change proposals [Create a proposal](https://github.com/ajv-validator/ajv/issues/new?template=change.md) for a new feature, option or some other improvement. @@ -57,20 +60,20 @@ Please include this information: 1. The version of Ajv you are using. 2. The problem you want to solve. -3. What do you think is the correct solution to problem? -4. Will you be able to implement it? +3. Your solution to the problem. +4. Would you like to implement it? If you’re requesting a change, it would be helpful to include this as well: 1. What you did. -2. What you would like to happen. -3. What actually happened. +2. What happened. +3. What you would like to happen. -Please include as much details as possible. +Please include as much details as possible - the more information, the better. -#### Browser and compatibility issues +### Browser and compatibility issues -[Create an issue](https://github.com/ajv-validator/ajv/issues/new?template=compatibility.md) to report a compatibility problem that only happens in a particular environment (when your code works correctly in node.js v8+ in linux systems but fails in some other environment). +[Create an issue](https://github.com/ajv-validator/ajv/issues/new?template=compatibility.md) to report a compatibility problem that only happens in a particular environment (when your code works correctly in the latest stable Node.js in linux systems but fails in some other environment). Please include this information: @@ -78,35 +81,35 @@ Please include this information: 2. The environment you have the problem with. 3. Your code (please make it as small as possible to reproduce the issue). 4. If your issue is in the browser, please list the other packages loaded in the page in the order they are loaded. Please check if the issue gets resolved (or results change) if you move Ajv bundle closer to the top. -5. Results in node.js v8+. +5. Results in the latest stable Node.js. 6. Results and error messages in your platform. -#### Installation and dependency issues +### Installation and dependency issues [Create an issue](https://github.com/ajv-validator/ajv/issues/new?template=installation.md) to report problems that happen during Ajv installation or when Ajv is missing some dependency. Before submitting the issue, please try the following: - use the latest stable Node.js and `npm` -- use `yarn` instead of `npm` - the issue can be related to https://github.com/npm/npm/issues/19877 -- remove `node_modules` and `package-lock.json` and run install again +- try using `yarn` instead of `npm` - the issue can be related to https://github.com/npm/npm/issues/19877 +- remove `node_modules` and `package-lock.json` and run `npm install` again If nothing helps, please submit: 1. The version of Ajv you are using -2. Operating system and node.js version +2. Operating system and Node.js version 3. Package manager and its version -4. Link to (or contents of) package.json +4. Link to (or contents of) package.json and package-lock.json 5. Error messages 6. The output of `npm ls` -#### Using JSON Schema standard +### Using JSON Schema standard Ajv implements JSON Schema standard draft-04 and draft-06/07. -If it is a general issue related to using the standard keywords included in JSON Schema or implementing some advanced validation logic please ask the question on [Stack Overflow](https://stackoverflow.com/questions/ask?tags=jsonschema,ajv) (my account is [esp](https://stackoverflow.com/users/1816503/esp)) or submitting the question to [JSON-Schema.org](https://github.com/json-schema-org/json-schema-spec/issues/new). Please mention @epoberezkin. +If it is a general issue related to using the standard keywords included in JSON Schema specification or implementing some advanced validation logic please ask the question on [Stack Overflow](https://stackoverflow.com/questions/ask?tags=jsonschema,ajv) (my account is [esp](https://stackoverflow.com/users/1816503/esp)) or submit the question to [json-schema.org](https://github.com/json-schema-org/json-schema-spec/issues/new). Please mention @epoberezkin. -#### Ajv usage questions +### Ajv usage questions The best place to ask a question about using Ajv is [Gitter chat](https://gitter.im/ajv-validator/ajv). @@ -114,9 +117,9 @@ If the question is advanced, it can be submitted to [Stack Overflow](http://stac ## Code -Thanks a lot for considering contributing to Ajv. Many great features were created by its users. +Thanks a lot for considering contributing to Ajv! Our users have created many great features, and we look forward to your contributions. -Please review [Code components](./docs/components.md) document as well - it will help navigating the code. +For help navigating the code, please review the [Code components](./docs/components.md) document. #### Development @@ -130,20 +133,20 @@ npm test `npm run build` - compiles typescript to dist folder. -`npm run watch` - automatically compiles typescript when files in lib folder change +`npm run watch` - automatically compiles typescript when files on lib folder changes. #### Pull requests -To make accepting your changes faster please follow these steps: +We want to iterate on the code efficiently. To speed up the process, please follow these steps: 1. Submit an [issue with the bug](https://github.com/ajv-validator/ajv/issues/new) or with the proposed change (unless the contribution is to fix the documentation typos and mistakes). -2. Please describe the proposed api and implementation plan (unless the issue is a relatively simple bug and fixing it doesn't change any api). -3. Once agreed, please write as little code as possible to achieve the desired result. -4. Please add the tests both for the added feature and, in case the feature is some option, for the existing behaviour when this option is disabled. +2. Describe the proposed api and implementation plan (unless the issue is a relatively simple bug and fixing it doesn't change any api). +3. Once agreed, please write as little code as possible to achieve the desired result. We are passionate about keeping our library size optimized. +4. Please add the tests both for the added feature and, if you are submitting an option, for the existing behaviour when this option is turned off or not passed. 5. Please avoid unnecessary changes, refactoring or changing coding styles as part of your change (unless the change was proposed as refactoring). -6. Please follow the coding conventions even if they are not validated (and/or you use different conventions in your code). +6. Follow the coding conventions even if they are not validated. 7. Please run the tests before committing your code. -8. If tests fail in Travis after you make a PR please investigate and fix the issue. +8. If tests fail in CI build after you make a PR please investigate and fix the issue. #### Contributions license @@ -152,6 +155,6 @@ When contributing the code you confirm that: 1. Your contribution is created by you. 2. You have the right to submit it under the MIT license. 3. You understand and agree that your contribution is public, will be stored indefinitely, can be redistributed as the part of Ajv or another related package under MIT license, modified or completely removed from Ajv. -4. You grant irrevocable MIT license to use your contribution as part of Ajv or another related package. +4. You grant irrevocable MIT license to use your contribution as part of Ajv or any other package. 5. You waive all rights to your contribution. 6. Unless you request otherwise, you can be mentioned as the author of the contribution in the Ajv documentation and change log. diff --git a/LICENSE b/LICENSE index 96ee71998..139162ad2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015-2017 Evgeny Poberezkin +Copyright (c) 2015-2021 Evgeny Poberezkin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index df205f44e..a930b92b4 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,32 @@ -Ajv logo +Ajv logo + +  # Ajv: Another JSON schema validator -The fastest JSON schema validator for Node.js and browser. +Super fast JSON schema validator for Node.js and browser. Supports JSON Schema draft-06/07/2019-09 (draft-04 is supported in [version 6](https://github.com/ajv-validator/ajv/tree/v6)) and JSON Type Definition [RFC8927](https://datatracker.ietf.org/doc/rfc8927/). +::: v-pre [![build](https://github.com/ajv-validator/ajv/workflows/build/badge.svg)](https://github.com/ajv-validator/ajv/actions?query=workflow%3Abuild) [![npm](https://img.shields.io/npm/v/ajv.svg)](https://www.npmjs.com/package/ajv) [![npm downloads](https://img.shields.io/npm/dm/ajv.svg)](https://www.npmjs.com/package/ajv) [![Coverage Status](https://coveralls.io/repos/github/ajv-validator/ajv/badge.svg?branch=master)](https://coveralls.io/github/ajv-validator/ajv?branch=master) [![Gitter](https://img.shields.io/gitter/room/ajv-validator/ajv.svg)](https://gitter.im/ajv-validator/ajv) [![GitHub Sponsors](https://img.shields.io/badge/$-sponsors-brightgreen)](https://github.com/sponsors/epoberezkin) +::: ## Platinum sponsors -[](https://www.mozilla.org)[](https://opencollective.com/ajv)[](https://opencollective.com/ajv) +[](https://www.mozilla.org)[](https://opencollective.com/ajv) ## Using version 7 Ajv version 7 has these new features: - NEW: support of JSON Type Definition [RFC8927](https://datatracker.ietf.org/doc/rfc8927/) (from [v7.1.0](https://github.com/ajv-validator/ajv-keywords/releases/tag/v7.1.0)), including generation of [serializers](./docs/api.md#jtd-serialize) and [parsers](./docs/api.md#jtd-parse) from JTD schemas that are more efficient than native JSON serialization/parsing, combining JSON string parsing and validation in one function. -- support of JSON Schema draft-2019-09 features: [`unevaluatedProperties`](./docs/json-schema.md#unevaluatedproperties) and [`unevaluatedItems`](./docs/json-schema.md#unevaluateditems), [dynamic recursive references](./docs/validation.md#extending-recursive-schemas) and other [additional keywords](./docs/json-schema.md#json-schema-draft-2019-09). +- support of JSON Schema draft-2019-09 features: [`unevaluatedProperties`](./docs/json-schema.md#unevaluatedproperties) and [`unevaluatedItems`](./docs/json-schema.md#unevaluateditems), [dynamic recursive references](./docs/guide/combining-schemas.md#extending-recursive-schemas) and other [additional keywords](./docs/json-schema.md#json-schema-draft-2019-09). - to reduce the mistakes in JSON schemas and unexpected validation results, [strict mode](./docs/strict-mode.md) is added - it prohibits ignored or ambiguous JSON Schema elements. - to make code injection from untrusted schemas impossible, [code generation](./docs/codegen.md) is fully re-written to be safe and to allow code optimization (compiled schema code size is reduced by more than 10%). - to simplify Ajv extensions, the new keyword API that is used by pre-defined keywords is available to user-defined keywords - it is much easier to define any keywords now, especially with subschemas. [ajv-keywords](https://github.com/ajv-validator/ajv-keywords) package was updated to use the new API (in [v4.0.0](https://github.com/ajv-validator/ajv-keywords/releases/tag/v4.0.0)) @@ -46,9 +50,25 @@ See [Getting started](#usage) for code example. ## Contributing -100+ people contributed to Ajv. You are very welcome to join by implementing new features that are valuable to many users and by improving documentation. +More than 100 people contributed to Ajv, and we would love to have you join the development. We welcome implementing new features that will benefit many users and ideas to improve our documentation. + +At Ajv, we are committed to creating more equitable and inclusive spaces for our community and team members to contribute to discussions that affect both this project and our ongoing work in the open source ecosystem. + +We strive to create an environment of respect and healthy discourse by setting standards for our interactions and we expect it from all members of our community - from long term project member to first time visitor. For more information, review our [code of conduct](./CODE_OF_CONDUCT.md) and values. + +### How we make decisions + +We value conscious curation of our library size, and balancing performance and functionality. To that end, we cannot accept every suggestion. When evaluating pull requests we consider: -Please do not be disappointed if your suggestion is not accepted - it is important to maintain the balance between the library size, performance and functionality. If it seems that a feature would benefit only a small number of users, its addition may be delayed until there is more support from the users community - so please submit the issue first to explain why this feature is important. +- Will this benefit many users or a niche use case? +- How will this impact the performance of Ajv? +- How will this expand our library size? + +To help us evaluate and understand, when you submit an issue and pull request: + +- Explain why this feature is important to the user base +- Include documentation +- Include test coverage with any new feature implementations Please include documentation and test coverage with any new feature implementations. @@ -62,7 +82,7 @@ npm test `npm run build` - compiles typescript to `dist` folder. -Please review [Contributing guidelines](./CONTRIBUTING.md) and [Code components](./docs/components.md). +Please also review [Contributing guidelines](./CONTRIBUTING.md) and [Code components](./docs/components.md). ## Contents @@ -74,14 +94,12 @@ Please review [Contributing guidelines](./CONTRIBUTING.md) and [Code components] - [Performance](#performance) - [Features](#features) - [Getting started](#usage) -- [Choosing schema language](#choosing-schema-language) - - [JSON Schema](#json-schema) - - [JSON Type Definition](#json-type-definition) +- [Choosing schema language: JSON Schema vs JSON Type Definition](./docs/guide/schema-language.md#comparison) - [Frequently Asked Questions](./docs/faq.md) -- [Using in browser](#using-in-browser) +- [Using in browser](./docs/guide/environments.md#browsers) - [Content Security Policy](./docs/security.md#content-security-policy) -- [Using in ES5 environment](#using-in-es5-environment) -- [Command line interface](#command-line-interface) +- [Using in ES5 environment](./docs/guide/environments.md#es5-environments) +- [Command line interface](./docs/guide/environments.md#command-line-interface) - [API reference](./docs/api.md) - [Methods](./docs/api.md#ajv-constructor-and-methods) - [Options](./docs/api.md#options) @@ -91,16 +109,17 @@ Please review [Contributing guidelines](./CONTRIBUTING.md) and [Code components] - [Prevent unexpected validation](./docs/strict-mode.md#prevent-unexpected-validation) - [Strict types](./docs/strict-mode.md#strict-types) - [Strict number validation](./docs/strict-mode.md#strict-number-validation) -- [Data validation](./docs/validation.md) - - [Validation basics](./docs/validation.md#validation-basics): [JSON Schema keywords](./docs/validation.md#validation-keywords), [annotations](./docs/validation.md#annotation-keywords), [formats](./docs/validation.md#formats) - - [Modular schemas](./docs/validation.md#modular-schemas): [combining with \$ref](./docs/validation.md#ref), [\$data reference](./docs/validation.md#data-reference), [$merge and $patch](./docs/validation.md#merge-and-patch-keywords) - - [Asynchronous schema compilation](./docs/validation.md#asynchronous-schema-compilation) +- [Validation guide](./docs/guide/getting-started.md) + - [Getting started](./docs/guide/getting-started.md) + - [Validating formats](./docs/guide/formats.md) + - [Modular schemas](./docs/guide/combining-schemas.md): [combining with \$ref](./docs/guide/combining-schemas#ref), [\$data reference](./docs/guide/combining-schemas.md#data-reference), [$merge and $patch](./docs/guide/combining-schemas#merge-and-patch-keywords) + - [Asynchronous schema compilation](./docs/guide/managing-schemas.md#asynchronous-schema-compilation) - [Standalone validation code](./docs/standalone.md) - - [Asynchronous validation](./docs/validation.md#asynchronous-validation) - - [Modifying data](./docs/validation.md#modifying-data-during-validation): [additional properties](./docs/validation.md#removing-additional-properties), [defaults](./docs/validation.md#assigning-defaults), [type coercion](./docs/validation.md#coercing-data-types) + - [Asynchronous validation](./docs/guide/async-validation.md) + - [Modifying data](./docs/guide/modifying-data.md): [additional properties](./docs/guide/modifying-data.md#removing-additional-properties), [defaults](./docs/guide/modifying-data.md#assigning-defaults), [type coercion](./docs/guide/modifying-data.md#coercing-data-types) - [Extending Ajv](#extending-ajv) - User-defined keywords: - - [basics](./docs/validation.md#user-defined-keywords) + - [basics](./docs/guide/user-keywords.md) - [guide](./docs/keywords.md) - [Plugins](#plugins) - [Related packages](#related-packages) @@ -117,7 +136,9 @@ Please review [Contributing guidelines](./CONTRIBUTING.md) and [Code components] ## Mozilla MOSS grant and OpenJS Foundation -[](https://www.mozilla.org/en-US/moss/)     [](https://openjsf.org/blog/2020/08/14/ajv-joins-openjs-foundation-as-an-incubation-project/) +::: v-pre +[](https://www.mozilla.org/en-US/moss/)[](https://openjsf.org/blog/2020/08/14/ajv-joins-openjs-foundation-as-an-incubation-project/) +::: Ajv has been awarded a grant from Mozilla’s [Open Source Support (MOSS) program](https://www.mozilla.org/en-US/moss/) in the “Foundational Technology” track! It will sponsor the development of Ajv support of [JSON Schema version 2019-09](https://tools.ietf.org/html/draft-handrews-json-schema-02) and of [JSON Type Definition (RFC8927)](https://datatracker.ietf.org/doc/rfc8927/). @@ -185,18 +206,18 @@ Performance of different validators by [json-schema-benchmark](https://github.co - meta-schema for JTD schemas - "union" keyword and user-defined keywords (can be used inside "metadata" member of the schema) - supports [browsers](#using-in-browser) and Node.js 0.10-14.x -- [asynchronous loading](./docs/validation.md#asynchronous-schema-compilation) of referenced schemas during compilation +- [asynchronous loading](./docs/guide/managing-schemas.md#asynchronous-schema-compilation) of referenced schemas during compilation - "All errors" validation mode with [option allErrors](./docs/api.md#options) - [error messages with parameters](./docs/api.md#validation-errors) describing error reasons to allow error message generation - i18n error messages support with [ajv-i18n](https://github.com/ajv-validator/ajv-i18n) package -- [removing-additional-properties](./docs/validation.md#removing-additional-properties) -- [assigning defaults](./docs/validation.md#assigning-defaults) to missing properties and items -- [coercing data](./docs/validation.md#coercing-data-types) to the types specified in `type` keywords +- [removing-additional-properties](./docs/guide/modifying-data.md#removing-additional-properties) +- [assigning defaults](./docs/guide/modifying-data.md#assigning-defaults) to missing properties and items +- [coercing data](./docs/guide/modifying-data.md#coercing-data-types) to the types specified in `type` keywords - [user-defined keywords](#user-defined-keywords) - draft-06/07 keywords `const`, `contains`, `propertyNames` and `if/then/else` - draft-06 boolean schemas (`true`/`false` as a schema to always pass/fail). - additional extension keywords with [ajv-keywords](https://github.com/ajv-validator/ajv-keywords) package -- [\$data reference](./docs/validation.md#data-reference) to use values from the validated data as values for the schema keywords +- [\$data reference](./docs/guide/combining-schemas.md#data-reference) to use values from the validated data as values for the schema keywords - [asynchronous validation](./docs/api.md#asynchronous-validation) of user-defined formats and keywords ## Install @@ -225,233 +246,13 @@ const valid = validate(data) if (!valid) console.log(validate.errors) ``` -In TypeScript: - -```typescript -import Ajv, {JSONSchemaType, DefinedError} from "ajv" - -const ajv = new Ajv() - -type MyData = {foo: number} - -// Optional schema type annotation for schema to match MyData type. -// To use JSONSchemaType set `strictNullChecks: true` in tsconfig `compilerOptions`. -const schema: JSONSchemaType = { - type: "object", - properties: { - foo: {type: "number", minimum: 0}, - }, - required: ["foo"], - additionalProperties: false, -} - -// validate is a type guard for MyData - type is inferred from schema type -const validate = ajv.compile(schema) - -// or, if you did not use type annotation for the schema, -// type parameter can be used to make it type guard: -// const validate = ajv.compile(schema) - -const data: any = {foo: 1} - -if (validate(data)) { - // data is MyData here - console.log(data.foo) -} else { - // The type cast is needed to allow user-defined keywords and errors - // You can extend this type to include your error types as needed. - for (const err of validate.errors as DefinedError[]) { - switch (err.keyword) { - case "minimum": - // err type is narrowed here to have "minimum" error params properties - console.log(err.params.limit) - break - // ... - } - } -} -``` - -With JSON Type Definition schema: - -In JavaScript: - -```javascript -const Ajv = require("ajv").default - -const ajv = new Ajv() // options can be passed, e.g. {allErrors: true} -const schema = { - properties: { - foo: {type: "float64"}, - }, -} -const validate = ajv.compile(schema) -const valid = validate({foo: 1}) // true -if (!valid) console.log(validate.errors) -// Unlike JSON Schema: -const valid1 = validate(1) // false, bot an object -const valid2 = validate({}) // false, foo is required -const valid3 = validate({foo: 1, bar: 2}) // false, bar is additional -``` - -In TypeScript: - -```typescript -import {JTDSchemaType} from "ajv" - -type MyData = {foo: number} - -// Optional schema type annotation for schema to match MyData type. -// To use JTDSchemaType set `strictNullChecks: true` in tsconfig `compilerOptions`. -const schema: JTDSchemaType = { - properties: { - foo: {type: "float64"}, - }, -} -``` - -See [this test](./spec/types/json-schema.spec.ts) for an advanced example, [API reference](./docs/api.md) and [Options](./docs/api.md#options) for more details. - -Ajv compiles schemas to functions and caches them in all cases (using schema itself as a key for Map) or another function passed via options), so that the next time the same schema is used (not necessarily the same object instance) it won't be compiled again. - -The best performance is achieved when using compiled functions returned by `compile` or `getSchema` methods (there is no additional function call). - -**Please note**: every time a validation function or `ajv.validate` are called `errors` property is overwritten. You need to copy `errors` array reference to another variable if you want to use it later (e.g., in the callback). See [Validation errors](./docs/api.md#validation-errors) - -## Using in browser - -See [Content Security Policy](./docs/security.md#content-security-policy) to decide the best approach how to use Ajv in the browser. - -Whether you use Ajv or compiled schemas, it is recommended that you bundle them together with your code. - -If you need to use Ajv in several bundles you can create a separate UMD bundles using `npm run bundle` script. - -Then you need to load Ajv with support of JSON Schema draft-07 in the browser: - -```html - - -``` - -To load the bundle that supports JSON Schema draft-2019-09: - -```html - - -``` - -To load the bundle that supports JSON Type Definition: - -```html - - -``` - -This bundle can be used with different module systems; it creates global `ajv` (or `ajv2019`) if no module system is found. - -The browser bundle is available on [cdnjs](https://cdnjs.com/libraries/ajv). - -**Please note**: some frameworks, e.g. Dojo, may redefine global require in a way that is not compatible with CommonJS module format. In this case Ajv bundle has to be loaded before the framework and then you can use global `ajv` (see issue [#234](https://github.com/ajv-validator/ajv/issues/234)). - -## Choosing schema language - -Both JSON Schema and JSON Type Definition are cross-platform specifications with implementations in multiple programming languages that help you define the shape and requirements to your JSON data. - -This section compares their pros/cons to help decide which specification fits your application better. - -### JSON Schema - -- Pros - - Wide specification adoption. - - Used as part of OpenAPI specification. - - Support of complex validation scenarios: - - untagged unions and boolean logic - - conditional schemas and dependencies - - restrictions on the number ranges and the size of strings, arrays and objects - - semantic validation with formats, patterns and content keywords - - distribute strict record definitions across multiple schemas (with unevaluatedProperties) - - Can be effectively used for validation of any JavaScript objects and configuration files. -- Cons - - Defines the collection of restrictions on the data, rather than the shape of the data. - - No standard support for tagged unions. - - Complex and error prone for the new users (Ajv has [strict mode](./docs/strict-mode) to compensate for it, but it is not cross-platform). - - Some parts of specification are difficult to implement, creating the risk of implementations divergence: - - reference resolution model - - unevaluatedProperties/unevaluatedItems - - dynamic recursive references - - Internet draft status (rather than RFC) - -See [JSON Schema](./docs/json-schema.md) for the list of defined keywords. - -### JSON Type Definition - -- Pros: - - Aligned with type systems of many languages - can be used to generate type definitions and efficient parsers and serializers to/from these types. - - Very simple, enforcing the best practices for cross-platform JSON API modelling. - - Simple to implement, ensuring consistency across implementations. - - Defines the shape of JSON data via strictly defined schema forms (rather than the collection of restrictions). - - Effective support for tagged unions. - - Designed to protect against user mistakes. - - Approved as [RFC8927](https://datatracker.ietf.org/doc/rfc8927/) -- Cons: - - Limited, compared with JSON Schema - no support for untagged unions\*, conditionals, references between different schema files\*\*, etc. - - No meta-schema in the specification\*. - - Brand new - limited industry adoption (as of January 2021). - -\* Ajv defines meta-schema for JTD schemas and non-standard keyword "union" that can be used inside "metadata" object. - -\*\* You can still combine schemas from multiple files in the application code. - -See [JSON Type Definition](./docs/json-type-definition.md) for the list of defined schema forms. - -## Using in ES5 environment - -You need to: - -- recompile Typescript to ES5 target - it is set to 2018 in the bundled compiled code. -- generate ES5 validation code: - -```javascript -const ajv = new Ajv({code: {es5: true}}) -``` - -See [Advanced options](https://github.com/ajv-validator/ajv/blob/master/docs/api.md#advanced-options). - -## Command line interface - -CLI is available as a separate npm package [ajv-cli](https://github.com/ajv-validator/ajv-cli). It supports: - -- compiling JSON Schemas to test their validity -- generating [standalone validation code](./docs/standalone.md) that exports validation function(s) to be used without Ajv -- migrating schemas to draft-07 and draft-2019-09 (using [json-schema-migrate](https://github.com/epoberezkin/json-schema-migrate)) -- validating data file(s) against JSON Schema -- testing expected validity of data against JSON Schema -- referenced schemas -- user-defined meta-schemas, validation keywords and formats -- files in JSON, JSON5, YAML, and JavaScript format -- all Ajv options -- reporting changes in data after validation in [JSON-patch](https://datatracker.ietf.org/doc/rfc6902/) format +See more examples in [Guide: getting started](./docs/guide/getting-started) ## Extending Ajv ### User defined keywords -See section in [data validation](./docs/validation.md#user-defined-keywords) and the [detailed guide](./docs/keywords.md). +See section in [data validation](./docs/guide/user-keywords.md) and the [detailed guide](./docs/keywords.md). ### Plugins @@ -504,7 +305,7 @@ If you have published a useful plugin please submit a PR to add it to the next s ## Changes history -See https://github.com/ajv-validator/ajv/releases +See [https://github.com/ajv-validator/ajv/releases](https://github.com/ajv-validator/ajv/releases) **Please note**: [Changes in version 7.0.0](https://github.com/ajv-validator/ajv/releases/tag/v7.0.0) diff --git a/benchmark/jtd.js b/benchmark/jtd.js index 88c2a5ae2..0127fdf5b 100644 --- a/benchmark/jtd.js +++ b/benchmark/jtd.js @@ -13,6 +13,7 @@ for (const testName in jtdValidationTests) { const valid = errors.length === 0 if (!valid) continue tests.push({ + validate: ajv.compile(schema), serialize: ajv.compileSerializer(schema), parse: ajv.compileParser(schema), data: instance, @@ -77,6 +78,12 @@ suite.add("JTD test suite: JSON.parse", () => { } }) +suite.add("JTD test suite: JSON.parse + validate", () => { + for (const test of tests) { + JSON.parse(test.json) + } +}) + const validTestData = JSON.stringify(testData) const invalidTestData = JSON.stringify({ @@ -91,11 +98,14 @@ const invalidTestData = JSON.stringify({ }) const parse = ajv.compileParser(testSchema) +const validate = ajv.compile(testSchema) suite.add("valid test data: compiled JTD parser", () => parse(validTestData)) suite.add("valid test data: JSON.parse", () => JSON.parse(validTestData)) +suite.add("valid test data: JSON.parse + validate", () => validate(JSON.parse(validTestData))) suite.add("invalid test data: compiled JTD parser", () => parse(invalidTestData)) suite.add("invalid test data: JSON.parse", () => JSON.parse(invalidTestData)) +suite.add("invalid test data: JSON.parse + validate", () => validate(JSON.parse(invalidTestData))) console.log() diff --git a/docs/.vuepress/components/GitHub.vue b/docs/.vuepress/components/GitHub.vue new file mode 100644 index 000000000..04b57f97f --- /dev/null +++ b/docs/.vuepress/components/GitHub.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js new file mode 100644 index 000000000..414561aa0 --- /dev/null +++ b/docs/.vuepress/config.js @@ -0,0 +1,116 @@ +const {slugify} = require("@vuepress/shared-utils") + +module.exports = { + title: "Ajv: Another JSON validator", + description: "Just playing around", + markdown: { + slugify: (str) => slugify(str.replace(/]*\/>/, "")), + toc: {includeLevel: [2, 3, 4]}, + }, + themeConfig: { + logo: "/img/ajv.svg", + nav: [ + {text: "Home", link: "/"}, + { + text: "Guide", + items: [ + {link: "/guide/getting-started", text: "Getting started"}, + {link: "/guide/typescript", text: "Using with TypeScript"}, + {link: "/guide/schema-language", text: "Choosing schema language"}, + {link: "/guide/managing-schemas", text: "Managing schemas"}, + {link: "/guide/combining-schemas", text: "Combining schemas"}, + {link: "/guide/formats", text: "Format validation"}, + {link: "/guide/modifying-data", text: "Modifying data"}, + {link: "/guide/user-keywords", text: "User-defined keywords"}, + {link: "/guide/async-validation", text: "Asynchronous validation"}, + {link: "/guide/environments", text: "Execution environments"}, + ], + }, + { + text: "Learn more", + items: [ + { + text: "Reference", + items: [ + {link: "/api", text: "API Reference"}, + {link: "/options", text: "Initialization options"}, + {link: "/json-schema", text: "JSON Schema"}, + {link: "/json-type-definition", text: "JSON Type Definition"}, + {link: "/strict-mode", text: "Strict mode"}, + {link: "/standalone", text: "Standalone validation code"}, + {link: "/keywords", text: "User defined keywords"}, + {link: "/coercion", text: "Type coercion rules"}, + ], + }, + { + text: "Contributors", + items: [ + {link: "/contributing", text: "Contributing guide"}, + {link: "/codegen", text: "Code generation design"}, + {link: "/components", text: "Code components"}, + {link: "/code_of_conduct", text: "Code of Conduct"}, + ], + }, + { + text: "Information", + items: [ + {link: "/security", text: "Security"}, + {link: "/faq", text: "FAQ"}, + {link: "/license", text: "License"}, + ], + }, + ], + }, + ], + sidebar: [ + { + title: "Guide", + collapsable: false, + children: [ + "/guide/getting-started", + "/guide/typescript", + "/guide/schema-language", + "/guide/managing-schemas", + "/guide/combining-schemas", + "/guide/formats", + "/guide/modifying-data", + "/guide/user-keywords", + "/guide/async-validation", + "/guide/environments", + ], + }, + { + title: "Reference", + collapsable: false, + children: [ + "/api", + "/options", + "/json-schema", + "/json-type-definition", + "/strict-mode", + "/standalone", + "/keywords", + "/coercion", + ], + }, + { + title: "Contributors", + collapsable: false, + children: [ + "/contributing", + "/codegen", + "/components", + ["/code_of_conduct", "Code of conduct"], + ], + }, + { + title: "Information", + collapsable: false, + children: ["/faq", "/security", ["/license", "License"]], + }, + ], + repo: "ajv-validator/ajv", + docsDir: "docs", + editLinks: true, + }, +} diff --git a/docs/.vuepress/public/favicon.ico b/docs/.vuepress/public/favicon.ico new file mode 100644 index 000000000..f19dfab7e Binary files /dev/null and b/docs/.vuepress/public/favicon.ico differ diff --git a/docs/.vuepress/public/img/ajv.svg b/docs/.vuepress/public/img/ajv.svg new file mode 100644 index 000000000..f6f89e097 --- /dev/null +++ b/docs/.vuepress/public/img/ajv.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/.vuepress/public/img/gap.svg b/docs/.vuepress/public/img/gap.svg new file mode 100644 index 000000000..7a634f80e --- /dev/null +++ b/docs/.vuepress/public/img/gap.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/docs/.vuepress/public/img/mozilla.svg b/docs/.vuepress/public/img/mozilla.svg new file mode 100644 index 000000000..daaf128de --- /dev/null +++ b/docs/.vuepress/public/img/mozilla.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/.vuepress/public/img/openjs.png b/docs/.vuepress/public/img/openjs.png new file mode 100644 index 000000000..cd91d8326 Binary files /dev/null and b/docs/.vuepress/public/img/openjs.png differ diff --git a/docs/.vuepress/public/img/reserved.svg b/docs/.vuepress/public/img/reserved.svg new file mode 100644 index 000000000..529135e09 --- /dev/null +++ b/docs/.vuepress/public/img/reserved.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/.vuepress/theme/LICENSE b/docs/.vuepress/theme/LICENSE new file mode 100644 index 000000000..15f1f7e7a --- /dev/null +++ b/docs/.vuepress/theme/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018-present, Yuxi (Evan) You + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/docs/.vuepress/theme/components/AlgoliaSearchBox.vue b/docs/.vuepress/theme/components/AlgoliaSearchBox.vue new file mode 100644 index 000000000..7071fb8fb --- /dev/null +++ b/docs/.vuepress/theme/components/AlgoliaSearchBox.vue @@ -0,0 +1,172 @@ + + + + + diff --git a/docs/.vuepress/theme/components/DropdownLink.vue b/docs/.vuepress/theme/components/DropdownLink.vue new file mode 100644 index 000000000..be6563fae --- /dev/null +++ b/docs/.vuepress/theme/components/DropdownLink.vue @@ -0,0 +1,252 @@ + + + + + diff --git a/docs/.vuepress/theme/components/DropdownTransition.vue b/docs/.vuepress/theme/components/DropdownTransition.vue new file mode 100644 index 000000000..eeaf12b5c --- /dev/null +++ b/docs/.vuepress/theme/components/DropdownTransition.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/docs/.vuepress/theme/components/Home.vue b/docs/.vuepress/theme/components/Home.vue new file mode 100644 index 000000000..acd87446d --- /dev/null +++ b/docs/.vuepress/theme/components/Home.vue @@ -0,0 +1,175 @@ + + + + + diff --git a/docs/.vuepress/theme/components/NavLink.vue b/docs/.vuepress/theme/components/NavLink.vue new file mode 100644 index 000000000..f7e65a445 --- /dev/null +++ b/docs/.vuepress/theme/components/NavLink.vue @@ -0,0 +1,90 @@ + + + diff --git a/docs/.vuepress/theme/components/NavLinks.vue b/docs/.vuepress/theme/components/NavLinks.vue new file mode 100644 index 000000000..75b2b7c45 --- /dev/null +++ b/docs/.vuepress/theme/components/NavLinks.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/docs/.vuepress/theme/components/Navbar.vue b/docs/.vuepress/theme/components/Navbar.vue new file mode 100644 index 000000000..f8dd49ca6 --- /dev/null +++ b/docs/.vuepress/theme/components/Navbar.vue @@ -0,0 +1,140 @@ + + + + + diff --git a/docs/.vuepress/theme/components/Page.vue b/docs/.vuepress/theme/components/Page.vue new file mode 100644 index 000000000..04ec7cb33 --- /dev/null +++ b/docs/.vuepress/theme/components/Page.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/docs/.vuepress/theme/components/PageEdit.vue b/docs/.vuepress/theme/components/PageEdit.vue new file mode 100644 index 000000000..cf9b2d25c --- /dev/null +++ b/docs/.vuepress/theme/components/PageEdit.vue @@ -0,0 +1,155 @@ + + + + + diff --git a/docs/.vuepress/theme/components/PageNav.vue b/docs/.vuepress/theme/components/PageNav.vue new file mode 100644 index 000000000..4c19aae5f --- /dev/null +++ b/docs/.vuepress/theme/components/PageNav.vue @@ -0,0 +1,163 @@ + + + + + diff --git a/docs/.vuepress/theme/components/Sidebar.vue b/docs/.vuepress/theme/components/Sidebar.vue new file mode 100644 index 000000000..e70e33367 --- /dev/null +++ b/docs/.vuepress/theme/components/Sidebar.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/docs/.vuepress/theme/components/SidebarButton.vue b/docs/.vuepress/theme/components/SidebarButton.vue new file mode 100644 index 000000000..3f54afd55 --- /dev/null +++ b/docs/.vuepress/theme/components/SidebarButton.vue @@ -0,0 +1,40 @@ + + + diff --git a/docs/.vuepress/theme/components/SidebarGroup.vue b/docs/.vuepress/theme/components/SidebarGroup.vue new file mode 100644 index 000000000..d7f192946 --- /dev/null +++ b/docs/.vuepress/theme/components/SidebarGroup.vue @@ -0,0 +1,141 @@ + + + + + diff --git a/docs/.vuepress/theme/components/SidebarLink.vue b/docs/.vuepress/theme/components/SidebarLink.vue new file mode 100644 index 000000000..4cd7665ae --- /dev/null +++ b/docs/.vuepress/theme/components/SidebarLink.vue @@ -0,0 +1,133 @@ + + + diff --git a/docs/.vuepress/theme/components/SidebarLinks.vue b/docs/.vuepress/theme/components/SidebarLinks.vue new file mode 100644 index 000000000..55e62885e --- /dev/null +++ b/docs/.vuepress/theme/components/SidebarLinks.vue @@ -0,0 +1,106 @@ + + + diff --git a/docs/.vuepress/theme/global-components/Badge.vue b/docs/.vuepress/theme/global-components/Badge.vue new file mode 100644 index 000000000..53951f9d5 --- /dev/null +++ b/docs/.vuepress/theme/global-components/Badge.vue @@ -0,0 +1,44 @@ + + + diff --git a/docs/.vuepress/theme/global-components/CodeBlock.vue b/docs/.vuepress/theme/global-components/CodeBlock.vue new file mode 100644 index 000000000..d59d85b26 --- /dev/null +++ b/docs/.vuepress/theme/global-components/CodeBlock.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/docs/.vuepress/theme/global-components/CodeGroup.vue b/docs/.vuepress/theme/global-components/CodeGroup.vue new file mode 100644 index 000000000..ac6ec5543 --- /dev/null +++ b/docs/.vuepress/theme/global-components/CodeGroup.vue @@ -0,0 +1,120 @@ + + + + + diff --git a/docs/.vuepress/theme/index.js b/docs/.vuepress/theme/index.js new file mode 100644 index 000000000..01958f462 --- /dev/null +++ b/docs/.vuepress/theme/index.js @@ -0,0 +1,71 @@ +const path = require("path") + +// Theme API. +module.exports = (options, ctx) => { + const {themeConfig, siteConfig} = ctx + + // resolve algolia + const isAlgoliaSearch = + themeConfig.algolia || + Object.keys((siteConfig.locales && themeConfig.locales) || {}).some( + (base) => themeConfig.locales[base].algolia + ) + + const enableSmoothScroll = themeConfig.smoothScroll === true + + return { + alias() { + return { + "@AlgoliaSearchBox": isAlgoliaSearch + ? path.resolve(__dirname, "components/AlgoliaSearchBox.vue") + : path.resolve(__dirname, "noopModule.js"), + } + }, + + plugins: [ + ["@vuepress/active-header-links", options.activeHeaderLinks], + "@vuepress/search", + "@vuepress/plugin-nprogress", + [ + "container", + { + type: "tip", + defaultTitle: { + "/": "TIP", + "/zh/": "提示", + }, + }, + ], + [ + "container", + { + type: "warning", + defaultTitle: { + "/": "WARNING", + "/zh/": "注意", + }, + }, + ], + [ + "container", + { + type: "danger", + defaultTitle: { + "/": "WARNING", + "/zh/": "警告", + }, + }, + ], + [ + "container", + { + type: "details", + before: (info) => + `
${info ? `${info}` : ""}\n`, + after: () => "
\n", + }, + ], + ["smooth-scroll", enableSmoothScroll], + ], + } +} diff --git a/docs/.vuepress/theme/layouts/404.vue b/docs/.vuepress/theme/layouts/404.vue new file mode 100644 index 000000000..2cbfa0f17 --- /dev/null +++ b/docs/.vuepress/theme/layouts/404.vue @@ -0,0 +1,30 @@ + + + diff --git a/docs/.vuepress/theme/layouts/Layout.vue b/docs/.vuepress/theme/layouts/Layout.vue new file mode 100644 index 000000000..329807099 --- /dev/null +++ b/docs/.vuepress/theme/layouts/Layout.vue @@ -0,0 +1,151 @@ + + + diff --git a/docs/.vuepress/theme/noopModule.js b/docs/.vuepress/theme/noopModule.js new file mode 100644 index 000000000..b1c6ea436 --- /dev/null +++ b/docs/.vuepress/theme/noopModule.js @@ -0,0 +1 @@ +export default {} diff --git a/docs/.vuepress/theme/styles/arrow.styl b/docs/.vuepress/theme/styles/arrow.styl new file mode 100644 index 000000000..20bffc0dc --- /dev/null +++ b/docs/.vuepress/theme/styles/arrow.styl @@ -0,0 +1,22 @@ +@require './config' + +.arrow + display inline-block + width 0 + height 0 + &.up + border-left 4px solid transparent + border-right 4px solid transparent + border-bottom 6px solid $arrowBgColor + &.down + border-left 4px solid transparent + border-right 4px solid transparent + border-top 6px solid $arrowBgColor + &.right + border-top 4px solid transparent + border-bottom 4px solid transparent + border-left 6px solid $arrowBgColor + &.left + border-top 4px solid transparent + border-bottom 4px solid transparent + border-right 6px solid $arrowBgColor diff --git a/docs/.vuepress/theme/styles/code.styl b/docs/.vuepress/theme/styles/code.styl new file mode 100644 index 000000000..9d3aa9a54 --- /dev/null +++ b/docs/.vuepress/theme/styles/code.styl @@ -0,0 +1,137 @@ +{$contentClass} + code + color lighten($textColor, 20%) + padding 0.25rem 0.5rem + margin 0 + font-size 0.85em + background-color rgba(27,31,35,0.05) + border-radius 3px + .token + &.deleted + color #EC5975 + &.inserted + color $accentColor + +{$contentClass} + pre, pre[class*="language-"] + line-height 1.4 + padding 1.25rem 1.5rem + margin 0.85rem 0 + background-color $codeBgColor + border-radius 6px + overflow auto + code + color #fff + padding 0 + background-color transparent + border-radius 0 + +div[class*="language-"] + position relative + background-color $codeBgColor + border-radius 6px + .highlight-lines + user-select none + padding-top 1.3rem + position absolute + top 0 + left 0 + width 100% + line-height 1.4 + .highlighted + background-color rgba(0, 0, 0, 66%) + pre, pre[class*="language-"] + background transparent + position relative + z-index 1 + &::before + position absolute + z-index 3 + top 0.8em + right 1em + font-size 0.75rem + color rgba(255, 255, 255, 0.4) + &:not(.line-numbers-mode) + .line-numbers-wrapper + display none + &.line-numbers-mode + .highlight-lines .highlighted + position relative + &:before + content ' ' + position absolute + z-index 3 + left 0 + top 0 + display block + width $lineNumbersWrapperWidth + height 100% + background-color rgba(0, 0, 0, 66%) + pre + padding-left $lineNumbersWrapperWidth + 1 rem + vertical-align middle + .line-numbers-wrapper + position absolute + top 0 + width $lineNumbersWrapperWidth + text-align center + color rgba(255, 255, 255, 0.3) + padding 1.25rem 0 + line-height 1.4 + br + user-select none + .line-number + position relative + z-index 4 + user-select none + font-size 0.85em + &::after + content '' + position absolute + z-index 2 + top 0 + left 0 + width $lineNumbersWrapperWidth + height 100% + border-radius 6px 0 0 6px + border-right 1px solid rgba(0, 0, 0, 66%) + background-color $codeBgColor + + +for lang in $codeLang + div{'[class~="language-' + lang + '"]'} + &:before + content ('' + lang) + +div[class~="language-javascript"] + &:before + content "js" + +div[class~="language-typescript"] + &:before + content "ts" + +div[class~="language-markup"] + &:before + content "html" + +div[class~="language-markdown"] + &:before + content "md" + +div[class~="language-json"]:before + content "json" + +div[class~="language-ruby"]:before + content "rb" + +div[class~="language-python"]:before + content "py" + +div[class~="language-bash"]:before + content "sh" + +div[class~="language-php"]:before + content "php" + +@import '~prismjs/themes/prism-tomorrow.css' diff --git a/docs/.vuepress/theme/styles/config.styl b/docs/.vuepress/theme/styles/config.styl new file mode 100644 index 000000000..9e403210f --- /dev/null +++ b/docs/.vuepress/theme/styles/config.styl @@ -0,0 +1 @@ +$contentClass = '.theme-default-content' diff --git a/docs/.vuepress/theme/styles/custom-blocks.styl b/docs/.vuepress/theme/styles/custom-blocks.styl new file mode 100644 index 000000000..5b868166a --- /dev/null +++ b/docs/.vuepress/theme/styles/custom-blocks.styl @@ -0,0 +1,44 @@ +.custom-block + .custom-block-title + font-weight 600 + margin-bottom -0.4rem + &.tip, &.warning, &.danger + padding .1rem 1.5rem + border-left-width .5rem + border-left-style solid + margin 1rem 0 + &.tip + background-color #f3f5f7 + border-color #42b983 + &.warning + background-color rgba(255,229,100,.3) + border-color darken(#ffe564, 35%) + color darken(#ffe564, 70%) + .custom-block-title + color darken(#ffe564, 50%) + a + color $textColor + &.danger + background-color #ffe6e6 + border-color darken(red, 20%) + color darken(red, 70%) + .custom-block-title + color darken(red, 40%) + a + color $textColor + &.details + display block + position relative + border-radius 2px + margin 1.6em 0 + padding 1.6em + background-color #eee + h4 + margin-top 0 + figure, p + &:last-child + margin-bottom 0 + padding-bottom 0 + summary + outline none + cursor pointer diff --git a/docs/.vuepress/theme/styles/index.styl b/docs/.vuepress/theme/styles/index.styl new file mode 100644 index 000000000..1b34f6022 --- /dev/null +++ b/docs/.vuepress/theme/styles/index.styl @@ -0,0 +1,202 @@ +@require './config' +@require './code' +@require './custom-blocks' +@require './arrow' +@require './wrapper' +@require './toc' + +html, body + padding 0 + margin 0 + background-color #fff + +body + font-family -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif + -webkit-font-smoothing antialiased + -moz-osx-font-smoothing grayscale + font-size 16px + color $textColor + +.page + padding-left $sidebarWidth + +.navbar + position fixed + z-index 20 + top 0 + left 0 + right 0 + height $navbarHeight + background-color #fff + box-sizing border-box + border-bottom 1px solid $borderColor + +.sidebar-mask + position fixed + z-index 9 + top 0 + left 0 + width 100vw + height 100vh + display none + +.sidebar + font-size 16px + background-color #fff + width $sidebarWidth + position fixed + z-index 10 + margin 0 + top $navbarHeight + left 0 + bottom 0 + box-sizing border-box + border-right 1px solid $borderColor + overflow-y auto + +{$contentClass}:not(.custom) + @extend $wrapper + > *:first-child + margin-top $navbarHeight + + a:hover + text-decoration underline + + p.demo + padding 1rem 1.5rem + border 1px solid #ddd + border-radius 4px + + img + max-width 100% + +{$contentClass}.custom + padding 0 + margin 0 + + img + max-width 100% + +a + font-weight 500 + color $accentColor + text-decoration none + +p a code + font-weight 400 + color $accentColor + +kbd + background #eee + border solid 0.15rem #ddd + border-bottom solid 0.25rem #ddd + border-radius 0.15rem + padding 0 0.15em + +blockquote + font-size 1rem + color #999; + border-left .2rem solid #dfe2e5 + margin 1rem 0 + padding .25rem 0 .25rem 1rem + + & > p + margin 0 + +ul, ol + padding-left 1.2em + +strong + font-weight 600 + +h1, h2, h3, h4, h5, h6 + font-weight 600 + line-height 1.25 + + {$contentClass}:not(.custom) > & + margin-top (0.5rem - $navbarHeight) + padding-top ($navbarHeight + 1rem) + margin-bottom 0 + + &:first-child + margin-top -1.5rem + margin-bottom 1rem + + + p, + pre, + .custom-block + margin-top 2rem + + &:focus .header-anchor, + &:hover .header-anchor + opacity: 1 + +h1 + font-size 2.2rem + +h2 + font-size 1.65rem + padding-bottom .3rem + border-bottom 1px solid $borderColor + +h3 + font-size 1.35rem + +a.header-anchor + font-size 0.85em + float left + margin-left -0.87em + padding-right 0.23em + margin-top 0.125em + opacity 0 + + &:focus, + &:hover + text-decoration none + +code, kbd, .line-number + font-family source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace + +p, ul, ol + line-height 1.7 + +hr + border 0 + border-top 1px solid $borderColor + +table + border-collapse collapse + margin 1rem 0 + display: block + overflow-x: auto + +tr + border-top 1px solid #dfe2e5 + + &:nth-child(2n) + background-color #f6f8fa + +th, td + border 1px solid #dfe2e5 + padding .6em 1em + +.theme-container + &.sidebar-open + .sidebar-mask + display: block + + &.no-navbar + {$contentClass}:not(.custom) > h1, h2, h3, h4, h5, h6 + margin-top 1.5rem + padding-top 0 + + .sidebar + top 0 + +@media (min-width: ($MQMobile + 1px)) + .theme-container.no-sidebar + .sidebar + display none + + .page + padding-left 0 + +@require 'mobile.styl' diff --git a/docs/.vuepress/theme/styles/mobile.styl b/docs/.vuepress/theme/styles/mobile.styl new file mode 100644 index 000000000..f5bd32739 --- /dev/null +++ b/docs/.vuepress/theme/styles/mobile.styl @@ -0,0 +1,37 @@ +@require './config' + +$mobileSidebarWidth = $sidebarWidth * 0.82 + +// narrow desktop / iPad +@media (max-width: $MQNarrow) + .sidebar + font-size 15px + width $mobileSidebarWidth + .page + padding-left $mobileSidebarWidth + +// wide mobile +@media (max-width: $MQMobile) + .sidebar + top 0 + padding-top $navbarHeight + transform translateX(-100%) + transition transform .2s ease + .page + padding-left 0 + .theme-container + &.sidebar-open + .sidebar + transform translateX(0) + &.no-navbar + .sidebar + padding-top: 0 + +// narrow mobile +@media (max-width: $MQMobileNarrow) + h1 + font-size 1.9rem + {$contentClass} + div[class*="language-"] + margin 0.85rem -1.5rem + border-radius 0 diff --git a/docs/.vuepress/theme/styles/toc.styl b/docs/.vuepress/theme/styles/toc.styl new file mode 100644 index 000000000..d3e71069b --- /dev/null +++ b/docs/.vuepress/theme/styles/toc.styl @@ -0,0 +1,3 @@ +.table-of-contents + .badge + vertical-align middle diff --git a/docs/.vuepress/theme/styles/wrapper.styl b/docs/.vuepress/theme/styles/wrapper.styl new file mode 100644 index 000000000..a99262c71 --- /dev/null +++ b/docs/.vuepress/theme/styles/wrapper.styl @@ -0,0 +1,9 @@ +$wrapper + max-width $contentWidth + margin 0 auto + padding 2rem 2.5rem + @media (max-width: $MQNarrow) + padding 2rem + @media (max-width: $MQMobileNarrow) + padding 1.5rem + diff --git a/docs/.vuepress/theme/util/index.js b/docs/.vuepress/theme/util/index.js new file mode 100644 index 000000000..8f018dd98 --- /dev/null +++ b/docs/.vuepress/theme/util/index.js @@ -0,0 +1,239 @@ +export const hashRE = /#.*$/ +export const extRE = /\.(md|html)$/ +export const endingSlashRE = /\/$/ +export const outboundRE = /^[a-z]+:/i + +export function normalize(path) { + return decodeURI(path).replace(hashRE, "").replace(extRE, "") +} + +export function getHash(path) { + const match = path.match(hashRE) + if (match) { + return match[0] + } +} + +export function isExternal(path) { + return outboundRE.test(path) +} + +export function isMailto(path) { + return /^mailto:/.test(path) +} + +export function isTel(path) { + return /^tel:/.test(path) +} + +export function ensureExt(path) { + if (isExternal(path)) { + return path + } + const hashMatch = path.match(hashRE) + const hash = hashMatch ? hashMatch[0] : "" + const normalized = normalize(path) + + if (endingSlashRE.test(normalized)) { + return path + } + return normalized + ".html" + hash +} + +export function isActive(route, path) { + const routeHash = decodeURIComponent(route.hash) + const linkHash = getHash(path) + if (linkHash && routeHash !== linkHash) { + return false + } + const routePath = normalize(route.path) + const pagePath = normalize(path) + return routePath === pagePath +} + +export function resolvePage(pages, rawPath, base) { + if (isExternal(rawPath)) { + return { + type: "external", + path: rawPath, + } + } + if (base) { + rawPath = resolvePath(rawPath, base) + } + const path = normalize(rawPath) + for (let i = 0; i < pages.length; i++) { + if (normalize(pages[i].regularPath) === path) { + return Object.assign({}, pages[i], { + type: "page", + path: ensureExt(pages[i].path), + }) + } + } + console.error(`[vuepress] No matching page found for sidebar item "${rawPath}"`) + return {} +} + +function resolvePath(relative, base, append) { + const firstChar = relative.charAt(0) + if (firstChar === "/") { + return relative + } + + if (firstChar === "?" || firstChar === "#") { + return base + relative + } + + const stack = base.split("/") + + // remove trailing segment if: + // - not appending + // - appending to trailing slash (last segment is empty) + if (!append || !stack[stack.length - 1]) { + stack.pop() + } + + // resolve relative path + const segments = relative.replace(/^\//, "").split("/") + for (let i = 0; i < segments.length; i++) { + const segment = segments[i] + if (segment === "..") { + stack.pop() + } else if (segment !== ".") { + stack.push(segment) + } + } + + // ensure leading slash + if (stack[0] !== "") { + stack.unshift("") + } + + return stack.join("/") +} + +/** + * @param { Page } page + * @param { string } regularPath + * @param { SiteData } site + * @param { string } localePath + * @returns { SidebarGroup } + */ +export function resolveSidebarItems(page, regularPath, site, localePath) { + const {pages, themeConfig} = site + + const localeConfig = + localePath && themeConfig.locales ? themeConfig.locales[localePath] || themeConfig : themeConfig + + const pageSidebarConfig = page.frontmatter.sidebar || localeConfig.sidebar || themeConfig.sidebar + if (pageSidebarConfig === "auto") { + return resolveHeaders(page) + } + + const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar + if (!sidebarConfig) { + return [] + } else { + const {base, config} = resolveMatchingConfig(regularPath, sidebarConfig) + if (config === "auto") { + return resolveHeaders(page) + } + return config ? config.map((item) => resolveItem(item, pages, base)) : [] + } +} + +/** + * @param { Page } page + * @returns { SidebarGroup } + */ +function resolveHeaders(page) { + const headers = groupHeaders(page.headers || []) + return [ + { + type: "group", + collapsable: false, + title: page.title, + path: null, + children: headers.map((h) => ({ + type: "auto", + title: h.title, + basePath: page.path, + path: page.path + "#" + h.slug, + children: h.children || [], + })), + }, + ] +} + +export function groupHeaders(headers) { + // group h3s under h2 + headers = headers.map((h) => Object.assign({}, h)) + let lastH2 + headers.forEach((h) => { + if (h.level === 2) { + lastH2 = h + } else if (lastH2) { + ;(lastH2.children || (lastH2.children = [])).push(h) + } + }) + return headers.filter((h) => h.level === 2) +} + +export function resolveNavLinkItem(linkItem) { + return Object.assign(linkItem, { + type: linkItem.items && linkItem.items.length ? "links" : "link", + }) +} + +/** + * @param { Route } route + * @param { Array | Array | [link: string]: SidebarConfig } config + * @returns { base: string, config: SidebarConfig } + */ +export function resolveMatchingConfig(regularPath, config) { + if (Array.isArray(config)) { + return { + base: "/", + config: config, + } + } + for (const base in config) { + if (ensureEndingSlash(regularPath).indexOf(encodeURI(base)) === 0) { + return { + base, + config: config[base], + } + } + } + return {} +} + +function ensureEndingSlash(path) { + return /(\.html|\/)$/.test(path) ? path : path + "/" +} + +function resolveItem(item, pages, base, groupDepth = 1) { + if (typeof item === "string") { + return resolvePage(pages, item, base) + } else if (Array.isArray(item)) { + return Object.assign(resolvePage(pages, item[0], base), { + title: item[1], + }) + } else { + const children = item.children || [] + if (children.length === 0 && item.path) { + return Object.assign(resolvePage(pages, item.path, base), { + title: item.title, + }) + } + return { + type: "group", + path: item.path, + title: item.title, + sidebarDepth: item.sidebarDepth, + initialOpenGroupIndex: item.initialOpenGroupIndex, + children: children.map((child) => resolveItem(child, pages, base, groupDepth + 1)), + collapsable: item.collapsable !== false, + } + } +} diff --git a/docs/HOME.md b/docs/HOME.md new file mode 100644 index 000000000..1f03ab021 --- /dev/null +++ b/docs/HOME.md @@ -0,0 +1,45 @@ +# Ajv JSON Validator + +Safety, security and reliability for JavaScript applications + +## Ajv News + +This section will include the last update and the headlines of several previous updates, e.g. these sections: + +https://github.com/ajv-validator/ajv#using-version-7 + +https://github.com/ajv-validator/ajv#mozilla-moss-grant-and-openjs-foundation + +### Write less code + +**Ensure your data is valid once it's received** + +Instead of having your data validation and sanitization logic scattered around your code, you can express the requirements to your data with concise, easy to read and cross-platform [JSON Schema](https://json-schema.org) or [JSON Type Definition](https://jsontypedef.com) specifications and validate the data as soon as it arrives to your application. TypeScript users can use validation functions as type guards, having type level guarantee that if your data is validated - it is correct. + +### Super fast and secure + +**The fastest and the most secure JSON validator** + +Ajv was designed at the time when there were no validators fully complying with JSON Schema specification, aiming to achieve the best possibly validation performance via just-in-time compilation of JSON schemas to code. Ajv achieved both speed and rigour, but initially security was an afterthought - many security flaws have been fixed thanks to the reports from its users. Ajv version 7 was rebuilt to have secure code generation embedded in its design as the primary objective - even if you use untrusted schemas (which is still not recommended) there are type-level guarantees against remote code execution. + +### Multi-specification + +**Choose your JSON schema standard** + +In addition to the latest JSON Schema draft 2020-12, Ajv version 8 added support for JSON Type Definition - a new [RFC8927](https://datatracker.ietf.org/doc/rfc8927/) that offers a much simpler and less error-prone alternative to JSON Schema. Designed to be well-aligned with type systems, JTD has tools for both validation and type code generation for multiple languages. + +## Introduction (no section heading) + +Ajv is a widely used library that provides reliability, safety and security to millions of JavaScript applications and other libraries. It can be used in all JavaScript environments - node.js, browsers, Electron apps, etc. If your environment or security policy prohibit run-time function construction you can compile your schemas during build time into a standalone validation code (it may still have dependencies on small parts of Ajv code, but doesn't use the whole library) - since version 7 it is fully supported for all JSON schemas. + +Installation + +Usage example / or small playground + +Try in the playground (TBC) + +## Who uses Ajv + +## Contributors + +Ajv is free to use and open-source that many developers contributed to. Join us! diff --git a/docs/api.md b/docs/api.md index 9bdee479c..114ff39c7 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,12 +1,10 @@ # API Reference -- [Ajv constructor and methods](#ajv-constructor-and-methods) -- [Options](#options) -- [Validation errors](#validation-errors) +[[toc]] ## Ajv constructor and methods -#### new Ajv(options: object) +### new Ajv(options: object) Create Ajv instance: @@ -14,13 +12,13 @@ Create Ajv instance: const ajv = new Ajv() ``` -See [Options](#options) +See [Options](./options) -#### ajv.compile(schema: object): (data: any) =\> boolean | Promise\ +### ajv.compile(schema: object): (data: any) => boolean | Promise < any > Generate validating function and cache the compiled schema for future use. -Validating function returns a boolean value (or promise for async schemas that must have `$async: true` property - see [Asynchronous validation](./validation.md#asynchronous-validation)). This function has properties `errors` and `schema`. Errors encountered during the last validation are assigned to `errors` property (it is assigned `null` if there was no errors). `schema` property contains the reference to the original schema. +Validating function returns a boolean value (or promise for async schemas that must have `$async: true` property - see [Asynchronous validation](./guide/async-validation.md)). This function has properties `errors` and `schema`. Errors encountered during the last validation are assigned to `errors` property (it is assigned `null` if there was no errors). `schema` property contains the reference to the original schema. The schema passed to this method will be validated against meta-schema unless `validateSchema` option is false. If schema is invalid, an error will be thrown. See [options](#options). @@ -50,7 +48,9 @@ if (validate(data)) { See more advanced example in [the test](../spec/types/json-schema.spec.ts). -#### ajv.compileSerializer(schema: object): (data: any) =\> string (NEW) + + +### ajv.compileSerializer(schema: object): (data: any) => string Generate serializing function based on the [JTD schema](./json-type-definition.md) (caches the schema) - only in JTD instance of Ajv (see example below). @@ -84,9 +84,13 @@ const serializeMyData = ajv.compileSerializer(mySchema) // it prevents you from accidentally passing the wrong type ``` -**Please note**: Compiled serializers do NOT validate passed data, it is assumed that the data is valid according to the schema. In the future there may be an option added that would make serializers also validate the data. +::: warning Please note +Compiled serializers do NOT validate passed data, it is assumed that the data is valid according to the schema. In the future there may be an option added that would make serializers also validate the data. +::: + + -#### ajv.compileParser(schema: object): (json: string) =\> any (NEW) +### ajv.compileParser(schema: object): (json: string) => any Generate parsing function based on the [JTD schema](./json-type-definition.md) (caches the schema) - only in JTD instance of Ajv (see example below). @@ -109,11 +113,15 @@ console.log(parseMyData.position) // 4 console.log(parseMyData.message) // property x not allowed ``` -**Please note**: generated parsers is a NEW Ajv functionality (as of March 2021), there can be some edge cases that are not handled correctly - please report any issues/submit fixes. +::: warning Please note +Generated parsers is a NEW Ajv functionality (as of March 2021), there can be some edge cases that are not handled correctly - please report any issues/submit fixes. +::: \* As long as empty schema `{}` is not used - there is a possibility to improve performance in this case. Also, the performance of parsing `discriminator` schemas depends on the position of discriminator tag in the schema - the best parsing performance will be achieved if the tag is the first property - this is how compiled JTD serializers generate JSON in case of discriminator schemas. -#### ajv.compileAsync(schema: object, meta?: boolean): Promise\ + + +### ajv.compileAsync(schema: object, meta?: boolean): Promise < Function > Asynchronous version of `compile` method that loads missing remote schemas using asynchronous function in `options.loadSchema`. This function returns a Promise that resolves to a validation function. An optional callback passed to `compileAsync` will be called with 2 parameters: error (or null) and validating function. The returned promise will reject (and the callback will be called with an error) when: @@ -127,9 +135,9 @@ You can asynchronously compile meta-schema by passing `true` as the second param Similarly to `compile`, it can return type guard in typescript. -See example in [Asynchronous compilation](./validation.md#asynchronous-schema-compilation). +See example in [Asynchronous compilation](./guide/managing-schemas.md#asynchronous-schema-compilation). -#### ajv.validate(schemaOrRef: object | string, data: any): boolean +### ajv.validate(schemaOrRef: object | string, data: any): boolean Validate data using passed schema (it will be compiled and cached). @@ -139,11 +147,15 @@ Validation errors will be available in the `errors` property of Ajv instance (`n In typescript this method can act as a type guard (similarly to function returned by `compile` method - see example there). -**Please note**: every time this method is called the errors are overwritten so you need to copy them to another variable if you want to use them later. +::: warning Please note +Every time this method is called the errors are overwritten so you need to copy them to another variable if you want to use them later. +::: + +If the schema is asynchronous (has `$async` keyword on the top level) this method returns a Promise. See [Asynchronous validation](./guide/async-validation.md). -If the schema is asynchronous (has `$async` keyword on the top level) this method returns a Promise. See [Asynchronous validation](./validation.md#asynchronous-validation). + -#### ajv.addSchema(schema: object | object[], key?: string): Ajv +### ajv.addSchema(schema: object | object[], key?: string): Ajv Add schema(s) to validator instance. This method does not compile schemas (but it still validates them). Because of that dependencies can be added in any order and circular dependencies are supported. It also prevents unnecessary compilation of schemas that are containers for other schemas but not used as a whole. @@ -157,19 +169,24 @@ Although `addSchema` does not compile schemas, explicit compilation is not requi By default the schema is validated against meta-schema before it is added, and if the schema does not pass validation the exception is thrown. This behaviour is controlled by `validateSchema` option. -**Please note**: Ajv return it instance for method chaining from all methods with the prefix `add*` and `remove*`: +::: tip Please note +Ajv return it instance for method chaining from all methods with the prefix `add*` and `remove*`: ```javascript const validate = new Ajv().addSchema(schema).addFormat(name, regex).getSchema(uri) ``` -#### ajv.addMetaSchema(schema: object | object[], key?: string): Ajv +::: + +### ajv.addMetaSchema(schema: object | object[], key?: string): Ajv Adds meta schema(s) that can be used to validate other schemas. That function should be used instead of `addSchema` because there may be instance options that would compile a meta schema incorrectly (at the moment it is `removeAdditional` option). There is no need to explicitly add draft-07 meta schema (http://json-schema.org/draft-07/schema) - it is added by default, unless option `meta` is set to `false`. You only need to use it if you have a changed meta-schema that you want to use to validate your schemas. See `validateSchema`. -#### ajv.validateSchema(schema: object): boolean + + +### ajv.validateSchema(schema: object): boolean Validates schema. This method should be used to validate schemas rather than `validate` due to the inconsistency of `uri` format in JSON Schema standard. @@ -181,11 +198,11 @@ If schema has `$schema` property, then the schema with this id (that should be p Errors will be available at `ajv.errors`. -#### ajv.getSchema(key: string): undefined | ((data: any) =\> boolean | Promise\) +### ajv.getSchema(key: string): undefined | ((data: any) => boolean | Promise < any >) Retrieve compiled schema previously added with `addSchema` by the key passed to `addSchema` or by its full reference (id). The returned validating function has `schema` property with the reference to the original schema. -#### ajv.removeSchema(schemaOrRef: object | string | RegExp): Ajv +### ajv.removeSchema(schemaOrRef: object | string | RegExp): Ajv Remove added/cached schema. Even if schema is referenced by other schemas it can be safely removed as dependent schemas have local references. @@ -198,7 +215,9 @@ Schema can be removed using: If no parameter is passed all schemas but meta-schemas will be removed and the cache will be cleared. -#### ajv.addFormat(name: string, format: Format): Ajv + + +### ajv.addFormat(name: string, format: Format): Ajv ```typescript type Format = @@ -229,7 +248,9 @@ interface FormatDefinition { // actual type definition is more precise - see typ Formats can be also added via `formats` option. -#### ajv.addKeyword(definition: object):s Ajv + + +### ajv.addKeyword(definition: object): Ajv Add validation keyword to Ajv instance. @@ -265,7 +286,7 @@ interface KeywordDefinition { modifying?: true // MUST be passed if keyword modifies data valid?: boolean // to pre-define validation result, validation function result will be ignored - // this option MUST NOT be used with `macro` keywords. - $data?: true // to support [\$data reference](./validation.md#data-reference) as the value of keyword. + $data?: true // to support [\$data reference](./guide/combining-schemas.md#data-reference) as the value of keyword. // The reference will be resolved at validation time. If the keyword has meta-schema, // it would be extended to allow $data and it will be used to validate the resolved value. // Supporting $data reference requires that keyword has `code` or `validate` function @@ -281,187 +302,37 @@ interface KeywordDefinition { } ``` -`compile`, `macro` and `code` are mutually exclusive, only one should be used at a time. `validate` can be used separately or in addition to `compile` or `macro` to support [\$data reference](./validation.md#data-reference). +`compile`, `macro` and `code` are mutually exclusive, only one should be used at a time. `validate` can be used separately or in addition to `compile` or `macro` to support [\$data reference](./guide/combining-schemas.md#data-reference). -**Please note**: If the keyword is validating data type that is different from the type(s) in its definition, the validation function will not be called (and expanded macro will not be used), so there is no need to check for data type inside validation function or inside schema returned by macro function (unless you want to enforce a specific type and for some reason do not want to use a separate `type` keyword for that). In the same way as standard keywords work, if the keyword does not apply to the data type being validated, the validation of this keyword will succeed. +::: tip Please note +If the keyword is validating data type that is different from the type(s) in its definition, the validation function will not be called (and expanded macro will not be used), so there is no need to check for data type inside validation function or inside schema returned by macro function (unless you want to enforce a specific type and for some reason do not want to use a separate `type` keyword for that). In the same way as standard keywords work, if the keyword does not apply to the data type being validated, the validation of this keyword will succeed. +::: See [User defined keywords](./keywords.md) for more details. -#### ajv.getKeyword(keyword: string): object | boolean +### ajv.getKeyword(keyword: string): object | boolean Returns keyword definition, `false` if the keyword is unknown. -#### ajv.removeKeyword(keyword: string): Ajv +### ajv.removeKeyword(keyword: string): Ajv Removes added or pre-defined keyword so you can redefine them. While this method can be used to extend pre-defined keywords, it can also be used to completely change their meaning - it may lead to unexpected results. -**Please note**: schemas compiled before the keyword is removed will continue to work without changes. To recompile schemas use `removeSchema` method and compile them again. +::: warning Please note +The schemas compiled before the keyword is removed will continue to work without changes. To recompile schemas use `removeSchema` method and compile them again. +::: -#### ajv.errorsText(errors?: object[], options?: object): string +### ajv.errorsText(errors?: object[], options?: object): string Returns the text with all errors in a String. Options can have properties `separator` (string used to separate errors, ", " by default) and `dataVar` (the variable name that dataPaths are prefixed with, "data" by default). -## Options - -Option defaults: - -```javascript -// see types/index.ts for actual types -const defaultOptions = { - // strict mode options (NEW) - strict: true, - strictTypes: "log", // * - strictTuples: "log", // * - strictRequired: false, // * - allowUnionTypes: false, // * - allowMatchingProperties: false, // * - validateFormats: true, // * - // validation and reporting options: - $data: false, // * - allErrors: false, - verbose: false, // * - $comment: false, // * - formats: {}, - keywords: {}, - schemas: {}, - logger: undefined, - loadSchema: undefined, // *, function(uri: string): Promise {} - // options to modify validated data: - removeAdditional: false, - useDefaults: false, // * - coerceTypes: false, // * - // advanced options: - meta: true, - validateSchema: true, - addUsedSchema: true, - inlineRefs: true, - passContext: false, - loopRequired: Infinity, // * - loopEnum: Infinity, // NEW - ownProperties: false, - multipleOfPrecision: undefined, // * - messages: true, // false with JTD - ajvErrors: false // only with JTD - code: { - // NEW - es5: false, - lines: false, - source: false, - process: undefined, // (code: string) => string - optimize: true, - }, -} -``` - -\* these options are not supported with JSON Type Definition schemas - -#### Strict mode options (NEW in v7) - -- _strict_: By default Ajv executes in strict mode, that is designed to prevent any unexpected behaviours or silently ignored mistakes in schemas (see [Strict Mode](./strict-mode.md) for more details). It does not change any validation results, but it makes some schemas invalid that would be otherwise valid according to JSON Schema specification. Option values: - - `true` (default) - use strict mode and throw an exception when any strict mode restriction is violated. - - `"log"` - log warning when any strict mode restriction is violated. - - `false` - ignore all strict mode restrictions. Also ignores `strictTypes` restrictions unless it is explicitly passed. -- _strictTypes_: By default Ajv logs warning when "type" keyword is used in a way that may be incorrect or confusing to other people - see [Strict types](./strict-mode.md#strict-types) for more details. This option does not change validation results. Option values: - - `true` - throw exception when any strictTypes restriction is violated. - - `"log"` (default, unless option strict is `false`) - log warning when any strictTypes restriction is violated. - - `false` - ignore all strictTypes restrictions violations. -- _strictTuples_: By default Ajv logs warning when "items" is array and "minItems" and "maxItems"/"additionalItems" not present or different from the number of items. See [Strict mode](./strict-mode.md) for more details. This option does not change validation results. Option values: - - `true` - throw exception. - - `"log"` (default, unless option strict is `false`) - log warning. - - `false` - ignore strictTuples restriction violations. -- _strictRequired_: Ajv can log warning or throw exception when the property used in "required" keyword is not defined in "properties" keyword. See [Strict mode](./strict-mode.md) for more details. This option does not change validation results. Option values: - - `true` - throw exception. - - `"log"` - log warning. - - `false` (default) - ignore strictRequired restriction violations. -- _allowUnionTypes_: pass true to allow using multiple non-null types in "type" keyword (one of `strictTypes` restrictions). see [Strict types](./strict-mode.md#strict-types) -- _allowMatchingProperties_: pass true to allow overlap between "properties" and "patternProperties". Does not affect other strict mode restrictions. See [Strict Mode](./strict-mode.md). -- _validateFormats_: format validation. Option values: - - `true` (default) - validate formats (see [Formats](./validation.md#formats)). In [strict mode](./strict-mode.md) unknown formats will throw exception during schema compilation (and fail validation in case format keyword value is [\$data reference](./validation.md#data-reference)). - - `false` - do not validate any format keywords (TODO they will still collect annotations once supported). - -#### Validation and reporting options - -- _\$data_: support [\$data references](./validation.md#data-reference). Draft 6 meta-schema that is added by default will be extended to allow them. If you want to use another meta-schema you need to use $dataMetaSchema method to add support for $data reference. See [API](#ajv-constructor-and-methods). -- _allErrors_: check all rules collecting all errors. Default is to return after the first error. -- _verbose_: include the reference to the part of the schema (`schema` and `parentSchema`) and validated data in errors (false by default). -- _\$comment_: log or pass the value of `$comment` keyword to a function. Option values: - - `false` (default): ignore \$comment keyword. - - `true`: log the keyword value to console. - - function: pass the keyword value, its schema path and root schema to the specified function -- _formats_: an object with format definitions. Keys and values will be passed to `addFormat` method. Pass `true` as format definition to ignore some formats. -- _keywords_: an array of keyword definitions or strings. Values will be passed to `addKeyword` method. -- _schemas_: an array or object of schemas that will be added to the instance. In case you pass the array the schemas must have IDs in them. When the object is passed the method `addSchema(value, key)` will be called for each schema in this object. -- _logger_: sets the logging method. Default is the global `console` object that should have methods `log`, `warn` and `error`. See [Error logging](#error-logging). Option values: - - logger instance - it should have methods `log`, `warn` and `error`. If any of these methods is missing an exception will be thrown. - - `false` - logging is disabled. -- _loadSchema_: asynchronous function that will be used to load remote schemas when `compileAsync` [method](#api-compileAsync) is used and some reference is missing (option `missingRefs` should NOT be 'fail' or 'ignore'). This function should accept remote schema uri as a parameter and return a Promise that resolves to a schema. See example in [Asynchronous compilation](./validation.md#asynchronous-schema-compilation). - -#### Options to modify validated data - -- _removeAdditional_: remove additional properties - see example in [Removing additional properties](./validation.md#removing-additional-properties). This option is not used if schema is added with `addMetaSchema` method. Option values: - - `false` (default) - not to remove additional properties - - `"all"` - all additional properties are removed, regardless of `additionalProperties` keyword in schema (and no validation is made for them). - - `true` - only additional properties with `additionalProperties` keyword equal to `false` are removed. - - `"failing"` - additional properties that fail schema validation will be removed (where `additionalProperties` keyword is `false` or schema). -- _useDefaults_: replace missing or undefined properties and items with the values from corresponding `default` keywords. Default behaviour is to ignore `default` keywords. This option is not used if schema is added with `addMetaSchema` method. See examples in [Assigning defaults](./validation.md#assigning-defaults). Option values: - - `false` (default) - do not use defaults - - `true` - insert defaults by value (object literal is used). - - `"empty"` - in addition to missing or undefined, use defaults for properties and items that are equal to `null` or `""` (an empty string). -- _coerceTypes_: change data type of data to match `type` keyword. See the example in [Coercing data types](./validation.md#coercing-data-types) and [coercion rules](./coercion.md). Option values: - - `false` (default) - no type coercion. - - `true` - coerce scalar data types. - - `"array"` - in addition to coercions between scalar types, coerce scalar data to an array with one element and vice versa (as required by the schema). - -#### Advanced options - -- _meta_: add [meta-schema](http://json-schema.org/documentation.html) so it can be used by other schemas (true by default). If an object is passed, it will be used as the default meta-schema for schemas that have no `$schema` keyword. This default meta-schema MUST have `$schema` keyword. -- _validateSchema_: validate added/compiled schemas against meta-schema (true by default). `$schema` property in the schema can be http://json-schema.org/draft-07/schema or absent (draft-07 meta-schema will be used) or can be a reference to the schema previously added with `addMetaSchema` method. Option values: - - `true` (default) - if the validation fails, throw the exception. - - `"log"` - if the validation fails, log error. - - `false` - skip schema validation. -- _addUsedSchema_: by default methods `compile` and `validate` add schemas to the instance if they have `$id` (or `id`) property that doesn't start with "#". If `$id` is present and it is not unique the exception will be thrown. Set this option to `false` to skip adding schemas to the instance and the `$id` uniqueness check when these methods are used. This option does not affect `addSchema` method. -- _inlineRefs_: Affects compilation of referenced schemas. Option values: - - `true` (default) - the referenced schemas that don't have refs in them are inlined, regardless of their size - it improves performance. - - `false` - to not inline referenced schemas (they will always be compiled as separate functions). - - integer number - to limit the maximum number of keywords of the schema that will be inlined (to balance the total size of compiled functions and performance). -- _passContext_: pass validation context to _compile_ and _validate_ keyword functions. If this option is `true` and you pass some context to the compiled validation function with `validate.call(context, data)`, the `context` will be available as `this` in your keywords. By default `this` is Ajv instance. -- _loopRequired_: by default `required` keyword is compiled into a single expression (or a sequence of statements in `allErrors` mode). In case of a very large number of properties in this keyword it may result in a very big validation function. Pass integer to set the number of properties above which `required` keyword will be validated in a loop - smaller validation function size but also worse performance. -- _loopEnum_ (NEW in v7): by default `enum` keyword is compiled into a single expression. In case of a very large number of allowed values it may result in a large validation function. Pass integer to set the number of values above which `enum` keyword will be validated in a loop. -- _ownProperties_: by default Ajv iterates over all enumerable object properties; when this option is `true` only own enumerable object properties (i.e. found directly on the object rather than on its prototype) are iterated. Contributed by @mbroadst. -- _multipleOfPrecision_: by default `multipleOf` keyword is validated by comparing the result of division with parseInt() of that result. It works for dividers that are bigger than 1. For small dividers such as 0.01 the result of the division is usually not integer (even when it should be integer, see issue [#84](https://github.com/ajv-validator/ajv/issues/84)). If you need to use fractional dividers set this option to some positive integer N to have `multipleOf` validated using this formula: `Math.abs(Math.round(division) - division) < 1e-N` (it is slower but allows for float arithmetic deviations). -- _messages_: Include human-readable messages in errors. `true` by default. `false` can be passed when messages are generated outside of Ajv code (e.g. with [ajv-i18n](https://github.com/ajv-validator/ajv-i18n)). -- _ajvErrors_: this option is only supported with JTD schemas to generate error objects with the properties described in the first part of [Validation errors](#validation-errors) section, otherwise JTD errors are generated when JTD schemas are used (see the second part of [the same section](#validation-errors)). -- _code_ (new in v7): code generation options: - -```typescript -type CodeOptions = { - es5?: boolean // to generate es5 code - by default code is es6, with "for-of" loops, "let" and "const" - lines?: boolean // add line-breaks to code - to simplify debugging of generated functions - source?: boolean // add `source` property (see Source below) to validating function. - process?: (code: string, schema?: SchemaEnv) => string // an optional function to process generated code - // before it is passed to Function constructor. - // It can be used to either beautify or to transpile code. - optimize?: boolean | number // code optimization flag or number of passes, 1 pass by default, - // code optimizations reduce the size of the generated code (bytes, based on the tests) by over 10%, - // the number of code tree nodes by nearly 17%. - // You would almost never need more than one optimization pass, unless you have some really complex schemas - - // the second pass in the tests (it has quite complex schemas) only improves optimization by less than 0.1%. - // See [Code optimization](./codegen.md#code-optimization) for details. -} - -type Source = { - code: string // unlike func.toString() it includes assignments external to function scope - scope: Scope // see Code generation (TODO) -} -``` - ## Validation errors -In case of validation failure, Ajv assigns the array of errors to `errors` property of validation function (or to `errors` property of Ajv instance when `validate` or `validateSchema` methods were called). In case of [asynchronous validation](./validation.md#asynchronous-validation), the returned promise is rejected with exception `Ajv.ValidationError` that has `errors` property. +In case of validation failure, Ajv assigns the array of errors to `errors` property of validation function (or to `errors` property of Ajv instance when `validate` or `validateSchema` methods were called). In case of [asynchronous validation](./guide/async-validation.md), the returned promise is rejected with exception `Ajv.ValidationError` that has `errors` property. ### Error objects @@ -496,7 +367,9 @@ interface JTDErrorObject { This error format is used when using JTD schemas. To simplify usage, you may still generate Ajv error objects using `ajvErrors` option. You can also add a human-readable error message to error objects using option `messages`. -**Please note**: Ajv is not fully consistent with JTD regarding the error objects in some scenarios - it will be consistent by the time Ajv version 8 is released. Therefore it is not recommended yet to use error objects for any advanced application logic. +::: warning Please note +Ajv is not fully consistent with JTD regarding the error objects in some scenarios - it will be consistent by the time Ajv version 8 is released. Therefore it is not recommended yet to use error objects for any advanced application logic. +::: ### Error parameters @@ -624,3 +497,7 @@ const ajv = new Ajv({ }, }) ``` + +## Options + +This section is moved to [Initialization options](./options) page diff --git a/docs/codegen.md b/docs/codegen.md index 61d008fb3..164a5caf5 100644 --- a/docs/codegen.md +++ b/docs/codegen.md @@ -1,6 +1,8 @@ -# Code generation +# Code generation design -Starting from v7 Ajv uses [CodeGen module](../lib/compile/codegen/index.ts) that replaced [doT](https://github.com/olado/dot) templates used earlier. +[[toc]] + +Starting from v7 Ajv uses [CodeGen module](https://github.com/ajv-validator/ajv/blob/master/lib/compile/codegen/index.ts) that replaced [doT](https://github.com/olado/dot) templates used earlier. The motivations for this change: @@ -37,7 +39,7 @@ function log(comparison: string): void { // type Code = _Code | Name, _Code can only be constructed with template literals const msg: Code = str`${num} is ${comparison} than ${x}` // msg is _Code instance, so it will be inserted via another template without quotes - gen.code(_`console log(${msg})`) + gen.code(_`console.log(${msg})`) } ``` @@ -52,7 +54,7 @@ if (num0 > 0) { } ``` -`.const`, `.if` and `.code` above are methods of CodeGen class that generate code inside class instance `gen` - see [source code](../lib/compile/codegen/index.ts) for all available methods and [tests](../spec/codegen.spec.ts) for other code generation examples. +`.const`, `.if` and `.code` above are methods of CodeGen class that generate code inside class instance `gen` - see [source code](https://github.com/ajv-validator/ajv/blob/master/lib/compile/codegen/index.ts) for all available methods and [tests](../spec/codegen.spec.ts) for other code generation examples. These methods only accept instances of private class `_Code`, other values will be rejected by Typescript compiler - the risk to pass unsafe string is mitigated on type level. @@ -66,7 +68,9 @@ CodeGen class generates code trees and performs several optimizations before the 2. removes unused variable declarations. 3. replaces variables that are used only once and assigned expressions that are explicitly marked as "constant" (i.e. having referential transparency) with the expressions themselves. -**Please note**: These optimizations assume that the expressions in `if` conditions, `for` statement headers and assigned expressions are free of any side effects - this is the case for all pre-defined validation keywords. +::: warning Please note +These optimizations assume that the expressions in `if` conditions, `for` statement headers and assigned expressions are free of any side effects - this is the case for all pre-defined validation keywords. +::: See [these tests](../spec/codegen.spec.ts) for examples. @@ -83,4 +87,6 @@ While tagged template literals wrap passed strings based on their run-time value It is strongly recommended to define additional keywords only with Typescript - using plain JavaScript would still allow passing unsafe strings to code generation methods. -**Please note**: If your user-defined keywords need to have side-effects that are removed by optimization (see above), you may need to disable it. +::: warning Please note +If your user-defined keywords need to have side-effects that are removed by optimization (see above), you may need to disable it. +::: diff --git a/docs/coercion.md b/docs/coercion.md index 288577ece..e4a0f595c 100644 --- a/docs/coercion.md +++ b/docs/coercion.md @@ -1,6 +1,6 @@ -# Ajv type coercion rules +# Type coercion rules -To enable type coercion pass option `coerceTypes` to Ajv with `true` or `array` (it is `false` by default). See [example](./validation.md#coercing-data-types). +To enable type coercion pass option `coerceTypes` to Ajv with `true` or `array` (it is `false` by default). See [example](./guide/modifying-data.md#coercing-data-types). The coercion rules are different from JavaScript: diff --git a/docs/components.md b/docs/components.md index 482606c25..b3bc0c8c5 100644 --- a/docs/components.md +++ b/docs/components.md @@ -1,33 +1,37 @@ # Code components +[[toc]] + ## Ajv classes -[lib/core.ts](../lib/core.ts) - core Ajv class without any keywords. All Ajv methods for managing schemas and extensions are defined in this class. +[lib/core.ts](https://github.com/ajv-validator/ajv/blob/master/lib/core.ts) - core Ajv class without any keywords. All Ajv methods for managing schemas and extensions are defined in this class. + +[lib/ajv.ts](https://github.com/ajv-validator/ajv/blob/master/lib/ajv.ts) - subclass of Ajv core with JSON Schema draft-07 keywords. -[lib/ajv.ts](../lib/ajv.ts) - subclass of Ajv core with JSON Schema draft-07 keywords. +[lib/2019.ts](https://github.com/ajv-validator/ajv/blob/master/lib/2019.ts) - subclass of Ajv core with JSON Schema draft-2019-09 keywords. -[lib/2019.ts](../lib/2019.ts) - subclass of Ajv core with JSON Schema draft-2019-09 keywords. +[lib/jtd.ts](https://github.com/ajv-validator/ajv/blob/master/lib/jtd.ts) - subclass of Ajv core with JSON Type Definition support. ## Schema compilation -[lib/compile](../lib/compile) - code for schema compilation +[lib/compile](https://github.com/ajv-validator/ajv/blob/master/lib/compile) - code for schema compilation -[lib/compile/index.ts](../lib/compile/index.ts) - the main recursive function code for schema compilation, functions for reference resolution, the interface for schema compilation context (`SchemaCxt`). +[lib/compile/index.ts](https://github.com/ajv-validator/ajv/blob/master/lib/compile/index.ts) - the main recursive function code for schema compilation, functions for reference resolution, the interface for schema compilation context (`SchemaCxt`). -[lib/compile/context.ts](../lib/compile/context.ts) - the class for keyword code generation `KeywordCxt`. All pre-defined keywords and user-defined keywords that use `code` function are passed an instance of this class. +[lib/compile/context.ts](https://github.com/ajv-validator/ajv/blob/master/lib/compile/context.ts) - the class for keyword code generation `KeywordCxt`. All pre-defined keywords and user-defined keywords that use `code` function are passed an instance of this class. -[lib/compile/rules.ts](../lib/compile/rules.ts) - data structure to store references to all all keyword definitions that were added to Ajv instance, organised by data type. +[lib/compile/rules.ts](https://github.com/ajv-validator/ajv/blob/master/lib/compile/rules.ts) - data structure to store references to all all keyword definitions that were added to Ajv instance, organised by data type. -[lib/compile/subschema.ts](../lib/compile/subschema.ts) - creates schema context (`SchemaCxt`) to generate code for subschemas - used by all applicator keywords in [lib/vocabularies/applicator](../lib/vocabularies/applicator). +[lib/compile/subschema.ts](https://github.com/ajv-validator/ajv/blob/master/lib/compile/subschema.ts) - creates schema context (`SchemaCxt`) to generate code for subschemas - used by all applicator keywords in [lib/vocabularies/applicator](https://github.com/ajv-validator/ajv/blob/master/lib/vocabularies/applicator). -[lib/compile/codegen](../lib/compile/codegen) - the api for [code generation](./codegen.md). +[lib/compile/codegen](https://github.com/ajv-validator/ajv/blob/master/lib/compile/codegen) - the api for [code generation](./codegen.md). -[lib/compile/validate](../lib/compile/validate) - code to iterate the schema to generate code of validation function. +[lib/compile/validate](https://github.com/ajv-validator/ajv/blob/master/lib/compile/validate) - code to iterate the schema to generate code of validation function. ## Other components -[lib/standalone](../lib/standalone) - module to generate [standalone validation code](./standalone.md). +[lib/standalone](https://github.com/ajv-validator/ajv/blob/master/lib/standalone) - module to generate [standalone validation code](./standalone.md). -[lib/vocabularies](../lib/vocabularies) - pre-defined validation keywords. +[lib/vocabularies](https://github.com/ajv-validator/ajv/blob/master/lib/vocabularies) - pre-defined validation keywords. -[lib/refs](../lib/refs) - JSON Schema meta-schemas. +[lib/refs](https://github.com/ajv-validator/ajv/blob/master/lib/refs) - JSON Schema meta-schemas. diff --git a/docs/faq.md b/docs/faq.md index 25e2213fe..b8535923e 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -2,12 +2,14 @@ The purpose of this document is to help find answers quicker. I am happy to continue the discussion about these issues, so please comment on some of the issues mentioned below or create a new issue if it seems more appropriate. +[[toc]] + ## Using JSON schema Ajv implements JSON schema specification. Before submitting the issue about the behaviour of any validation keywords please review them in: - [JSON Schema specification](https://tools.ietf.org/html/draft-handrews-json-schema-validation-00) (draft-07) -- [Validation keywords](./json-schema.md) in Ajv documentation +- [JSON Schema reference](./json-schema.md) in Ajv documentation - [JSON Schema tutorial](https://spacetelescope.github.io/understanding-json-schema/) (for draft-04) #### Why Ajv validates empty object as valid? @@ -76,11 +78,11 @@ This problem is related to the problem explained above - properties treated as a See the example in [Filtering Data](https://github.com/ajv-validator/ajv#filtering-data) section of readme. -## Generating schemas with resolved references (\$ref) +## Generating schemas with resolved references ($ref) See [#22](https://github.com/ajv-validator/ajv/issues/22), [#125](https://github.com/ajv-validator/ajv/issues/125), [#146](https://github.com/ajv-validator/ajv/issues/146), [#228](https://github.com/ajv-validator/ajv/issues/228), [#336](https://github.com/ajv-validator/ajv/issues/336), [#454](https://github.com/ajv-validator/ajv/issues/454). -#### Why Ajv does not replace references (\$ref) with the actual referenced schemas as some validators do? +#### Why Ajv does not replace references ($ref) with the actual referenced schemas as some validators do? 1. The scope of Ajv is validating data against JSON Schemas; inlining referenced schemas is not necessary for validation. When Ajv generates code for validation it either inlines the code of referenced schema or uses function calls. Doing schema manipulation is more complex and out of scope. 2. When schemas are recursive (or mutually recursive) resolving references would result in self-referencing recursive data-structures that can be difficult to process. diff --git a/docs/guide/async-validation.md b/docs/guide/async-validation.md new file mode 100644 index 000000000..d5f432ecd --- /dev/null +++ b/docs/guide/async-validation.md @@ -0,0 +1,72 @@ +# Asynchronous validation + +You can define formats and keywords that perform validation asynchronously by accessing database or some other service. You should add `async: true` in the keyword or format definition (see [addFormat](./api.md#api-addformat), [addKeyword](./api.md#api-addkeyword) and [User-defined keywords](./keywords.md)). + +If your schema uses asynchronous formats/keywords or refers to some schema that contains them it should have `"$async": true` keyword so that Ajv can compile it correctly. If asynchronous format/keyword or reference to asynchronous schema is used in the schema without `$async` keyword Ajv will throw an exception during schema compilation. + +::: warning Please note +All asynchronous subschemas that are referenced from the current or other schemas should have `"$async": true` keyword as well, otherwise the schema compilation will fail. +::: + +Validation function for an asynchronous format/keyword should return a promise that resolves with `true` or `false` (or rejects with `new Ajv.ValidationError(errors)` if you want to return errors from the keyword function). + +Ajv compiles asynchronous schemas to [async functions](http://tc39.github.io/ecmascript-asyncawait/). Async functions are supported in Node.js 7+ and all modern browsers. You can supply a transpiler as a function via `processCode` option. See [Options](./api.md#options). + +The compiled validation function has `$async: true` property (if the schema is asynchronous), so you can differentiate these functions if you are using both synchronous and asynchronous schemas. + +Validation result will be a promise that resolves with validated data or rejects with an exception `Ajv.ValidationError` that contains the array of validation errors in `errors` property. + +Example: + +```javascript +const ajv = new Ajv() + +ajv.addKeyword({ + keyword: "idExists" + async: true, + type: "number", + validate: checkIdExists, +}) + +async function checkIdExists(schema, data) { + // this is just an example, you would want to avoid SQL injection in your code + const rows = await sql(`SELECT id FROM ${schema.table} WHERE id = ${data}`) + return !!rows.length // true if record is found +} + +const schema = { + $async: true, + properties: { + userId: { + type: "integer", + idExists: {table: "users"}, + }, + postId: { + type: "integer", + idExists: {table: "posts"}, + }, + }, +} + +const validate = ajv.compile(schema) + +validate({userId: 1, postId: 19}) + .then(function (data) { + console.log("Data is valid", data) // { userId: 1, postId: 19 } + }) + .catch(function (err) { + if (!(err instanceof Ajv.ValidationError)) throw err + // data is invalid + console.log("Validation errors:", err.errors) + }) +``` + +### Using transpilers + +```javascript +const ajv = new Ajv({processCode: transpileFunc}) +const validate = ajv.compile(schema) // transpiled es7 async function +validate(data).then(successFunc).catch(errorFunc) +``` + +See [Options](../options). diff --git a/docs/guide/combining-schemas.md b/docs/guide/combining-schemas.md new file mode 100644 index 000000000..dc2995880 --- /dev/null +++ b/docs/guide/combining-schemas.md @@ -0,0 +1,235 @@ +# Combining schemas + +[[toc]] + +## Combining schemas with $ref + +You can structure your validation logic across multiple schema files and have schemas reference each other using `$ref` keyword. + +Example: + +```javascript +const schema = { + $id: "http://example.com/schemas/schema.json", + type: "object", + properties: { + foo: {$ref: "defs.json#/definitions/int"}, + bar: {$ref: "defs.json#/definitions/str"}, + }, +} + +const defsSchema = { + $id: "http://example.com/schemas/defs.json", + definitions: { + int: {type: "integer"}, + str: {type: "string"}, + }, +} +``` + +Now to compile your schema you can either pass all schemas to Ajv instance: + +```javascript +const ajv = new Ajv({schemas: [schema, defsSchema]}) +const validate = ajv.getSchema("http://example.com/schemas/schema.json") +``` + +or use `addSchema` method: + +```javascript +const ajv = new Ajv() +const validate = ajv.addSchema(defsSchema).compile(schema) +``` + +See [Options](./api.md#options) and [addSchema](./api.md#add-schema) method. + +::: tip Please note + +- `$ref` is resolved as the uri-reference using schema \$id as the base URI (see the example). +- References can be recursive (and mutually recursive) to implement the schemas for different data structures (such as linked lists, trees, graphs, etc.). +- You don't have to host your schema files at the URIs that you use as schema \$id. These URIs are only used to identify the schemas, and according to JSON Schema specification validators should not expect to be able to download the schemas from these URIs. +- The actual location of the schema file in the file system is not used. +- You can pass the identifier of the schema as the second parameter of `addSchema` method or as a property name in `schemas` option. This identifier can be used instead of (or in addition to) schema \$id. +- You cannot have the same \$id (or the schema identifier) used for more than one schema - the exception will be thrown. +- You can implement dynamic resolution of the referenced schemas using `compileAsync` method. In this way you can store schemas in any system (files, web, database, etc.) and reference them without explicitly adding to Ajv instance. See [Asynchronous schema compilation](./managing-schemas.md#asynchronous-schema-compilation). + ::: + +## Extending recursive schemas + +While statically defined `$ref` keyword allows to split schemas to multiple files, it is difficult to extend recursive schemas - the recursive reference(s) in the original schema points to the original schema, and not to the extended one. So in JSON Schema draft-07 the only available solution to extend the recursive schema was to redefine all sections of the original schema that have recursion. + +It was particularly repetitive when extending meta-schema, as it has many recursive references, but even in a schema with a single recursive reference extending it was very verbose. + +JSON Schema draft-2019-09 and the upcoming draft defined the mechanism for dynamic recursion using keywords `$recursiveRef`/`$recursiveAnchor` (draft-2019-09) or `$dynamicRef`/`$dynamicAnchor` (the next JSON Schema draft) that is somewhat similar to "open recursion" in functional programming. + +Consider this recursive schema with static recursion: + +```javascript +const treeSchema = { + $id: "https://example.com/tree", + type: "object", + required: ["data"], + properties: { + data: true, + children: { + type: "array", + items: {$ref: "#"}, + }, + }, +} +``` + +The only way to extend this schema to prohibit additional properties is by adding `additionalProperties` keyword right in the schema - this approach can be impossible if you do not control the source of the original schema. Ajv also provided the additional keywords in [ajv-merge-patch](https://github.com/ajv-validator/ajv-merge-patch) package to extend schemas by treating them as plain JSON data. While this approach may work for you, it is non-standard and therefore not portable. + +The new keywords for dynamic recursive references allow extending this schema without modifying it: + +```javascript +const treeSchema = { + $id: "https://example.com/tree", + $recursiveAnchor: true, + type: "object", + required: ["data"], + properties: { + data: true, + children: { + type: "array", + items: {$recursiveRef: "#"}, + }, + }, +} + +const strictTreeSchema = { + $id: "https://example.com/strict-tree", + $recursiveAnchor: true, + $ref: "tree", + unevaluatedProperties: false, +} + +import Ajv2019 from "ajv/dist/2019" +// const Ajv2019 = require("ajv/dist/2019").default +const ajv = new Ajv2019({ + schemas: [treeSchema, strictTreeSchema], +}) +const validate = ajv.getSchema("https://example.com/strict-tree") +``` + +See [dynamic-refs](../spec/dynamic-ref.spec.ts) test for the example using `$dynamicAnchor`/`$dynamicRef`. + +At the moment Ajv implements the spec for dynamic recursive references with these limitations: + +- `$recursiveAnchor`/`$dynamicAnchor` can only be used in the schema root. +- `$recursiveRef`/`$dynamicRef` can only be hash fragments, without URI. + +Ajv also does not support dynamic references in [asynchronous schemas](#asynchronous-validation) (Ajv extension) - it is assumed that the referenced schema is synchronous, and there is no validation-time check for it. + +## $data reference + +With `$data` option you can use values from the validated data as the values for the schema keywords. See [proposal](https://github.com/json-schema-org/json-schema-spec/issues/51) for more information about how it works. + +`$data` reference is supported in the keywords: const, enum, format, maximum/minimum, exclusiveMaximum / exclusiveMinimum, maxLength / minLength, maxItems / minItems, maxProperties / minProperties, formatMaximum / formatMinimum, formatExclusiveMaximum / formatExclusiveMinimum, multipleOf, pattern, required, uniqueItems. + +The value of "$data" should be a [JSON-pointer](https://datatracker.ietf.org/doc/rfc6901/) to the data (the root is always the top level data object, even if the $data reference is inside a referenced subschema) or a [relative JSON-pointer](http://tools.ietf.org/html/draft-luff-relative-json-pointer-00) (it is relative to the current point in data; if the \$data reference is inside a referenced subschema it cannot point to the data outside of the root level for this subschema). + +Examples. + +This schema requires that the value in property `smaller` is less or equal than the value in the property larger: + +```javascript +const ajv = new Ajv({$data: true}) + +const schema = { + properties: { + smaller: { + type: "number", + maximum: {$data: "1/larger"}, + }, + larger: {type: "number"}, + }, +} + +const validData = { + smaller: 5, + larger: 7, +} + +ajv.validate(schema, validData) // true +``` + +This schema requires that the properties have the same format as their field names: + +```javascript +const schema = { + additionalProperties: { + type: "string", + format: {$data: "0#"}, + }, +} + +const validData = { + "date-time": "1963-06-19T08:30:06.283185Z", + email: "joe.bloggs@example.com", +} +``` + +`$data` reference is resolved safely - it won't throw even if some property is undefined. If `$data` resolves to `undefined` the validation succeeds (with the exclusion of `const` keyword). If `$data` resolves to incorrect type (e.g. not "number" for maximum keyword) the validation fails. + +## $merge and $patch keywords + +With the package [ajv-merge-patch](https://github.com/ajv-validator/ajv-merge-patch) you can use the keywords `$merge` and `$patch` that allow extending JSON Schemas with patches using formats [JSON Merge Patch (RFC 7396)](https://datatracker.ietf.org/doc/rfc7396/) and [JSON Patch (RFC 6902)](https://datatracker.ietf.org/doc/rfc6902/). + +To add keywords `$merge` and `$patch` to Ajv instance use this code: + +```javascript +require("ajv-merge-patch")(ajv) +``` + +Examples. + +Using `$merge`: + +```javascript +{ + $merge: { + source: { + type: "object", + properties: {p: {type: "string"}}, + additionalProperties: false + }, + with: { + properties: {q: {type: "number"}} + } + } +} +``` + +Using `$patch`: + +```javascript +{ + $patch: { + source: { + type: "object", + properties: {p: {type: "string"}}, + additionalProperties: false + }, + with: [{op: "add", path: "/properties/q", value: {type: "number"}}] + } +} +``` + +The schemas above are equivalent to this schema: + +```javascript +{ + type: "object", + properties: { + p: {type: "string"}, + q: {type: "number"} + }, + additionalProperties: false +} +``` + +The properties `source` and `with` in the keywords `$merge` and `$patch` can use absolute or relative `$ref` to point to other schemas previously added to the Ajv instance or to the fragments of the current schema. + +See the package [ajv-merge-patch](https://github.com/ajv-validator/ajv-merge-patch) for more information. diff --git a/docs/guide/environments.md b/docs/guide/environments.md new file mode 100644 index 000000000..1b2267e9b --- /dev/null +++ b/docs/guide/environments.md @@ -0,0 +1,120 @@ +# Execution environments + +[[toc]] + +## Server-side Node.js + +The main consideration for using Ajv server-side is to [manage compiled schemas](./managing-schemas) correctly, ensuring that the same schema is not compiled more than once. + +## Short-lived environments + +Depending on the life-time of the environments, the benefits from "compile once - validate many times" model can be limited - you can consider using [standalone validation code](../standalone). + +If you have a pre-defined set of schemas, you can: + +1. compile all schemas in the build step - you can either write your own script or use [ajv-cli](https://github.com/ajv-validator/ajv). +2. generate and beautify standalone validation code - you can have all your schemas exported from one file. +3. additionally, you can inline all dependencies on Ajv or ajv-formats using any bundling tools. +4. deploy compiled schemas as part of your application or library (with or without dependency on Ajv, depending on whether you did step 3 and which validation keywords are used in the schemas) + +Please see [gajus/table](https://github.com/gajus/table) package that pre-compiles schemas in this way. + +Even if your schemas need to be stored in the database, you can still compile schemas once and store your validation functions alongside schemas in the database as well, loading them on demand. + +## Browsers + +See [Content Security Policy](../security.md#content-security-policy) to decide how best to use Ajv in the browser for your use case. + +Whether you compile schemas in the browser or use [standalone validation code](../standalone), it is recommended that you bundle them together with your application code. + +If you need to use Ajv in several application bundles you can create a separate UMD bundles of Ajv using `npm run bundle` script. + +In this case you need to load Ajv using the correct bundle, depending on which schema language and which version you need to use: + + + +```html + + +``` + + + +```html + + +``` + + + +```html + + +``` + + + +This bundle can be used with different module systems; it creates global `ajv`/`ajv2019`/`ajvJTD` if no module system is found. + +The browser bundles are available on [cdnjs](https://cdnjs.com/libraries/ajv). + +::: warning Please note +Some frameworks, e.g. Dojo, may redefine global require in a way that is not compatible with CommonJS module format. In this case Ajv bundle has to be loaded before the framework and then you can use global `ajv` (see issue [#234](https://github.com/ajv-validator/ajv/issues/234)). +::: + +## ES5 environments + +You need to: + +- recompile Typescript to ES5 target - it is set to 2018 in the bundled compiled code. +- generate ES5 validation code: + +```javascript +const ajv = new Ajv({code: {es5: true}}) +``` + +See [Advanced options](https://github.com/ajv-validator/ajv/blob/master/docs/api.md#advanced-options). + +## Other JavaScript environments + +Ajv is used in other JavaScript environments, including Electron apps, WeChat mini-apps and many others, where the same considerations apply as above: + +- compilation performance +- restrictive content security policy +- bundle size + +If any of this is important, you may have better results with pre-compiled [standalone validation code](../standalone). + +## Command line interface + +Ajv can be used from the terminal in any operating system supported by Node.js + +CLI is available as a separate npm package [ajv-cli](https://github.com/ajv-validator/ajv-cli). + +It supports: + +- compiling JSON Schemas to test their validity +- generating [standalone validation code](./docs/standalone.md) that exports validation function(s) +- migrating schemas to draft-07 and draft-2019-09 (using [json-schema-migrate](https://github.com/epoberezkin/json-schema-migrate)) +- validating data file(s) against JSON Schema +- testing expected validity of data against JSON Schema +- referenced schemas +- user-defined meta-schemas, validation keywords and formats +- files in JSON, JSON5, YAML, and JavaScript format +- all Ajv options +- reporting changes in data after validation in [JSON-patch](https://datatracker.ietf.org/doc/rfc6902/) format diff --git a/docs/guide/formats.md b/docs/guide/formats.md new file mode 100644 index 000000000..a8c39062f --- /dev/null +++ b/docs/guide/formats.md @@ -0,0 +1,107 @@ +# Format validation + +## String formats + +From version 7 Ajv does not include formats defined by JSON Schema specification - these and several other formats are provided by [ajv-formats](https://github.com/ajv-validator/ajv-formats) plugin. + +To add all formats from this plugin: + + + +```javascript +const Ajv = require("ajv").default +const addFormats = require("ajv-formats") + +const ajv = new Ajv() +addFormats(ajv) + +```` + + + +```typescript +import Ajv from "ajv" +import addFormats from "ajv-formats" + +const ajv = new Ajv() +addFormats(ajv) +```` + + + + +See [ajv-formats](https://github.com/ajv-validator/ajv-formats) documentation for further details. + +It is recommended NOT to use "format" keyword implementations with untrusted data, as they may use potentially unsafe regular expressions (even though known issues are fixed) - see [ReDoS attack](./security.md#redos-attack). + +::: danger Please note +If you need to use "format" keyword to validate untrusted data, you MUST assess their suitability and safety for your validation scenarios. +::: + +The following formats are defined in [ajv-formats](https://github.com/ajv-validator/ajv-formats) for string validation with "format" keyword: + +- _date_: full-date according to [RFC3339](http://tools.ietf.org/html/rfc3339#section-5.6). +- _time_: time with optional time-zone. +- _date-time_: date-time from the same source (time-zone is mandatory). +- _duration_: duration from [RFC3339](https://tools.ietf.org/html/rfc3339#appendix-A) +- _uri_: full URI. +- _uri-reference_: URI reference, including full and relative URIs. +- _uri-template_: URI template according to [RFC6570](https://datatracker.ietf.org/doc/rfc6570/) +- _url_ (deprecated): [URL record](https://url.spec.whatwg.org/#concept-url). +- _email_: email address. +- _hostname_: host name according to [RFC1034](http://tools.ietf.org/html/rfc1034#section-3.5). +- _ipv4_: IP address v4. +- _ipv6_: IP address v6. +- _regex_: tests whether a string is a valid regular expression by passing it to RegExp constructor. +- _uuid_: Universally Unique Identifier according to [RFC4122](https://datatracker.ietf.org/doc/rfc4122/). +- _json-pointer_: JSON-pointer according to [RFC6901](https://datatracker.ietf.org/doc/rfc6901/). +- _relative-json-pointer_: relative JSON-pointer according to [this draft](http://tools.ietf.org/html/draft-luff-relative-json-pointer-00). + +::: warning Please note +JSON Schema draft-07 also defines formats `iri`, `iri-reference`, `idn-hostname` and `idn-email` for URLs, hostnames and emails with international characters. These formats are available in [ajv-formats-draft2019](https://github.com/luzlab/ajv-formats-draft2019) plugin. +::: + +## User-defined formats + +You can add and replace any formats using [addFormat](./api.md#api-addformat) method: + +```javascript +ajv.addFormat("identifier", /^a-z\$_[a-zA-Z$_0-9]*$/) +``` + +Ajv also allows defining the formats that would be applied to numbers only: + +```javascript +ajv.addFormat("byte", { + type: "number", + validate: (x) => x >= 0 && x <= 255 && x % 1 == 0, +}) +``` + +## Formats and standalone validation code + +If you use formats from [ajv-formats](https://github.com/ajv-validator/ajv-formats) package, [standalone validation code](../standalone) will be supported out of the box. + +::: warning Please note +You need to make sure that ajv-formats imports the same version and the same code of ajv as the one you use in your application for standalone validation code to work (because of `instanceof` check that is currently used). + +`npm` and other package managers may not update the version of ajv dependency of ajv-formats when you update version of ajv in your application - the workaround is to use clean npm installation. +::: + +If you define your own formats, for standalone code generation to work you need to pass the code snippet that evaluates to an object with all defined formats to the option `code.formats`: + + + +```javascript +const {default: Ajv, _} = require("ajv") +const ajv = new Ajv({code: {formats: _`require("./my_formats")`}}) +``` + + + +```typescript +import Ajv, {_} from "ajv" +const ajv = new Ajv({code: {formats: _`require("./my_formats")`}}) +``` + + diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md new file mode 100644 index 000000000..dc64a4193 --- /dev/null +++ b/docs/guide/getting-started.md @@ -0,0 +1,168 @@ +# Getting started + +[[toc]] + +## Install + +::: tip Node REPL +You can try Ajv without installing it in the Node.js REPL: [https://runkit.com/npm/ajv](https://runkit.com/npm/ajv) +::: + +To install Ajv version 7: + +```bash +npm install ajv +``` + +If you need to use Ajv with [JSON Schema draft-04](./schema-language#draft-04), you need to install Ajv version 6: + +```bash +npm install ajv@6 +``` + +See [Contributing](../CONTRIBUTING.md) on how to run the tests locally + +## Basic data validation + +Ajv takes a schema for your JSON data and converts it into a very efficient JavaScript code +that validates your data according to the schema. To create schema you can use either +[JSON Schema](../json-schema) or [JSON Type Definition](../json-type-definition) - check out [Choosing schema language](./schema-language), they have +different advantages and disadvantages. + +For example, to validate an object that has a required property "foo" (an integer number), an optional property "bar" (a string) and no other properties: + + + +```javascript +const Ajv = require("ajv").default +const ajv = new Ajv() // options can be passed, e.g. {allErrors: true} + +const schema = { +type: "object", +properties: { +foo: {type: "integer"}, +bar: {type: "string"} +}, +required: ["foo"], +additionalProperties: false +} + +const validate = ajv.compile(schema) + +const validData = { +foo: 1, +bar: "abc" +} + +const valid = validate(data) +if (!valid) console.log(validate.errors) + +```` + + + +```javascript +const Ajv = require("ajv/dist/jtd").default +const ajv = new Ajv() // options can be passed, e.g. {allErrors: true} + +const schema = { + properties: { + foo: {type: "int32"} + }, + optionalProperties: { + bar: {type: "string"} + } +} + + +const validate = ajv.compile(schema) + +const validData = { + foo: 1, + bar: "abc" +} + +const valid = validate(data) +if (!valid) console.log(validate.errors) +```` + + + + +Ajv compiles schemas to functions and caches them in all cases (using schema itself as a key for Map), so that the next time the same schema object is used it won't be compiled again. + +::: tip Please note +The best performance is achieved when using compiled functions returned by `compile` or `getSchema` methods. + +While execution of the compiled validation function is very fast, its compilation is +relatively slow, so you need to make sure that you compile schemas only once and +re-use compiled validation functions. See [Managing multiple schemas](./managing-schemas). +::: + +::: warning Please note +Every time a validation function (or `ajv.validate`) is called `errors` property is overwritten. You need to copy `errors` array reference to another variable if you want to use it later (e.g., in the callback). See [Validation errors](../api.md#validation-errors) +::: + +## Parsing and serializing JSON + +Ajv can compile efficient parsers and serializers from [JSON Type Definition](../json-type-definition) schemas. + +Serializing the data with a function specialized to your data shape can be more than 10x compared with `JSON.stringify`. + +Parsing the data replaces the need for a separate validation after generic parsing with `JSON.parse` (although validation itself is usually much faster than parsing). In case your JSON string is valid specialized parsing is as approximately fast as JSON.parse, but in case your JSON is invalid, specialized parsing would fail much faster - so it can be very efficient in some scenarios. + +For the same data structure, you can compile parser and serializer in this way: + + + +```javascript +const Ajv = require("ajv/dist/jtd").default +const ajv = new Ajv() // options can be passed, e.g. {allErrors: true} + +const schema = { +properties: { +foo: {type: "int32"} +}, +optionalProperties: { +bar: {type: "string"} +} +} + +const serialize = ajv.compileSerializer(schema) +console.log(serialize(data)) + +const parse = ajv.compileParser(schema) + +const data = { +foo: 1, +bar: "abc" +} + +const json = '{"foo": 1, "bar": "abc"}' +const invalidJson = '{"unknown": "abc"}' + +console.log(parseAndLog(json)) // logs {foo: 1, bar: "abc"} +console.log(parseAndLog(invalidJson)) // logs error and position + +function parseAndLog(json) { +const data = parse(json) +if (data === undefined) { +console.log(parse.message) // error message from the last parse call +console.log(parse.position) // error position in string +} else { +console.log(data) +} +} + +``` + + + +::: tip Please note +You would have smaller performance benefits in case your schema contains some properties or other parts that are empty schemas (`{}`) - parser would call `JSON.parse` in this case. +::: + +::: warning Please note +Compiled parsers, unlike JSON.parse, do not throw the exception in case JSON string is not a valid JSON or in case data is invalid according to the schema. As soon as the parser determines that either JSON or data is invalid, it returns `undefined` and reports error and position via parsers properties `message` and `position`. +::: +``` diff --git a/docs/guide/managing-schemas.md b/docs/guide/managing-schemas.md new file mode 100644 index 000000000..b442e1cc2 --- /dev/null +++ b/docs/guide/managing-schemas.md @@ -0,0 +1,257 @@ +# Managing schemas + +[[toc]] + +## Re-using validation functions + +Ajv validation model is optimized for server side execution, when schema compilation happens only once and validation happens multiple times - this has a substantial performance benefit comparing with validators that interpret the schema in the process of validation. + +Transition from template-based code generation in Ajv v6 to the tree-based in v7 brought: + +- type-level safety against code injection via untrusted schemas +- more efficient validation code (via [tree optimizations](../codegen.md#code-optimization)) +- smaller memory footprint of compiled functions (schemas are no longer serialized) +- smaller bundle size +- more maintainable code + +These improvements cost slower schema compilation, and increased chance of re-compilation in case you pass a different schema object (see [#1413](https://github.com/ajv-validator/ajv/issues/1413)), so it is very important to manage schemas correctly, so they are only compiled once. + +There are several approaches to manage compiled schemas. + +## Standalone validation code + +The motivation to pre-compile schemas: + +- faster startup times +- lower memory footprint/bundle size +- compatible with strict content security policies +- almost no risk to compile schema more than once +- better for short-lived environments + +See [Standalone validation code](../standalone) for the details. + +There are scenarios when it can be not possible or difficult: + +- dynamic or user-provided schemas - while you can do caching, it can be either difficult to implement or inefficient. +- user-defined keywords that use closures that are difficult to serialize as code. + +## Compiling during initialization + +The simplest approach is to compile all your schemas when the application starts, outside of the code that handles requests. It can be done simply in the module scope: + + + +```javascript +const Ajv = require("ajv").defalt +const schema_user = require("./schema_user.json") +const ajv = new Ajv() +const validate_user = ajv.compile(schema_user) + +// this is just some abstract API framework +app.post("/user", async (cxt) => { +if (validate_user(cxt.body)) { +// create user +} else { +// report error +cxt.status(400) +} +}) + +```` + + + +```javascript +import Ajv from "ajv" +import * as schema_user from "./schema_user.json" +const ajv = new Ajv() +const validate_user = ajv.compile(schema_user) + +interface User { + username: string +} + +// this is just some abstract API framework +app.post("/user", async (cxt) => { + if (validate_user(cxt.body)) { + // create user + } else { + // report error + cxt.status(400) + } +}) +```` + + + + +::: warning Please note +It recommended to use a single Ajv instance for the whole application, so if you use validation in more than one module, you should: + +- require Ajv in a separate module responsible for validation +- compile all validators there +- export them to be used in multiple modules of your application + ::: + +## Using Ajv instance cache + +Another, more effective approach, is to use Ajv instance cache to have all compiled validators available anywhere in your application from a single import. + +In this case you would have a separate module where you instantiate Ajv and use this instance in your application. + +You can load all schemas and add them to Ajv instance in a single `validation` module: + + + +```javascript +const Ajv = require("ajv").defalt +const schema_user = require("./schema_user.json") +const schema_document = require("./schema_document.json") +const ajv = exports.ajv = new Ajv() +ajv.addSchema(schema_user, "user") +ajv.addSchema(schema_document, "document") +``` + + + +```typescript +import Ajv from "ajv" +import * as schema_user from "./schema_user.json" +import * as schema_document from "./schema_document.json" +export const ajv = new Ajv() +ajv.addSchema(schema_user, "user") +ajv.addSchema(schema_document, "document") +``` + + + +And then you can import Ajv instance and access any schema in any application module, for example `user` module: + + + +```javascript +const {ajv} = require("./validation") + +// this is just some abstract API framework +app.post("/user", async (cxt) => { +const validate = ajv.getSchema("user") +if (validate(cxt.body)) { +// create user +} else { +// report error +cxt.status(400) +} +}) + +```` + + + +```javascript +import ajv from "./validation" + +interface User { + username: string +} + +// this is just some abstract API framework +app.post("/user", async (cxt) => { + const validate = ajv.getSchema("user") + if (validate(cxt.body)) { + // create user + } else { + // report error + cxt.status(400) + } +}) +```` + + + + +::: tip Please note +In the example above, schema compilation happens only once, on the first API call, not at the application start-up time. It means that the application would start a bit faster, but the first API call would be a bit slower. If this is undesirable, you could, for example, call `getSchema` for all added schemas after they are added, then when `getSchema` is called inside route handler it would simply get compiled validation function from the instance cache. +::: + +### Cache key: schema vs key vs $id + +In the example above, the key passed to the `addSchema` method was used to retrieve schemas from the cache. Other options are: + +- use schema root $id attribute. While it usually looks like URI, it does not mean Ajv downloads it from this URI - this is simply $id used to identify and access the schema. You can though configure Ajv to download schemas on demand - see [Asynchronous schema loading](#asynchronous-schema-loading) +- use schema object itself as a key to the cache (it is possible, because Ajv uses Map). This approach is not recommended, because it would only work if you pass the same instance of the schema object that was passed to `addSchema` method - it is easy to make a mistake that would result in schema being compiled every time it is used. + +### Pre-adding all schemas vs adding on demand + +In the example above all schemas were added in advance. It is also possible, to add schemas as they are used - it can be helpful if there is many schemas. In this case, you need to check first whether the schema is already added by calling `getSchema` method - it would return `undefined` if not: + +```javascript +const schema_user = require("./schema_user.json") +let validate = ajv.getSchema("user") +if (!validate) { + ajv.addSchema(schema_user, "user") + validate = ajv.getSchema("user") +} +``` + +If your schema has `$id` attribute, for example: + + + +```json +{ + "$id": "https://example.com/user.json", + "type": "object", + "properties": { + "username": {"type": "string"} + }, + required: ["username"] +} +``` + + + +then the above logic can be simpler: + +```javascript +const schema_user = require("./schema_user.json") +const validate = ajv.getSchema("https://example.com/user.json") || ajv.compile(schema_user) +``` + +The above is possible because when the schema has `$id` attribute `compile` method both compiles the schema (returning the validation function) and adds it to the Ajv instance cache at the same time. + +### Asynchronous schema loading + +There are cases when you need to have a large collection of schemas stored in some database or on the remote server. In this case you are likely to use schema `$id` as some resource identifier to retrieve it - either network URI or database ID. + +You can use `compileAsync` [method](./api.md#api-compileAsync) to asynchronously load the schemas as they are compiled, loading the schemas that are referenced from compiled schemas on demand. Ajv itself does not do any IO operations, it uses the function you supply via `loadSchema` [option](./api.md#options) to load schema from the passed ID. This function should return `Promise` that resolves to the schema (you can use async function, as in the example). + +Example: + +```javascript +const ajv = new Ajv({loadSchema: loadSchema}) + +ajv.compileAsync(schema).then(function (validate) { + const valid = validate(data) + // ... +}) + +async function loadSchema(uri) { + const res = await request.json(uri) + if (res.statusCode >= 400) throw new Error("Loading error: " + res.statusCode) + return res.body +} +``` + +::: warning Please note +[Option](./api.md#options) `missingRefs` should NOT be set to `"ignore"` or `"fail"` for asynchronous compilation to work. +::: + +## Caching schemas in your code + +You can maintain cache of compiled schemas in your application independently from Ajv. It can be helpful in cases when you have multiple Ajv instances because, for example: + +- you need to compile different schemas with different options +- you use both JSON Schema and JSON Type Definition schemas in one application +- you have $id conflicts between different third party schemas you do not control + +Whatever approach you use, you need to ensure that each schema is compiled only once. diff --git a/docs/guide/modifying-data.md b/docs/guide/modifying-data.md new file mode 100644 index 000000000..0f95782ce --- /dev/null +++ b/docs/guide/modifying-data.md @@ -0,0 +1,235 @@ +# Modifying data during validation + +[[toc]] + +## General considerations + +Ajv has several options that allow to modify data during validation: + +- removeAdditional - to remove properties not defined in the schema object. +- useDefaults - to assign defaults from the schema to the validated data properties. +- coerceTypes - to change data type, when possible, to match the type(s) in the schema. + +You can also define keywords that modify data. + +::: tip Please note +It is not possible to modify the root data instance passed to the validation function, only data properties can be modified. This is related to how JavaScript passes parameters, and not a limitation of Ajv. +::: + +::: warning Please note +This functionality is non-standard - this is likely to be unsupported in other JSON Schema validator implementations. +::: + +::: danger Please note +While pure schema validation produces the results independent of the keywords and subschema order, enabling any feature that may modify the data makes validation impure and its results are likely to depend on the order of evaluation of keywords and subschemas. + +The order of evaluation of subschemas in keywords like `allOf` is always the same as the order of subschemas in the array. + +On another hand, the order of evaluation of keywords, while consistent between validations and not dependent on how schema object is created, is neither documented nor guaranteed, so it can change in the future major versions (and, in rare cases, it can change in minor version - e.g. when there is bug that needs to be fixed). + +It is strongly recommended to always put user-defined keywords that can mutate data in separate subschemas inside `allOf` keyword to make the order of evaluation unambiguous. The exceptions to this recommendation are pre-defined `default` and `type` keywords - they must remain in the same schema as other keywords. +::: + +## Removing additional properties + +With [option `removeAdditional`](./api.md#options) (added by [andyscott](https://github.com/andyscott)) you can filter data during the validation. + +This option modifies original data. + +Example: + +```javascript +const ajv = new Ajv({removeAdditional: true}) +const schema = { + additionalProperties: false, + properties: { + foo: {type: "number"}, + bar: { + additionalProperties: {type: "number"}, + properties: { + baz: {type: "string"}, + }, + }, + }, +} + +const data = { + foo: 0, + additional1: 1, // will be removed; `additionalProperties` == false + bar: { + baz: "abc", + additional2: 2, // will NOT be removed; `additionalProperties` != false + }, +} + +const validate = ajv.compile(schema) + +console.log(validate(data)) // true +console.log(data) // { "foo": 0, "bar": { "baz": "abc", "additional2": 2 } +``` + +If `removeAdditional` option in the example above were `"all"` then both `additional1` and `additional2` properties would have been removed. + +If the option were `"failing"` then property `additional1` would have been removed regardless of its value and property `additional2` would have been removed only if its value were failing the schema in the inner `additionalProperties` (so in the example above it would have stayed because it passes the schema, but any non-number would have been removed). + +::: warning Please note +If you use `removeAdditional` option with `additionalProperties` keyword inside `anyOf`/`oneOf` keywords your validation can fail with this schema +::: + +For example: + +```javascript +{ + type: "object", + oneOf: [ + { + properties: { + foo: {type: "string"} + }, + required: ["foo"], + additionalProperties: false + }, + { + properties: { + bar: {type: "integer"} + }, + required: ["bar"], + additionalProperties: false + } + ] +} +``` + +The intention of the schema above is to allow objects with either the string property "foo" or the integer property "bar", but not with both and not with any other properties. + +With the option `removeAdditional: true` the validation will pass for the object `{ "foo": "abc"}` but will fail for the object `{"bar": 1}`. It happens because while the first subschema in `oneOf` is validated, the property `bar` is removed because it is an additional property according to the standard (because it is not included in `properties` keyword in the same schema). + +While this behaviour is unexpected (issues [#129](https://github.com/ajv-validator/ajv/issues/129), [#134](https://github.com/ajv-validator/ajv/issues/134)), it is correct. To have the expected behaviour (both objects are allowed and additional properties are removed) the schema has to be refactored in this way: + +```javascript +{ + type: "object", + properties: { + foo: {type: "string"}, + bar: {type: "integer"} + }, + additionalProperties: false, + oneOf: [{required: ["foo"]}, {required: ["bar"]}] +} +``` + +The schema above is also more efficient - it will compile into a faster function. + +## Assigning defaults + +With [option `useDefaults`](./api.md#options) Ajv will assign values from `default` keyword in the schemas of `properties` and `items` (when it is the array of schemas) to the missing properties and items. + +With the option value `"empty"` properties and items equal to `null` or `""` (empty string) will be considered missing and assigned defaults. + +This option modifies original data. + +::: warning Please note +The default value is inserted in the generated validation code as a literal, so the value inserted in the data will be the deep clone of the default in the schema. +::: + +Example 1 (`default` in `properties`): + +```javascript +const ajv = new Ajv({useDefaults: true}) +const schema = { + type: "object", + properties: { + foo: {type: "number"}, + bar: {type: "string", default: "baz"}, + }, + required: ["foo", "bar"], +} + +const data = {foo: 1} + +const validate = ajv.compile(schema) + +console.log(validate(data)) // true +console.log(data) // { "foo": 1, "bar": "baz" } +``` + +Example 2 (`default` in `items`): + +```javascript +const schema = { + type: "array", + items: [{type: "number"}, {type: "string", default: "foo"}], +} + +const data = [1] + +const validate = ajv.compile(schema) + +console.log(validate(data)) // true +console.log(data) // [ 1, "foo" ] +``` + +With `useDefaults` option `default` keywords throws exception during schema compilation when used in: + +- not in `properties` or `items` subschemas +- in schemas inside `anyOf`, `oneOf` and `not` (see [#42](https://github.com/ajv-validator/ajv/issues/42)) +- in `if` schema +- in schemas generated by user-defined _macro_ keywords + +The strict mode option can change the behaviour for these unsupported defaults (`strict: false` to ignore them, `"log"` to log a warning). + +See [Strict mode](./strict-mode.md). + +## Coercing data types + +When you are validating user inputs all your data properties are usually strings. The option `coerceTypes` allows you to have your data types coerced to the types specified in your schema `type` keywords, both to pass the validation and to use the correctly typed data afterwards. + +This option modifies original data. + +::: warning Please note +If you pass a scalar value to the validating function its type will be coerced and it will pass the validation, but the value of the variable you pass won't be updated because scalars are passed by value. +::: + +Example 1: + +```javascript +const ajv = new Ajv({coerceTypes: true}) +const schema = { + type: "object", + properties: { + foo: {type: "number"}, + bar: {type: "boolean"}, + }, + required: ["foo", "bar"], +} + +const data = {foo: "1", bar: "false"} + +const validate = ajv.compile(schema) + +console.log(validate(data)) // true +console.log(data) // { "foo": 1, "bar": false } +``` + +Example 2 (array coercions): + +```javascript +const ajv = new Ajv({coerceTypes: "array"}) +const schema = { + properties: { + foo: {type: "array", items: {type: "number"}}, + bar: {type: "boolean"}, + }, +} + +const data = {foo: "1", bar: ["false"]} + +const validate = ajv.compile(schema) + +console.log(validate(data)) // true +console.log(data) // { "foo": [1], "bar": false } +``` + +The coercion rules, as you can see from the example, are different from JavaScript both to validate user input as expected and to have the coercion reversible (to correctly validate cases where different types are defined in subschemas of "anyOf" and other compound keywords). + +See [Type coercion rules](./coercion.md) for details. diff --git a/docs/guide/schema-language.md b/docs/guide/schema-language.md new file mode 100644 index 000000000..1a4fc914c --- /dev/null +++ b/docs/guide/schema-language.md @@ -0,0 +1,135 @@ +# Choosing schema language + +[[toc]] + +## JSON Type Definition + +Ajv supports the new specification focussed on defining cross-platform types of JSON messages/payloads - JSON Type Definition (JTD). See the informal reference of [JTD schema forms](../json-type-definition) and formal specification [RFC8927](https://datatracker.ietf.org/doc/rfc8927/). + +## JSON Schema + +Ajv supports most widely used drafts of JSON Schema specification. Please see the informal reference of available [JSON Schema validation keywords](../json-schema) and [specification drafts](https://json-schema.org/specification.html). + +### draft-04 + +Draft-04 is not included in Ajv v7, because of some differences it has with the following drafts: + +- different schema member for schema identifier (`id` in draft-04 instead of `$id`) +- different syntax of exclusiveMaximum/Minimum + +You can still use draft-04 schemas with Ajv v6 - while this is no longer actively developed, any security related issues would still be supported at least until 30/06/2021. + +To install v6: + +```bash +npm install ajv@6 +``` + +You can migrate schemas from draft-04 to draft-07 using [ajv-cli](https://github.com/ajv-validator/ajv-cli). + +### draft-07 (and draft-06) + +These are the most widely used versions of JSON Schema specification, and they are supported with the main ajv export. + +```javascript +import Ajv from "ajv" +const ajv = new Ajv() +``` + +If you need to support draft-06 schemas you need to add additional meta-schema, but you may just change (or remove) `$schema` attribute in your schemas - no other changes are needed: + +```javascript +const draft6MetaSchema = require("ajv/dist/refs/json-schema-draft-06.json") +ajv.addMetaSchema(draft6MetaSchema) +``` + +### draft 2019-09 (and draft-2012-12) + +The main advantage of this JSON Schema version over draft-07 is the ability to spread the definition of records that do not allow additional properties across multiple schemas. If you do not need it, you might be better off with draft-07. + +To use Ajv with the support of all JSON Schema draft-2019-09/2020-12 features you need to use a different export: + +```javascript +import Ajv2019 from "ajv/dist/2019" +const ajv = new Ajv2019() +``` + +Optionally, you can add draft-07 meta-schema, to use both draft-07 and draft-2019-09 schemas in one Ajv instance: + +```javascript +const draft7MetaSchema = require("ajv/dist/refs/json-schema-draft-07.json") +ajv.addMetaSchema(draft7MetaSchema) +``` + +Draft-2019-09 support is provided via a separate export in order to avoid increasing the bundle and generated code size for draft-07 users. + +With this import Ajv supports the following features: + +- keywords [`unevaluatedProperties`](../json-schema.md#unevaluatedproperties) and [`unevaluatedItems`](../json-schema.md#unevaluateditems) +- keywords [`dependentRequired`](../json-schema.md#dependentrequired), [`dependentSchemas`](../json-schema.md#dependentschemas), [`maxContains`/`minContain`](../json-schema.md#maxcontains--mincontains) +- dynamic recursive references with [`recursiveAnchor`/`recursiveReference`] - see [Extending recursive schemas](./combining-schemas.md#extending-recursive-schemas) +- draft-2019-09 meta-schema is the default. + +::: warning Please note +Supporting dynamic recursive references and `unevaluatedProperties`/`unevaluatedItems` keywords adds additional generated code even to the validation functions where these features are not used (when possible, Ajv determines which properties/items are "unevaluated" at compilation time, but support for dynamic references always adds additional generated code). If you are not using these features in your schemas it is recommended to use default Ajv export with JSON-Schema draft-07 support. +::: + +## Comparison + +Both [JSON Schema](../json-schema.md) and [JSON Type Definition](../json-type-definition.md) are cross-platform specifications with implementations in multiple programming languages that define the shape of your JSON data. + +You can see the difference between the two specifications in [Getting started](./getting-started) section examples. + +This section compares their pros/cons to help decide which specification fits your application better. + +### JSON Schema + +**Pros**: + +- Wide specification adoption. +- Used as part of OpenAPI specification. +- Support of complex validation scenarios: + - untagged unions and boolean logic + - conditional schemas and dependencies + - restrictions on the number ranges and the size of strings, arrays and objects + - semantic validation with formats, patterns and content keywords + - distribute strict record definitions across multiple schemas (with unevaluatedProperties) +- Can be effectively used for validation of any JavaScript objects and configuration files. + +**Cons**: + +- Defines the collection of restrictions on the data, rather than the shape of the data. +- No standard support for tagged unions. +- Complex and error prone for the new users (Ajv has [strict mode](../strict-mode) enabled by default to compensate for it, but it is not cross-platform). +- Some parts of specification are difficult to implement, creating the risk of implementations divergence: + - reference resolution model + - unevaluatedProperties/unevaluatedItems + - dynamic recursive references +- Internet draft status (rather than RFC) + +See [JSON Schema](../json-schema.md) for more information and the list of defined keywords. + +### JSON Type Definition + +**Pros**: + +- Aligned with type systems of many languages - can be used to generate type definitions and efficient parsers and serializers to/from these types. +- Very simple, enforcing the best practices for cross-platform JSON API modelling. +- Simple to implement, ensuring consistency across implementations. +- Defines the shape of JSON data via strictly defined schema forms (rather than the collection of restrictions). +- Effective support for tagged unions. +- Designed to protect against user mistakes. +- Supports compilation of schemas to efficient [serializers and parsers](./getting-started.md#parsing-and-serializing-json) (no need to validate as a separate step) +- Approved as [RFC8927](https://datatracker.ietf.org/doc/rfc8927/) + +**Cons**: + +- Limited, compared with JSON Schema - no support for untagged unions\*, conditionals, references between different schema files\*\*, etc. +- No meta-schema in the specification\*. +- Brand new - limited industry adoption (as of January 2021). + +\* Ajv defines meta-schema for JTD schemas and non-standard keyword "union" that can be used inside "metadata" object. + +\*\* You can still combine schemas from multiple files in the application code. + +See [JSON Type Definition](../json-type-definition.md) for more information and the list of defined schema forms. diff --git a/docs/guide/typescript.md b/docs/guide/typescript.md new file mode 100644 index 000000000..5253f52c7 --- /dev/null +++ b/docs/guide/typescript.md @@ -0,0 +1,209 @@ +# Using with TypeScript + +[[toc]] + +## Additional functionality + +Ajv takes advantage of TypeScript type system to provide additional functionality that is not possible in JavaScript: + +- utility types `JSONSchemaType` and `JTDSchemaType` to convert data type into the schema type to simplify writing schemas, both for [JSON Schema](../json-schema.md) (but without union support) and for [JSON Type Definition](../json-type-definition) (with tagged unions support). +- compiled validation functions are type guards that narrow the type after successful validation. +- validation errors for JSON Schema are defined as tagged unions, for type-safe error handling. +- when utility type is used, compiled JTD serializers only accept data of correct type (as they do not validate that the data is valid) and compiled parsers return correct data type. + +## Utility types for schemas + +For the same example as in [Getting started](./getting-started): + + + +```typescript +import Ajv, {JSONSchemaType} from "ajv" +const ajv = new Ajv() // options can be passed, e.g. {allErrors: true} + +interface MyData { +foo: number +bar?: string +} + +const schema: JSONSchemaType = { +type: "object", +properties: { +foo: {type: "integer"}, +bar: {type: "string"} +}, +required: ["foo"], +additionalProperties: false +} + +// validate is a type guard for MyData - type is inferred from schema type +const validate = ajv.compile(schema) + +// or, if you did not use type annotation for the schema, +// type parameter can be used to make it type guard: +// const validate = ajv.compile(schema) + +const validData = { +foo: 1, +bar: "abc" +} + +if (validate(data)) { +// data is MyData here +console.log(data.foo) +} else { +console.log(validate.errors) +} + +```` + + + +```typescript +import Ajv, {JTDSchemaType} from "ajv/dist/jtd" +const ajv = new Ajv() // options can be passed, e.g. {allErrors: true} + +interface MyData { + foo: number + bar?: string +} + +const schema: JTDSchemaType = { + properties: { + foo: {type: "int32"} + }, + optionalProperties: { + bar: {type: "string"} + } +} + + +// validate is a type guard for MyData - type is inferred from schema type +const validate = ajv.compile(schema) + +// or, if you did not use type annotation for the schema, +// type parameter can be used to make it type guard: +// const validate = ajv.compile(schema) + +const validData = { + foo: 1, + bar: "abc" +} + +if (validate(data)) { + // data is MyData here + console.log(data.foo) +} else { + console.log(validate.errors) +} +```` + + + + +See [this test](https://github.com/ajv-validator/ajv/tree/master/spec/types/json-schema.spec.ts) for an advanced example. + +## Type-safe error handling + +With [JSON Schema](../json-schema), the validation error type is an open union, but it can be cast to a tagged union (using validation keyword as tag) for easier error handling. + +This is not useful with [JSON Type Definition](../json-type-definition), as it defines errors for schema forms, not for keywords. + +Continuing the example above: + + + +```typescript +import {DefinedError} from "ajv" + +// ... + +if (validate(data)) { +// data is MyData here +console.log(data.foo) +} else { +// The type cast is needed, as Ajv uses a wider type to allow extension +// You can extend this type to include your error types as needed. +for (const err of validate.errors as DefinedError[]) { +switch (err.keyword) { +case "type": +// err type is narrowed here to have "type" error params properties +console.log(err.params.type) +break +// ... +} +} +} + +```` + + + +## Type-safe parsers and serializers + +With typescript, your compiled parsers and serializers can be type-safe, either taking their type from schema type or from type parameter passed to compilation functions. + +This example uses the same data and schema types as above: + + + +```typescript +import Ajv, {JTDSchemaType} from "ajv/dist/jtd" +const ajv = new Ajv() // options can be passed, e.g. {allErrors: true} + +interface MyData { + foo: number + bar?: string +} + +const schema: JTDSchemaType = { + properties: { + foo: {type: "int32"} + }, + optionalProperties: { + bar: {type: "string"} + } +} + +// serialize will only accept data compatible with MyData +const serialize = ajv.compileSerializer(schema) + +// parse will return MyData or undefined +const parse = ajv.compileParser(schema) + +// types of parse and serialize are inferred from schema, +// they can also be defined explicitly: +// const parse = ajv.compileParser(schema) + +const data = { + foo: 1, + bar: "abc" +} + +const invalidData = { + unknown: "abc" +} + +console.log(serialize(data)) +console.log(serialize(invalidData)) // type error + +const json = '{"foo": 1, "bar": "abc"}' +const invalidJson = '{"unknown": "abc"}' + +console.log(parseAndLogFoo(json)) // logs property +console.log(parseAndLogFoo(invalidJson)) // logs error and position + +function parseAndLogFoo(json: string): void { + const data = parse(json) // MyData | undefined + if (data === undefined) { + console.log(parse.message) // error message from the last parse call + console.log(parse.position) // error position in string + } else { + // data is MyData here + console.log(data.foo) + } +} +```` + + + diff --git a/docs/guide/user-keywords.md b/docs/guide/user-keywords.md new file mode 100644 index 000000000..c178ecb15 --- /dev/null +++ b/docs/guide/user-keywords.md @@ -0,0 +1,59 @@ +# User-defined keywords + +You can extend keyword available in Ajv by defining your own keywords. + +The advantages of defining keywords are: + +- allow creating validation scenarios that cannot be expressed using pre-defined keywords +- simplify your schemas +- help bringing a bigger part of the validation logic to your schemas +- make your schemas more expressive, less verbose and closer to your application domain +- implement data processors that modify your data (`modifying` option MUST be used in keyword definition) and/or create side effects while the data is being validated + +If a keyword is used only for side-effects and its validation result is pre-defined, use option `valid: true/false` in keyword definition to simplify both generated code (no error handling in case of `valid: true`) and your keyword functions (no need to return any validation result). + +::: warning Please note +When extending JSON Schema standard with additional keywords, you have several potential concerns to be aware of: + +- portability of your schemas - they would only work with JavaScript or TypeScript applications where you can use Ajv. +- additional documentation required to maintain your schemas. + ::: + +::: danger Please note +While it is possible to define additional keywords for JSON Type Definition schemas (these keywords can only be used in `metadata` member of the schema), it is strongly recommended not to do it - JTD is specifically designed for cross-platform APIs. +::: + +You can define keywords with [addKeyword](./api.md#api-addkeyword) method. Keywords are defined on the `ajv` instance level - new instances will not have previously defined keywords. + +Ajv allows defining keywords with: + +- code generation function (used by all pre-defined keywords) +- validation function +- compilation function +- macro function + +Example. `range` and `exclusiveRange` keywords using compiled schema: + +```javascript +ajv.addKeyword({ + keyword: "range", + type: "number", + schemaType: "array", + implements: "exclusiveRange", + compile: ([min, max], parentSchema) => + parentSchema.exclusiveRange === true + ? (data) => data > min && data < max + : (data) => data >= min && data <= max, +}) + +const schema = {range: [2, 4], exclusiveRange: true} +const validate = ajv.compile(schema) +console.log(validate(2.01)) // true +console.log(validate(3.99)) // true +console.log(validate(2)) // false +console.log(validate(4)) // false +``` + +Several keywords (typeof, instanceof, range and propertyNames) are defined in [ajv-keywords](https://github.com/ajv-validator/ajv-keywords) package - they can be used for your schemas and as a starting point for your own keywords. + +See [User-defined keywords](./keywords.md) reference for more details. diff --git a/docs/json-schema.md b/docs/json-schema.md index d6abf6e86..2a5da20c2 100644 --- a/docs/json-schema.md +++ b/docs/json-schema.md @@ -1,9 +1,11 @@ -# JSON Schema validation keywords +# JSON Schema In a simple way, JSON Schema is an object with validation keywords. The keywords and their values define what rules the data should satisfy to be valid. +[[toc]] + ## JSON Schema draft-2019-09 v7 added support for all new keywords in draft-2019-09: @@ -13,48 +15,9 @@ v7 added support for all new keywords in draft-2019-09: - [dependentRequired](#dependentrequired) - [dependentSchemas](#dependentschemas) - [maxContains/minContains](#maxcontains--mincontains) -- [$recursiveAnchor/$recursiveRef](./validation.md#extending-recursive-schemas) - -There is also support for [$dynamicAnchor/$dynamicRef](./validation.md#extending-recursive-schemas) from the next version of JSON Schema draft that will replace `$recursiveAnchor`/`$recursiveRef`. - -## Included keywords - -- [type](#type) -- [Keywords for numbers](#keywords-for-numbers) - - [maximum / minimum and exclusiveMaximum / exclusiveMinimum](#maximum--minimum-and-exclusivemaximum--exclusiveminimum) - - [multipleOf](#multipleof) -- [Keywords for strings](#keywords-for-strings) - - [maxLength/minLength](#maxlength--minlength) - - [pattern](#pattern) - - [format](#format) -- [Keywords for arrays](#keywords-for-arrays) - - [maxItems/minItems](#maxitems--minitems) - - [uniqueItems](#uniqueitems) - - [items](#items) - - [additionalItems](#additionalitems) - - [contains](#contains) - - [maxContains/minContains](#maxcontains--mincontains) - - [unevaluatedItems](#unevaluateditems) (NEW: added in draft 2019-09) -- [Keywords for objects](#keywords-for-objects) - - [maxProperties/minProperties](#maxproperties--minproperties) - - [required](#required) - - [properties](#properties) - - [patternProperties](#patternproperties) - - [additionalProperties](#additionalproperties) - - [dependencies](#dependencies) (deprecated from draft 2019-09) - - [dependentRequired](#dependentrequired) (NEW: added in draft 2019-09) - - [dependentSchemas](#dependentschemas) (NEW: added in draft 2019-09) - - [propertyNames](#propertynames) - - [unevaluatedProperties](#unevaluatedproperties) (NEW: added in draft 2019-09) -- [Keywords for all types](#keywords-for-all-types) - - [enum](#enum) - - [const](#const) (added in draft-06) -- [Compound keywords](#compound-keywords) - - [not](#not) - - [oneOf](#oneof) - - [anyOf](#anyof) - - [allOf](#allof) - - [if/then/else](#ifthenelse) +- [$recursiveAnchor/$recursiveRef](./guide/combining-schemas.md#extending-recursive-schemas) + +There is also support for [$dynamicAnchor/$dynamicRef](./guide/combining-schemas.md#extending-recursive-schemas) from the next version of JSON Schema draft that will replace `$recursiveAnchor`/`$recursiveRef`. ## `type` @@ -96,7 +59,9 @@ The value of keyword `maximum` (`minimum`) should be a number. This value is the The value of keyword `exclusiveMaximum` (`exclusiveMinimum`) should be a number. This value is the exclusive maximum (minimum) allowed value for the data to be valid (the data equal to this keyword value is invalid). -**Please note**: Boolean value for keywords `exclusiveMaximum` (`exclusiveMinimum`) is no longer supported. +::: warning Please note +Boolean value for keywords `exclusiveMaximum` (`exclusiveMinimum`) is no longer supported. +::: **Examples** @@ -339,7 +304,7 @@ _valid_: `[1, 2]`, `[1, 2, 3, "foo"]`, any array with 2 or 3 integers _invalid_: `[]`, `[1, "foo"]`, `[1, 2, 3, 4]`, any array with fewer than 2 or more than 3 integers -### `unevaluatedItems` +### `unevaluatedItems` The value of this keyword is a JSON Schema (can be a boolean). @@ -412,7 +377,9 @@ _invalid_: `{}`, `{a: 1}`, `{c: 3, d: 4}` The value of the keyword should be a map with keys equal to data object properties. Each value in the map should be a JSON Schema. For data object to be valid the corresponding values in data object properties should be valid according to these schemas. -**Please note**: `properties` keyword does not require that the properties mentioned in it are present in the object (see examples). +::: warning Please note +`properties` keyword does not require that the properties mentioned in it are present in the object (see examples). +::: **Example** @@ -441,10 +408,11 @@ The value of this keyword should be a map where keys should be regular expressio When the value in data object property matches multiple regular expressions it should be valid according to all the schemas for all matched regular expressions. -**Please note**: +::: warning Please note 1. `patternProperties` keyword does not require that properties matching patterns are present in the object (see examples). 2. By default, Ajv does not allow schemas where patterns in `patternProperties` match any property name in `properties` keyword - that leads to unexpected validation results. It can be allowed with option `allowMatchingProperties`. See [Strict mode](./strict-mode.md) + ::: **Example** @@ -542,7 +510,7 @@ If the value is a schema for the data object to be valid the values in all "addi _invalid_: `{bar: 2}`, `{baz: 3}`, `{foo: 1, bar: 2}`, etc. -### `dependencies` +### `dependencies` This keyword is deprecated. The same functionality is available with keywords `dependentRequired` and `dependentSchemas`. @@ -588,7 +556,7 @@ For schema dependency, if the data object contains a property that is a key in t _invalid_: `{foo: 1, bar: "a"}` -### `dependentRequired` +### `dependentRequired` The value of this keyword should be a map with keys equal to data object properties. Each value in the map should be an array of unique property names. @@ -611,7 +579,7 @@ _valid_: `{foo: 1, bar: 2, baz: 3}`, `{}`, `{a: 1}` _invalid_: `{foo: 1}`, `{foo: 1, bar: 2}`, `{foo: 1, baz: 3}` -### `dependentSchemas` +### `dependentSchemas` The value of the keyword should be a map with keys equal to data object properties. Each value in the map should be a JSON Schema. @@ -661,7 +629,7 @@ _valid_: `{"foo@bar.com": "any", "bar@bar.com": "any"}` _invalid_: `{foo: "any value"}` -### `unevaluatedProperties` +### `unevaluatedProperties` The value of this keyword is a JSON Schema (can be a boolean). @@ -733,7 +701,7 @@ _valid_: `"foo"` _invalid_: any other value -The same can be achieved with `enum` keyword using the array with one item. But `const` keyword is more than just a syntax sugar for `enum`. In combination with the [\$data reference](./validation.md#data-reference) it allows to define equality relations between different parts of the data. This cannot be achieved with `enum` keyword even with `$data` reference because `$data` cannot be used in place of one item - it can only be used in place of the whole array in `enum` keyword. +The same can be achieved with `enum` keyword using the array with one item. But `const` keyword is more than just a syntax sugar for `enum`. In combination with the [$data reference](./guide/combining-schemas.md#data-reference) it allows to define equality relations between different parts of the data. This cannot be achieved with `enum` keyword even with `$data` reference because `$data` cannot be used in place of one item - it can only be used in place of the whole array in `enum` keyword. **Example** @@ -883,3 +851,19 @@ If the data is invalid against the sub-schema in `if` keyword, then the validati - `2000` (>1000) - `11`, `57`, `123` (any integer with more than one non-zero digit) - non-integers + +## Metadata keywords + +JSON Schema specification defines several metadata keywords that describe the schema itself but do not perform any validation. + +- `title` and `description`: information about the data represented by that schema +- `$comment`: information for developers. With option `$comment` Ajv logs or passes the comment string to the user-supplied function. See [Options](./api.md#options). +- `default`: a default value of the data instance, see [Assigning defaults](#assigning-defaults). +- `examples`: an array of data instances. Ajv does not check the validity of these instances against the schema. +- `readOnly` and `writeOnly`: marks data-instance as read-only or write-only in relation to the source of the data (database, api, etc.). +- `contentEncoding`: [RFC 2045](https://tools.ietf.org/html/rfc2045#section-6.1), e.g., "base64". +- `contentMediaType`: [RFC 2046](https://datatracker.ietf.org/doc/rfc2046/), e.g., "image/png". + +::: warning Please note +Ajv does not implement validation of the keywords `examples`, `contentEncoding` and `contentMediaType` but it reserves them. If you want to create a plugin that implements any of them, it should remove these keywords from the instance. +::: diff --git a/docs/json-type-definition.md b/docs/json-type-definition.md index 2f4c8da3f..9cf260f84 100644 --- a/docs/json-type-definition.md +++ b/docs/json-type-definition.md @@ -11,23 +11,7 @@ const AjvJTD = require("ajv/dist/jtd").default const ajv = new AjvJTD() ``` -## Contents - -- [JTD schema forms](#jtd-schema-forms): - - [type](#type-schema-form) (for primitive values) - - [enum](#enum-schema-form) - - [elements](#elements-schema-form) (for arrays) - - [properties](#properties-schema-form) (for records) - - [discriminator](#discriminator-schema-form) (for tagged union of records) - - [values](#values-schema-form) (for dictionary) - - [ref](#ref-schema-form) (to reference a schema in definitions) - - [empty](#empty-schema-form) (for any data) -- [JTDSchemaType](#jtdschematype) -- [Extending JTD](#extending-jtd) - - [metadata](#metadata-schema-member) - - [union](#union-keyword) - - [user-defined keywords](#user-defined-keywords) -- [Validation errors](#validation-errors) +[[toc]] ## JTD schema forms @@ -42,7 +26,7 @@ All forms require that: Root schema can have member `definitions` that has a dictionary of schemas that can be references from any other schemas using form `ref` -### Type schema form +### Type form This form defines a primitive value. @@ -74,7 +58,7 @@ Unlike JSON Schema, JTD does not allow defining values that can take one of seve } ``` -### Enum schema form +### Enum form This form defines a string that can take one of the values from the list (the values in the list must be unique). @@ -90,7 +74,7 @@ Unlike JSON Schema, JTD does not allow defining `enum` with values of any other } ``` -### Elements schema form +### Elements form This form defines a homogenous array of any size (possibly empty) with the elements that satisfy a given schema. @@ -114,7 +98,7 @@ Valid data: `[]`, `["foo"]`, `["foo", "bar"]` Invalid data: `["foo", 1]`, any type other than array -### Properties schema form +### Properties form This form defines record (JSON object) that has defined required and optional properties. @@ -173,7 +157,7 @@ Invalid data: `{}`, `{foo: 1}`, `{foo: "bar", bar: "3"}`, any type other than ob } ``` -### Discriminator schema form +### Discriminator form This form defines discriminated (tagged) union of different record types. @@ -231,7 +215,7 @@ Invalid data: `{}`, `{foo: "1"}`, `{version: 1, foo: "1"}`, any type other than } ``` -### Values schema form +### Values form This form defines a homogenous dictionary where the values of members satisfy a given schema. @@ -255,7 +239,7 @@ Valid data: `{}`, `{"foo": 1}`, `{"foo": 1, "bar": 2}` Invalid data: `{"foo": "bar"}`, any type other than object -### Ref schema form +### Ref form This form defines a reference to the schema that is present in the corresponding key in the `definitions` member of the root schema. @@ -310,7 +294,7 @@ Unlike JSON Schema, JTD does not allow to reference: } ``` -### Empty schema form +### Empty form Empty JTD schema defines the data instance that can be of any type, including JSON `null` (even if `nullable` member is not present). It cannot have any member other than `nullable` and `metadata`. @@ -396,7 +380,9 @@ Each schema form may have an optional member `metadata` that JTD reserves for im - any user-defined keywords, for example keywords defined in [ajv-keywords](https://github.com/ajv-validator/ajv-keywords) package - JSON Schema keywords, as long as their names are different from standard JTD keywords. It can be used to enable a gradual migration from JSON Schema to JTD, should it be required. -**Please note**: Ajv-specific extension to JTD are likely to be unsupported by other tools, so while it may simplify adoption, it undermines the cross-platform objective of using JTD. While it is ok to put some human readable information in `metadata` member, it is recommended not to add any validation logic there (even if it is supported by Ajv). +::: warning Please note +Ajv-specific extension to JTD are likely to be unsupported by other tools, so while it may simplify adoption, it undermines the cross-platform objective of using JTD. While it is ok to put some human readable information in `metadata` member, it is recommended not to add any validation logic there (even if it is supported by Ajv). +::: Additional restrictions that Ajv enforces on `metadata` schema member: @@ -405,14 +391,22 @@ Additional restrictions that Ajv enforces on `metadata` schema member: ### Union keyword -Ajv defines `union` keyword that is used in the schema that validates JTD schemas ([meta-schema](../lib/refs/jtd-schema.ts)). +Ajv defines `union` keyword that is used in the schema that validates JTD schemas ([meta-schema](https://github.com/ajv-validator/ajv/blob/master/lib/refs/jtd-schema.ts)). This keyword can be used only inside `metadata` schema member. -**Please note**: This keyword is non-standard and it is not supported in other JTD tools, so it is recommended NOT to use this keyword in schemas for your data if you want them to be cross-platform. +::: warning Please note +This keyword is non-standard and it is not supported in other JTD tools, so it is recommended NOT to use this keyword in schemas for your data if you want them to be cross-platform. +::: ### User-defined keywords Any user-defined keywords that can be used in JSON Schema schemas can also be used in JTD schemas, including the keywords in [ajv-keywords](https://github.com/ajv-validator/ajv-keywords) package. -**Please note**: It is strongly recommended to only use it to simplify migration from JSON Schema to JTD and not to use non-standard keywords in the new schemas, as these keywords are not supported by any other tools. +::: warning Please note +It is strongly recommended to only use it to simplify migration from JSON Schema to JTD and not to use non-standard keywords in the new schemas, as these keywords are not supported by any other tools. +::: + +## Validation errors + +TODO diff --git a/docs/keywords.md b/docs/keywords.md index d8d3272f4..834959871 100644 --- a/docs/keywords.md +++ b/docs/keywords.md @@ -1,18 +1,8 @@ # User defined keywords -## Contents +[[toc]] -- Define keyword with: - - [code generation function](#define-keyword-with-code-generation-function) - used by all pre-defined keywords - - [validation function](#define-keyword-with-validation-function) - - [compilation function](#define-keyword-with-compilation-function) - - [macro function](#define-keyword-with-macro-function) -- [Schema compilation context](#schema-compilation-context) -- [Validation time variables](#validation-time-variables) -- [Ajv utilities](#ajv-utilities) -- [Defining keyword errors](#defining-keyword-errors) - -### Common attributes of keyword definitions +## Common attributes of keyword definitions The usual interface to define all keywords has these properties: @@ -29,11 +19,11 @@ interface _KeywordDef { } ``` -Keyword definitions may have additional optional properties - see [types](../lib/types/index.ts) and [KeywordCxt](../lib/compile/context.ts). +Keyword definitions may have additional optional properties - see [types](https://github.com/ajv-validator/ajv/blob/master/lib/types/index.ts) and [KeywordCxt](https://github.com/ajv-validator/ajv/blob/master/lib/compile/context.ts). -### Define keyword with code generation function +### Define keyword with code generation function -Starting from v7 Ajv uses [CodeGen module](../lib/compile/codegen/index.ts) for all pre-defined keywords - see [codegen.md](./codegen.md) for details. +Starting from v7 Ajv uses [CodeGen module](https://github.com/ajv-validator/ajv/blob/master/lib/compile/codegen/index.ts) for all pre-defined keywords - see [codegen.md](./codegen.md) for details. This is the best approach for user defined keywords: @@ -61,7 +51,7 @@ ajv.addKeyword({ keyword: "even", type: "number", schemaType: "boolean", - // $data: true // to support [$data reference](./validation.md#data-reference), ... + // $data: true // to support [$data reference](./guide/combining-schemas.md#data-reference), ... code(cxt: KeywordCxt) { const {data, schema} = cxt const op = schema ? _`!==` : _`===` @@ -98,9 +88,9 @@ ajv.addKeyword({ }) ``` -You can review pre-defined Ajv keywords in [validation](../lib/validation) folder for more advanced examples - it is much easier to define code generation keywords than it was in the previous version of Ajv. +You can review pre-defined Ajv keywords in [validation](https://github.com/ajv-validator/ajv/blob/master/lib/validation) folder for more advanced examples - it is much easier to define code generation keywords than it was in the previous version of Ajv. -See [KeywordCxt](../lib/compile/context.ts) and [SchemaCxt](../lib/compile/index.ts) type definitions for more information about properties you can use in your keywords. +See [KeywordCxt](https://github.com/ajv-validator/ajv/blob/master/lib/compile/context.ts) and [SchemaCxt](https://github.com/ajv-validator/ajv/blob/master/lib/compile/index.ts) type definitions for more information about properties you can use in your keywords. ### Define keyword with "validate" function @@ -136,7 +126,7 @@ The function should return validation result as boolean. It can return an array - testing your keywords before converting them to compiled/code keywords - defining keywords that do not depend on the schema value (e.g., when the value is always `true`). In this case you can add option `schema: false` to the keyword definition and the schemas won't be passed to the validation function, it will only receive the same parameters as compiled validation function. - defining keywords where the schema is a value used in some expression. -- defining keywords that support [\$data reference](./validation.md#data-reference) - in this case `validate` or `code` function is required, either as the only option or in addition to `compile` or `macro`. +- defining keywords that support [\$data reference](./guide/combining-schemas.md#data-reference) - in this case `validate` or `code` function is required, either as the only option or in addition to `compile` or `macro`. Example: `constant` keyword (a synonym for draft-06 keyword `const`, it is equivalent to `enum` keyword with one item): @@ -165,7 +155,9 @@ console.log(validate({foo: "baz"})) // false `const` keyword is already available in Ajv. -**Please note:** If the keyword does not define errors (see [Reporting errors](./api.md#reporting-errors)) pass `errors: false` in its definition; it will make generated code more efficient. +::: tip Please note +If the keyword does not define errors (see [Reporting errors](./api.md#reporting-errors)) pass `errors: false` in its definition; it will make generated code more efficient. +::: To add asynchronous keyword pass `async: true` in its definition. @@ -251,11 +243,11 @@ Macro keywords an be recursive - i.e. return schemas containing the same keyword ## Schema compilation context -Schema compilation context [SchemaCxt](../lib/compile/index.ts) is available in property `it` of [KeywordCxt](../lib/compile/context.ts) (and it is also the 3rd parameter of `compile` and `macro` keyword functions). See types in the source code on the properties you can use in this object. +Schema compilation context [SchemaCxt](https://github.com/ajv-validator/ajv/blob/master/lib/compile/index.ts) is available in property `it` of [KeywordCxt](https://github.com/ajv-validator/ajv/blob/master/lib/compile/context.ts) (and it is also the 3rd parameter of `compile` and `macro` keyword functions). See types in the source code on the properties you can use in this object. ## Validation time variables -All function scoped variables available during validation are defined in [names](../lib/compile/names.ts). +All function scoped variables available during validation are defined in [names](https://github.com/ajv-validator/ajv/blob/master/lib/compile/names.ts). ## Reporting errors diff --git a/docs/options.md b/docs/options.md new file mode 100644 index 000000000..4f0591f3d --- /dev/null +++ b/docs/options.md @@ -0,0 +1,302 @@ +# Initialization options + +[[toc]] + +## Usage + +This page describes properties of the options object that can be passed to Ajv constructor. + +For example, to report all validation errors (rather than failing on the first errors) you should pass `allErrors` option to constructor: + +```javascript +const ajv = new Ajv({allErrors: true}) +``` + +## Option defaults + +::: tip Please note +Passing the value below for some of the options is equivalent to not passing this option at all. There is no need to pass default option values - it is recommended to only pass option values that are different from defaults. +::: + +```javascript +// see types/index.ts for actual types +const defaultOptions = { + // strict mode options (NEW) + strict: true, + strictTypes: "log", // * + strictTuples: "log", // * + strictRequired: false, // * + allowUnionTypes: false, // * + allowMatchingProperties: false, // * + validateFormats: true, // * + // validation and reporting options: + $data: false, // * + allErrors: false, + verbose: false, // * + $comment: false, // * + formats: {}, + keywords: {}, + schemas: {}, + logger: undefined, + loadSchema: undefined, // *, function(uri: string): Promise {} + // options to modify validated data: + removeAdditional: false, + useDefaults: false, // * + coerceTypes: false, // * + // advanced options: + meta: true, + validateSchema: true, + addUsedSchema: true, + inlineRefs: true, + passContext: false, + loopRequired: Infinity, // * + loopEnum: Infinity, // NEW + ownProperties: false, + multipleOfPrecision: undefined, // * + messages: true, // false with JTD + ajvErrors: false // only with JTD + code: { + // NEW + es5: false, + lines: false, + source: false, + process: undefined, // (code: string) => string + optimize: true, + }, +} +``` + +\* these options are not supported with JSON Type Definition schemas + +## Strict mode options + +### strict + +By default Ajv executes in strict mode, that is designed to prevent any unexpected behaviours or silently ignored mistakes in schemas (see [Strict Mode](./strict-mode.md) for more details). It does not change any validation results, but it makes some schemas invalid that would be otherwise valid according to JSON Schema specification. + +Option values: + +- `true` (default) - use strict mode and throw an exception when any strict mode restriction is violated. +- `"log"` - log warning when any strict mode restriction is violated. +- `false` - ignore all strict mode restrictions. Also ignores `strictTypes` restrictions unless it is explicitly passed. + +### strictTypes + +By default Ajv logs warning when "type" keyword is used in a way that may be incorrect or confusing to other people - see [Strict types](./strict-mode.md#strict-types) for more details. This option does not change validation results. + +Option values: + +- `true` - throw exception when any strictTypes restriction is violated. +- `"log"` (default, unless option strict is `false`) - log warning when any strictTypes restriction is violated. +- `false` - ignore all strictTypes restrictions violations. + +### strictTuples + +By default Ajv logs warning when "items" is array and "minItems" and "maxItems"/"additionalItems" not present or different from the number of items. See [Strict mode](./strict-mode.md) for more details. This option does not change validation results. + +Option values: + +- `true` - throw exception. +- `"log"` (default, unless option strict is `false`) - log warning. +- `false` - ignore strictTuples restriction violations. + +### strictRequired + +Ajv can log warning or throw exception when the property used in "required" keyword is not defined in "properties" keyword. See [Strict mode](./strict-mode.md) for more details. This option does not change validation results. + +Option values: + +- `true` - throw exception. +- `"log"` - log warning. +- `false` (default) - ignore strictRequired restriction violations. + +### allowUnionTypes + +Pass true to allow using multiple non-null types in "type" keyword (one of `strictTypes` restrictions). see [Strict types](./strict-mode.md#strict-types) + +### allowMatchingProperties + +Pass true to allow overlap between "properties" and "patternProperties". Does not affect other strict mode restrictions. See [Strict Mode](./strict-mode.md). + +### validateFormats + +Format validation. + +Option values: + +- `true` (default) - validate formats (see [Formats](./guide/formats.md)). In [strict mode](./strict-mode.md) unknown formats will throw exception during schema compilation (and fail validation in case format keyword value is [\$data reference](./guide/combining-schemas.md#data-reference)). +- `false` - do not validate any format keywords (TODO they will still collect annotations once supported). + +## Validation and reporting options + +### $data + +Support [\$data references](./guide/combining-schemas.md#data-reference). Draft 6 meta-schema that is added by default will be extended to allow them. If you want to use another meta-schema you need to use $dataMetaSchema method to add support for $data reference. See [API](#ajv-constructor-and-methods). + +### allErrors + +Check all rules collecting all errors. Default is to return after the first error. + +### verbose + +Include the reference to the part of the schema (`schema` and `parentSchema`) and validated data in errors (false by default). + +### $comment + +Log or pass the value of `$comment` keyword to a function. + +Option values: + +- `false` (default): ignore \$comment keyword. +- `true`: log the keyword value to console. +- function: pass the keyword value, its schema path and root schema to the specified function + +### formats + +An object with format definitions. Keys and values will be passed to `addFormat` method. Pass `true` as format definition to ignore some formats. + +### keywords + +An array of keyword definitions or strings. Values will be passed to `addKeyword` method. + +### schemas + +An array or object of schemas that will be added to the instance. In case you pass the array the schemas must have IDs in them. When the object is passed the method `addSchema(value, key)` will be called for each schema in this object. + +### logger + +Sets the logging method. Default is the global `console` object that should have methods `log`, `warn` and `error`. See [Error logging](#error-logging). + +Option values: + +- logger instance - it should have methods `log`, `warn` and `error`. If any of these methods is missing an exception will be thrown. +- `false` - logging is disabled. + +### loadSchema + +Asynchronous function that will be used to load remote schemas when `compileAsync` [method](#api-compileAsync) is used and some reference is missing (option `missingRefs` should NOT be 'fail' or 'ignore'). This function should accept remote schema uri as a parameter and return a Promise that resolves to a schema. See example in [Asynchronous compilation](./guide/managing-schemas.md#asynchronous-schema-compilation). + +## Options to modify validated data + +### removeAdditional + +Remove additional properties - see example in [Removing additional properties](./guide/modifying-data.md#removing-additional-properties). + +This option is not used if schema is added with `addMetaSchema` method. + +Option values: + +- `false` (default) - not to remove additional properties +- `"all"` - all additional properties are removed, regardless of `additionalProperties` keyword in schema (and no validation is made for them). +- `true` - only additional properties with `additionalProperties` keyword equal to `false` are removed. +- `"failing"` - additional properties that fail schema validation will be removed (where `additionalProperties` keyword is `false` or schema). + +### useDefaults + +Replace missing or undefined properties and items with the values from corresponding `default` keywords. Default behaviour is to ignore `default` keywords. This option is not used if schema is added with `addMetaSchema` method. + +See examples in [Assigning defaults](./guide/modifying-data.md#assigning-defaults). + +Option values: + +- `false` (default) - do not use defaults +- `true` - insert defaults by value (object literal is used). +- `"empty"` - in addition to missing or undefined, use defaults for properties and items that are equal to `null` or `""` (an empty string). + +### coerceTypes + +Change data type of data to match `type` keyword. See the example in [Coercing data types](./guide/modifying-data.md#coercing-data-types) and [coercion rules](./coercion.md). + +Option values: + +- `false` (default) - no type coercion. +- `true` - coerce scalar data types. +- `"array"` - in addition to coercions between scalar types, coerce scalar data to an array with one element and vice versa (as required by the schema). + +## Advanced options + +### meta + +Add [meta-schema](http://json-schema.org/documentation.html) so it can be used by other schemas (true by default). If an object is passed, it will be used as the default meta-schema for schemas that have no `$schema` keyword. This default meta-schema MUST have `$schema` keyword. + +### validateSchema + +Validate added/compiled schemas against meta-schema (true by default). `$schema` property in the schema can be http://json-schema.org/draft-07/schema or absent (draft-07 meta-schema will be used) or can be a reference to the schema previously added with `addMetaSchema` method. + +Option values: + +- `true` (default) - if the validation fails, throw the exception. +- `"log"` - if the validation fails, log error. +- `false` - skip schema validation. + +### addUsedSchema + +By default methods `compile` and `validate` add schemas to the instance if they have `$id` (or `id`) property that doesn't start with "#". If `$id` is present and it is not unique the exception will be thrown. Set this option to `false` to skip adding schemas to the instance and the `$id` uniqueness check when these methods are used. This option does not affect `addSchema` method. + +### inlineRefs + +Affects compilation of referenced schemas. + +Option values: + +- `true` (default) - the referenced schemas that don't have refs in them are inlined, regardless of their size - it improves performance. +- `false` - to not inline referenced schemas (they will always be compiled as separate functions). +- integer number - to limit the maximum number of keywords of the schema that will be inlined (to balance the total size of compiled functions and performance). + +### passContext + +Pass validation context to _compile_ and _validate_ keyword functions. If this option is `true` and you pass some context to the compiled validation function with `validate.call(context, data)`, the `context` will be available as `this` in your keywords. By default `this` is Ajv instance. + +### loopRequired + +By default `required` keyword is compiled into a single expression (or a sequence of statements in `allErrors` mode). In case of a very large number of properties in this keyword it may result in a very big validation function. Pass integer to set the number of properties above which `required` keyword will be validated in a loop - smaller validation function size but also worse performance. + +### loopEnum + +By default `enum` keyword is compiled into a single expression. In case of a very large number of allowed values it may result in a large validation function. Pass integer to set the number of values above which `enum` keyword will be validated in a loop. + +### ownProperties + +By default Ajv iterates over all enumerable object properties; when this option is `true` only own enumerable object properties (i.e. found directly on the object rather than on its prototype) are iterated. Contributed by @mbroadst. + +### multipleOfPrecision + +By default `multipleOf` keyword is validated by comparing the result of division with `parseInt()` of that result. It works for dividers that are bigger than 1. For small dividers such as 0.01 the result of the division is usually not integer (even when it should be integer, see issue [#84](https://github.com/ajv-validator/ajv/issues/84)). If you need to use fractional dividers set this option to some positive integer N to have `multipleOf` validated using this formula: `Math.abs(Math.round(division) - division) < 1e-N` (it is slower but allows for float arithmetic deviations). + +### messages + +Include human-readable messages in errors. `true` by default. `false` can be passed when messages are generated outside of Ajv code (e.g. with [ajv-i18n](https://github.com/ajv-validator/ajv-i18n)). + +### ajvErrors + +This option is only supported with JTD schemas to generate error objects with the properties described in the first part of [Validation errors](#validation-errors) section, otherwise JTD errors are generated when JTD schemas are used (see the second part of [the same section](#validation-errors)). + +### code + +Code generation options: + +```typescript +type CodeOptions = { + es5?: boolean // to generate es5 code - by default code is es6, with "for-of" loops, "let" and "const" + lines?: boolean // add line-breaks to code - to simplify debugging of generated functions + source?: boolean // add `source` property (see Source below) to validating function. + process?: (code: string, schema?: SchemaEnv) => string // an optional function to process generated code + // before it is passed to Function constructor. + // It can be used to either beautify or to transpile code. + optimize?: boolean | number // code optimization flag or number of passes, 1 pass by default, + // code optimizations reduce the size of the generated code (bytes, based on the tests) by over 10%, + // the number of code tree nodes by nearly 17%. + // You would almost never need more than one optimization pass, unless you have some really complex schemas - + // the second pass in the tests (it has quite complex schemas) only improves optimization by less than 0.1%. + // See [Code optimization](./codegen.md#code-optimization) for details. + formats?: Code + // Code snippet created with `_` tagged template literal that contains all format definitions, + // it can be the code of actual definitions or `require` call: + // _`require("./my-formats")` +} + +type Source = { + code: string // unlike func.toString() it includes assignments external to function scope + scope: Scope // see Code generation (TODO) +} +``` diff --git a/docs/security.md b/docs/security.md index 94b32b61f..419fd90c1 100644 --- a/docs/security.md +++ b/docs/security.md @@ -2,12 +2,7 @@ JSON Schema, if properly used, can replace data sanitisation. It doesn't replace other API security considerations. It also introduces additional security aspects to consider. -- [Security contact](#security-contact) -- [Untrusted schemas](#untrusted-schemas) -- [Circular references in objects](#circular-references-in-javascript-objects) -- [Trusted schemas](#security-risks-of-trusted-schemas) -- [ReDoS attack](#redos-attack) -- [Content Security Policy](#content-security-policy) +[[toc]] ## Security contact @@ -43,9 +38,11 @@ Some keywords in JSON Schemas can lead to very slow validation for certain data. - `patternProperties` for large property names - use `propertyNames` to mitigate, but some regular expressions can have exponential evaluation time as well. - `uniqueItems` for large non-scalar arrays - use `maxItems` to mitigate -**Please note**: The suggestions above to prevent slow validation would only work if you do NOT use `allErrors: true` in production code (using it would continue validation after validation errors). +::: danger Please note +The suggestions above to prevent slow validation would only work if you do NOT use `allErrors: true` in production code (using it would continue validation after validation errors). +::: -You can validate your JSON schemas against [this meta-schema](../lib/refs/json-schema-secure.json) to check that these recommendations are followed: +You can validate your JSON schemas against [this meta-schema](https://github.com/ajv-validator/ajv/blob/master/lib/refs/json-schema-secure.json) to check that these recommendations are followed: ```javascript ajv = new Ajv({strictTypes: false}) // this option is required for this schema @@ -58,7 +55,9 @@ const schema2 = {format: "email", maxLength: MAX_LENGTH} isSchemaSecure(schema2) // true ``` -**Please note**: following all these recommendation is not a guarantee that validation of untrusted data is safe - it can still lead to some undesirable results. +::: danger Please note +Following all these recommendation is not a guarantee that validation using of untrusted data is safe - it can still lead to some undesirable results. +::: ## ReDoS attack @@ -66,7 +65,11 @@ Certain regular expressions can lead to the exponential evaluation time even wit Please assess the regular expressions you use in the schemas on their vulnerability to this attack - see [safe-regex](https://github.com/substack/safe-regex), for example. -**Please note**: some formats that [ajv-formats](https://github.com/ajv-validator/ajv-formats) package implements use [regular expressions](https://github.com/ajv-validator/ajv-formats/blob/master/src/formats.ts) that can be vulnerable to ReDoS attack, so if you use Ajv to validate data from untrusted sources **it is strongly recommended** to consider the following: +::: warning Please note +Some formats that [ajv-formats](https://github.com/ajv-validator/ajv-formats) package implements use [regular expressions](https://github.com/ajv-validator/ajv-formats/blob/master/src/formats.ts) that can be vulnerable to ReDoS attack. +::: + +If you use Ajv to validate data from untrusted sources **it is strongly recommended** to consider the following: - making assessment of "format" implementations in [ajv-formats](https://github.com/ajv-validator/ajv-formats). - passing `"fast"` option to ajv-formats plugin (see its docs) that simplifies some of the regular expressions (although it does not guarantee that they are safe). @@ -79,6 +82,8 @@ Whatever mitigation you choose, please assume all formats provided by ajv-format When using Ajv in a browser page with enabled Content Security Policy (CSP), `script-src` directive must include `'unsafe-eval'`. -**Please note**: `unsafe-eval` is NOT recommended in a secure CSP[[1]](https://developer.chrome.com/extensions/contentSecurityPolicy#relaxing-eval), as it has the potential to open the document to cross-site scripting (XSS) attacks. +::: warning Please note +`unsafe-eval` is NOT recommended in a secure CSP[[1]](https://developer.chrome.com/extensions/contentSecurityPolicy#relaxing-eval), as it has the potential to open the document to cross-site scripting (XSS) attacks. +::: In order to use Ajv without relaxing CSP, you can [compile the schemas using CLI](https://github.com/ajv-validator/ajv-cli#compile-schemas) or programmatically in your build code - see [Standalone validation code](./standalone.md). Compiled JavaScript file can export one or several validation functions that have the same code as the schemas compiled at runtime. diff --git a/docs/standalone.md b/docs/standalone.md index c2facb7b2..7549286cd 100644 --- a/docs/standalone.md +++ b/docs/standalone.md @@ -1,5 +1,7 @@ # Standalone validation code +[[toc]] + Ajv supports generating standalone modules with exported validation function(s), with one default export or multiple named exports, that are pre-compiled and can be used without Ajv. It is useful for several reasons: - to reduce the browser bundle size - Ajv is not included in the bundle (although if you have a large number of schemas the bundle can become bigger - when the total size of generated validation code is bigger than Ajv code). @@ -76,7 +78,7 @@ To support standalone code generation: - Ajv option `source.code` must be set to `true` - only `code` and `macro` user-defined keywords are supported (see [User defined keywords](./keywords.md)). -- when `code` keywords define variables in shared scope using `gen.scopeValue`, they must provide `code` property with the code snippet. See source code of pre-defined keywords for examples in [vocabularies folder](../lib/vocabularies). +- when `code` keywords define variables in shared scope using `gen.scopeValue`, they must provide `code` property with the code snippet. See source code of pre-defined keywords for examples in [vocabularies folder](https://github.com/ajv-validator/ajv/blob/master/lib/vocabularies). - if formats are used in standalone code, ajv option `code.formats` should contain the code snippet that will evaluate to an object with all used formats definition - it can be a call to `require("...")` with the correct path (relative to the location of saved module): ```javascript diff --git a/docs/strict-mode.md b/docs/strict-mode.md index b6be34593..66dbe95cb 100644 --- a/docs/strict-mode.md +++ b/docs/strict-mode.md @@ -4,24 +4,7 @@ Strict mode intends to prevent any unexpected behaviours or silently ignored mis To disable all strict mode restrictions use option `strict: false`. Some of the restrictions can be changed with their own options -- [JSON Type Definition schemas](#json-type-definition-schemas) -- [JSON Schema schemas](#json-schema-schemas) - - [Prohibit ignored keywords](#prohibit-ignored-keywords) - - unknown keywords - - ignored "additionalItems" keyword - - ignored "if", "then", "else" keywords - - ignored "contains", "maxContains" and "minContains" keywords - - unknown formats - - ignored defaults - - [Prevent unexpected validation](#prevent-unexpected-validation) - - overlap between "properties" and "patternProperties" keywords (also `allowMatchingProperties` option) - - required properties have to be present in properties [Strict required](#strict-required) - - unconstrained tuples (also `strictTuples` option) - - [Strict types](#strict-types) (also `strictTypes` option) - - union types (also `allowUnionTypes` option) - - contradictory types - - require applicable types - - [Strict number validation](#strict-number-validation) +[[toc]] ## JSON Type Definition schemas @@ -42,7 +25,7 @@ JSON Schema specification is very permissive and allows many elements in the sch ### Prohibit ignored keywords -#### Prohibit unknown keywords +#### Unknown keywords JSON Schema [section 6.5](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-6.5) requires to ignore unknown keywords. The motivation is to increase cross-platform portability of schemas, so that implementations that do not support certain keywords can still do partial validation. @@ -63,19 +46,19 @@ or ajv.addVocabulary(["allowed1", "allowed2"]) ``` -#### Prohibit ignored "additionalItems" keyword +#### Ignored "additionalItems" keyword JSON Schema section [9.3.1.2](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.2) requires to ignore "additionalItems" keyword if "items" keyword is absent or if it is not an array of items. This is inconsistent with the interaction of "additionalProperties" and "properties", and may cause unexpected results. By default Ajv fails schema compilation when "additionalItems" is used without "items" (or if "items" is not an array). -#### Prohibit ignored "if", "then", "else" keywords +#### Ignored "if", "then", "else" keywords JSON Schema section [9.2.2](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2) requires to ignore "if" (only annotations are collected) if both "then" and "else" are absent, and ignore "then"/"else" if "if" is absent. By default Ajv fails schema compilation in these cases. -#### Prohibit ignored "contains", "maxContains" and "minContains" keywords +#### Ignored "contains", "maxContains" and "minContains" keywords JSON Schema sections [6.4.4, 6.4.5](https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.6.4.4) require to ignore keywords "maxContains" and "minContains" if "contains" keyword is absent. @@ -83,9 +66,9 @@ It is also implied that when "minContains" is 0 and "maxContains" is absent, "co By default Ajv fails schema compilation in these cases. -#### Prohibit unknown formats +#### Unknown formats -By default unknown formats throw exception during schema compilation (and fail validation in case format keyword value is [\$data reference](./validation.md#data-reference)). It is possible to opt out of format validation completely with options `validateFormats: false`. You can define all known formats with `addFormat` method or `formats` option - to have some format ignored pass `true` as its definition: +By default unknown formats throw exception during schema compilation (and fail validation in case format keyword value is [\$data reference](./guide/combining-schemas.md#data-reference)). It is possible to opt out of format validation completely with options `validateFormats: false`. You can define all known formats with `addFormat` method or `formats` option - to have some format ignored pass `true` as its definition: ```javascript const ajv = new Ajv({formats: { @@ -93,15 +76,15 @@ const ajv = new Ajv({formats: { }) ``` -Standard JSON Schema formats are provided in [ajv-formats](https://github.com/ajv-validator/ajv-formats) package - see [Formats](./validation.md#formats) section. +Standard JSON Schema formats are provided in [ajv-formats](https://github.com/ajv-validator/ajv-formats) package - see [Formats](./guide/formats) section. -#### Prohibit ignored defaults +#### Ignored defaults -With `useDefaults` option Ajv modifies validated data by assigning defaults from the schema, but there are different limitations when the defaults can be ignored (see [Assigning defaults](./validation.md#assigning-defaults)). In strict mode Ajv fails schema compilation if such defaults are used in the schema. +With `useDefaults` option Ajv modifies validated data by assigning defaults from the schema, but there are different limitations when the defaults can be ignored (see [Assigning defaults](./guide/modifying-data.md#assigning-defaults)). In strict mode Ajv fails schema compilation if such defaults are used in the schema. ### Prevent unexpected validation -#### Prohibit overlap between "properties" and "patternProperties" keywords +#### Overlap between "properties" and "patternProperties" keywords The expectation of users (see #196, #286) is that "patternProperties" only apply to properties not already defined in "properties" keyword, but JSON Schema section [9.3.2](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2) defines these two keywords as independent. It means that to some properties two subschemas can be applied - one defined in "properties" keyword and another defined in "patternProperties" for the pattern matching this property. @@ -111,15 +94,17 @@ In addition to allowing such patterns by using option `strict: false`, there is To reiterate, neither this nor other strict mode restrictions change the validation results - they only restrict which schemas are valid. -#### Strict required +#### Defined required properties With option `strictRequired` set to `"log"` or `true` Ajv logs warning or throws exception if the property used in "required" keyword is not defined in "properties" keyword in the same or some parent schema relating to the same object (data instance). By default this option is disabled. -**Please note** there are certain scenarios when property defined in the parent schema will not be taken into account. +::: warning Please note +there are certain scenarios when property defined in the parent schema will not be taken into account. +::: -#### Prohibit unconstrained tuples +#### Unconstrained tuples Ajv also logs a warning if "items" is an array (for schema that defines a tuple) but neither "minItems" nor "additionalItems"/"maxItems" keyword is present (or have a wrong value): @@ -149,11 +134,11 @@ Use `strictTuples` option to suppress this warning (`false`) or turn it into exc If you use `JSONSchemaType` this mistake will also be prevented on a type level. -### Strict types +### Strict types An additional option `strictTypes` ("log" by default) imposes additional restrictions on how type keyword is used: -#### Prohibit union types +#### Union types With `strictTypes` option "type" keywords with multiple types (other than with "null") are prohibited. @@ -239,7 +224,7 @@ It also can be refactored: This restriction can be lifted separately from other `strictTypes` restrictions with `allowUnionTypes: true` option. -#### Prohibit contradictory types +#### Contradictory types Subschemas can apply to the same data instance, and it is possible to have contradictory type keywords - it usually indicate some mistake. For example: @@ -265,7 +250,9 @@ The schema above violates `strictTypes` as "array" type is not compatible with o } ``` -**Please note**: type "number" can be narrowed to "integer", the opposite would violate `strictTypes`. +::: warning Please note +Type "number" can be narrowed to "integer", the opposite would violate `strictTypes`. +::: #### Require applicable types diff --git a/docs/validation.md b/docs/validation.md deleted file mode 100644 index 1a43c374f..000000000 --- a/docs/validation.md +++ /dev/null @@ -1,729 +0,0 @@ -# Data validation - -- [Data validation](#data-validation) - - [JSON Schema draft-2019-09](#json-schema-draft-2019-09) - - [Validation basics](#validation-basics) - - [JSON Schema validation keywords](#json-schema-validation-keywords) - - [Annotation keywords](#annotation-keywords) - - [Formats](#formats) - - [Modular schemas](#modular-schemas) - - [Combining schemas with \$ref](#combining-schemas-with-ref) - - [Extending recursive schemas](#extending-recursive-schemas) - - [\$data reference](#data-reference) - - [$merge and $patch keywords](#merge-and-patch-keywords) - - [User-defined keywords](#user-defined-keywords) - - [Asynchronous schema compilation](#asynchronous-schema-compilation) - - [Asynchronous validation](#asynchronous-validation) - - [Using transpilers](#using-transpilers) - - [Modifying data during validation](#modifying-data-during-validation) - - [Removing additional properties](#removing-additional-properties) - - [Assigning defaults](#assigning-defaults) - - [Coercing data types](#coercing-data-types) - -## JSON Schema draft-2019-09 - -The default export of Ajv provides support of JSON-Schema draft-07, without any of draft-2019-09 features: - -```javascript -import Ajv from "ajv" -// const Ajv = require("ajv").default -const ajv = new Ajv() -``` - -To use Ajv with the support of all JSON Schema draft-2019-09 features you need to use a different export: - -```javascript -import Ajv from "ajv/dist/2019" -// const Ajv2019 = require("ajv/dist/2019").default -const ajv = new Ajv2019() -``` - -Optionally, you can add draft-07 meta-schema, to use both draft-07 and draft-2019-09 schemas in one Ajv instance: - -```javascript -const draft7MetaSchema = require("ajv/dist/refs/json-schema-draft-07.json") -ajv.addMetaSchema(draft7MetaSchema) -``` - -Draft-2019-09 support is provided via a separate export in order to avoid increasing the bundle and generated code size for draft-07 users. - -With this import Ajv supports the following features: - -- keywords [`unevaluatedProperties`](./json-schema.md#unevaluatedproperties) and [`unevaluatedItems`](./json-schema.md#unevaluateditems) -- keywords [`dependentRequired`](./json-schema.md#dependentrequired), [`dependentSchemas`](./json-schema.md#dependentschemas), [`maxContains`/`minContain`](./json-schema.md#maxcontains--mincontains) -- dynamic recursive references with [`recursiveAnchor`/`recursiveReference`] - see [Extending recursive schemas](#extending-recursive-schemas) -- draft-2019-09 meta-schema is the default. - -**Please note**: Supporting dynamic recursive references and `unevaluatedProperties/Items` adds additional generated code even to the validation functions where these features are not used (when possible, Ajv determines which properties/items are "unevaluated" at compilation time, but support for dynamic references always adds additional generated code). If you are not using these features in your schemas it is recommended to use default Ajv export with JSON-Schema draft-07 support. - -You can also use individual draft-2019-09 features to Ajv with the advanced options `dynamicRef`, `next` and `unevaluated`. These options are changing how the code is generated for draft-07 keywords to support the new features of draft-2019-09, but they do not add the new keywords - they should be added separately. The code examples below shows how to enable individual draft-2019-09 features: - -```javascript -import Ajv from "ajv" -// const Ajv = require("ajv").default - -// add support for unevaluatedProperties and unevaluatedItems without other 2019-09 features -const ajv = new Ajv({unevaluated: true}) -import unevaluatedVocabulary from "ajv/dist/vocabularies/unevaluated" -// const unevaluatedVocabulary = require("ajv/dist/vocabularies/unevaluated").default -ajv.addVocabulary(unevaluatedVocabulary) - -// add support for dependentRequired, dependentSchemas, maxContains and minContains -const ajv = new Ajv({next: true}) -import nextVocabulary from "ajv/dist/vocabularies/next" -// const nextVocabulary = require("ajv/dist/vocabularies/next").default -ajv.addVocabulary(nextVocabulary) - -// add support for dynamic recursive references -const ajv = new Ajv({dynamicRef: true}) -import dynamicVocabulary from "ajv/dist/vocabularies/dynamic" -// const dynamicVocabulary = require("ajv/dist/vocabularies/dynamic").default -ajv.addVocabulary(dynamicVocabulary) -``` - -If you want to have support of all these features you should import Ajv from `"ajv/dist/2019"` as shown above. - -## Validation basics - -### JSON Schema validation keywords - -Ajv supports all validation keywords from draft-07 of JSON Schema standard - see [JSON Schema validation keywords](./json-schema.md) for more details. - -[ajv-keywords](https://github.com/ajv-validator/ajv-keywords) package provides additional validation keywords that can be used with Ajv. - -### Metadata keywords - -JSON Schema specification defines several metadata keywords that describe the schema itself but do not perform any validation. - -- `title` and `description`: information about the data represented by that schema -- `$comment` (NEW in draft-07): information for developers. With option `$comment` Ajv logs or passes the comment string to the user-supplied function. See [Options](./api.md#options). -- `default`: a default value of the data instance, see [Assigning defaults](#assigning-defaults). -- `examples` (NEW in draft-06): an array of data instances. Ajv does not check the validity of these instances against the schema. -- `readOnly` and `writeOnly` (NEW in draft-07): marks data-instance as read-only or write-only in relation to the source of the data (database, api, etc.). -- `contentEncoding`: [RFC 2045](https://tools.ietf.org/html/rfc2045#section-6.1), e.g., "base64". -- `contentMediaType`: [RFC 2046](https://datatracker.ietf.org/doc/rfc2046/), e.g., "image/png". - -**Please note**: Ajv does not implement validation of the keywords `examples`, `contentEncoding` and `contentMediaType` but it reserves them. If you want to create a plugin that implements any of them, it should remove these keywords from the instance. - -### Formats - -From version 7 Ajv does not include formats defined by JSON Schema specification - these and several other formats are provided by [ajv-formats](https://github.com/ajv-validator/ajv-formats) plugin. - -To add all formats from this plugin: - -```javascript -import Ajv from "ajv" -import addFormats from "ajv-formats" - -const ajv = new Ajv() -addFormats(ajv) -``` - -See ajv-formats documentation for further details. - -It is recommended NOT to use "format" keyword implementations with untrusted data, as they may use potentially unsafe regular expressions (even though known issues are fixed) - see [ReDoS attack](./security.md#redos-attack). - -**Please note**: if you need to use "format" keyword to validate untrusted data, you MUST assess their suitability and safety for your validation scenarios. - -The following formats are defined in [ajv-formats](https://github.com/ajv-validator/ajv-formats) for string validation with "format" keyword: - -- _date_: full-date according to [RFC3339](http://tools.ietf.org/html/rfc3339#section-5.6). -- _time_: time with optional time-zone. -- _date-time_: date-time from the same source (time-zone is mandatory). -- _duration_: duration from [RFC3339](https://tools.ietf.org/html/rfc3339#appendix-A) -- _uri_: full URI. -- _uri-reference_: URI reference, including full and relative URIs. -- _uri-template_: URI template according to [RFC6570](https://datatracker.ietf.org/doc/rfc6570/) -- _url_ (deprecated): [URL record](https://url.spec.whatwg.org/#concept-url). -- _email_: email address. -- _hostname_: host name according to [RFC1034](http://tools.ietf.org/html/rfc1034#section-3.5). -- _ipv4_: IP address v4. -- _ipv6_: IP address v6. -- _regex_: tests whether a string is a valid regular expression by passing it to RegExp constructor. -- _uuid_: Universally Unique Identifier according to [RFC4122](https://datatracker.ietf.org/doc/rfc4122/). -- _json-pointer_: JSON-pointer according to [RFC6901](https://datatracker.ietf.org/doc/rfc6901/). -- _relative-json-pointer_: relative JSON-pointer according to [this draft](http://tools.ietf.org/html/draft-luff-relative-json-pointer-00). - -**Please note**: JSON Schema draft-07 also defines formats `iri`, `iri-reference`, `idn-hostname` and `idn-email` for URLs, hostnames and emails with international characters. These formats are available in [ajv-formats-draft2019](https://github.com/luzlab/ajv-formats-draft2019) plugin. - -You can add and replace any formats using [addFormat](./api.md#api-addformat) method. - -## Modular schemas - -### Combining schemas with \$ref - -You can structure your validation logic across multiple schema files and have schemas reference each other using `$ref` keyword. - -Example: - -```javascript -const schema = { - $id: "http://example.com/schemas/schema.json", - type: "object", - properties: { - foo: {$ref: "defs.json#/definitions/int"}, - bar: {$ref: "defs.json#/definitions/str"}, - }, -} - -const defsSchema = { - $id: "http://example.com/schemas/defs.json", - definitions: { - int: {type: "integer"}, - str: {type: "string"}, - }, -} -``` - -Now to compile your schema you can either pass all schemas to Ajv instance: - -```javascript -const ajv = new Ajv({schemas: [schema, defsSchema]}) -const validate = ajv.getSchema("http://example.com/schemas/schema.json") -``` - -or use `addSchema` method: - -```javascript -const ajv = new Ajv() -const validate = ajv.addSchema(defsSchema).compile(schema) -``` - -See [Options](./api.md#options) and [addSchema](./api.md#add-schema) method. - -**Please note**: - -- `$ref` is resolved as the uri-reference using schema \$id as the base URI (see the example). -- References can be recursive (and mutually recursive) to implement the schemas for different data structures (such as linked lists, trees, graphs, etc.). -- You don't have to host your schema files at the URIs that you use as schema \$id. These URIs are only used to identify the schemas, and according to JSON Schema specification validators should not expect to be able to download the schemas from these URIs. -- The actual location of the schema file in the file system is not used. -- You can pass the identifier of the schema as the second parameter of `addSchema` method or as a property name in `schemas` option. This identifier can be used instead of (or in addition to) schema \$id. -- You cannot have the same \$id (or the schema identifier) used for more than one schema - the exception will be thrown. -- You can implement dynamic resolution of the referenced schemas using `compileAsync` method. In this way you can store schemas in any system (files, web, database, etc.) and reference them without explicitly adding to Ajv instance. See [Asynchronous schema compilation](./validation.md#asynchronous-schema-compilation). - -### Extending recursive schemas - -While statically defined `$ref` keyword allows to split schemas to multiple files, it is difficult to extend recursive schemas - the recursive reference(s) in the original schema points to the original schema, and not to the extended one. So in JSON Schema draft-07 the only available solution to extend the recursive schema was to redefine all sections of the original schema that have recursion. - -It was particularly repetitive when extending meta-schema, as it has many recursive references, but even in a schema with a single recursive reference extending it was very verbose. - -JSON Schema draft-2019-09 and the upcoming draft defined the mechanism for dynamic recursion using keywords `$recursiveRef`/`$recursiveAnchor` (draft-2019-09) or `$dynamicRef`/`$dynamicAnchor` (the next JSON Schema draft) that is somewhat similar to "open recursion" in functional programming. - -Consider this recursive schema with static recursion: - -```javascript -const treeSchema = { - $id: "https://example.com/tree", - type: "object", - required: ["data"], - properties: { - data: true, - children: { - type: "array", - items: {$ref: "#"}, - }, - }, -} -``` - -The only way to extend this schema to prohibit additional properties is by adding `additionalProperties` keyword right in the schema - this approach can be impossible if you do not control the source of the original schema. Ajv also provided the additional keywords in [ajv-merge-patch](https://github.com/ajv-validator/ajv-merge-patch) package to extend schemas by treating them as plain JSON data. While this approach may work for you, it is non-standard and therefore not portable. - -The new keywords for dynamic recursive references allow extending this schema without modifying it: - -```javascript -const treeSchema = { - $id: "https://example.com/tree", - $recursiveAnchor: true, - type: "object", - required: ["data"], - properties: { - data: true, - children: { - type: "array", - items: {$recursiveRef: "#"}, - }, - }, -} - -const strictTreeSchema = { - $id: "https://example.com/strict-tree", - $recursiveAnchor: true, - $ref: "tree", - unevaluatedProperties: false, -} - -import Ajv2019 from "ajv/dist/2019" -// const Ajv2019 = require("ajv/dist/2019").default -const ajv = new Ajv2019({ - schemas: [treeSchema, strictTreeSchema], -}) -const validate = ajv.getSchema("https://example.com/strict-tree") -``` - -See [dynamic-refs](../spec/dynamic-ref.spec.ts) test for the example using `$dynamicAnchor`/`$dynamicRef`. - -At the moment Ajv implements the spec for dynamic recursive references with these limitations: - -- `$recursiveAnchor`/`$dynamicAnchor` can only be used in the schema root. -- `$recursiveRef`/`$dynamicRef` can only be hash fragments, without URI. - -Ajv also does not support dynamic references in [asynchronous schemas](#asynchronous-validation) (Ajv extension) - it is assumed that the referenced schema is synchronous, and there is no validation-time check for it. - -### \$data reference - -With `$data` option you can use values from the validated data as the values for the schema keywords. See [proposal](https://github.com/json-schema-org/json-schema-spec/issues/51) for more information about how it works. - -`$data` reference is supported in the keywords: const, enum, format, maximum/minimum, exclusiveMaximum / exclusiveMinimum, maxLength / minLength, maxItems / minItems, maxProperties / minProperties, formatMaximum / formatMinimum, formatExclusiveMaximum / formatExclusiveMinimum, multipleOf, pattern, required, uniqueItems. - -The value of "$data" should be a [JSON-pointer](https://datatracker.ietf.org/doc/rfc6901/) to the data (the root is always the top level data object, even if the $data reference is inside a referenced subschema) or a [relative JSON-pointer](http://tools.ietf.org/html/draft-luff-relative-json-pointer-00) (it is relative to the current point in data; if the \$data reference is inside a referenced subschema it cannot point to the data outside of the root level for this subschema). - -Examples. - -This schema requires that the value in property `smaller` is less or equal than the value in the property larger: - -```javascript -const ajv = new Ajv({$data: true}) - -const schema = { - properties: { - smaller: { - type: "number", - maximum: {$data: "1/larger"}, - }, - larger: {type: "number"}, - }, -} - -const validData = { - smaller: 5, - larger: 7, -} - -ajv.validate(schema, validData) // true -``` - -This schema requires that the properties have the same format as their field names: - -```javascript -const schema = { - additionalProperties: { - type: "string", - format: {$data: "0#"}, - }, -} - -const validData = { - "date-time": "1963-06-19T08:30:06.283185Z", - email: "joe.bloggs@example.com", -} -``` - -`$data` reference is resolved safely - it won't throw even if some property is undefined. If `$data` resolves to `undefined` the validation succeeds (with the exclusion of `const` keyword). If `$data` resolves to incorrect type (e.g. not "number" for maximum keyword) the validation fails. - -### $merge and $patch keywords - -With the package [ajv-merge-patch](https://github.com/ajv-validator/ajv-merge-patch) you can use the keywords `$merge` and `$patch` that allow extending JSON Schemas with patches using formats [JSON Merge Patch (RFC 7396)](https://datatracker.ietf.org/doc/rfc7396/) and [JSON Patch (RFC 6902)](https://datatracker.ietf.org/doc/rfc6902/). - -To add keywords `$merge` and `$patch` to Ajv instance use this code: - -```javascript -require("ajv-merge-patch")(ajv) -``` - -Examples. - -Using `$merge`: - -```javascript -{ - $merge: { - source: { - type: "object", - properties: {p: {type: "string"}}, - additionalProperties: false - }, - with: { - properties: {q: {type: "number"}} - } - } -} -``` - -Using `$patch`: - -```javascript -{ - $patch: { - source: { - type: "object", - properties: {p: {type: "string"}}, - additionalProperties: false - }, - with: [{op: "add", path: "/properties/q", value: {type: "number"}}] - } -} -``` - -The schemas above are equivalent to this schema: - -```javascript -{ - type: "object", - properties: { - p: {type: "string"}, - q: {type: "number"} - }, - additionalProperties: false -} -``` - -The properties `source` and `with` in the keywords `$merge` and `$patch` can use absolute or relative `$ref` to point to other schemas previously added to the Ajv instance or to the fragments of the current schema. - -See the package [ajv-merge-patch](https://github.com/ajv-validator/ajv-merge-patch) for more information. - -## User-defined keywords - -The advantages of defining keywords are: - -- allow creating validation scenarios that cannot be expressed using pre-defined keywords -- simplify your schemas -- help bringing a bigger part of the validation logic to your schemas -- make your schemas more expressive, less verbose and closer to your application domain -- implement data processors that modify your data (`modifying` option MUST be used in keyword definition) and/or create side effects while the data is being validated - -If a keyword is used only for side-effects and its validation result is pre-defined, use option `valid: true/false` in keyword definition to simplify both generated code (no error handling in case of `valid: true`) and your keyword functions (no need to return any validation result). - -The concerns you have to be aware of when extending JSON Schema standard with additional keywords are the portability and understanding of your schemas. You will have to support these keywords on other platforms and to properly document them so that everybody can understand and use your schemas. - -You can define keywords with [addKeyword](./api.md#api-addkeyword) method. Keywords are defined on the `ajv` instance level - new instances will not have previously defined keywords. - -Ajv allows defining keywords with: - -- code generation function (used by all pre-defined keywords) -- validation function -- compilation function -- macro function - -Example. `range` and `exclusiveRange` keywords using compiled schema: - -```javascript -ajv.addKeyword({ - keyword: "range", - type: "number", - schemaType: "array", - implements: "exclusiveRange", - compile: ([min, max], parentSchema) => - parentSchema.exclusiveRange === true - ? (data) => data > min && data < max - : (data) => data >= min && data <= max, -}) - -const schema = {range: [2, 4], exclusiveRange: true} -const validate = ajv.compile(schema) -console.log(validate(2.01)) // true -console.log(validate(3.99)) // true -console.log(validate(2)) // false -console.log(validate(4)) // false -``` - -Several keywords (typeof, instanceof, range and propertyNames) are defined in [ajv-keywords](https://github.com/ajv-validator/ajv-keywords) package - they can be used for your schemas and as a starting point for your own keywords. - -See [User-defined keywords](./keywords.md) for more details. - -## Asynchronous schema compilation - -During asynchronous compilation remote references are loaded using supplied function. See `compileAsync` [method](./api.md#api-compileAsync) and `loadSchema` [option](./api.md#options). - -Example: - -```javascript -const ajv = new Ajv({loadSchema: loadSchema}) - -ajv.compileAsync(schema).then(function (validate) { - const valid = validate(data) - // ... -}) - -function loadSchema(uri) { - return request.json(uri).then(function (res) { - if (res.statusCode >= 400) throw new Error("Loading error: " + res.statusCode) - return res.body - }) -} -``` - -**Please note**: [Option](./api.md#options) `missingRefs` should NOT be set to `"ignore"` or `"fail"` for asynchronous compilation to work. - -## Asynchronous validation - -Example in Node.js REPL: https://runkit.com/esp/ajv-asynchronous-validation - -You can define formats and keywords that perform validation asynchronously by accessing database or some other service. You should add `async: true` in the keyword or format definition (see [addFormat](./api.md#api-addformat), [addKeyword](./api.md#api-addkeyword) and [User-defined keywords](./keywords.md)). - -If your schema uses asynchronous formats/keywords or refers to some schema that contains them it should have `"$async": true` keyword so that Ajv can compile it correctly. If asynchronous format/keyword or reference to asynchronous schema is used in the schema without `$async` keyword Ajv will throw an exception during schema compilation. - -**Please note**: all asynchronous subschemas that are referenced from the current or other schemas should have `"$async": true` keyword as well, otherwise the schema compilation will fail. - -Validation function for an asynchronous format/keyword should return a promise that resolves with `true` or `false` (or rejects with `new Ajv.ValidationError(errors)` if you want to return errors from the keyword function). - -Ajv compiles asynchronous schemas to [async functions](http://tc39.github.io/ecmascript-asyncawait/). Async functions are supported in Node.js 7+ and all modern browsers. You can supply a transpiler as a function via `processCode` option. See [Options](./api.md#options). - -The compiled validation function has `$async: true` property (if the schema is asynchronous), so you can differentiate these functions if you are using both synchronous and asynchronous schemas. - -Validation result will be a promise that resolves with validated data or rejects with an exception `Ajv.ValidationError` that contains the array of validation errors in `errors` property. - -Example: - -```javascript -const ajv = new Ajv() - -ajv.addKeyword({ - keyword: "idExists" - async: true, - type: "number", - validate: checkIdExists, -}) - -function checkIdExists(schema, data) { - return knex(schema.table) - .select("id") - .where("id", data) - .then(function (rows) { - return !!rows.length // true if record is found - }) -} - -const schema = { - $async: true, - properties: { - userId: { - type: "integer", - idExists: {table: "users"}, - }, - postId: { - type: "integer", - idExists: {table: "posts"}, - }, - }, -} - -const validate = ajv.compile(schema) - -validate({userId: 1, postId: 19}) - .then(function (data) { - console.log("Data is valid", data) // { userId: 1, postId: 19 } - }) - .catch(function (err) { - if (!(err instanceof Ajv.ValidationError)) throw err - // data is invalid - console.log("Validation errors:", err.errors) - }) -``` - -### Using transpilers - -```javascript -const ajv = new Ajv({processCode: transpileFunc}) -const validate = ajv.compile(schema) // transpiled es7 async function -validate(data).then(successFunc).catch(errorFunc) -``` - -See [Options](./api.md#options). - -## Modifying data during validation - -### Removing additional properties - -With [option `removeAdditional`](./api.md#options) (added by [andyscott](https://github.com/andyscott)) you can filter data during the validation. - -This option modifies original data. - -Example: - -```javascript -const ajv = new Ajv({removeAdditional: true}) -const schema = { - additionalProperties: false, - properties: { - foo: {type: "number"}, - bar: { - additionalProperties: {type: "number"}, - properties: { - baz: {type: "string"}, - }, - }, - }, -} - -const data = { - foo: 0, - additional1: 1, // will be removed; `additionalProperties` == false - bar: { - baz: "abc", - additional2: 2, // will NOT be removed; `additionalProperties` != false - }, -} - -const validate = ajv.compile(schema) - -console.log(validate(data)) // true -console.log(data) // { "foo": 0, "bar": { "baz": "abc", "additional2": 2 } -``` - -If `removeAdditional` option in the example above were `"all"` then both `additional1` and `additional2` properties would have been removed. - -If the option were `"failing"` then property `additional1` would have been removed regardless of its value and property `additional2` would have been removed only if its value were failing the schema in the inner `additionalProperties` (so in the example above it would have stayed because it passes the schema, but any non-number would have been removed). - -**Please note**: If you use `removeAdditional` option with `additionalProperties` keyword inside `anyOf`/`oneOf` keywords your validation can fail with this schema, for example: - -```javascript -{ - type: "object", - oneOf: [ - { - properties: { - foo: {type: "string"} - }, - required: ["foo"], - additionalProperties: false - }, - { - properties: { - bar: {type: "integer"} - }, - required: ["bar"], - additionalProperties: false - } - ] -} -``` - -The intention of the schema above is to allow objects with either the string property "foo" or the integer property "bar", but not with both and not with any other properties. - -With the option `removeAdditional: true` the validation will pass for the object `{ "foo": "abc"}` but will fail for the object `{"bar": 1}`. It happens because while the first subschema in `oneOf` is validated, the property `bar` is removed because it is an additional property according to the standard (because it is not included in `properties` keyword in the same schema). - -While this behaviour is unexpected (issues [#129](https://github.com/ajv-validator/ajv/issues/129), [#134](https://github.com/ajv-validator/ajv/issues/134)), it is correct. To have the expected behaviour (both objects are allowed and additional properties are removed) the schema has to be refactored in this way: - -```javascript -{ - type: "object", - properties: { - foo: {type: "string"}, - bar: {type: "integer"} - }, - additionalProperties: false, - oneOf: [{required: ["foo"]}, {required: ["bar"]}] -} -``` - -The schema above is also more efficient - it will compile into a faster function. - -### Assigning defaults - -With [option `useDefaults`](./api.md#options) Ajv will assign values from `default` keyword in the schemas of `properties` and `items` (when it is the array of schemas) to the missing properties and items. - -With the option value `"empty"` properties and items equal to `null` or `""` (empty string) will be considered missing and assigned defaults. - -This option modifies original data. - -**Please note**: the default value is inserted in the generated validation code as a literal, so the value inserted in the data will be the deep clone of the default in the schema. - -Example 1 (`default` in `properties`): - -```javascript -const ajv = new Ajv({useDefaults: true}) -const schema = { - type: "object", - properties: { - foo: {type: "number"}, - bar: {type: "string", default: "baz"}, - }, - required: ["foo", "bar"], -} - -const data = {foo: 1} - -const validate = ajv.compile(schema) - -console.log(validate(data)) // true -console.log(data) // { "foo": 1, "bar": "baz" } -``` - -Example 2 (`default` in `items`): - -```javascript -const schema = { - type: "array", - items: [{type: "number"}, {type: "string", default: "foo"}], -} - -const data = [1] - -const validate = ajv.compile(schema) - -console.log(validate(data)) // true -console.log(data) // [ 1, "foo" ] -``` - -With `useDefaults` option `default` keywords throws exception during schema compilation when used in: - -- not in `properties` or `items` subschemas -- in schemas inside `anyOf`, `oneOf` and `not` (see [#42](https://github.com/ajv-validator/ajv/issues/42)) -- in `if` schema -- in schemas generated by user-defined _macro_ keywords - -The strict mode option can change the behaviour for these unsupported defaults (`strict: false` to ignore them, `"log"` to log a warning). - -See [Strict mode](./strict-mode.md). - -### Coercing data types - -When you are validating user inputs all your data properties are usually strings. The option `coerceTypes` allows you to have your data types coerced to the types specified in your schema `type` keywords, both to pass the validation and to use the correctly typed data afterwards. - -This option modifies original data. - -**Please note**: if you pass a scalar value to the validating function its type will be coerced and it will pass the validation, but the value of the variable you pass won't be updated because scalars are passed by value. - -Example 1: - -```javascript -const ajv = new Ajv({coerceTypes: true}) -const schema = { - type: "object", - properties: { - foo: {type: "number"}, - bar: {type: "boolean"}, - }, - required: ["foo", "bar"], -} - -const data = {foo: "1", bar: "false"} - -const validate = ajv.compile(schema) - -console.log(validate(data)) // true -console.log(data) // { "foo": 1, "bar": false } -``` - -Example 2 (array coercions): - -```javascript -const ajv = new Ajv({coerceTypes: "array"}) -const schema = { - properties: { - foo: {type: "array", items: {type: "number"}}, - bar: {type: "boolean"}, - }, -} - -const data = {foo: "1", bar: ["false"]} - -const validate = ajv.compile(schema) - -console.log(validate(data)) // true -console.log(data) // { "foo": [1], "bar": false } -``` - -The coercion rules, as you can see from the example, are different from JavaScript both to validate user input as expected and to have the coercion reversible (to correctly validate cases where different types are defined in subschemas of "anyOf" and other compound keywords). - -See [Coercion rules](./coercion.md) for details. diff --git a/lib/types/index.ts b/lib/types/index.ts index 79ffa12df..7aa1c32f6 100644 --- a/lib/types/index.ts +++ b/lib/types/index.ts @@ -102,7 +102,7 @@ interface _KeywordDef { type?: JSONType | JSONType[] // data types that keyword applies to schemaType?: JSONType | JSONType[] // allowed type(s) of keyword value in the schema allowUndefined?: boolean // used for keywords that can be invoked by other keywords, not being present in the schema - $data?: boolean // keyword supports [$data reference](../../docs/validation.md#data-reference) + $data?: boolean // keyword supports [$data reference](../../docs/guide/combining-schemas.md#data-reference) implements?: string[] // other schema keywords that this keyword implements before?: string // keyword should be executed before this keyword (should be applicable to the same type) post?: boolean // keyword should be executed after other keywords without post flag diff --git a/lib/types/jtd-schema.ts b/lib/types/jtd-schema.ts index aecf5b3b0..1dc4e3fb9 100644 --- a/lib/types/jtd-schema.ts +++ b/lib/types/jtd-schema.ts @@ -46,14 +46,20 @@ type StringType = "string" | "timestamp" /** actual schema */ export type JTDSchemaType = Record> = ( | // refs - where null wasn't specified, must match exactly - ({[K in keyof D]: [T] extends [D[K]] ? {ref: K} : never}[keyof D] & {nullable?: false}) + (null extends EnumString + ? never + : {[K in keyof D]: [T] extends [D[K]] ? {ref: K} : never}[keyof D] & {nullable?: false}) // nulled refs - if ref is nullable and nullable is specified, then it can // match either null or non-null definitions - | (null extends T + | (null extends EnumString + ? never + : null extends T ? { [K in keyof D]: [Exclude] extends [Exclude] ? {ref: K} : never }[keyof D] & {nullable: true} : never) + // empty - empty schemas also treat nullable differently in that it's now fully ignored + | (unknown extends T ? {nullable?: boolean} : never) // all other types | (( | // numbers - only accepts the type number @@ -129,14 +135,6 @@ export type JTDSchemaType = Record] : never) - // empty schema - // NOTE there should only be one type that extends Record so unions - // shouldn't be a worry - | (T extends Record ? unknown : never) - // null - // NOTE we have to check this too because null as an exclusive type also - // qualifies for the empty schema - | (true extends TypeEquality ? unknown : never) ) & (null extends T ? { diff --git a/package.json b/package.json index f70790479..13f1c6887 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,9 @@ "test": "npm link && npm link ajv && npm run json-tests && npm run eslint && npm run test-cov", "test-ci": "AJV_FULL_TEST=true npm test", "prepublish": "npm run build", - "benchmark": "npm i && npm run build && npm link && cd ./benchmark && npm link ajv && npm i && node ./jtd" + "benchmark": "npm i && npm run build && npm link && cd ./benchmark && npm link ajv && npm i && node ./jtd", + "docs:dev": "vuepress dev docs", + "docs:build": "vuepress build docs" }, "nyc": { "exclude": [ @@ -75,6 +77,7 @@ "@types/require-from-string": "^1.2.0", "@typescript-eslint/eslint-plugin": "^3.8.0", "@typescript-eslint/parser": "^3.8.0", + "@vuepress/shared-utils": "^1.8.2", "ajv-formats": "^1.5.0", "browserify": "^17.0.0", "chai": "^4.0.1", @@ -96,7 +99,8 @@ "terser": "^5.2.1", "ts-node": "^9.0.0", "tsify": "^5.0.2", - "typescript": "^4.2.0" + "typescript": "^4.2.0", + "vuepress": "^1.8.2" }, "collective": { "type": "opencollective", diff --git a/scripts/publish-gh-pages b/scripts/publish-site similarity index 66% rename from scripts/publish-gh-pages rename to scripts/publish-site index 6119ac45c..c09758432 100755 --- a/scripts/publish-gh-pages +++ b/scripts/publish-site @@ -3,23 +3,26 @@ set -ex echo "About to publish $GITHUB_REF to gh-pages..." - rm -rf ../gh-pages +function copyReplace { + sed "s/](.\/docs\//](.\//g" $1 > $2 +} + +copyReplace README.md docs/README.md +copyReplace CODE_OF_CONDUCT.md docs/code_of_conduct.md +copyReplace CONTRIBUTING.md docs/contributing.md +copyReplace LICENSE docs/license.md + +npm run docs:build + git config --global user.name "$GIT_USER_NAME" git config --global user.email "$GIT_USER_EMAIL" git clone -b gh-pages --single-branch https://"${GH_TOKEN_PUBLIC}"@github.com/ajv-validator/ajv.git ../gh-pages -SOURCE=../gh-pages/_source -mkdir -p $SOURCE -cp *.md $SOURCE -cp -R docs $SOURCE -cp LICENSE $SOURCE -cd ../gh-pages -node ./generate -# remove logo from README -sed -E "s/]+ajv_logo[^>]+>//" index.md > new-index.md -mv new-index.md index.md +rsync -a ./docs/.vuepress/dist/ ../gh-pages/docs + +cd ../gh-pages if [[ $(git status --porcelain) ]]; then echo "Changes detected. Updating gh-pages branch..." diff --git a/spec/types/jtd-schema.spec.ts b/spec/types/jtd-schema.spec.ts index 60e3c1662..da88e8982 100644 --- a/spec/types/jtd-schema.spec.ts +++ b/spec/types/jtd-schema.spec.ts @@ -52,8 +52,10 @@ describe("JTDSchemaType", () => { // @ts-expect-error const nums: JTDSchemaType<1 | 2 | 3> = {type: "int32"} const numNull: JTDSchemaType = {type: "int32", nullable: true} + // @ts-expect-error + const numNotNull: JTDSchemaType = {type: "float32"} - void [numf, numi, numl, nums, numNull] + void [numf, numi, numl, nums, numNull, numNotNull] }) it("should typecheck boolean schemas", () => { @@ -175,17 +177,7 @@ describe("JTDSchemaType", () => { nullable: true, } - // can't use properties for any object (e.g. keyof = never) - const noProperties: TypeEquality, never> = true - - void [ - properties, - optionalProperties, - mixedProperties, - fewerProperties, - propertiesNull, - noProperties, - ] + void [properties, optionalProperties, mixedProperties, fewerProperties, propertiesNull] }) it("should typecheck discriminator schemas", () => { @@ -258,14 +250,20 @@ describe("JTDSchemaType", () => { }) it("should typecheck empty schemas", () => { - const empty: JTDSchemaType> = {} + const empty: JTDSchemaType = {} + // unknown can be null + const emptyUnknown: JTDSchemaType = {nullable: true} + // somewhat unintuitively, it can still have nullable: false even though it can be null + const falseUnknown: JTDSchemaType = {nullable: false} // can only use empty for empty and null // @ts-expect-error const emptyButFull: JTDSchemaType<{a: string}> = {} - const emptyNull: JTDSchemaType = {nullable: true} - const emptyMeta: JTDSchemaType> = {metadata: {}} + const emptyMeta: JTDSchemaType = {metadata: {}} + + // constant null not representable + const emptyNull: TypeEquality, never> = true - void [empty, emptyButFull, emptyNull, emptyMeta] + void [empty, emptyUnknown, falseUnknown, emptyButFull, emptyMeta, emptyNull] }) it("should typecheck ref schemas", () => { @@ -311,17 +309,9 @@ describe("JTDSchemaType", () => { it("should typecheck metadata schemas", () => { const meta: JTDSchemaType = {type: "float32", metadata: {key: "val"}} - const emptyMeta: JTDSchemaType> = {metadata: {key: "val"}} - const nullMeta: JTDSchemaType = {nullable: true, metadata: {key: "val"}} - - void [meta, emptyMeta, nullMeta] - }) - - it("should typecheck nullable schemas", () => { - const isNull: JTDSchemaType = {nullable: true} - // @ts-expect-error - const numNotNull: JTDSchemaType = {type: "float32"} + const emptyMeta: JTDSchemaType = {metadata: {key: "val"}} + const unknownMeta: JTDSchemaType = {nullable: true, metadata: {key: "val"}} - void [isNull, numNotNull] + void [meta, emptyMeta, unknownMeta] }) })