Skip to content

Commit

Permalink
JTD options, support inlineRefs option, fix removeAdditional option
Browse files Browse the repository at this point in the history
  • Loading branch information
epoberezkin committed Feb 10, 2021
1 parent 4a9084a commit 7e1711e
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 35 deletions.
45 changes: 30 additions & 15 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,36 +251,37 @@ Option defaults:
const defaultOptions = {
// strict mode options (NEW)
strict: true,
strictTypes: "log",
strictTuples: "log",
allowUnionTypes: false,
allowMatchingProperties: false,
validateFormats: true,
strictTypes: "log", // *
strictTuples: "log", // *
allowUnionTypes: false, // *
allowMatchingProperties: false, // *
validateFormats: true, // *
// validation and reporting options:
$data: false,
$data: false, // *
allErrors: false,
verbose: false,
$comment: false,
verbose: false, // *
$comment: false, // *
formats: {},
keywords: {},
schemas: {},
logger: undefined,
loadSchema: undefined, // function(uri: string): Promise {}
loadSchema: undefined, // *, function(uri: string): Promise {}
// options to modify validated data:
removeAdditional: false,
useDefaults: false,
coerceTypes: false,
useDefaults: false, // *
coerceTypes: false, // *
// advanced options:
meta: true,
validateSchema: true,
addUsedSchema: true,
inlineRefs: true,
passContext: false,
loopRequired: Infinity,
loopRequired: Infinity, // *
loopEnum: Infinity, // NEW
ownProperties: false,
multipleOfPrecision: undefined,
messages: true,
multipleOfPrecision: undefined, // *
messages: true, // false with JTD
ajvErrors: false // only with JTD
code: {
// NEW
es5: false,
Expand All @@ -292,6 +293,8 @@ const defaultOptions = {
}
```

<sup>\*</sup> these options are not supported with JSON Type Definition schemas

#### Strict mode options (NEW in v7)

- _strict_: By default Ajv executes in strict mode, that is designed to prevent any unexpected behaviours or silently ignored mistakes in schemas (see [Strict Mode](./strict-mode.md) for more details). It does not change any validation results, but it makes some schemas invalid that would be otherwise valid according to JSON Schema specification. Option values:
Expand Down Expand Up @@ -363,6 +366,7 @@ const defaultOptions = {
- _ownProperties_: by default Ajv iterates over all enumerable object properties; when this option is `true` only own enumerable object properties (i.e. found directly on the object rather than on its prototype) are iterated. Contributed by @mbroadst.
- _multipleOfPrecision_: by default `multipleOf` keyword is validated by comparing the result of division with parseInt() of that result. It works for dividers that are bigger than 1. For small dividers such as 0.01 the result of the division is usually not integer (even when it should be integer, see issue [#84](https://github.com/ajv-validator/ajv/issues/84)). If you need to use fractional dividers set this option to some positive integer N to have `multipleOf` validated using this formula: `Math.abs(Math.round(division) - division) < 1e-N` (it is slower but allows for float arithmetic deviations).
- _messages_: Include human-readable messages in errors. `true` by default. `false` can be passed when messages are generated outside of Ajv code (e.g. with [ajv-i18n](https://github.com/ajv-validator/ajv-i18n)).
- _ajvErrors_: this option is only supported with JTD schemas to generate error objects with the properties described in the first part of [Validation errors](#validation-errors) section, otherwise JTD errors are generated when JTD schemas are used (see the second part of [the same section](#validation-errors)).
- _code_ (new in v7): code generation options:

```typescript
Expand Down Expand Up @@ -393,7 +397,7 @@ In case of validation failure, Ajv assigns the array of errors to `errors` prope
### Error objects
Each error is an object with the following properties:
Each error reported when validating against JSON Schema (also when validating against JTD schema with option `ajvErrors`) is an object with the following properties:
```typescript
interface ErrorObject {
Expand All @@ -413,6 +417,17 @@ interface ErrorObject {
}
```

[JTD specification](./json-type-definition.md) defines strict format for validation errors, where each error is an object with the following properties:

```typescript
interface JTDErrorObject {
instancePath: string // JSON Pointer to the location in the data instance
schemaPath: string // JSON Pointer to the location in the schema
}
```

This error format is used when using JTD schemas. To simplify usage, you may still generate Ajv error objects using `ajvErrors` option. You can also add a human-readable error message to error objects using option `messages`.

### Error parameters

Properties of `params` object in errors depend on the keyword that failed validation.
Expand Down
10 changes: 10 additions & 0 deletions docs/json-type-definition.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

This document informally describes JSON Type Definition (JTD) specification to help Ajv users to start using it. For formal definition please refer to [RFC8927](https://datatracker.ietf.org/doc/rfc8927/). Please report any contradictions in this document with the specification.

To use JTD schemas you need to import a different Ajv class:

```javascript
const AjvJTD = require("ajv/dist/jtd").default
// or in TypeScript:
// import Ajv from "ajv/dist/jtd"
const ajv = new AjvJTD()
```

## Contents

- [JTD schema forms](#jtd-schema-forms):
Expand All @@ -17,6 +26,7 @@ This document informally describes JSON Type Definition (JTD) specification to h
- [metadata](#metadata-schema-member)
- [union](#union-keyword)
- [user-defined keywords](#user-defined-keywords)
- [Validation errors](#validation-errors)

## JTD schema forms

Expand Down
26 changes: 15 additions & 11 deletions lib/compile/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,20 +110,25 @@ const E = {
function errorObjectCode(cxt: KeywordErrorCxt, error: KeywordErrorDefinition): Code {
const {createErrors, opts} = cxt.it
if (createErrors === false) return _`{}`
return opts.jtd ? jtdErrorObject(cxt) : ajvErrorObject(cxt, error)
return opts.jtd && !opts.ajvErrors ? jtdErrorObject(cxt, error) : ajvErrorObject(cxt, error)
}

function jtdErrorObject({gen, keyword, it}: KeywordErrorCxt): Code {
const {errorPath, errSchemaPath} = it
return gen.object(
function jtdErrorObject(cxt: KeywordErrorCxt, {message}: KeywordErrorDefinition): Code {
const {gen, keyword, it} = cxt
const {errorPath, errSchemaPath, opts} = it
const keyValues: [Name, SafeExpr | string][] = [
[E.instancePath, strConcat(N.dataPath, errorPath)],
[E.schemaPath, str`${errSchemaPath}/${keyword}`]
)
[E.schemaPath, str`${errSchemaPath}/${keyword}`],
]
if (opts.messages) {
keyValues.push([E.message, typeof message == "function" ? message(cxt) : message])
}
return gen.object(...keyValues)
}

function ajvErrorObject(cxt: KeywordErrorCxt, error: KeywordErrorDefinition): Code {
const {keyword, data, schemaValue, it} = cxt
const {gen, topSchemaRef, schemaPath, errorPath, errSchemaPath, propertyName, opts} = it
const {gen, keyword, data, schemaValue, it} = cxt
const {topSchemaRef, schemaPath, errorPath, errSchemaPath, propertyName, opts} = it
const {params, message} = error
const keyValues: [Name, SafeExpr | string][] = [
[E.keyword, keyword],
Expand All @@ -132,9 +137,8 @@ function ajvErrorObject(cxt: KeywordErrorCxt, error: KeywordErrorDefinition): Co
[E.params, typeof params == "function" ? params(cxt) : params || _`{}`],
]
if (propertyName) keyValues.push([E.propertyName, propertyName])
if (opts.messages !== false) {
const msg = typeof message == "function" ? message(cxt) : message
keyValues.push([E.message, msg])
if (opts.messages) {
keyValues.push([E.message, typeof message == "function" ? message(cxt) : message])
}
if (opts.verbose) {
keyValues.push(
Expand Down
3 changes: 2 additions & 1 deletion lib/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const EXT_SCOPE_NAMES = new Set([

export type Options = CurrentOptions & DeprecatedOptions

interface CurrentOptions {
export interface CurrentOptions {
// strict mode options (NEW)
strict?: boolean | "log"
strictTypes?: boolean | "log"
Expand Down Expand Up @@ -118,6 +118,7 @@ interface CurrentOptions {
multipleOfPrecision?: number
messages?: boolean
code?: CodeOptions // NEW
ajvErrors?: boolean
}

export interface CodeOptions {
Expand Down
33 changes: 31 additions & 2 deletions lib/jtd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,48 @@ export {KeywordCxt}
export {_, str, stringify, nil, Name, Code, CodeGen, CodeGenOptions} from "./compile/codegen"

import type {AnySchemaObject} from "./types"
import AjvCore, {Options} from "./core"
import AjvCore, {CurrentOptions} from "./core"
import jtdVocabulary from "./vocabularies/jtd"
import jtdMetaSchema from "./refs/jtd-schema"

// const META_SUPPORT_DATA = ["/properties"]

const META_SCHEMA_ID = "JTD-meta-schema"

export type JTDOptions = CurrentOptions & {
// strict mode options not supported with JTD:
strictTypes?: never
strictTuples?: never
allowMatchingProperties?: never
allowUnionTypes?: never
validateFormats?: never
// validation and reporting options not supported with JTD:
$data?: never
verbose?: never
$comment?: never
formats?: never
loadSchema?: never
// options to modify validated data:
useDefaults?: never
coerceTypes?: never
// advanced options:
next?: never
unevaluated?: never
dynamicRef?: never
meta?: boolean
defaultMeta?: never
inlineRefs?: boolean
loopRequired?: never
multipleOfPrecision?: never
ajvErrors?: boolean
}

export default class Ajv extends AjvCore {
constructor(opts: Options = {}) {
constructor(opts: JTDOptions = {}) {
super({
...opts,
jtd: true,
messages: opts.messages ?? false,
})
}

Expand Down
13 changes: 8 additions & 5 deletions lib/vocabularies/jtd/properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,14 @@ export function validateProperties(cxt: KeywordCxt): void {
const extra =
addProp === true ? addOptProp : addOptProp === true ? addProp : and(addProp, addOptProp)
gen.if(extra, () => {
if (it.opts.removeAdditional) gen.code(_`delete ${data}[${key}]`)
// cxt.setParams({additionalProperty: key})
cxt.error()
gen.assign(valid, false)
if (!it.opts.allErrors) gen.break()
if (it.opts.removeAdditional) {
gen.code(_`delete ${data}[${key}]`)
} else {
// cxt.setParams({additionalProperty: key})
cxt.error()
gen.assign(valid, false)
if (!it.opts.allErrors) gen.break()
}
})
})
}
Expand Down
2 changes: 1 addition & 1 deletion lib/vocabularies/jtd/ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const def: CodeKeywordDefinition = {
function validateJtdRef(): void {
const refSchema = (root.schema as AnySchemaObject).definitions?.[ref]
if (!refSchema) throw new MissingRefError("", ref, `No definition ${ref}`)
if (hasRef(refSchema)) callValidate(refSchema)
if (hasRef(refSchema) || !it.opts.inlineRefs) callValidate(refSchema)
else inlineRefSchema(refSchema)
}

Expand Down
1 change: 1 addition & 0 deletions spec/jtd-schema.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ describe("JSON Type Definition", () => {
before(() => {
ajvs = getAjvInstances(_AjvJTD, {
allErrors: true,
inlineRefs: false,
code: {es5: true, lines: true, optimize: false},
})
ajvs.forEach((ajv) => (ajv.opts.code.source = true))
Expand Down

0 comments on commit 7e1711e

Please sign in to comment.