Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: ajv-validator/ajv
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v8.2.0
Choose a base ref
...
head repository: ajv-validator/ajv
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v8.3.0
Choose a head ref
  • 12 commits
  • 11 files changed
  • 8 contributors

Commits on Apr 29, 2021

  1. 3
    Copy the full SHA
    5cccbdf View commit details
  2. Update managing-schemas.md

    Fix typo "defalt" to "default".
    sahilda authored Apr 29, 2021

    Unverified

    The email in this signature doesn’t match the committer email.
    Copy the full SHA
    c024185 View commit details

Commits on May 4, 2021

  1. Merge pull request #1583 from erikbrinkman/snc

    Add checks for strict null checks
    epoberezkin authored May 4, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    b3cc337 View commit details
  2. Merge pull request #1579 from sahilda/patch-1

    Update managing-schemas.md - fix typo
    epoberezkin authored May 4, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    380de72 View commit details

Commits on May 8, 2021

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    a2d1974 View commit details
  2. Copy the full SHA
    13730a5 View commit details

Commits on May 9, 2021

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    0404a50 View commit details
  2. Copy the full SHA
    a60eff7 View commit details
  3. JTD timestamp option (#1584)

    * add timestamp option for jtd
    
    * tests for JTD timestamp option
    
    * configurable timestamp check
    
    * address feedback
    
    * doc
    
    Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
    jrr and epoberezkin authored May 9, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    df964e4 View commit details
  4. build(deps-dev): bump @types/node from 14.14.44 to 15.0.2 (#1590)

    Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.14.44 to 15.0.2.
    - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
    - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)
    
    Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
    
    Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
    dependabot-preview[bot] authored May 9, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    bfb3e58 View commit details
  5. build(deps-dev): bump @rollup/plugin-node-resolve from 11.2.1 to 13.0…

    ….0 (#1589)
    
    Bumps [@rollup/plugin-node-resolve](https://github.com/rollup/plugins/tree/HEAD/packages/node-resolve) from 11.2.1 to 13.0.0.
    - [Release notes](https://github.com/rollup/plugins/releases)
    - [Changelog](https://github.com/rollup/plugins/blob/master/packages/node-resolve/CHANGELOG.md)
    - [Commits](https://github.com/rollup/plugins/commits/commonjs-v13.0.0/packages/node-resolve)
    
    Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
    
    Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
    Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
    dependabot-preview[bot] and epoberezkin authored May 9, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    4ef4804 View commit details
  6. 8.3.0

    epoberezkin committed May 9, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    dec8909 View commit details
2 changes: 1 addition & 1 deletion docs/guide/managing-schemas.md
Original file line number Diff line number Diff line change
@@ -42,7 +42,7 @@ The simplest approach is to compile all your schemas when the application starts
<code-group>
<code-block title="JavaScript">
```javascript
const Ajv = require("ajv").defalt
const Ajv = require("ajv").default
const schema_user = require("./schema_user.json")
const ajv = new Ajv()
const validate_user = ajv.compile(schema_user)
2 changes: 1 addition & 1 deletion docs/json-schema.md
Original file line number Diff line number Diff line change
@@ -91,7 +91,7 @@ Ajv supports all keywords of JSON Schema draft-2020-12:
- changed [items](#items-in-draft-2020-12) keyword that combined parts of functionality of items and additionalItems
- [$dynamicAnchor/$dynamicRef](./guide/combining-schemas.md#extending-recursive-schemas)

To use draft-2019-09 schemas you need to import a different Ajv class:
To use draft-2020-12 schemas you need to import a different Ajv class:

<code-group>
<code-block title="JavaScript">
14 changes: 7 additions & 7 deletions docs/json-type-definition.md
Original file line number Diff line number Diff line change
@@ -45,7 +45,7 @@ It has a required member `type` and an optional members `nullable` and `metadata

- `"string"` - defines a string
- `"boolean"` - defines boolean value `true` or `false`
- `"timestamp"` - defines timestamp (JSON string, Ajv would also allow Date object with this type) according to [RFC3339](https://datatracker.ietf.org/doc/rfc3339/)
- `"timestamp"` - defines timestamp ( accepting either an [RFC3339](https://datatracker.ietf.org/doc/rfc3339/) JSON string or a Date object, configurable via the `timestamp` Ajv option)
- `type` values that define integer numbers:
- `"int8"` - signed byte value (-128 .. 127)
- `"uint8"` - unsigned byte value (0 .. 255)
@@ -170,11 +170,11 @@ Invalid data: `{}`, `{foo: 1}`, `{foo: "bar", bar: "3"}`, any type other than ob

This form defines discriminated (tagged) union of different record types.

It has required members `discriminator` and `mappings` and optional members `nullable` and `metadata`, no other members are allowed.
It has required members `discriminator` and `mapping` and optional members `nullable` and `metadata`, no other members are allowed.

The string value of `discriminator` schema member contains the name of the data member that is the tag of the union. `mappings` schema member contains the dictionary of schemas that are applied according to the value of the tag member in the data. Schemas inside `mappings` must have "properties" form.
The string value of `discriminator` schema member contains the name of the data member that is the tag of the union. `mapping` schema member contains the dictionary of schemas that are applied according to the value of the tag member in the data. Schemas inside `mapping` must have "properties" form.

Properties forms inside `mappings` cannot be `nullable` and cannot define the same property as discriminator tag.
Properties forms inside `mapping` cannot be `nullable` and cannot define the same property as discriminator tag.

**Example 1.**

@@ -183,7 +183,7 @@ Schema:
```javascript
{
discriminator: "version",
mappings: {
mapping: {
"1": {
properties: {
foo: {type: "string"}
@@ -202,12 +202,12 @@ Valid data: `{version: "1", foo: "1"}`, `{version: "2", foo: 1}`

Invalid data: `{}`, `{foo: "1"}`, `{version: 1, foo: "1"}`, any type other than object

**Example 3: invalid schema (discriminator tag member defined in mappings)**
**Example 3: invalid schema (discriminator tag member defined in mapping)**

```javascript
{
discriminator: "version",
mappings: {
mapping: {
"1": {
properties: {
version: {enum: ["1"]},
6 changes: 5 additions & 1 deletion docs/options.md
Original file line number Diff line number Diff line change
@@ -36,7 +36,7 @@ const defaultOptions = {
allErrors: false,
verbose: false,
discriminator: false, // *
unicodeRegExp: true // *
unicodeRegExp: true, // *
$comment: false, // *
formats: {},
keywords: {},
@@ -212,6 +212,10 @@ Option values:

Asynchronous function that will be used to load remote schemas when `compileAsync` [method](#api-compileAsync) is used and some reference is missing (option `missingRefs` should NOT be 'fail' or 'ignore'). This function should accept remote schema uri as a parameter and return a Promise that resolves to a schema. See example in [Asynchronous compilation](./guide/managing-schemas.md#asynchronous-schema-compilation).

### timestamp

(JTD only) This governs what Javascript types will be accepted for the [JTD timestamp type](./json-type-definition#type-form). By default Ajv will accept either Date objects or [RFC3339](https://datatracker.ietf.org/doc/rfc3339/) strings. You can adjust this behavior by specifying `timestamp: "date"` or `timestamp: "string"`.

## Options to modify validated data

### removeAdditional
2 changes: 2 additions & 0 deletions lib/core.ts
Original file line number Diff line number Diff line change
@@ -115,6 +115,8 @@ export interface CurrentOptions {
unevaluated?: boolean // NEW
dynamicRef?: boolean // NEW
jtd?: boolean // NEW
/** (JTD only) Accepted Javascript types for `timestamp` type */
timestamp?: "string" | "date"
meta?: SchemaObject | boolean
defaultMeta?: string | AnySchemaObject
validateSchema?: boolean | "log"
103 changes: 63 additions & 40 deletions lib/types/json-schema.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
/* eslint-disable @typescript-eslint/no-empty-interface */
export type SomeJSONSchema = JSONSchemaType<Known, true>
type StrictNullChecksWrapper<Name extends string, Type> = undefined extends null
? `strictNullChecks must be true in tsconfig to use ${Name}`
: Type

export type PartialSchema<T> = Partial<JSONSchemaType<T, true>>
export type SomeJSONSchema = UncheckedJSONSchemaType<Known, true>

type JSONType<T extends string, _partial extends boolean> = _partial extends true
type UncheckedPartialSchema<T> = Partial<UncheckedJSONSchemaType<T, true>>

export type PartialSchema<T> = StrictNullChecksWrapper<"PartialSchema", UncheckedPartialSchema<T>>

type JSONType<T extends string, IsPartial extends boolean> = IsPartial extends true
? T | undefined
: T

@@ -23,22 +29,22 @@ interface StringKeywords {
format?: string
}

export type JSONSchemaType<T, _partial extends boolean = false> = (
type UncheckedJSONSchemaType<T, IsPartial extends boolean> = (
| // these two unions allow arbitrary unions of types
{
anyOf: readonly JSONSchemaType<T, _partial>[]
anyOf: readonly UncheckedJSONSchemaType<T, IsPartial>[]
}
| {
oneOf: readonly JSONSchemaType<T, _partial>[]
oneOf: readonly UncheckedJSONSchemaType<T, IsPartial>[]
}
// this union allows for { type: (primitive)[] } style schemas
| ({
type: (T extends number
? JSONType<"number" | "integer", _partial>
? JSONType<"number" | "integer", IsPartial>
: T extends string
? JSONType<"string", _partial>
? JSONType<"string", IsPartial>
: T extends boolean
? JSONType<"boolean", _partial>
? JSONType<"boolean", IsPartial>
: never)[]
} & (T extends number
? NumberKeywords
@@ -50,11 +56,11 @@ export type JSONSchemaType<T, _partial extends boolean = false> = (
// this covers "normal" types; it's last so typescript looks to it first for errors
| ((T extends number
? {
type: JSONType<"number" | "integer", _partial>
type: JSONType<"number" | "integer", IsPartial>
} & NumberKeywords
: T extends string
? {
type: JSONType<"string", _partial>
type: JSONType<"string", IsPartial>
} & StringKeywords
: T extends boolean
? {
@@ -63,17 +69,17 @@ export type JSONSchemaType<T, _partial extends boolean = false> = (
: T extends [any, ...any[]]
? {
// JSON AnySchema for tuple
type: JSONType<"array", _partial>
type: JSONType<"array", IsPartial>
items: {
readonly [K in keyof T]-?: JSONSchemaType<T[K]> & Nullable<T[K]>
readonly [K in keyof T]-?: UncheckedJSONSchemaType<T[K], false> & Nullable<T[K]>
} & {length: T["length"]}
minItems: T["length"]
} & ({maxItems: T["length"]} | {additionalItems: false})
: T extends readonly any[]
? {
type: JSONType<"array", _partial>
items: JSONSchemaType<T[0]>
contains?: PartialSchema<T[0]>
type: JSONType<"array", IsPartial>
items: UncheckedJSONSchemaType<T[0], false>
contains?: UncheckedPartialSchema<T[0]>
minItems?: number
maxItems?: number
minContains?: number
@@ -87,62 +93,79 @@ export type JSONSchemaType<T, _partial extends boolean = false> = (
// "required" is not optional because it is often forgotten
// "properties" are optional for more concise dictionary schemas
// "patternProperties" and can be only used with interfaces that have string index
type: JSONType<"object", _partial>
additionalProperties?: boolean | JSONSchemaType<T[string]>
unevaluatedProperties?: boolean | JSONSchemaType<T[string]>
properties?: _partial extends true ? Partial<PropertiesSchema<T>> : PropertiesSchema<T>
patternProperties?: {[Pattern in string]?: JSONSchemaType<T[string]>}
propertyNames?: Omit<JSONSchemaType<string>, "type"> & {type?: "string"}
dependencies?: {[K in keyof T]?: Readonly<(keyof T)[]> | PartialSchema<T>}
type: JSONType<"object", IsPartial>
additionalProperties?: boolean | UncheckedJSONSchemaType<T[string], false>
unevaluatedProperties?: boolean | UncheckedJSONSchemaType<T[string], false>
properties?: IsPartial extends true
? Partial<UncheckedPropertiesSchema<T>>
: UncheckedPropertiesSchema<T>
patternProperties?: {[Pattern in string]?: UncheckedJSONSchemaType<T[string], false>}
propertyNames?: Omit<UncheckedJSONSchemaType<string, false>, "type"> & {type?: "string"}
dependencies?: {[K in keyof T]?: Readonly<(keyof T)[]> | UncheckedPartialSchema<T>}
dependentRequired?: {[K in keyof T]?: Readonly<(keyof T)[]>}
dependentSchemas?: {[K in keyof T]?: PartialSchema<T>}
dependentSchemas?: {[K in keyof T]?: UncheckedPartialSchema<T>}
minProperties?: number
maxProperties?: number
} & (// "required" type does not guarantee that all required properties
// are listed it only asserts that optional cannot be listed.
// "required" is not necessary if it's a non-partial type with no required keys
_partial extends true
IsPartial extends true
? {required: Readonly<(keyof T)[]>}
: [RequiredMembers<T>] extends [never]
? {required?: Readonly<RequiredMembers<T>[]>}
: {required: Readonly<RequiredMembers<T>[]>})
: [UncheckedRequiredMembers<T>] extends [never]
? {required?: Readonly<UncheckedRequiredMembers<T>[]>}
: {required: Readonly<UncheckedRequiredMembers<T>[]>})
: T extends null
? {
nullable: true
}
: never) & {
allOf?: Readonly<PartialSchema<T>[]>
anyOf?: Readonly<PartialSchema<T>[]>
oneOf?: Readonly<PartialSchema<T>[]>
if?: PartialSchema<T>
then?: PartialSchema<T>
else?: PartialSchema<T>
not?: PartialSchema<T>
allOf?: Readonly<UncheckedPartialSchema<T>[]>
anyOf?: Readonly<UncheckedPartialSchema<T>[]>
oneOf?: Readonly<UncheckedPartialSchema<T>[]>
if?: UncheckedPartialSchema<T>
then?: UncheckedPartialSchema<T>
else?: UncheckedPartialSchema<T>
not?: UncheckedPartialSchema<T>
})
) & {
[keyword: string]: any
$id?: string
$ref?: string
$defs?: {
[Key in string]?: JSONSchemaType<Known, true>
[Key in string]?: UncheckedJSONSchemaType<Known, true>
}
definitions?: {
[Key in string]?: JSONSchemaType<Known, true>
[Key in string]?: UncheckedJSONSchemaType<Known, true>
}
}

export type JSONSchemaType<T> = StrictNullChecksWrapper<
"JSONSchemaType",
UncheckedJSONSchemaType<T, false>
>

type Known = KnownRecord | [Known, ...Known[]] | Known[] | number | string | boolean | null

interface KnownRecord extends Record<string, Known> {}

export type PropertiesSchema<T> = {
[K in keyof T]-?: (JSONSchemaType<T[K]> & Nullable<T[K]>) | {$ref: string}
type UncheckedPropertiesSchema<T> = {
[K in keyof T]-?: (UncheckedJSONSchemaType<T[K], false> & Nullable<T[K]>) | {$ref: string}
}

export type RequiredMembers<T> = {
export type PropertiesSchema<T> = StrictNullChecksWrapper<
"PropertiesSchema",
UncheckedPropertiesSchema<T>
>

type UncheckedRequiredMembers<T> = {
[K in keyof T]-?: undefined extends T[K] ? never : K
}[keyof T]

export type RequiredMembers<T> = StrictNullChecksWrapper<
"RequiredMembers",
UncheckedRequiredMembers<T>
>

type Nullable<T> = undefined extends T
? {
nullable: true
2 changes: 2 additions & 0 deletions lib/types/jtd-schema.ts
Original file line number Diff line number Diff line change
@@ -206,6 +206,8 @@ type JTDDataDef<S, D extends Record<string, unknown>> =
: // type
S extends {type: NumberType}
? number
: S extends {type: "boolean"}
? boolean
: S extends {type: "string"}
? string
: S extends {type: "timestamp"}
22 changes: 19 additions & 3 deletions lib/vocabularies/jtd/type.ts
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import validTimestamp from "../../runtime/timestamp"
import {useFunc} from "../../compile/util"
import {checkMetadata} from "./metadata"
import {typeErrorMessage, typeErrorParams, _JTDTypeError} from "./error"
import {_Code} from "../../compile/codegen/code"

export type JTDTypeError = _JTDTypeError<"type", JTDType, JTDType>

@@ -26,22 +27,37 @@ const error: KeywordErrorDefinition = {
params: (cxt) => typeErrorParams(cxt, cxt.schema),
}

function timestampCode(cxt: KeywordCxt): _Code {
const {gen, data} = cxt
switch (cxt.it.opts.timestamp) {
case "date":
return _`${data} instanceof Date `
case "string": {
const vts = useFunc(gen, validTimestamp)
return _`typeof ${data} == "string" && ${vts}(${data})`
}
default: {
const vts = useFunc(gen, validTimestamp)
return _`${data} instanceof Date || (typeof ${data} == "string" && ${vts}(${data}))`
}
}
}

const def: CodeKeywordDefinition = {
keyword: "type",
schemaType: "string",
error,
code(cxt: KeywordCxt) {
checkMetadata(cxt)
const {gen, data, schema, parentSchema} = cxt
const {data, schema, parentSchema} = cxt
let cond: Code
switch (schema) {
case "boolean":
case "string":
cond = _`typeof ${data} == ${schema}`
break
case "timestamp": {
const vts = useFunc(gen, validTimestamp)
cond = _`${data} instanceof Date || (typeof ${data} == "string" && ${vts}(${data}))`
cond = timestampCode(cxt)
break
}
case "float32":
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ajv",
"version": "8.2.0",
"version": "8.3.0",
"description": "Another JSON Schema Validator",
"main": "dist/ajv.js",
"types": "dist/ajv.d.ts",
@@ -66,11 +66,11 @@
"@ajv-validator/config": "^0.3.0",
"@rollup/plugin-commonjs": "^18.0.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^11.2.1",
"@rollup/plugin-node-resolve": "^13.0.0",
"@rollup/plugin-typescript": "^8.2.1",
"@types/chai": "^4.2.12",
"@types/mocha": "^8.0.3",
"@types/node": "^14.0.27",
"@types/node": "^15.0.2",
"@types/require-from-string": "^1.2.0",
"@typescript-eslint/eslint-plugin": "^3.8.0",
"@typescript-eslint/parser": "^3.8.0",
34 changes: 34 additions & 0 deletions spec/jtd-timestamps.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import _AjvJTD from "./ajv_jtd"
import assert = require("assert")

describe("JTD Timestamps", () => {
it("Should accept dates or strings by default", () => {
const ajv = new _AjvJTD()
const schema = {
type: "timestamp",
}
assert.strictEqual(ajv.validate(schema, new Date()), true)
assert.strictEqual(ajv.validate(schema, "2021-05-03T05:24:43.906Z"), true)
assert.strictEqual(ajv.validate(schema, "foo"), false)
})

it("Should enforce timestamp=string", () => {
const ajv = new _AjvJTD({timestamp: "string"})
const schema = {
type: "timestamp",
}
assert.strictEqual(ajv.validate(schema, new Date()), false)
assert.strictEqual(ajv.validate(schema, "2021-05-03T05:24:43.906Z"), true)
assert.strictEqual(ajv.validate(schema, "foo"), false)
})

it("Should enforce timestamp=date", () => {
const ajv = new _AjvJTD({timestamp: "date"})
const schema = {
type: "timestamp",
}
assert.strictEqual(ajv.validate(schema, new Date()), true)
assert.strictEqual(ajv.validate(schema, "2021-05-03T05:24:43.906Z"), false)
assert.strictEqual(ajv.validate(schema, "foo"), false)
})
})
7 changes: 7 additions & 0 deletions spec/types/jtd-schema.spec.ts
Original file line number Diff line number Diff line change
@@ -373,6 +373,13 @@ describe("JTDDataType", () => {
void [num]
})

it("should typecheck boolean schemas", () => {
const booleanSchema = {type: "boolean"} as const
const bool: TypeEquality<JTDDataType<typeof booleanSchema>, boolean> = true

void [bool]
})

it("should typecheck string schemas", () => {
const strSchema = {type: "string"} as const
const str: TypeEquality<JTDDataType<typeof strSchema>, string> = true