更新前端静态网页获取方式,放弃使用后端获取api

This commit is contained in:
2025-09-09 10:47:51 +08:00
parent 6889ca37e5
commit 44a4f1bae1
25558 changed files with 2463152 additions and 153 deletions

20
frontend/node_modules/schema-utils/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,20 @@
Copyright JS Foundation and other contributors
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.

317
frontend/node_modules/schema-utils/README.md generated vendored Normal file
View File

@@ -0,0 +1,317 @@
<div align="center">
<a href="http://json-schema.org">
<img width="160" height="160"
src="https://raw.githubusercontent.com/webpack-contrib/schema-utils/master/.github/assets/logo.png">
</a>
<a href="https://github.com/webpack/webpack">
<img width="200" height="200"
src="https://webpack.js.org/assets/icon-square-big.svg">
</a>
</div>
[![npm][npm]][npm-url]
[![node][node]][node-url]
[![tests][tests]][tests-url]
[![coverage][cover]][cover-url]
[![GitHub Discussions][discussion]][discussion-url]
[![size][size]][size-url]
# schema-utils
Package for validate options in loaders and plugins.
## Getting Started
To begin, you'll need to install `schema-utils`:
```console
npm install schema-utils
```
## API
**schema.json**
```json
{
"type": "object",
"properties": {
"option": {
"type": "boolean"
}
},
"additionalProperties": false
}
```
```js
import schema from "./path/to/schema.json";
import { validate } from "schema-utils";
const options = { option: true };
const configuration = { name: "Loader Name/Plugin Name/Name" };
validate(schema, options, configuration);
```
### `schema`
Type: `String`
JSON schema.
Simple example of schema:
```json
{
"type": "object",
"properties": {
"name": {
"description": "This is description of option.",
"type": "string"
}
},
"additionalProperties": false
}
```
### `options`
Type: `Object`
Object with options.
```js
import schema from "./path/to/schema.json";
import { validate } from "schema-utils";
const options = { foo: "bar" };
validate(schema, { name: 123 }, { name: "MyPlugin" });
```
### `configuration`
Allow to configure validator.
There is an alternative method to configure the `name` and`baseDataPath` options via the `title` property in the schema.
For example:
```json
{
"title": "My Loader options",
"type": "object",
"properties": {
"name": {
"description": "This is description of option.",
"type": "string"
}
},
"additionalProperties": false
}
```
The last word used for the `baseDataPath` option, other words used for the `name` option.
Based on the example above the `name` option equals `My Loader`, the `baseDataPath` option equals `options`.
#### `name`
Type: `Object`
Default: `"Object"`
Allow to setup name in validation errors.
```js
import schema from "./path/to/schema.json";
import { validate } from "schema-utils";
const options = { foo: "bar" };
validate(schema, options, { name: "MyPlugin" });
```
```shell
Invalid configuration object. MyPlugin has been initialised using a configuration object that does not match the API schema.
- configuration.optionName should be a integer.
```
#### `baseDataPath`
Type: `String`
Default: `"configuration"`
Allow to setup base data path in validation errors.
```js
import schema from "./path/to/schema.json";
import { validate } from "schema-utils";
const options = { foo: "bar" };
validate(schema, options, { name: "MyPlugin", baseDataPath: "options" });
```
```shell
Invalid options object. MyPlugin has been initialised using an options object that does not match the API schema.
- options.optionName should be a integer.
```
#### `postFormatter`
Type: `Function`
Default: `undefined`
Allow to reformat errors.
```js
import schema from "./path/to/schema.json";
import { validate } from "schema-utils";
const options = { foo: "bar" };
validate(schema, options, {
name: "MyPlugin",
postFormatter: (formattedError, error) => {
if (error.keyword === "type") {
return `${formattedError}\nAdditional Information.`;
}
return formattedError;
},
});
```
```shell
Invalid options object. MyPlugin has been initialized using an options object that does not match the API schema.
- options.optionName should be a integer.
Additional Information.
```
## Examples
**schema.json**
```json
{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"test": {
"anyOf": [
{ "type": "array" },
{ "type": "string" },
{ "instanceof": "RegExp" }
]
},
"transform": {
"instanceof": "Function"
},
"sourceMap": {
"type": "boolean"
}
},
"additionalProperties": false
}
```
### `Loader`
```js
import { getOptions } from "loader-utils";
import { validate } from "schema-utils";
import schema from "path/to/schema.json";
function loader(src, map) {
const options = getOptions(this);
validate(schema, options, {
name: "Loader Name",
baseDataPath: "options",
});
// Code...
}
export default loader;
```
### `Plugin`
```js
import { validate } from "schema-utils";
import schema from "path/to/schema.json";
class Plugin {
constructor(options) {
validate(schema, options, {
name: "Plugin Name",
baseDataPath: "options",
});
this.options = options;
}
apply(compiler) {
// Code...
}
}
export default Plugin;
```
### Allow to disable and enable validation (the `validate` function do nothing)
This can be useful when you don't want to do validation for `production` builds.
```js
import { disableValidation, enableValidation, validate } from "schema-utils";
// Disable validation
disableValidation();
// Do nothing
validate(schema, options);
// Enable validation
enableValidation();
// Will throw an error if schema is not valid
validate(schema, options);
// Allow to undestand do you need validation or not
const need = needValidate();
console.log(need);
```
Also you can enable/disable validation using the `process.env.SKIP_VALIDATION` env variable.
Supported values (case insensitive):
- `yes`/`y`/`true`/`1`/`on`
- `no`/`n`/`false`/`0`/`off`
## Contributing
Please take a moment to read our contributing guidelines if you haven't yet done so.
[CONTRIBUTING](./.github/CONTRIBUTING.md)
## License
[MIT](./LICENSE)
[npm]: https://img.shields.io/npm/v/schema-utils.svg
[npm-url]: https://npmjs.com/package/schema-utils
[node]: https://img.shields.io/node/v/schema-utils.svg
[node-url]: https://nodejs.org
[tests]: https://github.com/webpack/schema-utils/workflows/schema-utils/badge.svg
[tests-url]: https://github.com/webpack/schema-utils/actions
[cover]: https://codecov.io/gh/webpack/schema-utils/branch/master/graph/badge.svg
[cover-url]: https://codecov.io/gh/webpack/schema-utils
[discussion]: https://img.shields.io/github/discussions/webpack/webpack
[discussion-url]: https://github.com/webpack/webpack/discussions
[size]: https://packagephobia.com/badge?p=schema-utils
[size-url]: https://packagephobia.com/result?p=schema-utils

View File

@@ -0,0 +1,74 @@
export default ValidationError;
export type JSONSchema6 = import("json-schema").JSONSchema6;
export type JSONSchema7 = import("json-schema").JSONSchema7;
export type Schema = import("./validate").Schema;
export type ValidationErrorConfiguration =
import("./validate").ValidationErrorConfiguration;
export type PostFormatter = import("./validate").PostFormatter;
export type SchemaUtilErrorObject = import("./validate").SchemaUtilErrorObject;
declare class ValidationError extends Error {
/**
* @param {Array<SchemaUtilErrorObject>} errors
* @param {Schema} schema
* @param {ValidationErrorConfiguration} configuration
*/
constructor(
errors: Array<SchemaUtilErrorObject>,
schema: Schema,
configuration?: ValidationErrorConfiguration
);
/** @type {Array<SchemaUtilErrorObject>} */
errors: Array<SchemaUtilErrorObject>;
/** @type {Schema} */
schema: Schema;
/** @type {string} */
headerName: string;
/** @type {string} */
baseDataPath: string;
/** @type {PostFormatter | null} */
postFormatter: PostFormatter | null;
/**
* @param {string} path
* @returns {Schema}
*/
getSchemaPart(path: string): Schema;
/**
* @param {Schema} schema
* @param {boolean} logic
* @param {Array<Object>} prevSchemas
* @returns {string}
*/
formatSchema(
schema: Schema,
logic?: boolean,
prevSchemas?: Array<Object>
): string;
/**
* @param {Schema=} schemaPart
* @param {(boolean | Array<string>)=} additionalPath
* @param {boolean=} needDot
* @param {boolean=} logic
* @returns {string}
*/
getSchemaPartText(
schemaPart?: Schema | undefined,
additionalPath?: (boolean | Array<string>) | undefined,
needDot?: boolean | undefined,
logic?: boolean | undefined
): string;
/**
* @param {Schema=} schemaPart
* @returns {string}
*/
getSchemaPartDescription(schemaPart?: Schema | undefined): string;
/**
* @param {SchemaUtilErrorObject} error
* @returns {string}
*/
formatValidationError(error: SchemaUtilErrorObject): string;
/**
* @param {Array<SchemaUtilErrorObject>} errors
* @returns {string}
*/
formatValidationErrors(errors: Array<SchemaUtilErrorObject>): string;
}

View File

@@ -0,0 +1,17 @@
export type Schema = import("./validate").Schema;
export type JSONSchema4 = import("./validate").JSONSchema4;
export type JSONSchema6 = import("./validate").JSONSchema6;
export type JSONSchema7 = import("./validate").JSONSchema7;
export type ExtendedSchema = import("./validate").ExtendedSchema;
import { validate } from "./validate";
import { ValidationError } from "./validate";
import { enableValidation } from "./validate";
import { disableValidation } from "./validate";
import { needValidate } from "./validate";
export {
validate,
ValidationError,
enableValidation,
disableValidation,
needValidate,
};

View File

@@ -0,0 +1,11 @@
export default addAbsolutePathKeyword;
export type Ajv = import("ajv").default;
export type SchemaValidateFunction = import("ajv").SchemaValidateFunction;
export type AnySchemaObject = import("ajv").AnySchemaObject;
export type SchemaUtilErrorObject = import("../validate").SchemaUtilErrorObject;
/**
*
* @param {Ajv} ajv
* @returns {Ajv}
*/
declare function addAbsolutePathKeyword(ajv: Ajv): Ajv;

View File

@@ -0,0 +1,14 @@
export default addLimitKeyword;
export type Ajv = import("ajv").default;
export type Code = import("ajv").Code;
export type Name = import("ajv").Name;
export type KeywordErrorDefinition = import("ajv").KeywordErrorDefinition;
/** @typedef {import("ajv").default} Ajv */
/** @typedef {import("ajv").Code} Code */
/** @typedef {import("ajv").Name} Name */
/** @typedef {import("ajv").KeywordErrorDefinition} KeywordErrorDefinition */
/**
* @param {Ajv} ajv
* @returns {Ajv}
*/
declare function addLimitKeyword(ajv: Ajv): Ajv;

View File

@@ -0,0 +1,15 @@
export default addUndefinedAsNullKeyword;
export type Ajv = import("ajv").default;
export type SchemaValidateFunction = import("ajv").SchemaValidateFunction;
export type AnySchemaObject = import("ajv").AnySchemaObject;
export type ValidateFunction = import("ajv").ValidateFunction;
/** @typedef {import("ajv").default} Ajv */
/** @typedef {import("ajv").SchemaValidateFunction} SchemaValidateFunction */
/** @typedef {import("ajv").AnySchemaObject} AnySchemaObject */
/** @typedef {import("ajv").ValidateFunction} ValidateFunction */
/**
*
* @param {Ajv} ajv
* @returns {Ajv}
*/
declare function addUndefinedAsNullKeyword(ajv: Ajv): Ajv;

View File

@@ -0,0 +1,79 @@
export = Range;
/**
* @typedef {[number, boolean]} RangeValue
*/
/**
* @callback RangeValueCallback
* @param {RangeValue} rangeValue
* @returns {boolean}
*/
declare class Range {
/**
* @param {"left" | "right"} side
* @param {boolean} exclusive
* @returns {">" | ">=" | "<" | "<="}
*/
static getOperator(
side: "left" | "right",
exclusive: boolean
): ">" | ">=" | "<" | "<=";
/**
* @param {number} value
* @param {boolean} logic is not logic applied
* @param {boolean} exclusive is range exclusive
* @returns {string}
*/
static formatRight(value: number, logic: boolean, exclusive: boolean): string;
/**
* @param {number} value
* @param {boolean} logic is not logic applied
* @param {boolean} exclusive is range exclusive
* @returns {string}
*/
static formatLeft(value: number, logic: boolean, exclusive: boolean): string;
/**
* @param {number} start left side value
* @param {number} end right side value
* @param {boolean} startExclusive is range exclusive from left side
* @param {boolean} endExclusive is range exclusive from right side
* @param {boolean} logic is not logic applied
* @returns {string}
*/
static formatRange(
start: number,
end: number,
startExclusive: boolean,
endExclusive: boolean,
logic: boolean
): string;
/**
* @param {Array<RangeValue>} values
* @param {boolean} logic is not logic applied
* @return {RangeValue} computed value and it's exclusive flag
*/
static getRangeValue(values: Array<RangeValue>, logic: boolean): RangeValue;
/** @type {Array<RangeValue>} */
_left: Array<RangeValue>;
/** @type {Array<RangeValue>} */
_right: Array<RangeValue>;
/**
* @param {number} value
* @param {boolean=} exclusive
*/
left(value: number, exclusive?: boolean | undefined): void;
/**
* @param {number} value
* @param {boolean=} exclusive
*/
right(value: number, exclusive?: boolean | undefined): void;
/**
* @param {boolean} logic is not logic applied
* @return {string} "smart" range string representation
*/
format(logic?: boolean): string;
}
declare namespace Range {
export { RangeValue, RangeValueCallback };
}
type RangeValue = [number, boolean];
type RangeValueCallback = (rangeValue: RangeValue) => boolean;

View File

@@ -0,0 +1,3 @@
export function stringHints(schema: Schema, logic: boolean): string[];
export function numberHints(schema: Schema, logic: boolean): string[];
export type Schema = import("../validate").Schema;

View File

@@ -0,0 +1,7 @@
export default memoize;
/**
* @template T
* @param fn {(function(): any) | undefined}
* @returns {function(): T}
*/
declare function memoize<T>(fn: (() => any) | undefined): () => T;

