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: v7.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: v7.2.1
Choose a head ref
  • 5 commits
  • 10 files changed
  • 1 contributor

Commits on Mar 7, 2021

  1. Copy the full SHA
    0249f0f View commit details
  2. Copy the full SHA
    6cd8eb8 View commit details
  3. fix: JSON parsers

    epoberezkin committed Mar 7, 2021
    Copy the full SHA
    8e07f30 View commit details
  4. Copy the full SHA
    8136725 View commit details
  5. 7.2.1

    epoberezkin committed Mar 7, 2021
    Copy the full SHA
    dd0eab5 View commit details
Showing with 1,858 additions and 67 deletions.
  1. +1 −5 README.md
  2. +1 −5 docs/.vuepress/config.js
  3. +3 −0 docs/.vuepress/styles/index.styl
  4. +4 −0 docs/guide/schema-language.md
  5. +1 −1 docs/options.md
  6. +6 −34 lib/compile/jtd/parse.ts
  7. +19 −11 lib/runtime/parseJson.ts
  8. +1 −1 package.json
  9. +1,756 −0 spec/json_parse_tests.json
  10. +66 −10 spec/jtd-schema.spec.ts
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -8,18 +8,16 @@ Super fast JSON schema validator for Node.js and browser.

Supports JSON Schema draft-06/07/2019-09 (draft-04 is supported in [version 6](https://github.com/ajv-validator/ajv/tree/v6)) and JSON Type Definition [RFC8927](https://datatracker.ietf.org/doc/rfc8927/).

::: v-pre
[![build](https://github.com/ajv-validator/ajv/workflows/build/badge.svg)](https://github.com/ajv-validator/ajv/actions?query=workflow%3Abuild)
[![npm](https://img.shields.io/npm/v/ajv.svg)](https://www.npmjs.com/package/ajv)
[![npm downloads](https://img.shields.io/npm/dm/ajv.svg)](https://www.npmjs.com/package/ajv)
[![Coverage Status](https://coveralls.io/repos/github/ajv-validator/ajv/badge.svg?branch=master)](https://coveralls.io/github/ajv-validator/ajv?branch=master)
[![Gitter](https://img.shields.io/gitter/room/ajv-validator/ajv.svg)](https://gitter.im/ajv-validator/ajv)
[![GitHub Sponsors](https://img.shields.io/badge/$-sponsors-brightgreen)](https://github.com/sponsors/epoberezkin)
:::

## Platinum sponsors

[<img src="https://ajv.js.org/img/mozilla.svg" width="45%">](https://www.mozilla.org)<img src="https://ajv.js.org/img/gap.svg" width="5%">[<img src="https://ajv.js.org/img/reserved.svg" width="45%">](https://opencollective.com/ajv)
[<img src="https://ajv.js.org/img/mozilla.svg" width="45%">](https://www.mozilla.org)<img src="https://ajv.js.org/img/gap.svg" width="8%">[<img src="https://ajv.js.org/img/reserved.svg" width="45%">](https://opencollective.com/ajv)

## Using version 7

@@ -136,9 +134,7 @@ Please also review [Contributing guidelines](./CONTRIBUTING.md) and [Code compon

## Mozilla MOSS grant and OpenJS Foundation

::: v-pre
[<img src="https://ajv.js.org/img/mozilla.svg" width="240" height="68">](https://www.mozilla.org/en-US/moss/)<img src="https://ajv.js.org/img/gap.svg" width="5%">[<img src="https://ajv.js.org/img/openjs.png" width="220" height="68">](https://openjsf.org/blog/2020/08/14/ajv-joins-openjs-foundation-as-an-incubation-project/)
:::

Ajv has been awarded a grant from Mozilla’s [Open Source Support (MOSS) program](https://www.mozilla.org/en-US/moss/) in the “Foundational Technology” track! It will sponsor the development of Ajv support of [JSON Schema version 2019-09](https://tools.ietf.org/html/draft-handrews-json-schema-02) and of [JSON Type Definition (RFC8927)](https://datatracker.ietf.org/doc/rfc8927/).

6 changes: 1 addition & 5 deletions docs/.vuepress/config.js
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ module.exports = {
text: "Reference",
items: [
{link: "/api", text: "API Reference"},
{link: "/options", text: "Initialization options"},
{link: "/options", text: "Ajv options"},
{link: "/json-schema", text: "JSON Schema"},
{link: "/json-type-definition", text: "JSON Type Definition"},
{link: "/strict-mode", text: "Strict mode"},
@@ -65,7 +65,6 @@ module.exports = {
sidebar: [
{
title: "Guide",
collapsable: false,
children: [
"/guide/getting-started",
"/guide/typescript",
@@ -81,7 +80,6 @@ module.exports = {
},
{
title: "Reference",
collapsable: false,
children: [
"/api",
"/options",
@@ -95,7 +93,6 @@ module.exports = {
},
{
title: "Contributors",
collapsable: false,
children: [
"/contributing",
"/codegen",
@@ -105,7 +102,6 @@ module.exports = {
},
{
title: "Information",
collapsable: false,
children: ["/faq", "/security", ["/license", "License"]],
},
],
3 changes: 3 additions & 0 deletions docs/.vuepress/styles/index.styl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
img + span > .icon.outbound {
display: none;
}
4 changes: 4 additions & 0 deletions docs/guide/schema-language.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
tags:
- JTD
---
# Choosing schema language

[[toc]]
2 changes: 1 addition & 1 deletion docs/options.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Initialization options
# Ajv options

[[toc]]

40 changes: 6 additions & 34 deletions lib/compile/jtd/parse.ts
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ import {SchemaEnv, getCompilingSchema} from ".."
import {_, str, and, nil, not, CodeGen, Code, Name, SafeExpr} from "../codegen"
import {MissingRefError} from "../error_classes"
import N from "../names"
import {isOwnProperty, hasPropFunc} from "../../vocabularies/code"
import {hasPropFunc} from "../../vocabularies/code"
import {hasRef} from "../../vocabularies/jtd/ref"
import {intRange, IntType} from "../../vocabularies/jtd/type"
import {parseJson, parseJsonNumber, parseJsonString} from "../../runtime/parseJson"
@@ -117,26 +117,6 @@ function parseCode(cxt: ParseCxt): void {

const parseBoolean = parseBooleanToken(true, parseBooleanToken(false, jsonSyntaxError))

// function parseEmptyCode(cxt: ParseCxt): void {
// const {gen, data, char: c} = cxt
// skipWhitespace(cxt)
// gen.assign(c, _`${N.json}[${N.jsonPos}]`)
// gen.if(_`${c} === "t" || ${c} === "f"`)
// parseBoolean(cxt)
// gen.elseIf(_`${c} === "n"`)
// tryParseToken(cxt, "null", jsonSyntaxError, () => gen.assign(data, null))
// gen.elseIf(_`${c} === '"'`)
// parseString(cxt)
// gen.elseIf(_`${c} === "["`)
// parseElements({...cxt, schema: {elements: {}}})
// gen.elseIf(_`${c} === "{"`)
// parseValues({...cxt, schema: {values: {}}})
// gen.else()
// parseNumber(cxt)
// gen.endIf()
// skipWhitespace(cxt)
// }

function parseNullable(cxt: ParseCxt, parseForm: GenParse): void {
const {gen, schema, data} = cxt
if (!schema.nullable) return parseForm(cxt)
@@ -171,15 +151,18 @@ function tryParseItems(cxt: ParseCxt, endToken: string, block: () => void): void
const {gen} = cxt
gen.for(_`;${N.jsonPos}<${N.jsonLen} && ${jsonSlice(1)}!==${endToken};`, () => {
block()
tryParseToken(cxt, ",", () => gen.break())
tryParseToken(cxt, ",", () => gen.break(), hasItem)
})

function hasItem(): void {
tryParseToken(cxt, endToken, () => {}, jsonSyntaxError)
}
}

function parseKeyValue(cxt: ParseCxt, schema: SchemaObject): void {
const {gen} = cxt
const key = gen.let("key")
parseString({...cxt, data: key})
checkDuplicateProperty(cxt, key)
parseToken(cxt, ":")
parsePropertyValue(cxt, key, schema)
}
@@ -231,11 +214,6 @@ function parseSchemaProperties(cxt: ParseCxt, discriminator?: string): void {
parseItems(cxt, "}", () => {
const key = gen.let("key")
parseString({...cxt, data: key})
if (discriminator) {
gen.if(_`${key} !== ${discriminator}`, () => checkDuplicateProperty(cxt, key))
} else {
checkDuplicateProperty(cxt, key)
}
parseToken(cxt, ":")
gen.if(false)
parseDefinedProperty(cxt, key, properties)
@@ -270,12 +248,6 @@ function parseDefinedProperty(cxt: ParseCxt, key: Name, schemas: SchemaObjectMap
}
}

function checkDuplicateProperty({gen, data}: ParseCxt, key: Name): void {
gen.if(isOwnProperty(gen, data, key), () =>
gen.throw(_`new Error("JSON: duplicate property " + ${key})`)
)
}

function parsePropertyValue(cxt: ParseCxt, key: Name, schema: SchemaObject): void {
parseCode({...cxt, schema, data: _`${cxt.data}[${key}]`})
}
30 changes: 19 additions & 11 deletions lib/runtime/parseJson.ts
Original file line number Diff line number Diff line change
@@ -17,12 +17,13 @@ export function parseJson(s: string, pos: number): unknown {
return undefined
}
endPos = +matches[1]
const c = s[endPos]
s = s.slice(0, endPos)
parseJson.position = pos + endPos
try {
return JSON.parse(s)
} catch (e1) {
parseJson.message = `unexpected token ${s[endPos]}`
parseJson.message = `unexpected token ${c}`
return undefined
}
}
@@ -87,7 +88,8 @@ export function parseJsonNumber(s: string, pos: number, maxDigits?: number): num
}

function errorMessage(): void {
parseJson.message = pos < s.length ? `unexpected token ${s[pos]}` : "unexpected end"
parseJsonNumber.position = pos
parseJsonNumber.message = pos < s.length ? `unexpected token ${s[pos]}` : "unexpected end"
}
}

@@ -106,51 +108,57 @@ const escapedChars: {[X in string]?: string} = {
"\\": "\\",
}

const A_CODE: number = "a".charCodeAt(0)
const CODE_A: number = "a".charCodeAt(0)
const CODE_0: number = "0".charCodeAt(0)

export function parseJsonString(s: string, pos: number): string | undefined {
let str = ""
let c: string | undefined
parseJsonString.message = undefined
// eslint-disable-next-line no-constant-condition, @typescript-eslint/no-unnecessary-condition
while (true) {
c = s[pos]
pos++
c = s[pos++]
if (c === '"') break
if (c === "\\") {
c = s[pos]
if (c in escapedChars) {
str += escapedChars[c]
pos++
} else if (c === "u") {
pos++
let count = 4
let code = 0
while (count--) {
code <<= 4
c = s[pos].toLowerCase()
if (c >= "a" && c <= "f") {
c += c.charCodeAt(0) - A_CODE + 10
code += c.charCodeAt(0) - CODE_A + 10
} else if (c >= "0" && c <= "9") {
code += +c
code += c.charCodeAt(0) - CODE_0
} else if (c === undefined) {
errorMessage("unexpected end")
return undefined
} else {
errorMessage(`unexpected token ${s[pos]}`)
errorMessage(`unexpected token ${c}`)
return undefined
}
pos++
}
str += String.fromCharCode(code)
} else {
errorMessage(`unexpected token ${s[pos]}`)
errorMessage(`unexpected token ${c}`)
return undefined
}
pos++
} else if (c === undefined) {
errorMessage("unexpected end")
return undefined
} else {
str += c
if (c.charCodeAt(0) >= 0x20) {
str += c
} else {
errorMessage(`unexpected token ${c}`)
return undefined
}
}
}
parseJsonString.position = pos
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ajv",
"version": "7.2.0",
"version": "7.2.1",
"description": "Another JSON Schema Validator",
"main": "dist/ajv.js",
"types": "dist/ajv.d.ts",
1,756 changes: 1,756 additions & 0 deletions spec/json_parse_tests.json

Large diffs are not rendered by default.

76 changes: 66 additions & 10 deletions spec/jtd-schema.spec.ts
Original file line number Diff line number Diff line change
@@ -5,6 +5,8 @@ import getAjvInstances from "./ajv_instances"
import {withStandalone} from "./ajv_standalone"
import jtdValidationTests = require("./json-typedef-spec/tests/validation.json")
import jtdInvalidSchemasTests = require("./json-typedef-spec/tests/invalid_schemas.json")
// tests from https://github.com/nst/JSONTestSuite
import jsonParseTests = require("./json_parse_tests.json")
import assert = require("assert")
// import AjvPack from "../dist/standalone/instance"

@@ -19,6 +21,20 @@ interface TestCaseError {
schemaPath: string[]
}

interface JSONParseTest {
name: string
valid: boolean | null
json: string
data?: unknown
only?: boolean
skip?: boolean
}

interface JSONParseTestSuite {
suite: string
tests: JSONParseTest[]
}

// interface JTDError {
// instancePath: string
// schemaPath: string
@@ -83,7 +99,6 @@ describe("JSON Type Definition", () => {

describe("invalid schemas", () => {
let ajv: AjvJTD

before(() => (ajv = new _AjvJTD()))

for (const testName in jtdInvalidSchemasTests) {
@@ -112,7 +127,8 @@ describe("JSON Type Definition", () => {
})

describe("parse", () => {
const ajv = new _AjvJTD()
let ajv: AjvJTD
before(() => (ajv = new _AjvJTD()))

for (const testName in jtdValidationTests) {
const {schema, instance, errors} = jtdValidationTests[testName] as TestCase
@@ -135,21 +151,61 @@ describe("JSON Type Definition", () => {
}
})
}
})

function shouldParse(parse: JTDParser, str: string, res: unknown): void {
assert.deepStrictEqual(parse(str), res)
assert.strictEqual(parse.message, undefined)
assert.strictEqual(parse.position, undefined)
describe("parse tests nst/JSONTestSuite", () => {
const ajv = new _AjvJTD()
const parseJson: JTDParser = ajv.compileParser({})
const parse: {[K in "string" | "number" | "array" | "object"]: JTDParser} = {
string: ajv.compileParser({elements: {type: "string"}}),
number: ajv.compileParser({elements: {type: "float64"}}),
array: ajv.compileParser({elements: {}}),
object: ajv.compileParser({values: {}}),
}

function shouldFail(parse: JTDParser, str: string): void {
assert.strictEqual(parse(str), undefined)
assert.strictEqual(typeof parse.message, "string")
assert.strictEqual(typeof parse.position, "number")
for (const {suite, tests} of jsonParseTests as JSONParseTestSuite[]) {
describe(suite, () => {
for (const test of tests) {
const {valid, name, json, data} = test
if (valid) {
it(`should parse ${name}`, () => shouldParse(parseJson, json, data))
if (suite in parse) {
_it(test)(`should parse as ${suite}: ${name}`, () =>
shouldParse(parse[suite], json, data)
)
}
} else if (valid === false) {
it(`should fail parsing ${name}`, () => shouldFail(parseJson, json))
if (suite in parse) {
_it(test)(`should fail parsing as ${suite}: ${name}`, () =>
shouldFail(parse[suite], json)
)
}
}
}
})
}
})
})

type TestFunc = typeof it | typeof it.only | typeof it.skip

function _it({only, skip}: JSONParseTest): TestFunc {
return skip ? it.skip : only ? it.only : it
}

function shouldParse(parse: JTDParser, str: string, res: unknown): void {
assert.deepStrictEqual(parse(str), res)
assert.strictEqual(parse.message, undefined)
assert.strictEqual(parse.position, undefined)
}

function shouldFail(parse: JTDParser, str: string): void {
assert.strictEqual(parse(str), undefined)
assert.strictEqual(typeof parse.message, "string")
assert.strictEqual(typeof parse.position, "number")
}

function describeOnly(name: string, func: () => void) {
if (ONLY.length === 0 || ONLY.some((p) => p.test(name))) {
describe(name, func)