View File

@@ -0,0 +1,42 @@
export type JSONSchema4 = import("json-schema").JSONSchema4;
export type JSONSchema6 = import("json-schema").JSONSchema6;
export type JSONSchema7 = import("json-schema").JSONSchema7;
export type ErrorObject = import("ajv").ErrorObject;
export type ExtendedSchema = {
formatMinimum?: (string | number) | undefined;
formatMaximum?: (string | number) | undefined;
formatExclusiveMinimum?: (string | boolean) | undefined;
formatExclusiveMaximum?: (string | boolean) | undefined;
link?: string | undefined;
undefinedAsNull?: boolean | undefined;
};
export type Extend = ExtendedSchema;
export type Schema = (JSONSchema4 | JSONSchema6 | JSONSchema7) & ExtendedSchema;
export type SchemaUtilErrorObject = ErrorObject & {
children?: Array<ErrorObject>;
};
export type PostFormatter = (
formattedError: string,
error: SchemaUtilErrorObject
) => string;
export type ValidationErrorConfiguration = {
name?: string | undefined;
baseDataPath?: string | undefined;
postFormatter?: PostFormatter | undefined;
};
/**
* @param {Schema} schema
* @param {Array<object> | object} options
* @param {ValidationErrorConfiguration=} configuration
* @returns {void}
*/
export function validate(
schema: Schema,
options: Array<object> | object,
configuration?: ValidationErrorConfiguration | undefined
): void;
export function enableValidation(): void;
export function disableValidation(): void;
export function needValidate(): boolean;
import ValidationError from "./ValidationError";
export { ValidationError };

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 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
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.

View File

@@ -0,0 +1,745 @@
# ajv-keywords
Custom JSON-Schema keywords for [Ajv](https://github.com/epoberezkin/ajv) validator
[![build](https://github.com/ajv-validator/ajv-keywords/workflows/build/badge.svg)](https://github.com/ajv-validator/ajv-keywords/actions?query=workflow%3Abuild)
[![npm](https://img.shields.io/npm/v/ajv-keywords.svg)](https://www.npmjs.com/package/ajv-keywords)
[![npm downloads](https://img.shields.io/npm/dm/ajv-keywords.svg)](https://www.npmjs.com/package/ajv-keywords)
[![coverage](https://coveralls.io/repos/github/ajv-validator/ajv-keywords/badge.svg?branch=master)](https://coveralls.io/github/ajv-validator/ajv-keywords?branch=master)
[![gitter](https://img.shields.io/gitter/room/ajv-validator/ajv.svg)](https://gitter.im/ajv-validator/ajv)
**Please note**: This readme file is for [ajv-keywords v5.0.0](https://github.com/ajv-validator/ajv-keywords/releases/tag/v5.0.0) that should be used with [ajv v8](https://github.com/ajv-validator/ajv).
[ajv-keywords v3](https://github.com/ajv-validator/ajv-keywords/tree/v3) should be used with [ajv v6](https://github.com/ajv-validator/ajv/tree/v6).
## Contents
- [Install](#install)
- [Usage](#usage)
- [Keywords](#keywords)
- [Types](#types)
- [typeof](#typeof)
- [instanceof](#instanceof)<sup>\+</sup>
- [Keywords for numbers](#keywords-for-numbers)
- [range and exclusiveRange](#range-and-exclusiverange)
- [Keywords for strings](#keywords-for-strings)
- [regexp](#regexp)
- [transform](#transform)<sup>\*</sup>
- [Keywords for arrays](#keywords-for-arrays)
- [uniqueItemProperties](#uniqueitemproperties)<sup>\+</sup>
- [Keywords for objects](#keywords-for-objects)
- [allRequired](#allrequired)
- [anyRequired](#anyrequired)
- [oneRequired](#onerequired)
- [patternRequired](#patternrequired)
- [prohibited](#prohibited)
- [deepProperties](#deepproperties)
- [deepRequired](#deeprequired)
- [dynamicDefaults](#dynamicdefaults)<sup>\*</sup><sup>\+</sup>
- [Keywords for all types](#keywords-for-all-types)
- [select/selectCases/selectDefault](#selectselectcasesselectdefault)
- [Security contact](#security-contact)
- [Open-source software support](#open-source-software-support)
- [License](#license)
<sup>\*</sup> - keywords that modify data
<sup>\+</sup> - keywords that are not supported in [standalone validation code](https://github.com/ajv-validator/ajv/blob/master/docs/standalone.md)
## Install
To install version 4 to use with [Ajv v7](https://github.com/ajv-validator/ajv):
```
npm install ajv-keywords
```
## Usage
To add all available keywords:
```javascript
const Ajv = require("ajv")
const ajv = new Ajv()
require("ajv-keywords")(ajv)
ajv.validate({instanceof: "RegExp"}, /.*/) // true
ajv.validate({instanceof: "RegExp"}, ".*") // false
```
To add a single keyword:
```javascript
require("ajv-keywords")(ajv, "instanceof")
```
To add multiple keywords:
```javascript
require("ajv-keywords")(ajv, ["typeof", "instanceof"])
```
To add a single keyword directly (to avoid adding unused code):
```javascript
require("ajv-keywords/dist/keywords/select")(ajv, opts)
```
To add all keywords via Ajv options:
```javascript
const ajv = new Ajv({keywords: require("ajv-keywords/dist/definitions")(opts)})
```
To add one or several keywords via options:
```javascript
const ajv = new Ajv({
keywords: [
require("ajv-keywords/dist/definitions/typeof")(),
require("ajv-keywords/dist/definitions/instanceof")(),
// select exports an array of 3 definitions - see "select" in docs
...require("ajv-keywords/dist/definitions/select")(opts),
],
})
```
`opts` is an optional object with a property `defaultMeta` - URI of meta-schema to use for keywords that use subschemas (`select` and `deepProperties`). The default is `"http://json-schema.org/schema"`.
## Keywords
### Types
#### `typeof`
Based on JavaScript `typeof` operation.
The value of the keyword should be a string (`"undefined"`, `"string"`, `"number"`, `"object"`, `"function"`, `"boolean"` or `"symbol"`) or an array of strings.
To pass validation the result of `typeof` operation on the value should be equal to the string (or one of the strings in the array).
```javascript
ajv.validate({typeof: "undefined"}, undefined) // true
ajv.validate({typeof: "undefined"}, null) // false
ajv.validate({typeof: ["undefined", "object"]}, null) // true
```
#### `instanceof`
Based on JavaScript `instanceof` operation.
The value of the keyword should be a string (`"Object"`, `"Array"`, `"Function"`, `"Number"`, `"String"`, `"Date"`, `"RegExp"` or `"Promise"`) or an array of strings.
To pass validation the result of `data instanceof ...` operation on the value should be true:
```javascript
ajv.validate({instanceof: "Array"}, []) // true
ajv.validate({instanceof: "Array"}, {}) // false
ajv.validate({instanceof: ["Array", "Function"]}, function () {}) // true
```
You can add your own constructor function to be recognised by this keyword:
```javascript
class MyClass {}
const instanceofDef = require("ajv-keywords/dist/definitions/instanceof")
instanceofDef.CONSTRUCTORS.MyClass = MyClass
ajv.validate({instanceof: "MyClass"}, new MyClass()) // true
```
**Please note**: currently `instanceof` is not supported in [standalone validation code](https://github.com/ajv-validator/ajv/blob/master/docs/standalone.md) - it has to be implemented as [`code` keyword](https://github.com/ajv-validator/ajv/blob/master/docs/keywords.md#define-keyword-with-code-generation-function) to support it (PR is welcome).
### Keywords for numbers
#### `range` and `exclusiveRange`
Syntax sugar for the combination of minimum and maximum keywords (or exclusiveMinimum and exclusiveMaximum), also fails schema compilation if there are no numbers in the range.
The value of these keywords must be an array consisting of two numbers, the second must be greater or equal than the first one.
If the validated value is not a number the validation passes, otherwise to pass validation the value should be greater (or equal) than the first number and smaller (or equal) than the second number in the array.
```javascript
const schema = {type: "number", range: [1, 3]}
ajv.validate(schema, 1) // true
ajv.validate(schema, 2) // true
ajv.validate(schema, 3) // true
ajv.validate(schema, 0.99) // false
ajv.validate(schema, 3.01) // false
const schema = {type: "number", exclusiveRange: [1, 3]}
ajv.validate(schema, 1.01) // true
ajv.validate(schema, 2) // true
ajv.validate(schema, 2.99) // true
ajv.validate(schema, 1) // false
ajv.validate(schema, 3) // false
```
### Keywords for strings
#### `regexp`
This keyword allows to use regular expressions with flags in schemas, and also without `"u"` flag when needed (the standard `pattern` keyword does not support flags and implies the presence of `"u"` flag).
This keyword applies only to strings. If the data is not a string, the validation succeeds.
The value of this keyword can be either a string (the result of `regexp.toString()`) or an object with the properties `pattern` and `flags` (the same strings that should be passed to RegExp constructor).
```javascript
const schema = {
type: "object",
properties: {
foo: {type: "string", regexp: "/foo/i"},
bar: {type: "string", regexp: {pattern: "bar", flags: "i"}},
},
}
const validData = {
foo: "Food",
bar: "Barmen",
}
const invalidData = {
foo: "fog",
bar: "bad",
}
```
#### `transform`
This keyword allows a string to be modified during validation.
This keyword applies only to strings. If the data is not a string, the `transform` keyword is ignored.
A standalone string cannot be modified, i.e. `data = 'a'; ajv.validate(schema, data);`, because strings are passed by value
**Supported transformations:**
- `trim`: remove whitespace from start and end
- `trimStart`/`trimLeft`: remove whitespace from start
- `trimEnd`/`trimRight`: remove whitespace from end
- `toLowerCase`: convert to lower case
- `toUpperCase`: convert to upper case
- `toEnumCase`: change string case to be equal to one of `enum` values in the schema
Transformations are applied in the order they are listed.
Note: `toEnumCase` requires that all allowed values are unique when case insensitive.
**Example: multiple transformations**
```javascript
require("ajv-keywords")(ajv, "transform")
const schema = {
type: "array",
items: {
type: "string",
transform: ["trim", "toLowerCase"],
},
}
const data = [" MixCase "]
ajv.validate(schema, data)
console.log(data) // ['mixcase']
```
**Example: `enumcase`**
```javascript
require("ajv-keywords")(ajv, ["transform"])
const schema = {
type: "array",
items: {
type: "string",
transform: ["trim", "toEnumCase"],
enum: ["pH"],
},
}
const data = ["ph", " Ph", "PH", "pH "]
ajv.validate(schema, data)
console.log(data) // ['pH','pH','pH','pH']
```
### Keywords for arrays
#### `uniqueItemProperties`
The keyword allows to check that some properties in array items are unique.
This keyword applies only to arrays. If the data is not an array, the validation succeeds.
The value of this keyword must be an array of strings - property names that should have unique values across all items.
```javascript
const schema = {
type: "array",
uniqueItemProperties: ["id", "name"],
}
const validData = [{id: 1}, {id: 2}, {id: 3}]
const invalidData1 = [
{id: 1},
{id: 1}, // duplicate "id"
{id: 3},
]
const invalidData2 = [
{id: 1, name: "taco"},
{id: 2, name: "taco"}, // duplicate "name"
{id: 3, name: "salsa"},
]
```
This keyword is contributed by [@blainesch](https://github.com/blainesch).
**Please note**: currently `uniqueItemProperties` is not supported in [standalone validation code](https://github.com/ajv-validator/ajv/blob/master/docs/standalone.md) - it has to be implemented as [`code` keyword](https://github.com/ajv-validator/ajv/blob/master/docs/keywords.md#define-keyword-with-code-generation-function) to support it (PR is welcome).
### Keywords for objects
#### `allRequired`
This keyword allows to require the presence of all properties used in `properties` keyword in the same schema object.
This keyword applies only to objects. If the data is not an object, the validation succeeds.
The value of this keyword must be boolean.
If the value of the keyword is `false`, the validation succeeds.
If the value of the keyword is `true`, the validation succeeds if the data contains all properties defined in `properties` keyword (in the same schema object).
If the `properties` keyword is not present in the same schema object, schema compilation will throw exception.
```javascript
const schema = {
type: "object",
properties: {
foo: {type: "number"},
bar: {type: "number"},
},
allRequired: true,
}
const validData = {foo: 1, bar: 2}
const alsoValidData = {foo: 1, bar: 2, baz: 3}
const invalidDataList = [{}, {foo: 1}, {bar: 2}]
```
#### `anyRequired`
This keyword allows to require the presence of any (at least one) property from the list.
This keyword applies only to objects. If the data is not an object, the validation succeeds.
The value of this keyword must be an array of strings, each string being a property name. For data object to be valid at least one of the properties in this array should be present in the object.
```javascript
const schema = {
type: "object",
anyRequired: ["foo", "bar"],
}
const validData = {foo: 1}
const alsoValidData = {foo: 1, bar: 2}
const invalidDataList = [{}, {baz: 3}]
```
#### `oneRequired`
This keyword allows to require the presence of only one property from the list.
This keyword applies only to objects. If the data is not an object, the validation succeeds.
The value of this keyword must be an array of strings, each string being a property name. For data object to be valid exactly one of the properties in this array should be present in the object.
```javascript
const schema = {
type: "object",
oneRequired: ["foo", "bar"],
}
const validData = {foo: 1}
const alsoValidData = {bar: 2, baz: 3}
const invalidDataList = [{}, {baz: 3}, {foo: 1, bar: 2}]
```
#### `patternRequired`
This keyword allows to require the presence of properties that match some pattern(s).
This keyword applies only to objects. If the data is not an object, the validation succeeds.
The value of this keyword should be an array of strings, each string being a regular expression. For data object to be valid each regular expression in this array should match at least one property name in the data object.
If the array contains multiple regular expressions, more than one expression can match the same property name.
```javascript
const schema = {
type: "object",
patternRequired: ["f.*o", "b.*r"],
}
const validData = {foo: 1, bar: 2}
const alsoValidData = {foobar: 3}
const invalidDataList = [{}, {foo: 1}, {bar: 2}]
```
#### `prohibited`
This keyword allows to prohibit that any of the properties in the list is present in the object.
This keyword applies only to objects. If the data is not an object, the validation succeeds.
The value of this keyword should be an array of strings, each string being a property name. For data object to be valid none of the properties in this array should be present in the object.
```javascript
const schema = {
type: "object",
prohibited: ["foo", "bar"],
}
const validData = {baz: 1}
const alsoValidData = {}
const invalidDataList = [{foo: 1}, {bar: 2}, {foo: 1, bar: 2}]
```
**Please note**: `{prohibited: ['foo', 'bar']}` is equivalent to `{not: {anyRequired: ['foo', 'bar']}}` (i.e. it has the same validation result for any data).
#### `deepProperties`
This keyword allows to validate deep properties (identified by JSON pointers).
This keyword applies only to objects. If the data is not an object, the validation succeeds.
The value should be an object, where keys are JSON pointers to the data, starting from the current position in data, and the values are JSON schemas. For data object to be valid the value of each JSON pointer should be valid according to the corresponding schema.
```javascript
const schema = {
type: "object",
deepProperties: {
"/users/1/role": {enum: ["admin"]},
},
}
const validData = {
users: [
{},
{
id: 123,
role: "admin",
},
],
}
const alsoValidData = {
users: {
1: {
id: 123,
role: "admin",
},
},
}
const invalidData = {
users: [
{},
{
id: 123,
role: "user",
},
],
}
const alsoInvalidData = {
users: {
1: {
id: 123,
role: "user",
},
},
}
```
#### `deepRequired`
This keyword allows to check that some deep properties (identified by JSON pointers) are available.
This keyword applies only to objects. If the data is not an object, the validation succeeds.
The value should be an array of JSON pointers to the data, starting from the current position in data. For data object to be valid each JSON pointer should be some existing part of the data.
```javascript
const schema = {
type: "object",
deepRequired: ["/users/1/role"],
}
const validData = {
users: [
{},
{
id: 123,
role: "admin",
},
],
}
const invalidData = {
users: [
{},
{
id: 123,
},
],
}
```
See [json-schema-org/json-schema-spec#203](https://github.com/json-schema-org/json-schema-spec/issues/203#issue-197211916) for an example of the equivalent schema without `deepRequired` keyword.
### Keywords for all types
#### `select`/`selectCases`/`selectDefault`
**Please note**: these keywords are deprecated. It is recommended to use OpenAPI [discriminator](https://ajv.js.org/json-schema.html#discriminator) keyword supported by Ajv v8 instead of `select`.
These keywords allow to choose the schema to validate the data based on the value of some property in the validated data.
These keywords must be present in the same schema object (`selectDefault` is optional).
The value of `select` keyword should be a [\$data reference](https://github.com/ajv-validator/ajv/blob/master/docs/validation.md#data-reference) that points to any primitive JSON type (string, number, boolean or null) in the data that is validated. You can also use a constant of primitive type as the value of this keyword (e.g., for debugging purposes).
The value of `selectCases` keyword must be an object where each property name is a possible string representation of the value of `select` keyword and each property value is a corresponding schema (from draft-06 it can be boolean) that must be used to validate the data.
The value of `selectDefault` keyword is a schema (also can be boolean) that must be used to validate the data in case `selectCases` has no key equal to the stringified value of `select` keyword.
The validation succeeds in one of the following cases:
- the validation of data using selected schema succeeds,
- none of the schemas is selected for validation,
- the value of select is undefined (no property in the data that the data reference points to).
If `select` value (in data) is not a primitive type the validation fails.
This keyword correctly tracks evaluated properties and items to work with `unevaluatedProperties` and `unevaluatedItems` keywords - only properties and items from the subschema that was used (one of `selectCases` subschemas or `selectDefault` subschema) are marked as evaluated.
**Please note**: these keywords require Ajv `$data` option to support [\$data reference](https://github.com/ajv-validator/ajv/blob/master/docs/validation.md#data-reference).
```javascript
require("ajv-keywords")(ajv, "select")
const schema = {
type: "object",
required: ["kind"],
properties: {
kind: {type: "string"},
},
select: {$data: "0/kind"},
selectCases: {
foo: {
required: ["foo"],
properties: {
kind: {},
foo: {type: "string"},
},
additionalProperties: false,
},
bar: {
required: ["bar"],
properties: {
kind: {},
bar: {type: "number"},
},
additionalProperties: false,
},
},
selectDefault: {
propertyNames: {
not: {enum: ["foo", "bar"]},
},
},
}
const validDataList = [
{kind: "foo", foo: "any"},
{kind: "bar", bar: 1},
{kind: "anything_else", not_bar_or_foo: "any value"},
]
const invalidDataList = [
{kind: "foo"}, // no property foo
{kind: "bar"}, // no property bar
{kind: "foo", foo: "any", another: "any value"}, // additional property
{kind: "bar", bar: 1, another: "any value"}, // additional property
{kind: "anything_else", foo: "any"}, // property foo not allowed
{kind: "anything_else", bar: 1}, // property bar not allowed
]
```
#### `dynamicDefaults`
This keyword allows to assign dynamic defaults to properties, such as timestamps, unique IDs etc.
This keyword only works if `useDefaults` options is used and not inside `anyOf` keywords etc., in the same way as [default keyword treated by Ajv](https://github.com/epoberezkin/ajv#assigning-defaults).
The keyword should be added on the object level. Its value should be an object with each property corresponding to a property name, in the same way as in standard `properties` keyword. The value of each property can be:
- an identifier of dynamic default function (a string)
- an object with properties `func` (an identifier) and `args` (an object with parameters that will be passed to this function during schema compilation - see examples).
The properties used in `dynamicDefaults` should not be added to `required` keyword in the same schema (or validation will fail), because unlike `default` this keyword is processed after validation.
There are several predefined dynamic default functions:
- `"timestamp"` - current timestamp in milliseconds
- `"datetime"` - current date and time as string (ISO, valid according to `date-time` format)
- `"date"` - current date as string (ISO, valid according to `date` format)
- `"time"` - current time as string (ISO, valid according to `time` format)
- `"random"` - pseudo-random number in [0, 1) interval
- `"randomint"` - pseudo-random integer number. If string is used as a property value, the function will randomly return 0 or 1. If object `{ func: 'randomint', args: { max: N } }` is used then the default will be an integer number in [0, N) interval.
- `"seq"` - sequential integer number starting from 0. If string is used as a property value, the default sequence will be used. If object `{ func: 'seq', args: { name: 'foo'} }` is used then the sequence with name `"foo"` will be used. Sequences are global, even if different ajv instances are used.
```javascript
const schema = {
type: "object",
dynamicDefaults: {
ts: "datetime",
r: {func: "randomint", args: {max: 100}},
id: {func: "seq", args: {name: "id"}},
},
properties: {
ts: {
type: "string",
format: "date-time",
},
r: {
type: "integer",
minimum: 0,
exclusiveMaximum: 100,
},
id: {
type: "integer",
minimum: 0,
},
},
}
const data = {}
ajv.validate(data) // true
data // { ts: '2016-12-01T22:07:28.829Z', r: 25, id: 0 }
const data1 = {}
ajv.validate(data1) // true
data1 // { ts: '2016-12-01T22:07:29.832Z', r: 68, id: 1 }
ajv.validate(data1) // true
data1 // didn't change, as all properties were defined
```
When using the `useDefaults` option value `"empty"`, properties and items equal to `null` or `""` (empty string) will be considered missing and assigned defaults. Use `allOf` [compound keyword](https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#compound-keywords) to execute `dynamicDefaults` before validation.
```javascript
const schema = {
type: "object",
allOf: [
{
dynamicDefaults: {
ts: "datetime",
r: {func: "randomint", args: {min: 5, max: 100}},
id: {func: "seq", args: {name: "id"}},
},
},
{
properties: {
ts: {
type: "string",
},
r: {
type: "number",
minimum: 5,
exclusiveMaximum: 100,
},
id: {
type: "integer",
minimum: 0,
},
},
},
],
}
const data = {ts: "", r: null}
ajv.validate(data) // true
data // { ts: '2016-12-01T22:07:28.829Z', r: 25, id: 0 }
```
You can add your own dynamic default function to be recognised by this keyword:
```javascript
const uuid = require("uuid")
const def = require("ajv-keywords/dist/definitions/dynamicDefaults")
def.DEFAULTS.uuid = () => uuid.v4
const schema = {
dynamicDefaults: {id: "uuid"},
properties: {id: {type: "string", format: "uuid"}},
}
const data = {}
ajv.validate(schema, data) // true
data // { id: 'a1183fbe-697b-4030-9bcc-cfeb282a9150' };
const data1 = {}
ajv.validate(schema, data1) // true
data1 // { id: '5b008de7-1669-467a-a5c6-70fa244d7209' }
```
You also can define dynamic default that accept parameters, e.g. version of uuid:
```javascript
const uuid = require("uuid")
function getUuid(args) {
const version = "v" + ((arvs && args.v) || "4")
return uuid[version]
}
const def = require("ajv-keywords/dist/definitions/dynamicDefaults")
def.DEFAULTS.uuid = getUuid
const schema = {
dynamicDefaults: {
id1: "uuid", // v4
id2: {func: "uuid", v: 4}, // v4
id3: {func: "uuid", v: 1}, // v1
},
}
```
**Please note**: dynamic default functions are differentiated by the number of parameters they have (`function.length`). Functions that do not expect default must have one non-optional argument so that `function.length` > 0.
`dynamicDefaults` is not supported in [standalone validation code](https://github.com/ajv-validator/ajv/blob/master/docs/standalone.md).
## Security contact
To report a security vulnerability, please use the
[Tidelift security contact](https://tidelift.com/security).
Tidelift will coordinate the fix and disclosure.
Please do NOT report security vulnerabilities via GitHub issues.
## Open-source software support
Ajv-keywords is a part of [Tidelift subscription](https://tidelift.com/subscription/pkg/npm-ajv-keywords?utm_source=npm-ajv-keywords&utm_medium=referral&utm_campaign=readme) - it provides a centralised support to open-source software users, in addition to the support provided by software maintainers.
## License
[MIT](https://github.com/epoberezkin/ajv-keywords/blob/master/LICENSE)

View File

@@ -0,0 +1,74 @@
{
"name": "ajv-keywords",
"version": "5.1.0",
"description": "Additional JSON-Schema keywords for Ajv JSON validator",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "rm -rf dist && tsc",
"prepublish": "npm run build",
"prettier:write": "prettier --write \"./**/*.{md,json,yaml,js,ts}\"",
"prettier:check": "prettier --list-different \"./**/*.{md,json,yaml,js,ts}\"",
"test": "npm link && npm link ajv-keywords && npm run eslint && npm run test-cov",
"eslint": "eslint \"src/**/*.*s\" \"spec/**/*.*s\"",
"test-spec": "jest spec/*.ts",
"test-cov": "jest spec/*.ts --coverage"
},
"repository": {
"type": "git",
"url": "git+https://github.com/epoberezkin/ajv-keywords.git"
},
"keywords": [
"JSON-Schema",
"ajv",
"keywords"
],
"files": [
"src",
"dist",
"ajv-keywords.d.ts"
],
"author": "Evgeny Poberezkin",
"license": "MIT",
"bugs": {
"url": "https://github.com/epoberezkin/ajv-keywords/issues"
},
"homepage": "https://github.com/epoberezkin/ajv-keywords#readme",
"dependencies": {
"fast-deep-equal": "^3.1.3"
},
"peerDependencies": {
"ajv": "^8.8.2"
},
"devDependencies": {
"@ajv-validator/config": "^0.2.3",
"@types/chai": "^4.2.14",
"@types/jest": "^26.0.14",
"@types/node": "^16.4.10",
"@types/uuid": "^8.3.0",
"@typescript-eslint/eslint-plugin": "^4.4.1",
"@typescript-eslint/parser": "^4.4.1",
"ajv": "^8.8.2",
"ajv-formats": "^2.0.0",
"chai": "^4.2.0",
"eslint": "^7.2.0",
"eslint-config-prettier": "^7.0.0",
"husky": "^7.0.1",
"jest": "^26.5.3",
"json-schema-test": "^2.0.0",
"lint-staged": "^11.1.1",
"prettier": "^2.1.2",
"ts-jest": "^26.4.1",
"typescript": "^4.2.0",
"uuid": "^8.1.0"
},
"prettier": "@ajv-validator/config/prettierrc.json",
"husky": {
"hooks": {
"pre-commit": "lint-staged && npm test"
}
},
"lint-staged": {
"*.{md,json,yaml,js,ts}": "prettier --write"
}
}

View File

@@ -0,0 +1,30 @@
import type {MacroKeywordDefinition} from "ajv"
import type {GetDefinition} from "./_types"
type RangeKwd = "range" | "exclusiveRange"
export default function getRangeDef(keyword: RangeKwd): GetDefinition<MacroKeywordDefinition> {
return () => ({
keyword,
type: "number",
schemaType: "array",
macro: function ([min, max]: [number, number]) {
validateRangeSchema(min, max)
return keyword === "range"
? {minimum: min, maximum: max}
: {exclusiveMinimum: min, exclusiveMaximum: max}
},
metaSchema: {
type: "array",
minItems: 2,
maxItems: 2,
items: {type: "number"},
},
})
function validateRangeSchema(min: number, max: number): void {
if (min > max || (keyword === "exclusiveRange" && min === max)) {
throw new Error("There are no numbers in range")
}
}
}

View File

@@ -0,0 +1,24 @@
import type {MacroKeywordDefinition} from "ajv"
import type {GetDefinition} from "./_types"
type RequiredKwd = "anyRequired" | "oneRequired"
export default function getRequiredDef(
keyword: RequiredKwd
): GetDefinition<MacroKeywordDefinition> {
return () => ({
keyword,
type: "object",
schemaType: "array",
macro(schema: string[]) {
if (schema.length === 0) return true
if (schema.length === 1) return {required: schema}
const comb = keyword === "anyRequired" ? "anyOf" : "oneOf"
return {[comb]: schema.map((p) => ({required: [p]}))}
},
metaSchema: {
type: "array",
items: {type: "string"},
},
})
}

View File

@@ -0,0 +1,7 @@
import type {KeywordDefinition} from "ajv"
export interface DefinitionOptions {
defaultMeta?: string | boolean
}
export type GetDefinition<T extends KeywordDefinition> = (opts?: DefinitionOptions) => T

View File

@@ -0,0 +1,22 @@
import type {DefinitionOptions} from "./_types"
import type {SchemaObject, KeywordCxt, Name} from "ajv"
import {_} from "ajv/dist/compile/codegen"
const META_SCHEMA_ID = "http://json-schema.org/schema"
export function metaSchemaRef({defaultMeta}: DefinitionOptions = {}): SchemaObject {
return defaultMeta === false ? {} : {$ref: defaultMeta || META_SCHEMA_ID}
}
export function usePattern(
{gen, it: {opts}}: KeywordCxt,
pattern: string,
flags = opts.unicodeRegExp ? "u" : ""
): Name {
const rx = new RegExp(pattern, flags)
return gen.scopeValue("pattern", {
key: rx.toString(),
ref: rx,
code: _`new RegExp(${pattern}, ${flags})`,
})
}

View File

@@ -0,0 +1,18 @@
import type {MacroKeywordDefinition} from "ajv"
export default function getDef(): MacroKeywordDefinition {
return {
keyword: "allRequired",
type: "object",
schemaType: "boolean",
macro(schema: boolean, parentSchema) {
if (!schema) return true
const required = Object.keys(parentSchema.properties)
if (required.length === 0) return true
return {required}
},
dependencies: ["properties"],
}
}
module.exports = getDef

View File

@@ -0,0 +1,8 @@
import type {MacroKeywordDefinition} from "ajv"
import type {GetDefinition} from "./_types"
import getRequiredDef from "./_required"
const getDef: GetDefinition<MacroKeywordDefinition> = getRequiredDef("anyRequired")
export default getDef
module.exports = getDef

View File

@@ -0,0 +1,52 @@
import type {MacroKeywordDefinition, SchemaObject, Schema} from "ajv"
import type {DefinitionOptions} from "./_types"
import {metaSchemaRef} from "./_util"
export default function getDef(opts?: DefinitionOptions): MacroKeywordDefinition {
return {
keyword: "deepProperties",
type: "object",
schemaType: "object",
macro: function (schema: Record<string, SchemaObject>) {
const allOf = []
for (const pointer in schema) allOf.push(getSchema(pointer, schema[pointer]))
return {allOf}
},
metaSchema: {
type: "object",
propertyNames: {type: "string", format: "json-pointer"},
additionalProperties: metaSchemaRef(opts),
},
}
}
function getSchema(jsonPointer: string, schema: SchemaObject): SchemaObject {
const segments = jsonPointer.split("/")
const rootSchema: SchemaObject = {}
let pointerSchema: SchemaObject = rootSchema
for (let i = 1; i < segments.length; i++) {
let segment: string = segments[i]
const isLast = i === segments.length - 1
segment = unescapeJsonPointer(segment)
const properties: Record<string, Schema> = (pointerSchema.properties = {})
let items: SchemaObject[] | undefined
if (/[0-9]+/.test(segment)) {
let count = +segment
items = pointerSchema.items = []
pointerSchema.type = ["object", "array"]
while (count--) items.push({})
} else {
pointerSchema.type = "object"
}
pointerSchema = isLast ? schema : {}
properties[segment] = pointerSchema
if (items) items.push(pointerSchema)
}
return rootSchema
}
function unescapeJsonPointer(str: string): string {
return str.replace(/~1/g, "/").replace(/~0/g, "~")
}
module.exports = getDef

View File

@@ -0,0 +1,35 @@
import type {CodeKeywordDefinition, KeywordCxt} from "ajv"
import {_, or, and, getProperty, Code} from "ajv/dist/compile/codegen"
export default function getDef(): CodeKeywordDefinition {
return {
keyword: "deepRequired",
type: "object",
schemaType: "array",
code(ctx: KeywordCxt) {
const {schema, data} = ctx
const props = (schema as string[]).map((jp: string) => _`(${getData(jp)}) === undefined`)
ctx.fail(or(...props))
function getData(jsonPointer: string): Code {
if (jsonPointer === "") throw new Error("empty JSON pointer not allowed")
const segments = jsonPointer.split("/")
let x: Code = data
const xs = segments.map((s, i) =>
i ? (x = _`${x}${getProperty(unescapeJPSegment(s))}`) : x
)
return and(...xs)
}
},
metaSchema: {
type: "array",
items: {type: "string", format: "json-pointer"},
},
}
}
function unescapeJPSegment(s: string): string {
return s.replace(/~1/g, "/").replace(/~0/g, "~")
}
module.exports = getDef

View File

@@ -0,0 +1,98 @@
import type {FuncKeywordDefinition, SchemaCxt} from "ajv"
const sequences: Record<string, number | undefined> = {}
export type DynamicDefaultFunc = (args?: Record<string, any>) => () => any
const DEFAULTS: Record<string, DynamicDefaultFunc | undefined> = {
timestamp: () => () => Date.now(),
datetime: () => () => new Date().toISOString(),
date: () => () => new Date().toISOString().slice(0, 10),
time: () => () => new Date().toISOString().slice(11),
random: () => () => Math.random(),
randomint: (args?: {max?: number}) => {
const max = args?.max ?? 2
return () => Math.floor(Math.random() * max)
},
seq: (args?: {name?: string}) => {
const name = args?.name ?? ""
sequences[name] ||= 0
return () => (sequences[name] as number)++
},
}
interface PropertyDefaultSchema {
func: string
args: Record<string, any>
}
type DefaultSchema = Record<string, string | PropertyDefaultSchema | undefined>
const getDef: (() => FuncKeywordDefinition) & {
DEFAULTS: typeof DEFAULTS
} = Object.assign(_getDef, {DEFAULTS})
function _getDef(): FuncKeywordDefinition {
return {
keyword: "dynamicDefaults",
type: "object",
schemaType: ["string", "object"],
modifying: true,
valid: true,
compile(schema: DefaultSchema, _parentSchema, it: SchemaCxt) {
if (!it.opts.useDefaults || it.compositeRule) return () => true
const fs: Record<string, () => any> = {}
for (const key in schema) fs[key] = getDefault(schema[key])
const empty = it.opts.useDefaults === "empty"
return (data: Record<string, any>) => {
for (const prop in schema) {
if (data[prop] === undefined || (empty && (data[prop] === null || data[prop] === ""))) {
data[prop] = fs[prop]()
}
}
return true
}
},
metaSchema: {
type: "object",
additionalProperties: {
anyOf: [
{type: "string"},
{
type: "object",
additionalProperties: false,
required: ["func", "args"],
properties: {
func: {type: "string"},
args: {type: "object"},
},
},
],
},
},
}
}
function getDefault(d: string | PropertyDefaultSchema | undefined): () => any {
return typeof d == "object" ? getObjDefault(d) : getStrDefault(d)
}
function getObjDefault({func, args}: PropertyDefaultSchema): () => any {
const def = DEFAULTS[func]
assertDefined(func, def)
return def(args)
}
function getStrDefault(d = ""): () => any {
const def = DEFAULTS[d]
assertDefined(d, def)
return def()
}
function assertDefined(name: string, def?: DynamicDefaultFunc): asserts def is DynamicDefaultFunc {
if (!def) throw new Error(`invalid "dynamicDefaults" keyword property value: ${name}`)
}
export default getDef
module.exports = getDef

View File

@@ -0,0 +1,8 @@
import type {MacroKeywordDefinition} from "ajv"
import type {GetDefinition} from "./_types"
import getRangeDef from "./_range"
const getDef: GetDefinition<MacroKeywordDefinition> = getRangeDef("exclusiveRange")
export default getDef
module.exports = getDef

View File

@@ -0,0 +1,61 @@
import type {Vocabulary, KeywordDefinition, ErrorNoParams} from "ajv"
import type {DefinitionOptions, GetDefinition} from "./_types"
import typeofDef from "./typeof"
import instanceofDef from "./instanceof"
import range from "./range"
import exclusiveRange from "./exclusiveRange"
import regexp from "./regexp"
import transform from "./transform"
import uniqueItemProperties from "./uniqueItemProperties"
import allRequired from "./allRequired"
import anyRequired from "./anyRequired"
import oneRequired from "./oneRequired"
import patternRequired, {PatternRequiredError} from "./patternRequired"
import prohibited from "./prohibited"
import deepProperties from "./deepProperties"
import deepRequired from "./deepRequired"
import dynamicDefaults from "./dynamicDefaults"
import selectDef, {SelectError} from "./select"
const definitions: GetDefinition<KeywordDefinition>[] = [
typeofDef,
instanceofDef,
range,
exclusiveRange,
regexp,
transform,
uniqueItemProperties,
allRequired,
anyRequired,
oneRequired,
patternRequired,
prohibited,
deepProperties,
deepRequired,
dynamicDefaults,
]
export default function ajvKeywords(opts?: DefinitionOptions): Vocabulary {
return definitions.map((d) => d(opts)).concat(selectDef(opts))
}
export type AjvKeywordsError =
| PatternRequiredError
| SelectError
| ErrorNoParams<
| "range"
| "exclusiveRange"
| "anyRequired"
| "oneRequired"
| "allRequired"
| "deepProperties"
| "deepRequired"
| "dynamicDefaults"
| "instanceof"
| "prohibited"
| "regexp"
| "transform"
| "uniqueItemProperties"
>
module.exports = ajvKeywords

View File

@@ -0,0 +1,61 @@
import type {FuncKeywordDefinition} from "ajv"
type Constructor = new (...args: any[]) => any
const CONSTRUCTORS: Record<string, Constructor | undefined> = {
Object,
Array,
Function,
Number,
String,
Date,
RegExp,
}
/* istanbul ignore else */
if (typeof Buffer != "undefined") CONSTRUCTORS.Buffer = Buffer
/* istanbul ignore else */
if (typeof Promise != "undefined") CONSTRUCTORS.Promise = Promise
const getDef: (() => FuncKeywordDefinition) & {
CONSTRUCTORS: typeof CONSTRUCTORS
} = Object.assign(_getDef, {CONSTRUCTORS})
function _getDef(): FuncKeywordDefinition {
return {
keyword: "instanceof",
schemaType: ["string", "array"],
compile(schema: string | string[]) {
if (typeof schema == "string") {
const C = getConstructor(schema)
return (data) => data instanceof C
}
if (Array.isArray(schema)) {
const constructors = schema.map(getConstructor)
return (data) => {
for (const C of constructors) {
if (data instanceof C) return true
}
return false
}
}
/* istanbul ignore next */
throw new Error("ajv implementation error")
},
metaSchema: {
anyOf: [{type: "string"}, {type: "array", items: {type: "string"}}],
},
}
}
function getConstructor(c: string): Constructor {
const C = CONSTRUCTORS[c]
if (C) return C
throw new Error(`invalid "instanceof" keyword value ${c}`)
}
export default getDef
module.exports = getDef

View File

@@ -0,0 +1,8 @@
import type {MacroKeywordDefinition} from "ajv"
import type {GetDefinition} from "./_types"
import getRequiredDef from "./_required"
const getDef: GetDefinition<MacroKeywordDefinition> = getRequiredDef("oneRequired")
export default getDef
module.exports = getDef

View File

@@ -0,0 +1,46 @@
import type {CodeKeywordDefinition, KeywordCxt, KeywordErrorDefinition, ErrorObject} from "ajv"
import {_, str, and} from "ajv/dist/compile/codegen"
import {usePattern} from "./_util"
export type PatternRequiredError = ErrorObject<"patternRequired", {missingPattern: string}>
const error: KeywordErrorDefinition = {
message: ({params: {missingPattern}}) =>
str`should have property matching pattern '${missingPattern}'`,
params: ({params: {missingPattern}}) => _`{missingPattern: ${missingPattern}}`,
}
export default function getDef(): CodeKeywordDefinition {
return {
keyword: "patternRequired",
type: "object",
schemaType: "array",
error,
code(cxt: KeywordCxt) {
const {gen, schema, data} = cxt
if (schema.length === 0) return
const valid = gen.let("valid", true)
for (const pat of schema) validateProperties(pat)
function validateProperties(pattern: string): void {
const matched = gen.let("matched", false)
gen.forIn("key", data, (key) => {
gen.assign(matched, _`${usePattern(cxt, pattern)}.test(${key})`)
gen.if(matched, () => gen.break())
})
cxt.setParams({missingPattern: pattern})
gen.assign(valid, and(valid, matched))
cxt.pass(valid)
}
},
metaSchema: {
type: "array",
items: {type: "string", format: "regex"},
uniqueItems: true,
},
}
}
module.exports = getDef

View File

@@ -0,0 +1,20 @@
import type {MacroKeywordDefinition} from "ajv"
export default function getDef(): MacroKeywordDefinition {
return {
keyword: "prohibited",
type: "object",
schemaType: "array",
macro: function (schema: string[]) {
if (schema.length === 0) return true
if (schema.length === 1) return {not: {required: schema}}
return {not: {anyOf: schema.map((p) => ({required: [p]}))}}
},
metaSchema: {
type: "array",
items: {type: "string"},
},
}
}
module.exports = getDef

View File

@@ -0,0 +1,8 @@
import type {MacroKeywordDefinition} from "ajv"
import type {GetDefinition} from "./_types"
import getRangeDef from "./_range"
const getDef: GetDefinition<MacroKeywordDefinition> = getRangeDef("range")
export default getDef
module.exports = getDef

View File

@@ -0,0 +1,45 @@
import type {CodeKeywordDefinition, KeywordCxt, JSONSchemaType, Name} from "ajv"
import {_} from "ajv/dist/compile/codegen"
import {usePattern} from "./_util"
interface RegexpSchema {
pattern: string
flags?: string
}
const regexpMetaSchema: JSONSchemaType<RegexpSchema> = {
type: "object",
properties: {
pattern: {type: "string"},
flags: {type: "string", nullable: true},
},
required: ["pattern"],
additionalProperties: false,
}
const metaRegexp = /^\/(.*)\/([gimuy]*)$/
export default function getDef(): CodeKeywordDefinition {
return {
keyword: "regexp",
type: "string",
schemaType: ["string", "object"],
code(cxt: KeywordCxt) {
const {data, schema} = cxt
const regx = getRegExp(schema)
cxt.pass(_`${regx}.test(${data})`)
function getRegExp(sch: string | RegexpSchema): Name {
if (typeof sch == "object") return usePattern(cxt, sch.pattern, sch.flags)
const rx = metaRegexp.exec(sch)
if (rx) return usePattern(cxt, rx[1], rx[2])
throw new Error("cannot parse string into RegExp")
}
},
metaSchema: {
anyOf: [{type: "string"}, regexpMetaSchema],
},
}
}
module.exports = getDef

View File

@@ -0,0 +1,69 @@
import type {KeywordDefinition, KeywordErrorDefinition, KeywordCxt, ErrorObject} from "ajv"
import {_, str, nil, Name} from "ajv/dist/compile/codegen"
import type {DefinitionOptions} from "./_types"
import {metaSchemaRef} from "./_util"
export type SelectError = ErrorObject<"select", {failingCase?: string; failingDefault?: true}>
const error: KeywordErrorDefinition = {
message: ({params: {schemaProp}}) =>
schemaProp
? str`should match case "${schemaProp}" schema`
: str`should match default case schema`,
params: ({params: {schemaProp}}) =>
schemaProp ? _`{failingCase: ${schemaProp}}` : _`{failingDefault: true}`,
}
export default function getDef(opts?: DefinitionOptions): KeywordDefinition[] {
const metaSchema = metaSchemaRef(opts)
return [
{
keyword: "select",
schemaType: ["string", "number", "boolean", "null"],
$data: true,
error,
dependencies: ["selectCases"],
code(cxt: KeywordCxt) {
const {gen, schemaCode, parentSchema} = cxt
cxt.block$data(nil, () => {
const valid = gen.let("valid", true)
const schValid = gen.name("_valid")
const value = gen.const("value", _`${schemaCode} === null ? "null" : ${schemaCode}`)
gen.if(false) // optimizer should remove it from generated code
for (const schemaProp in parentSchema.selectCases) {
cxt.setParams({schemaProp})
gen.elseIf(_`"" + ${value} == ${schemaProp}`) // intentional ==, to match numbers and booleans
const schCxt = cxt.subschema({keyword: "selectCases", schemaProp}, schValid)
cxt.mergeEvaluated(schCxt, Name)
gen.assign(valid, schValid)
}
gen.else()
if (parentSchema.selectDefault !== undefined) {
cxt.setParams({schemaProp: undefined})
const schCxt = cxt.subschema({keyword: "selectDefault"}, schValid)
cxt.mergeEvaluated(schCxt, Name)
gen.assign(valid, schValid)
}
gen.endIf()
cxt.pass(valid)
})
},
},
{
keyword: "selectCases",
dependencies: ["select"],
metaSchema: {
type: "object",
additionalProperties: metaSchema,
},
},
{
keyword: "selectDefault",
dependencies: ["select", "selectCases"],
metaSchema,
},
]
}
module.exports = getDef

View File

@@ -0,0 +1,98 @@
import type {CodeKeywordDefinition, AnySchemaObject, KeywordCxt, Code, Name} from "ajv"
import {_, stringify, getProperty} from "ajv/dist/compile/codegen"
type TransformName =
| "trimStart"
| "trimEnd"
| "trimLeft"
| "trimRight"
| "trim"
| "toLowerCase"
| "toUpperCase"
| "toEnumCase"
interface TransformConfig {
hash: Record<string, string | undefined>
}
type Transform = (s: string, cfg?: TransformConfig) => string
const transform: {[key in TransformName]: Transform} = {
trimStart: (s) => s.trimStart(),
trimEnd: (s) => s.trimEnd(),
trimLeft: (s) => s.trimStart(),
trimRight: (s) => s.trimEnd(),
trim: (s) => s.trim(),
toLowerCase: (s) => s.toLowerCase(),
toUpperCase: (s) => s.toUpperCase(),
toEnumCase: (s, cfg) => cfg?.hash[configKey(s)] || s,
}
const getDef: (() => CodeKeywordDefinition) & {
transform: typeof transform
} = Object.assign(_getDef, {transform})
function _getDef(): CodeKeywordDefinition {
return {
keyword: "transform",
schemaType: "array",
before: "enum",
code(cxt: KeywordCxt) {
const {gen, data, schema, parentSchema, it} = cxt
const {parentData, parentDataProperty} = it
const tNames: string[] = schema
if (!tNames.length) return
let cfg: Name | undefined
if (tNames.includes("toEnumCase")) {
const config = getEnumCaseCfg(parentSchema)
cfg = gen.scopeValue("obj", {ref: config, code: stringify(config)})
}
gen.if(_`typeof ${data} == "string" && ${parentData} !== undefined`, () => {
gen.assign(data, transformExpr(tNames.slice()))
gen.assign(_`${parentData}[${parentDataProperty}]`, data)
})
function transformExpr(ts: string[]): Code {
if (!ts.length) return data
const t = ts.pop() as string
if (!(t in transform)) throw new Error(`transform: unknown transformation ${t}`)
const func = gen.scopeValue("func", {
ref: transform[t as TransformName],
code: _`require("ajv-keywords/dist/definitions/transform").transform${getProperty(t)}`,
})
const arg = transformExpr(ts)
return cfg && t === "toEnumCase" ? _`${func}(${arg}, ${cfg})` : _`${func}(${arg})`
}
},
metaSchema: {
type: "array",
items: {type: "string", enum: Object.keys(transform)},
},
}
}
function getEnumCaseCfg(parentSchema: AnySchemaObject): TransformConfig {
// build hash table to enum values
const cfg: TransformConfig = {hash: {}}
// requires `enum` in the same schema as transform
if (!parentSchema.enum) throw new Error('transform: "toEnumCase" requires "enum"')
for (const v of parentSchema.enum) {
if (typeof v !== "string") continue
const k = configKey(v)
// requires all `enum` values have unique keys
if (cfg.hash[k]) {
throw new Error('transform: "toEnumCase" requires all lowercased "enum" values to be unique')
}
cfg.hash[k] = v
}
return cfg
}
function configKey(s: string): string {
return s.toLowerCase()
}
export default getDef
module.exports = getDef

View File

@@ -0,0 +1,27 @@
import type {CodeKeywordDefinition, KeywordCxt} from "ajv"
import {_} from "ajv/dist/compile/codegen"
const TYPES = ["undefined", "string", "number", "object", "function", "boolean", "symbol"]
export default function getDef(): CodeKeywordDefinition {
return {
keyword: "typeof",
schemaType: ["string", "array"],
code(cxt: KeywordCxt) {
const {data, schema, schemaValue} = cxt
cxt.fail(
typeof schema == "string"
? _`typeof ${data} != ${schema}`
: _`${schemaValue}.indexOf(typeof ${data}) < 0`
)
},
metaSchema: {
anyOf: [
{type: "string", enum: TYPES},
{type: "array", items: {type: "string", enum: TYPES}},
],
},
}
}
module.exports = getDef

View File

@@ -0,0 +1,58 @@
import type {FuncKeywordDefinition, AnySchemaObject} from "ajv"
import equal = require("fast-deep-equal")
const SCALAR_TYPES = ["number", "integer", "string", "boolean", "null"]
export default function getDef(): FuncKeywordDefinition {
return {
keyword: "uniqueItemProperties",
type: "array",
schemaType: "array",
compile(keys: string[], parentSchema: AnySchemaObject) {
const scalar = getScalarKeys(keys, parentSchema)
return (data) => {
if (data.length <= 1) return true
for (let k = 0; k < keys.length; k++) {
const key = keys[k]
if (scalar[k]) {
const hash: Record<string, any> = {}
for (const x of data) {
if (!x || typeof x != "object") continue
let p = x[key]
if (p && typeof p == "object") continue
if (typeof p == "string") p = '"' + p
if (hash[p]) return false
hash[p] = true
}
} else {
for (let i = data.length; i--; ) {
const x = data[i]
if (!x || typeof x != "object") continue
for (let j = i; j--; ) {
const y = data[j]
if (y && typeof y == "object" && equal(x[key], y[key])) return false
}
}
}
}
return true
}
},
metaSchema: {
type: "array",
items: {type: "string"},
},
}
}
function getScalarKeys(keys: string[], schema: AnySchemaObject): boolean[] {
return keys.map((key) => {
const t = schema.items?.properties?.[key]?.type
return Array.isArray(t)
? !t.includes("object") && !t.includes("array")
: SCALAR_TYPES.includes(t)
})
}
module.exports = getDef

View File

@@ -0,0 +1,32 @@
import type Ajv from "ajv"
import type {Plugin} from "ajv"
import plugins from "./keywords"
export {AjvKeywordsError} from "./definitions"
const ajvKeywords: Plugin<string | string[]> = (ajv: Ajv, keyword?: string | string[]): Ajv => {
if (Array.isArray(keyword)) {
for (const k of keyword) get(k)(ajv)
return ajv
}
if (keyword) {
get(keyword)(ajv)
return ajv
}
for (keyword in plugins) get(keyword)(ajv)
return ajv
}
ajvKeywords.get = get
function get(keyword: string): Plugin<any> {
const defFunc = plugins[keyword]
if (!defFunc) throw new Error("Unknown keyword " + keyword)
return defFunc
}
export default ajvKeywords
module.exports = ajvKeywords
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
module.exports.default = ajvKeywords

View File

@@ -0,0 +1,7 @@
import type {Plugin} from "ajv"
import getDef from "../definitions/allRequired"
const allRequired: Plugin<undefined> = (ajv) => ajv.addKeyword(getDef())
export default allRequired
module.exports = allRequired

View File

@@ -0,0 +1,7 @@
import type {Plugin} from "ajv"
import getDef from "../definitions/anyRequired"
const anyRequired: Plugin<undefined> = (ajv) => ajv.addKeyword(getDef())
export default anyRequired
module.exports = anyRequired

View File

@@ -0,0 +1,9 @@
import type {Plugin} from "ajv"
import getDef from "../definitions/deepProperties"
import type {DefinitionOptions} from "../definitions/_types"
const deepProperties: Plugin<DefinitionOptions> = (ajv, opts?: DefinitionOptions) =>
ajv.addKeyword(getDef(opts))
export default deepProperties
module.exports = deepProperties

View File

@@ -0,0 +1,7 @@
import type {Plugin} from "ajv"
import getDef from "../definitions/deepRequired"
const deepRequired: Plugin<undefined> = (ajv) => ajv.addKeyword(getDef())
export default deepRequired
module.exports = deepRequired

View File

@@ -0,0 +1,7 @@
import type {Plugin} from "ajv"
import getDef from "../definitions/dynamicDefaults"
const dynamicDefaults: Plugin<undefined> = (ajv) => ajv.addKeyword(getDef())
export default dynamicDefaults
module.exports = dynamicDefaults

View File

@@ -0,0 +1,7 @@
import type {Plugin} from "ajv"
import getDef from "../definitions/exclusiveRange"
const exclusiveRange: Plugin<undefined> = (ajv) => ajv.addKeyword(getDef())
export default exclusiveRange
module.exports = exclusiveRange

View File

@@ -0,0 +1,40 @@
import type {Plugin} from "ajv"
import typeofPlugin from "./typeof"
import instanceofPlugin from "./instanceof"
import range from "./range"
import exclusiveRange from "./exclusiveRange"
import regexp from "./regexp"
import transform from "./transform"
import uniqueItemProperties from "./uniqueItemProperties"
import allRequired from "./allRequired"
import anyRequired from "./anyRequired"
import oneRequired from "./oneRequired"
import patternRequired from "./patternRequired"
import prohibited from "./prohibited"
import deepProperties from "./deepProperties"
import deepRequired from "./deepRequired"
import dynamicDefaults from "./dynamicDefaults"
import select from "./select"
// TODO type
const ajvKeywords: Record<string, Plugin<any> | undefined> = {
typeof: typeofPlugin,
instanceof: instanceofPlugin,
range,
exclusiveRange,
regexp,
transform,
uniqueItemProperties,
allRequired,
anyRequired,
oneRequired,
patternRequired,
prohibited,
deepProperties,
deepRequired,
dynamicDefaults,
select,
}
export default ajvKeywords
module.exports = ajvKeywords

View File

@@ -0,0 +1,7 @@
import type {Plugin} from "ajv"
import getDef from "../definitions/instanceof"
const instanceofPlugin: Plugin<undefined> = (ajv) => ajv.addKeyword(getDef())
export default instanceofPlugin
module.exports = instanceofPlugin

View File

@@ -0,0 +1,7 @@
import type {Plugin} from "ajv"
import getDef from "../definitions/oneRequired"
const oneRequired: Plugin<undefined> = (ajv) => ajv.addKeyword(getDef())
export default oneRequired
module.exports = oneRequired

View File

@@ -0,0 +1,7 @@
import type {Plugin} from "ajv"
import getDef from "../definitions/patternRequired"
const patternRequired: Plugin<undefined> = (ajv) => ajv.addKeyword(getDef())
export default patternRequired
module.exports = patternRequired

View File

@@ -0,0 +1,7 @@
import type {Plugin} from "ajv"
import getDef from "../definitions/prohibited"
const prohibited: Plugin<undefined> = (ajv) => ajv.addKeyword(getDef())
export default prohibited
module.exports = prohibited

View File

@@ -0,0 +1,7 @@
import type {Plugin} from "ajv"
import getDef from "../definitions/range"
const range: Plugin<undefined> = (ajv) => ajv.addKeyword(getDef())
export default range
module.exports = range

View File

@@ -0,0 +1,7 @@
import type {Plugin} from "ajv"
import getDef from "../definitions/regexp"
const regexp: Plugin<undefined> = (ajv) => ajv.addKeyword(getDef())
export default regexp
module.exports = regexp

View File

@@ -0,0 +1,11 @@
import type {Plugin} from "ajv"
import getDefs from "../definitions/select"
import type {DefinitionOptions} from "../definitions/_types"
const select: Plugin<DefinitionOptions> = (ajv, opts?: DefinitionOptions) => {
getDefs(opts).forEach((d) => ajv.addKeyword(d))
return ajv
}
export default select
module.exports = select

View File

@@ -0,0 +1,7 @@
import type {Plugin} from "ajv"
import getDef from "../definitions/transform"
const transform: Plugin<undefined> = (ajv) => ajv.addKeyword(getDef())
export default transform
module.exports = transform

View File

@@ -0,0 +1,7 @@
import type {Plugin} from "ajv"
import getDef from "../definitions/typeof"
const typeofPlugin: Plugin<undefined> = (ajv) => ajv.addKeyword(getDef())
export default typeofPlugin
module.exports = typeofPlugin

View File

@@ -0,0 +1,7 @@
import type {Plugin} from "ajv"
import getDef from "../definitions/uniqueItemProperties"
const uniqueItemProperties: Plugin<undefined> = (ajv) => ajv.addKeyword(getDef())
export default uniqueItemProperties
module.exports = uniqueItemProperties

View File

@@ -0,0 +1,23 @@
const Ajv = require("ajv")
const ajv = new Ajv({allErrors: true})
const schema = {
type: "object",
properties: {
foo: {type: "string"},
bar: {type: "number", maximum: 3},
},
required: ["foo", "bar"],
additionalProperties: false,
}
const validate = ajv.compile(schema)
test({foo: "abc", bar: 2})
test({foo: 2, bar: 4})
function test(data) {
const valid = validate(data)
if (valid) console.log("Valid!")
else console.log("Invalid: " + ajv.errorsText(validate.errors))
}

View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
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
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.

View File

@@ -0,0 +1,207 @@
<img align="right" alt="Ajv logo" width="160" src="https://ajv.js.org/img/ajv.svg">
&nbsp;
# Ajv JSON schema validator
The fastest JSON validator for Node.js and browser.
Supports JSON Schema draft-04/06/07/2019-09/2020-12 ([draft-04 support](https://ajv.js.org/json-schema.html#draft-04) requires ajv-draft-04 package) and JSON Type Definition [RFC8927](https://datatracker.ietf.org/doc/rfc8927/).
[![build](https://github.com/ajv-validator/ajv/actions/workflows/build.yml/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)
[![SimpleX](https://img.shields.io/badge/chat-on%20SimpleX-70F0F9)](https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2F8KvvURM6J38Gdq9dCuPswMOkMny0xCOJ%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAr8rPVRuMOXv6kwF2yUAap-eoVg-9ssOFCi1fIrxTUw0%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%224pwLRgWHU9tlroMWHz0uOg%3D%3D%22%7D)
[![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)
## Ajv sponsors
[<img src="https://ajv.js.org/img/mozilla.svg" width="45%" alt="Mozilla">](https://www.mozilla.org)<img src="https://ajv.js.org/img/gap.svg" width="9%">[<img src="https://ajv.js.org/img/reserved.svg" width="45%">](https://opencollective.com/ajv)
[<img src="https://ajv.js.org/img/microsoft.png" width="31%" alt="Microsoft">](https://opensource.microsoft.com)<img src="https://ajv.js.org/img/gap.svg" width="3%">[<img src="https://ajv.js.org/img/reserved.svg" width="31%">](https://opencollective.com/ajv)<img src="https://ajv.js.org/img/gap.svg" width="3%">[<img src="https://ajv.js.org/img/reserved.svg" width="31%">](https://opencollective.com/ajv)
[<img src="https://ajv.js.org/img/retool.svg" width="22.5%" alt="Retool">](https://retool.com/?utm_source=sponsor&utm_campaign=ajv)<img src="https://ajv.js.org/img/gap.svg" width="3%">[<img src="https://ajv.js.org/img/tidelift.svg" width="22.5%" alt="Tidelift">](https://tidelift.com/subscription/pkg/npm-ajv?utm_source=npm-ajv&utm_medium=referral&utm_campaign=enterprise)<img src="https://ajv.js.org/img/gap.svg" width="3%">[<img src="https://ajv.js.org/img/simplex.svg" width="22.5%" alt="SimpleX">](https://github.com/simplex-chat/simplex-chat)<img src="https://ajv.js.org/img/gap.svg" width="3%">[<img src="https://ajv.js.org/img/reserved.svg" width="22.5%">](https://opencollective.com/ajv)
## Contributing
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.
Please review [Contributing guidelines](./CONTRIBUTING.md) and [Code components](https://ajv.js.org/components.html).
## Documentation
All documentation is available on the [Ajv website](https://ajv.js.org).
Some useful site links:
- [Getting started](https://ajv.js.org/guide/getting-started.html)
- [JSON Schema vs JSON Type Definition](https://ajv.js.org/guide/schema-language.html)
- [API reference](https://ajv.js.org/api.html)
- [Strict mode](https://ajv.js.org/strict-mode.html)
- [Standalone validation code](https://ajv.js.org/standalone.html)
- [Security considerations](https://ajv.js.org/security.html)
- [Command line interface](https://ajv.js.org/packages/ajv-cli.html)
- [Frequently Asked Questions](https://ajv.js.org/faq.html)
## <a name="sponsors"></a>Please [sponsor Ajv development](https://github.com/sponsors/epoberezkin)
Since I asked to support Ajv development 40 people and 6 organizations contributed via GitHub and OpenCollective - this support helped receiving the MOSS grant!
Your continuing support is very important - the funds will be used to develop and maintain Ajv once the next major version is released.
Please sponsor Ajv via:
- [GitHub sponsors page](https://github.com/sponsors/epoberezkin) (GitHub will match it)
- [Ajv Open Collective](https://opencollective.com/ajv)
Thank you.
#### Open Collective sponsors
<a href="https://opencollective.com/ajv"><img src="https://opencollective.com/ajv/individuals.svg?width=890"></a>
<a href="https://opencollective.com/ajv/organization/0/website"><img src="https://opencollective.com/ajv/organization/0/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/1/website"><img src="https://opencollective.com/ajv/organization/1/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/2/website"><img src="https://opencollective.com/ajv/organization/2/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/3/website"><img src="https://opencollective.com/ajv/organization/3/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/4/website"><img src="https://opencollective.com/ajv/organization/4/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/5/website"><img src="https://opencollective.com/ajv/organization/5/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/6/website"><img src="https://opencollective.com/ajv/organization/6/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/7/website"><img src="https://opencollective.com/ajv/organization/7/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/8/website"><img src="https://opencollective.com/ajv/organization/8/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/9/website"><img src="https://opencollective.com/ajv/organization/9/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/10/website"><img src="https://opencollective.com/ajv/organization/10/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/11/website"><img src="https://opencollective.com/ajv/organization/11/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/12/website"><img src="https://opencollective.com/ajv/organization/12/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/13/website"><img src="https://opencollective.com/ajv/organization/13/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/14/website"><img src="https://opencollective.com/ajv/organization/14/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/15/website"><img src="https://opencollective.com/ajv/organization/15/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/16/website"><img src="https://opencollective.com/ajv/organization/16/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/17/website"><img src="https://opencollective.com/ajv/organization/17/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/18/website"><img src="https://opencollective.com/ajv/organization/18/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/19/website"><img src="https://opencollective.com/ajv/organization/19/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/20/website"><img src="https://opencollective.com/ajv/organization/20/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/21/website"><img src="https://opencollective.com/ajv/organization/21/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/22/website"><img src="https://opencollective.com/ajv/organization/22/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/23/website"><img src="https://opencollective.com/ajv/organization/23/avatar.svg"></a>
<a href="https://opencollective.com/ajv/organization/24/website"><img src="https://opencollective.com/ajv/organization/24/avatar.svg"></a>
## Performance
Ajv generates code to turn JSON Schemas into super-fast validation functions that are efficient for v8 optimization.
Currently Ajv is the fastest and the most standard compliant validator according to these benchmarks:
- [json-schema-benchmark](https://github.com/ebdrup/json-schema-benchmark) - 50% faster than the second place
- [jsck benchmark](https://github.com/pandastrike/jsck#benchmarks) - 20-190% faster
- [z-schema benchmark](https://rawgit.com/zaggino/z-schema/master/benchmark/results.html)
- [themis benchmark](https://cdn.rawgit.com/playlyfe/themis/master/benchmark/results.html)
Performance of different validators by [json-schema-benchmark](https://github.com/ebdrup/json-schema-benchmark):
[![performance](https://chart.googleapis.com/chart?chxt=x,y&cht=bhs&chco=76A4FB&chls=2.0&chbh=62,4,1&chs=600x416&chxl=-1:|ajv|@exodus/schemasafe|is-my-json-valid|djv|@cfworker/json-schema|jsonschema/=t:100,69.2,51.5,13.1,5.1,1.2)](https://github.com/ebdrup/json-schema-benchmark/blob/master/README.md#performance)
## Features
- Ajv implements JSON Schema [draft-06/07/2019-09/2020-12](http://json-schema.org/) standards (draft-04 is supported in v6):
- all validation keywords (see [JSON Schema validation keywords](https://ajv.js.org/json-schema.html))
- [OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md) extensions:
- NEW: keyword [discriminator](https://ajv.js.org/json-schema.html#discriminator).
- keyword [nullable](https://ajv.js.org/json-schema.html#nullable).
- full support of remote references (remote schemas have to be added with `addSchema` or compiled to be available)
- support of recursive references between schemas
- correct string lengths for strings with unicode pairs
- JSON Schema [formats](https://ajv.js.org/guide/formats.html) (with [ajv-formats](https://github.com/ajv-validator/ajv-formats) plugin).
- [validates schemas against meta-schema](https://ajv.js.org/api.html#api-validateschema)
- NEW: supports [JSON Type Definition](https://datatracker.ietf.org/doc/rfc8927/):
- all keywords (see [JSON Type Definition schema forms](https://ajv.js.org/json-type-definition.html))
- meta-schema for JTD schemas
- "union" keyword and user-defined keywords (can be used inside "metadata" member of the schema)
- supports [browsers](https://ajv.js.org/guide/environments.html#browsers) and Node.js 10.x - current
- [asynchronous loading](https://ajv.js.org/guide/managing-schemas.html#asynchronous-schema-loading) of referenced schemas during compilation
- "All errors" validation mode with [option allErrors](https://ajv.js.org/options.html#allerrors)
- [error messages with parameters](https://ajv.js.org/api.html#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](https://ajv.js.org/guide/modifying-data.html#removing-additional-properties)
- [assigning defaults](https://ajv.js.org/guide/modifying-data.html#assigning-defaults) to missing properties and items
- [coercing data](https://ajv.js.org/guide/modifying-data.html#coercing-data-types) to the types specified in `type` keywords
- [user-defined keywords](https://ajv.js.org/guide/user-keywords.html)
- additional extension keywords with [ajv-keywords](https://github.com/ajv-validator/ajv-keywords) package
- [\$data reference](https://ajv.js.org/guide/combining-schemas.html#data-reference) to use values from the validated data as values for the schema keywords
- [asynchronous validation](https://ajv.js.org/guide/async-validation.html) of user-defined formats and keywords
## Install
To install version 8:
```
npm install ajv
```
## <a name="usage"></a>Getting started
Try it in the Node.js REPL: https://runkit.com/npm/ajv
In JavaScript:
```javascript
// or ESM/TypeScript import
import Ajv from "ajv"
// Node.js require:
const Ajv = require("ajv")
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 data = {
foo: 1,
bar: "abc",
}
const validate = ajv.compile(schema)
const valid = validate(data)
if (!valid) console.log(validate.errors)
```
Learn how to use Ajv and see more examples in the [Guide: getting started](https://ajv.js.org/guide/getting-started.html)
## Changes history
See [https://github.com/ajv-validator/ajv/releases](https://github.com/ajv-validator/ajv/releases)
**Please note**: [Changes in version 8.0.0](https://github.com/ajv-validator/ajv/releases/tag/v8.0.0)
[Version 7.0.0](https://github.com/ajv-validator/ajv/releases/tag/v7.0.0)
[Version 6.0.0](https://github.com/ajv-validator/ajv/releases/tag/v6.0.0).
## Code of conduct
Please review and follow the [Code of conduct](./CODE_OF_CONDUCT.md).
Please report any unacceptable behaviour to ajv.validator@gmail.com - it will be reviewed by the project team.
## Security contact
To report a security vulnerability, please use the
[Tidelift security contact](https://tidelift.com/security).
Tidelift will coordinate the fix and disclosure. Please do NOT report security vulnerabilities via GitHub issues.
## Open-source software support
Ajv is a part of [Tidelift subscription](https://tidelift.com/subscription/pkg/npm-ajv?utm_source=npm-ajv&utm_medium=referral&utm_campaign=readme) - it provides a centralised support to open-source software users, in addition to the support provided by software maintainers.
## License
[MIT](./LICENSE)

View File

@@ -0,0 +1,126 @@
{
"name": "ajv",
"version": "8.17.1",
"description": "Another JSON Schema Validator",
"main": "dist/ajv.js",
"types": "dist/ajv.d.ts",
"files": [
"lib/",
"dist/",
".runkit_example.js"
],
"scripts": {
"eslint": "eslint \"lib/**/*.ts\" \"spec/**/*.*s\" --ignore-pattern spec/JSON-Schema-Test-Suite",
"prettier:write": "prettier --write \"./**/*.{json,yaml,js,ts}\"",
"prettier:check": "prettier --list-different \"./**/*.{json,yaml,js,ts}\"",
"test-spec": "cross-env TS_NODE_PROJECT=spec/tsconfig.json mocha -r ts-node/register \"spec/**/*.spec.{ts,js}\" -R dot",
"test-codegen": "nyc cross-env TS_NODE_PROJECT=spec/tsconfig.json mocha -r ts-node/register 'spec/codegen.spec.ts' -R spec",
"test-debug": "npm run test-spec -- --inspect-brk",
"test-cov": "nyc npm run test-spec",
"rollup": "rm -rf bundle && rollup -c",
"bundle": "rm -rf bundle && node ./scripts/bundle.js ajv ajv7 ajv7 && node ./scripts/bundle.js 2019 ajv2019 ajv2019 && node ./scripts/bundle.js 2020 ajv2020 ajv2020 && node ./scripts/bundle.js jtd ajvJTD ajvJTD",
"build": "rm -rf dist && tsc && cp -r lib/refs dist && rm dist/refs/json-schema-2019-09/index.ts && rm dist/refs/json-schema-2020-12/index.ts && rm dist/refs/jtd-schema.ts",
"json-tests": "rm -rf spec/_json/*.js && node scripts/jsontests",
"test-karma": "karma start",
"test-browser": "rm -rf .browser && npm run bundle && scripts/prepare-tests && karma start",
"test-all": "npm run test-cov && if-node-version 12 npm run test-browser",
"test": "npm run json-tests && npm run prettier:check && npm run eslint && npm link && npm link --legacy-peer-deps ajv && 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 --legacy-peer-deps ajv && npm i && node ./jtd",
"docs:dev": "./scripts/prepare-site && vuepress dev docs",
"docs:build": "./scripts/prepare-site && vuepress build docs"
},
"nyc": {
"exclude": [
"**/spec/**",
"node_modules"
],
"reporter": [
"lcov",
"text-summary"
]
},
"repository": "ajv-validator/ajv",
"keywords": [
"JSON",
"schema",
"validator",
"validation",
"jsonschema",
"json-schema",
"json-schema-validator",
"json-schema-validation"
],
"author": "Evgeny Poberezkin",
"license": "MIT",
"bugs": "https://github.com/ajv-validator/ajv/issues",
"homepage": "https://ajv.js.org",
"runkitExampleFilename": ".runkit_example.js",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"devDependencies": {
"@ajv-validator/config": "^0.5.0",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-typescript": "^11.1.6",
"@types/chai": "^4.3.11",
"@types/mocha": "^10.0.6",
"@types/node": "^20.11.30",
"@types/require-from-string": "^1.2.3",
"@typescript-eslint/eslint-plugin": "^7.3.1",
"@typescript-eslint/parser": "^7.3.1",
"ajv-formats": "^3.0.1",
"browserify": "^17.0.0",
"chai": "^4.4.1",
"cross-env": "^7.0.3",
"dayjs": "^1.11.10",
"dayjs-plugin-utc": "^0.1.2",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"glob": "^10.3.10",
"husky": "^9.0.11",
"if-node-version": "^1.1.1",
"jimp": "^0.22.10",
"js-beautify": "^1.15.1",
"json-schema-test": "^2.0.0",
"karma": "^6.4.2",
"karma-chrome-launcher": "^3.2.0",
"karma-mocha": "^2.0.1",
"lint-staged": "^15.2.2",
"mocha": "^10.3.0",
"module-from-string": "^3.3.0",
"node-fetch": "^3.3.2",
"nyc": "^15.1.0",
"prettier": "3.0.3",
"re2": "^1.20.9",
"rollup": "^2.79.1",
"rollup-plugin-terser": "^7.0.2",
"ts-node": "^10.9.2",
"tsify": "^5.0.4",
"typescript": "5.3.3",
"uri-js": "^4.4.1"
},
"collective": {
"type": "opencollective",
"url": "https://opencollective.com/ajv"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
},
"prettier": "@ajv-validator/config/prettierrc.json",
"husky": {
"hooks": {
"pre-commit": "lint-staged && npm test"
}
},
"lint-staged": {
"*.{json,yaml,js,ts}": "prettier --write"
}
}

View File

@@ -0,0 +1,27 @@
extends: eslint:recommended
env:
node: true
browser: true
rules:
block-scoped-var: 2
complexity: [2, 15]
curly: [2, multi-or-nest, consistent]
dot-location: [2, property]
dot-notation: 2
indent: [2, 2, SwitchCase: 1]
linebreak-style: [2, unix]
new-cap: 2
no-console: [2, allow: [warn, error]]
no-else-return: 2
no-eq-null: 2
no-fallthrough: 2
no-invalid-this: 2
no-return-assign: 2
no-shadow: 1
no-trailing-spaces: 2
no-use-before-define: [2, nofunc]
quotes: [2, single, avoid-escape]
semi: [2, always]
strict: [2, global]
valid-jsdoc: [2, requireReturn: false]
no-control-regex: 0

View File

@@ -0,0 +1,2 @@
github: epoberezkin
tidelift: "npm/json-schema-traverse"

View File

@@ -0,0 +1,28 @@
name: build
on:
push:
branches: [master]
pull_request:
branches: ["*"]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 12.x, 14.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm test
- name: Coveralls
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -0,0 +1,27 @@
name: publish
on:
release:
types: [published]
jobs:
publish-npm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 14
registry-url: https://registry.npmjs.org/
- run: npm install
- run: npm test
- name: Publish beta version to npm
if: "github.event.release.prerelease"
run: npm publish --tag beta
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish to npm
if: "!github.event.release.prerelease"
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 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
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.

View File

@@ -0,0 +1,95 @@
# json-schema-traverse
Traverse JSON Schema passing each schema object to callback
[![build](https://github.com/epoberezkin/json-schema-traverse/workflows/build/badge.svg)](https://github.com/epoberezkin/json-schema-traverse/actions?query=workflow%3Abuild)
[![npm](https://img.shields.io/npm/v/json-schema-traverse)](https://www.npmjs.com/package/json-schema-traverse)
[![coverage](https://coveralls.io/repos/github/epoberezkin/json-schema-traverse/badge.svg?branch=master)](https://coveralls.io/github/epoberezkin/json-schema-traverse?branch=master)
## Install
```
npm install json-schema-traverse
```
## Usage
```javascript
const traverse = require('json-schema-traverse');
const schema = {
properties: {
foo: {type: 'string'},
bar: {type: 'integer'}
}
};
traverse(schema, {cb});
// cb is called 3 times with:
// 1. root schema
// 2. {type: 'string'}
// 3. {type: 'integer'}
// Or:
traverse(schema, {cb: {pre, post}});
// pre is called 3 times with:
// 1. root schema
// 2. {type: 'string'}
// 3. {type: 'integer'}
//
// post is called 3 times with:
// 1. {type: 'string'}
// 2. {type: 'integer'}
// 3. root schema
```
Callback function `cb` is called for each schema object (not including draft-06 boolean schemas), including the root schema, in pre-order traversal. Schema references ($ref) are not resolved, they are passed as is. Alternatively, you can pass a `{pre, post}` object as `cb`, and then `pre` will be called before traversing child elements, and `post` will be called after all child elements have been traversed.
Callback is passed these parameters:
- _schema_: the current schema object
- _JSON pointer_: from the root schema to the current schema object
- _root schema_: the schema passed to `traverse` object
- _parent JSON pointer_: from the root schema to the parent schema object (see below)
- _parent keyword_: the keyword inside which this schema appears (e.g. `properties`, `anyOf`, etc.)
- _parent schema_: not necessarily parent object/array; in the example above the parent schema for `{type: 'string'}` is the root schema
- _index/property_: index or property name in the array/object containing multiple schemas; in the example above for `{type: 'string'}` the property name is `'foo'`
## Traverse objects in all unknown keywords
```javascript
const traverse = require('json-schema-traverse');
const schema = {
mySchema: {
minimum: 1,
maximum: 2
}
};
traverse(schema, {allKeys: true, cb});
// cb is called 2 times with:
// 1. root schema
// 2. mySchema
```
Without option `allKeys: true` callback will be called only with root schema.
## Enterprise support
json-schema-traverse package is a part of [Tidelift enterprise subscription](https://tidelift.com/subscription/pkg/npm-json-schema-traverse?utm_source=npm-json-schema-traverse&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) - it provides a centralised commercial support to open-source software users, in addition to the support provided by software maintainers.
## Security contact
To report a security vulnerability, please use the
[Tidelift security contact](https://tidelift.com/security).
Tidelift will coordinate the fix and disclosure. Please do NOT report security vulnerability via GitHub issues.
## License
[MIT](https://github.com/epoberezkin/json-schema-traverse/blob/master/LICENSE)

View File

@@ -0,0 +1,40 @@
declare function traverse(
schema: traverse.SchemaObject,
opts: traverse.Options,
cb?: traverse.Callback
): void;
declare function traverse(
schema: traverse.SchemaObject,
cb: traverse.Callback
): void;
declare namespace traverse {
interface SchemaObject {
$id?: string;
$schema?: string;
[x: string]: any;
}
type Callback = (
schema: SchemaObject,
jsonPtr: string,
rootSchema: SchemaObject,
parentJsonPtr?: string,
parentKeyword?: string,
parentSchema?: SchemaObject,
keyIndex?: string | number
) => void;
interface Options {
allKeys?: boolean;
cb?:
| Callback
| {
pre?: Callback;
post?: Callback;
};
}
}
export = traverse;

View File

@@ -0,0 +1,93 @@
'use strict';
var traverse = module.exports = function (schema, opts, cb) {
// Legacy support for v0.3.1 and earlier.
if (typeof opts == 'function') {
cb = opts;
opts = {};
}
cb = opts.cb || cb;
var pre = (typeof cb == 'function') ? cb : cb.pre || function() {};
var post = cb.post || function() {};
_traverse(opts, pre, post, schema, '', schema);
};
traverse.keywords = {
additionalItems: true,
items: true,
contains: true,
additionalProperties: true,
propertyNames: true,
not: true,
if: true,
then: true,
else: true
};
traverse.arrayKeywords = {
items: true,
allOf: true,
anyOf: true,
oneOf: true
};
traverse.propsKeywords = {
$defs: true,
definitions: true,
properties: true,
patternProperties: true,
dependencies: true
};
traverse.skipKeywords = {
default: true,
enum: true,
const: true,
required: true,
maximum: true,
minimum: true,
exclusiveMaximum: true,
exclusiveMinimum: true,
multipleOf: true,
maxLength: true,
minLength: true,
pattern: true,
format: true,
maxItems: true,
minItems: true,
uniqueItems: true,
maxProperties: true,
minProperties: true
};
function _traverse(opts, pre, post, schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex) {
if (schema && typeof schema == 'object' && !Array.isArray(schema)) {
pre(schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex);
for (var key in schema) {
var sch = schema[key];
if (Array.isArray(sch)) {
if (key in traverse.arrayKeywords) {
for (var i=0; i<sch.length; i++)
_traverse(opts, pre, post, sch[i], jsonPtr + '/' + key + '/' + i, rootSchema, jsonPtr, key, schema, i);
}
} else if (key in traverse.propsKeywords) {
if (sch && typeof sch == 'object') {
for (var prop in sch)
_traverse(opts, pre, post, sch[prop], jsonPtr + '/' + key + '/' + escapeJsonPtr(prop), rootSchema, jsonPtr, key, schema, prop);
}
} else if (key in traverse.keywords || (opts.allKeys && !(key in traverse.skipKeywords))) {
_traverse(opts, pre, post, sch, jsonPtr + '/' + key, rootSchema, jsonPtr, key, schema);
}
}
post(schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex);
}
}
function escapeJsonPtr(str) {
return str.replace(/~/g, '~0').replace(/\//g, '~1');
}

View File

@@ -0,0 +1,43 @@
{
"name": "json-schema-traverse",
"version": "1.0.0",
"description": "Traverse JSON Schema passing each schema object to callback",
"main": "index.js",
"types": "index.d.ts",
"scripts": {
"eslint": "eslint index.js spec",
"test-spec": "mocha spec -R spec",
"test": "npm run eslint && nyc npm run test-spec"
},
"repository": {
"type": "git",
"url": "git+https://github.com/epoberezkin/json-schema-traverse.git"
},
"keywords": [
"JSON-Schema",
"traverse",
"iterate"
],
"author": "Evgeny Poberezkin",
"license": "MIT",
"bugs": {
"url": "https://github.com/epoberezkin/json-schema-traverse/issues"
},
"homepage": "https://github.com/epoberezkin/json-schema-traverse#readme",
"devDependencies": {
"eslint": "^7.3.1",
"mocha": "^8.0.1",
"nyc": "^15.0.0",
"pre-commit": "^1.2.2"
},
"nyc": {
"exclude": [
"**/spec/**",
"node_modules"
],
"reporter": [
"lcov",
"text-summary"
]
}
}

View File

@@ -0,0 +1,6 @@
parserOptions:
ecmaVersion: 6
globals:
beforeEach: false
describe: false
it: false

View File

@@ -0,0 +1,125 @@
'use strict';
var schema = {
additionalItems: subschema('additionalItems'),
items: subschema('items'),
contains: subschema('contains'),
additionalProperties: subschema('additionalProperties'),
propertyNames: subschema('propertyNames'),
not: subschema('not'),
allOf: [
subschema('allOf_0'),
subschema('allOf_1'),
{
items: [
subschema('items_0'),
subschema('items_1'),
]
}
],
anyOf: [
subschema('anyOf_0'),
subschema('anyOf_1'),
],
oneOf: [
subschema('oneOf_0'),
subschema('oneOf_1'),
],
definitions: {
foo: subschema('definitions_foo'),
bar: subschema('definitions_bar'),
},
properties: {
foo: subschema('properties_foo'),
bar: subschema('properties_bar'),
},
patternProperties: {
foo: subschema('patternProperties_foo'),
bar: subschema('patternProperties_bar'),
},
dependencies: {
foo: subschema('dependencies_foo'),
bar: subschema('dependencies_bar'),
},
required: ['foo', 'bar']
};
function subschema(keyword) {
var sch = {
properties: {},
additionalProperties: false,
additionalItems: false,
anyOf: [
{format: 'email'},
{format: 'hostname'}
]
};
sch.properties['foo_' + keyword] = {title: 'foo'};
sch.properties['bar_' + keyword] = {title: 'bar'};
return sch;
}
module.exports = {
schema: schema,
// schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex
expectedCalls: [[schema, '', schema, undefined, undefined, undefined, undefined]]
.concat(expectedCalls('additionalItems'))
.concat(expectedCalls('items'))
.concat(expectedCalls('contains'))
.concat(expectedCalls('additionalProperties'))
.concat(expectedCalls('propertyNames'))
.concat(expectedCalls('not'))
.concat(expectedCallsChild('allOf', 0))
.concat(expectedCallsChild('allOf', 1))
.concat([
[schema.allOf[2], '/allOf/2', schema, '', 'allOf', schema, 2],
[schema.allOf[2].items[0], '/allOf/2/items/0', schema, '/allOf/2', 'items', schema.allOf[2], 0],
[schema.allOf[2].items[0].properties.foo_items_0, '/allOf/2/items/0/properties/foo_items_0', schema, '/allOf/2/items/0', 'properties', schema.allOf[2].items[0], 'foo_items_0'],
[schema.allOf[2].items[0].properties.bar_items_0, '/allOf/2/items/0/properties/bar_items_0', schema, '/allOf/2/items/0', 'properties', schema.allOf[2].items[0], 'bar_items_0'],
[schema.allOf[2].items[0].anyOf[0], '/allOf/2/items/0/anyOf/0', schema, '/allOf/2/items/0', 'anyOf', schema.allOf[2].items[0], 0],
[schema.allOf[2].items[0].anyOf[1], '/allOf/2/items/0/anyOf/1', schema, '/allOf/2/items/0', 'anyOf', schema.allOf[2].items[0], 1],
[schema.allOf[2].items[1], '/allOf/2/items/1', schema, '/allOf/2', 'items', schema.allOf[2], 1],
[schema.allOf[2].items[1].properties.foo_items_1, '/allOf/2/items/1/properties/foo_items_1', schema, '/allOf/2/items/1', 'properties', schema.allOf[2].items[1], 'foo_items_1'],
[schema.allOf[2].items[1].properties.bar_items_1, '/allOf/2/items/1/properties/bar_items_1', schema, '/allOf/2/items/1', 'properties', schema.allOf[2].items[1], 'bar_items_1'],
[schema.allOf[2].items[1].anyOf[0], '/allOf/2/items/1/anyOf/0', schema, '/allOf/2/items/1', 'anyOf', schema.allOf[2].items[1], 0],
[schema.allOf[2].items[1].anyOf[1], '/allOf/2/items/1/anyOf/1', schema, '/allOf/2/items/1', 'anyOf', schema.allOf[2].items[1], 1]
])
.concat(expectedCallsChild('anyOf', 0))
.concat(expectedCallsChild('anyOf', 1))
.concat(expectedCallsChild('oneOf', 0))
.concat(expectedCallsChild('oneOf', 1))
.concat(expectedCallsChild('definitions', 'foo'))
.concat(expectedCallsChild('definitions', 'bar'))
.concat(expectedCallsChild('properties', 'foo'))
.concat(expectedCallsChild('properties', 'bar'))
.concat(expectedCallsChild('patternProperties', 'foo'))
.concat(expectedCallsChild('patternProperties', 'bar'))
.concat(expectedCallsChild('dependencies', 'foo'))
.concat(expectedCallsChild('dependencies', 'bar'))
};
function expectedCalls(keyword) {
return [
[schema[keyword], `/${keyword}`, schema, '', keyword, schema, undefined],
[schema[keyword].properties[`foo_${keyword}`], `/${keyword}/properties/foo_${keyword}`, schema, `/${keyword}`, 'properties', schema[keyword], `foo_${keyword}`],
[schema[keyword].properties[`bar_${keyword}`], `/${keyword}/properties/bar_${keyword}`, schema, `/${keyword}`, 'properties', schema[keyword], `bar_${keyword}`],
[schema[keyword].anyOf[0], `/${keyword}/anyOf/0`, schema, `/${keyword}`, 'anyOf', schema[keyword], 0],
[schema[keyword].anyOf[1], `/${keyword}/anyOf/1`, schema, `/${keyword}`, 'anyOf', schema[keyword], 1]
];
}
function expectedCallsChild(keyword, i) {
return [
[schema[keyword][i], `/${keyword}/${i}`, schema, '', keyword, schema, i],
[schema[keyword][i].properties[`foo_${keyword}_${i}`], `/${keyword}/${i}/properties/foo_${keyword}_${i}`, schema, `/${keyword}/${i}`, 'properties', schema[keyword][i], `foo_${keyword}_${i}`],
[schema[keyword][i].properties[`bar_${keyword}_${i}`], `/${keyword}/${i}/properties/bar_${keyword}_${i}`, schema, `/${keyword}/${i}`, 'properties', schema[keyword][i], `bar_${keyword}_${i}`],
[schema[keyword][i].anyOf[0], `/${keyword}/${i}/anyOf/0`, schema, `/${keyword}/${i}`, 'anyOf', schema[keyword][i], 0],
[schema[keyword][i].anyOf[1], `/${keyword}/${i}/anyOf/1`, schema, `/${keyword}/${i}`, 'anyOf', schema[keyword][i], 1]
];
}

View File

@@ -0,0 +1,171 @@
'use strict';
var traverse = require('../index');
var assert = require('assert');
describe('json-schema-traverse', function() {
var calls;
beforeEach(function() {
calls = [];
});
it('should traverse all keywords containing schemas recursively', function() {
var schema = require('./fixtures/schema').schema;
var expectedCalls = require('./fixtures/schema').expectedCalls;
traverse(schema, {cb: callback});
assert.deepStrictEqual(calls, expectedCalls);
});
describe('Legacy v0.3.1 API', function() {
it('should traverse all keywords containing schemas recursively', function() {
var schema = require('./fixtures/schema').schema;
var expectedCalls = require('./fixtures/schema').expectedCalls;
traverse(schema, callback);
assert.deepStrictEqual(calls, expectedCalls);
});
it('should work when an options object is provided', function() {
// schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex
var schema = require('./fixtures/schema').schema;
var expectedCalls = require('./fixtures/schema').expectedCalls;
traverse(schema, {}, callback);
assert.deepStrictEqual(calls, expectedCalls);
});
});
describe('allKeys option', function() {
var schema = {
someObject: {
minimum: 1,
maximum: 2
}
};
it('should traverse objects with allKeys: true option', function() {
// schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex
var expectedCalls = [
[schema, '', schema, undefined, undefined, undefined, undefined],
[schema.someObject, '/someObject', schema, '', 'someObject', schema, undefined]
];
traverse(schema, {allKeys: true, cb: callback});
assert.deepStrictEqual(calls, expectedCalls);
});
it('should NOT traverse objects with allKeys: false option', function() {
// schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex
var expectedCalls = [
[schema, '', schema, undefined, undefined, undefined, undefined]
];
traverse(schema, {allKeys: false, cb: callback});
assert.deepStrictEqual(calls, expectedCalls);
});
it('should NOT traverse objects without allKeys option', function() {
// schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex
var expectedCalls = [
[schema, '', schema, undefined, undefined, undefined, undefined]
];
traverse(schema, {cb: callback});
assert.deepStrictEqual(calls, expectedCalls);
});
it('should NOT travers objects in standard keywords which value is not a schema', function() {
var schema2 = {
const: {foo: 'bar'},
enum: ['a', 'b'],
required: ['foo'],
another: {
},
patternProperties: {}, // will not traverse - no properties
dependencies: true, // will not traverse - invalid
properties: {
smaller: {
type: 'number'
},
larger: {
type: 'number',
minimum: {$data: '1/smaller'}
}
}
};
// schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex
var expectedCalls = [
[schema2, '', schema2, undefined, undefined, undefined, undefined],
[schema2.another, '/another', schema2, '', 'another', schema2, undefined],
[schema2.properties.smaller, '/properties/smaller', schema2, '', 'properties', schema2, 'smaller'],
[schema2.properties.larger, '/properties/larger', schema2, '', 'properties', schema2, 'larger'],
];
traverse(schema2, {allKeys: true, cb: callback});
assert.deepStrictEqual(calls, expectedCalls);
});
});
describe('pre and post', function() {
var schema = {
type: 'object',
properties: {
name: {type: 'string'},
age: {type: 'number'}
}
};
it('should traverse schema in pre-order', function() {
traverse(schema, {cb: {pre}});
var expectedCalls = [
['pre', schema, '', schema, undefined, undefined, undefined, undefined],
['pre', schema.properties.name, '/properties/name', schema, '', 'properties', schema, 'name'],
['pre', schema.properties.age, '/properties/age', schema, '', 'properties', schema, 'age'],
];
assert.deepStrictEqual(calls, expectedCalls);
});
it('should traverse schema in post-order', function() {
traverse(schema, {cb: {post}});
var expectedCalls = [
['post', schema.properties.name, '/properties/name', schema, '', 'properties', schema, 'name'],
['post', schema.properties.age, '/properties/age', schema, '', 'properties', schema, 'age'],
['post', schema, '', schema, undefined, undefined, undefined, undefined],
];
assert.deepStrictEqual(calls, expectedCalls);
});
it('should traverse schema in pre- and post-order at the same time', function() {
traverse(schema, {cb: {pre, post}});
var expectedCalls = [
['pre', schema, '', schema, undefined, undefined, undefined, undefined],
['pre', schema.properties.name, '/properties/name', schema, '', 'properties', schema, 'name'],
['post', schema.properties.name, '/properties/name', schema, '', 'properties', schema, 'name'],
['pre', schema.properties.age, '/properties/age', schema, '', 'properties', schema, 'age'],
['post', schema.properties.age, '/properties/age', schema, '', 'properties', schema, 'age'],
['post', schema, '', schema, undefined, undefined, undefined, undefined],
];
assert.deepStrictEqual(calls, expectedCalls);
});
});
function callback() {
calls.push(Array.prototype.slice.call(arguments));
}
function pre() {
calls.push(['pre'].concat(Array.prototype.slice.call(arguments)));
}
function post() {
calls.push(['post'].concat(Array.prototype.slice.call(arguments)));
}
});

79
frontend/node_modules/schema-utils/package.json generated vendored Normal file
View File

@@ -0,0 +1,79 @@
{
"name": "schema-utils",
"version": "4.3.2",
"description": "webpack Validation Utils",
"license": "MIT",
"repository": "webpack/schema-utils",
"author": "webpack Contrib (https://github.com/webpack-contrib)",
"homepage": "https://github.com/webpack/schema-utils",
"bugs": "https://github.com/webpack/schema-utils/issues",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"main": "dist/index.js",
"types": "declarations/index.d.ts",
"engines": {
"node": ">= 10.13.0"
},
"scripts": {
"start": "npm run build -- -w",
"clean": "del-cli dist declarations",
"prebuild": "npm run clean",
"build:types": "tsc --declaration --emitDeclarationOnly --outDir declarations && prettier \"declarations/**/*.ts\" --write",
"build:code": "cross-env NODE_ENV=production babel src -d dist --copy-files",
"build": "npm-run-all -p \"build:**\"",
"commitlint": "commitlint --from=master",
"security": "npm audit --production",
"fmt:check": "prettier \"{**/*,*}.{js,json,md,yml,css,ts}\" --list-different",
"lint:js": "eslint --cache .",
"lint:types": "tsc --pretty --noEmit",
"lint": "npm-run-all lint:js lint:types fmt:check",
"fmt": "npm run fmt:check -- --write",
"fix:js": "npm run lint:js -- --fix",
"fix": "npm-run-all fix:js fmt",
"test:only": "cross-env NODE_ENV=test jest",
"test:watch": "npm run test:only -- --watch",
"test:coverage": "npm run test:only -- --collectCoverageFrom=\"src/**/*.js\" --coverage",
"pretest": "npm run lint",
"test": "npm run test:coverage",
"prepare": "npm run build && husky install",
"release": "standard-version"
},
"files": [
"dist",
"declarations"
],
"dependencies": {
"@types/json-schema": "^7.0.9",
"ajv": "^8.9.0",
"ajv-formats": "^2.1.1",
"ajv-keywords": "^5.1.0"
},
"devDependencies": {
"@babel/cli": "^7.17.0",
"@babel/core": "^7.17.0",
"@babel/preset-env": "^7.16.11",
"@commitlint/cli": "^17.6.1",
"@commitlint/config-conventional": "^16.0.0",
"@webpack-contrib/eslint-config-webpack": "^3.0.0",
"babel-jest": "^27.4.6",
"cross-env": "^7.0.3",
"del": "^6.0.0",
"del-cli": "^4.0.1",
"eslint": "^8.8.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.25.4",
"husky": "^7.0.4",
"jest": "^27.4.7",
"lint-staged": "^13.2.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.5.1",
"standard-version": "^9.3.2",
"typescript": "^4.9.5",
"webpack": "^5.97.1"
},
"keywords": [
"webpack"
]
}