2010 lines
97 KiB
JavaScript
2010 lines
97 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) IBM Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
const testHelper_1 = require("./utils/testHelper");
|
|
const serviceSetup_1 = require("./utils/serviceSetup");
|
|
const chai_1 = require("chai");
|
|
const yamlSettings_1 = require("../src/yamlSettings");
|
|
const schemaUrls_1 = require("../src/languageservice/utils/schemaUrls");
|
|
describe('Validation Tests', () => {
|
|
let languageSettingsSetup;
|
|
let validationHandler;
|
|
let yamlSettings;
|
|
let schemaProvider;
|
|
const toContent = (data) => JSON.stringify(data, null, 2);
|
|
before(() => {
|
|
languageSettingsSetup = new serviceSetup_1.ServiceSetup()
|
|
.withValidate()
|
|
.withCompletion()
|
|
.withCustomTags(['!Test', '!Ref sequence'])
|
|
.withSchemaFileMatch({ uri: schemaUrls_1.KUBERNETES_SCHEMA_URL, fileMatch: ['.drone.yml'] })
|
|
.withSchemaFileMatch({ uri: 'https://json.schemastore.org/drone', fileMatch: ['.drone.yml'] })
|
|
.withSchemaFileMatch({ uri: schemaUrls_1.KUBERNETES_SCHEMA_URL, fileMatch: ['test.yml'] })
|
|
.withSchemaFileMatch({
|
|
uri: 'https://raw.githubusercontent.com/composer/composer/master/res/composer-schema.json',
|
|
fileMatch: ['test.yml'],
|
|
});
|
|
const { validationHandler: valHandler, yamlSettings: settings, schemaProvider: testSchemaProvider, } = (0, testHelper_1.setupLanguageService)(languageSettingsSetup.languageSettings);
|
|
validationHandler = valHandler;
|
|
yamlSettings = settings;
|
|
schemaProvider = testSchemaProvider;
|
|
});
|
|
function parseSetup(content, customSchemaID) {
|
|
const testTextDocument = (0, testHelper_1.setupSchemaIDTextDocument)(content, customSchemaID);
|
|
yamlSettings.documents = new yamlSettings_1.TextDocumentTestManager();
|
|
yamlSettings.documents.set(testTextDocument);
|
|
return validationHandler.validateTextDocument(testTextDocument);
|
|
}
|
|
afterEach(() => {
|
|
schemaProvider.deleteSchema(testHelper_1.SCHEMA_ID);
|
|
});
|
|
describe('keyword: prefixItems + items', () => {
|
|
describe('Open tuple', () => {
|
|
beforeEach(() => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
type: 'array',
|
|
prefixItems: [{ type: 'string' }, { type: 'number' }],
|
|
});
|
|
});
|
|
it('allows extra items by default (items is unconstrained)', async () => {
|
|
const content = `- hello
|
|
- 123
|
|
- { totally: "anything" }`;
|
|
const result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
});
|
|
it('fails when a prefixItems position has the wrong type', async () => {
|
|
const content = `- 123\n- hello`;
|
|
const result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(2);
|
|
(0, chai_1.expect)(result[0].message).to.include('Incorrect type');
|
|
(0, chai_1.expect)(result[0].message).to.include('string');
|
|
(0, chai_1.expect)(result[1].message).to.include('Incorrect type');
|
|
(0, chai_1.expect)(result[1].message).to.include('number');
|
|
});
|
|
});
|
|
describe('Closed tuple', () => {
|
|
beforeEach(() => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
type: 'array',
|
|
prefixItems: [{ type: 'string' }, { type: 'number' }],
|
|
items: false,
|
|
});
|
|
});
|
|
it('forbids extra items after prefixItems', async () => {
|
|
const content = `- hello\n- 123\n- extra`;
|
|
const result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 2 or fewer.');
|
|
});
|
|
it('passes when length is within prefixItems', async () => {
|
|
const content = `- hello\n- 123`;
|
|
const result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
});
|
|
});
|
|
describe('Tuple with constrained extra items', () => {
|
|
beforeEach(() => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
type: 'array',
|
|
prefixItems: [{ type: 'string' }, { type: 'number' }],
|
|
items: { type: 'boolean' },
|
|
});
|
|
});
|
|
it('fails when an extra item does not match items schema', async () => {
|
|
const content = `- hello
|
|
- 123
|
|
- notBoolean`;
|
|
const result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Incorrect type');
|
|
(0, chai_1.expect)(result[0].message).to.include('boolean');
|
|
});
|
|
it('passes when extra items match items schema', async () => {
|
|
const content = `- hello
|
|
- 123
|
|
- true
|
|
- false`;
|
|
const result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
});
|
|
});
|
|
});
|
|
describe('keyword: prefixItems', () => {
|
|
it('a schema given for prefixItems', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
prefixItems: [{ type: 'integer' }, { type: 'string' }],
|
|
});
|
|
// correct types
|
|
let content = toContent([1, 'foo']);
|
|
let result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// wrong types
|
|
content = toContent(['foo', 1]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(2);
|
|
(0, chai_1.expect)(result[0].message).to.include('Incorrect type');
|
|
(0, chai_1.expect)(result[1].message).to.include('Incorrect type');
|
|
// incomplete array of items
|
|
content = toContent([1]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// array with additional items
|
|
content = toContent([1, 'foo', true]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// empty array
|
|
content = toContent([]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// JavaScript pseudo-array is valid
|
|
content = toContent({
|
|
'0': 'invalid',
|
|
'1': 'valid',
|
|
length: 2,
|
|
});
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
});
|
|
it('prefixItems with boolean schemas', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
prefixItems: [true, false],
|
|
});
|
|
// array with one item is valid
|
|
let content = toContent([1]);
|
|
let result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// array with two items is invalid
|
|
content = toContent([1, 'foo']);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Matches a schema that is not allowed');
|
|
// empty array is valid
|
|
content = toContent([]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
});
|
|
it('additional items are allowed by default', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
prefixItems: [{ type: 'integer' }],
|
|
});
|
|
// only the first item is validated
|
|
const content = toContent([1, 'foo', false]);
|
|
const result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
});
|
|
it('prefixItems with null instance elements', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
prefixItems: [
|
|
{
|
|
type: 'null',
|
|
},
|
|
],
|
|
});
|
|
// allows null elements
|
|
const content = toContent([null]);
|
|
const result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
});
|
|
});
|
|
describe('keyword: items', () => {
|
|
it('a schema given for items', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
items: { type: 'integer' },
|
|
});
|
|
// valid items
|
|
let content = toContent([1, 2, 3]);
|
|
let result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// wrong type of items
|
|
content = toContent([1, 'x']);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Incorrect type');
|
|
// ignores non-arrays
|
|
content = toContent({ foo: 'bar' });
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// JavaScript pseudo-array is valid
|
|
content = toContent({
|
|
'0': 'invalid',
|
|
length: 1,
|
|
});
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
});
|
|
it('items with boolean schema (true)', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
items: true,
|
|
});
|
|
// any array is valid
|
|
let content = toContent([1, 'foo', true]);
|
|
let result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// empty array is valid
|
|
content = toContent([]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
});
|
|
it('items with boolean schema (false)', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
items: false,
|
|
});
|
|
// any non-empty array is invalid
|
|
let content = toContent([1, 'foo', true]);
|
|
let result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 0 or fewer.');
|
|
// empty array is valid
|
|
content = toContent([]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
});
|
|
it('items and subitems', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$defs: {
|
|
item: {
|
|
type: 'array',
|
|
items: false,
|
|
prefixItems: [{ $ref: '#/$defs/sub-item' }, { $ref: '#/$defs/sub-item' }],
|
|
},
|
|
'sub-item': {
|
|
type: 'object',
|
|
required: ['foo'],
|
|
},
|
|
},
|
|
type: 'array',
|
|
items: false,
|
|
prefixItems: [{ $ref: '#/$defs/item' }, { $ref: '#/$defs/item' }, { $ref: '#/$defs/item' }],
|
|
});
|
|
// valid items
|
|
let content = toContent([
|
|
[{ foo: null }, { foo: null }],
|
|
[{ foo: null }, { foo: null }],
|
|
[{ foo: null }, { foo: null }],
|
|
]);
|
|
let result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// too many items
|
|
content = toContent([
|
|
[{ foo: null }, { foo: null }],
|
|
[{ foo: null }, { foo: null }],
|
|
[{ foo: null }, { foo: null }],
|
|
[{ foo: null }, { foo: null }],
|
|
]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too many items');
|
|
// too many sub-items
|
|
content = toContent([
|
|
[{ foo: null }, { foo: null }, { foo: null }],
|
|
[{ foo: null }, { foo: null }],
|
|
[{ foo: null }, { foo: null }],
|
|
]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too many items');
|
|
// wrong item
|
|
content = toContent([{ foo: null }, [{ foo: null }, { foo: null }], [{ foo: null }, { foo: null }]]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Incorrect type');
|
|
// wrong sub-item
|
|
content = toContent([
|
|
[{}, { foo: null }],
|
|
[{ foo: null }, { foo: null }],
|
|
[{ foo: null }, { foo: null }],
|
|
]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Missing property');
|
|
// fewer items is valid
|
|
content = toContent([[{ foo: null }], [{ foo: null }]]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
});
|
|
it('nested items', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
type: 'array',
|
|
items: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'number',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
// valid nested array
|
|
let content = toContent([[[[1]], [[2], [3]]], [[[4], [5], [6]]]]);
|
|
let result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// nested array with invalid type
|
|
content = toContent([[[['1']], [[2], [3]]], [[[4], [5], [6]]]]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Incorrect type');
|
|
// not deep enough
|
|
content = toContent([
|
|
[[1], [2], [3]],
|
|
[[4], [5], [6]],
|
|
]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(6);
|
|
(0, chai_1.expect)(result[0].message).to.include('Incorrect type');
|
|
});
|
|
it('prefixItems with no additional items allowed', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
prefixItems: [{}, {}, {}],
|
|
items: false,
|
|
});
|
|
// empty array
|
|
let content = toContent([]);
|
|
let result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// fewer number of items present (1)
|
|
content = toContent([1]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// fewer number of items present (2)
|
|
content = toContent([1, 2]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// equal number of items present
|
|
content = toContent([1, 2, 3]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// additional items are not permitted
|
|
content = toContent([1, 2, 3, 4]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too many items');
|
|
});
|
|
it('items does not look in applicators, valid case', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
allOf: [{ prefixItems: [{ minimum: 3 }] }],
|
|
items: { minimum: 5 },
|
|
});
|
|
// prefixItems in allOf does not constrain items, invalid case
|
|
let content = toContent([3, 5]);
|
|
let result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Value is below the minimum of 5.');
|
|
// prefixItems in allOf does not constrain items, valid case
|
|
content = toContent([5, 5]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
});
|
|
it('prefixItems validation adjusts the starting index for items', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
prefixItems: [{ type: 'string' }],
|
|
items: { type: 'integer' },
|
|
});
|
|
// valid items
|
|
let content = toContent(['x', 2, 3]);
|
|
let result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// wrong type of second item
|
|
content = toContent(['x', 'y']);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Incorrect type');
|
|
});
|
|
it('items with heterogeneous array', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
prefixItems: [{}],
|
|
items: false,
|
|
});
|
|
// heterogeneous invalid instance
|
|
let content = toContent(['foo', 'bar', 37]);
|
|
let result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too many items');
|
|
// valid instance
|
|
content = toContent([null]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
});
|
|
it('items with null instance elements', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
items: {
|
|
type: 'null',
|
|
},
|
|
});
|
|
// allows null elements
|
|
const content = toContent([null]);
|
|
const result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
});
|
|
});
|
|
describe('keyword: unevaluatedItems', () => {
|
|
it('unevaluatedItems true', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
unevaluatedItems: true,
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent([]))).to.be.empty;
|
|
(0, chai_1.expect)(await parseSetup(toContent(['foo']))).to.be.empty;
|
|
});
|
|
it('unevaluatedItems false', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
unevaluatedItems: false,
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent([]))).to.be.empty;
|
|
const result = await parseSetup(toContent(['foo']));
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 0 or fewer.');
|
|
});
|
|
it('unevaluatedItems as schema', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
unevaluatedItems: { type: 'string' },
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent([]))).to.be.empty;
|
|
(0, chai_1.expect)(await parseSetup(toContent(['foo']))).to.be.empty;
|
|
const result = await parseSetup(toContent([42]));
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Incorrect type. Expected');
|
|
(0, chai_1.expect)(result[0].message).to.include('string');
|
|
});
|
|
it('unevaluatedItems with uniform items', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
items: { type: 'string' },
|
|
unevaluatedItems: false,
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent(['foo', 'bar']))).to.be.empty;
|
|
});
|
|
it('unevaluatedItems with tuple', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
prefixItems: [{ type: 'string' }],
|
|
unevaluatedItems: false,
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent(['foo']))).to.be.empty;
|
|
const result = await parseSetup(toContent(['foo', 'bar']));
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 1 or fewer.');
|
|
});
|
|
it('unevaluatedItems with items and prefixItems', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
prefixItems: [{ type: 'string' }],
|
|
items: true,
|
|
unevaluatedItems: false,
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent(['foo', 42]))).to.be.empty;
|
|
});
|
|
it('unevaluatedItems with items', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
items: { type: 'number' },
|
|
unevaluatedItems: { type: 'string' },
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent([5, 6, 7, 8]))).to.be.empty;
|
|
const result = await parseSetup(toContent(['foo', 'bar', 'baz']));
|
|
(0, chai_1.expect)(result).to.have.length(3);
|
|
(0, chai_1.expect)(result[0].message).to.include('Incorrect type. Expected');
|
|
(0, chai_1.expect)(result[0].message).to.include('number');
|
|
});
|
|
it('unevaluatedItems with nested tuple', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
prefixItems: [{ type: 'string' }],
|
|
allOf: [
|
|
{
|
|
prefixItems: [true, { type: 'number' }],
|
|
},
|
|
],
|
|
unevaluatedItems: false,
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent(['foo', 42]))).to.be.empty;
|
|
const result = await parseSetup(toContent(['foo', 42, true]));
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 2 or fewer.');
|
|
});
|
|
it('unevaluatedItems with nested items', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
unevaluatedItems: { type: 'boolean' },
|
|
anyOf: [{ items: { type: 'string' } }, true],
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent([true, false]))).to.be.empty;
|
|
(0, chai_1.expect)(await parseSetup(toContent(['yes', 'no']))).to.be.empty;
|
|
const result = await parseSetup(toContent(['yes', false]));
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Incorrect type. Expected');
|
|
(0, chai_1.expect)(result[0].message).to.include('boolean');
|
|
});
|
|
it('unevaluatedItems with nested prefixItems and items', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
allOf: [
|
|
{
|
|
prefixItems: [{ type: 'string' }],
|
|
items: true,
|
|
},
|
|
],
|
|
unevaluatedItems: false,
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent(['foo']))).to.be.empty;
|
|
(0, chai_1.expect)(await parseSetup(toContent(['foo', 42, true]))).to.be.empty;
|
|
});
|
|
it('unevaluatedItems with nested unevaluatedItems', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
allOf: [
|
|
{
|
|
prefixItems: [{ type: 'string' }],
|
|
},
|
|
{ unevaluatedItems: true },
|
|
],
|
|
unevaluatedItems: false,
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent(['foo']))).to.be.empty;
|
|
(0, chai_1.expect)(await parseSetup(toContent(['foo', 42, true]))).to.be.empty;
|
|
});
|
|
it('unevaluatedItems with anyOf', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
prefixItems: [{ const: 'foo' }],
|
|
anyOf: [
|
|
{
|
|
prefixItems: [true, { const: 'bar' }],
|
|
},
|
|
{
|
|
prefixItems: [true, true, { const: 'baz' }],
|
|
},
|
|
],
|
|
unevaluatedItems: false,
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent(['foo', 'bar']))).to.be.empty;
|
|
const result1 = await parseSetup(toContent(['foo', 'bar', 42]));
|
|
(0, chai_1.expect)(result1).to.have.length(1);
|
|
(0, chai_1.expect)(result1[0].message).to.include('Array has too many items according to schema. Expected 2 or fewer.');
|
|
(0, chai_1.expect)(await parseSetup(toContent(['foo', 'bar', 'baz']))).to.be.empty;
|
|
const result2 = await parseSetup(toContent(['foo', 'bar', 'baz', 42]));
|
|
(0, chai_1.expect)(result2).to.have.length(1);
|
|
(0, chai_1.expect)(result2[0].message).to.include('Array has too many items according to schema. Expected 3 or fewer.');
|
|
});
|
|
it('unevaluatedItems with oneOf', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
prefixItems: [{ const: 'foo' }],
|
|
oneOf: [
|
|
{
|
|
prefixItems: [true, { const: 'bar' }],
|
|
},
|
|
{
|
|
prefixItems: [true, { const: 'baz' }],
|
|
},
|
|
],
|
|
unevaluatedItems: false,
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent(['foo', 'bar']))).to.be.empty;
|
|
const result = await parseSetup(toContent(['foo', 'bar', 42]));
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 2 or fewer.');
|
|
});
|
|
it('unevaluatedItems with not', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
prefixItems: [{ const: 'foo' }],
|
|
not: {
|
|
not: {
|
|
prefixItems: [true, { const: 'bar' }],
|
|
},
|
|
},
|
|
unevaluatedItems: false,
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
const result = await parseSetup(toContent(['foo', 'bar']));
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 1 or fewer.');
|
|
});
|
|
it('unevaluatedItems with if/then/else', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
prefixItems: [{ const: 'foo' }],
|
|
if: {
|
|
prefixItems: [true, { const: 'bar' }],
|
|
},
|
|
then: {
|
|
prefixItems: [true, true, { const: 'then' }],
|
|
},
|
|
else: {
|
|
prefixItems: [true, true, true, { const: 'else' }],
|
|
},
|
|
unevaluatedItems: false,
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent(['foo', 'bar', 'then']))).to.be.empty;
|
|
const result1 = await parseSetup(toContent(['foo', 'bar', 'then', 'else']));
|
|
(0, chai_1.expect)(result1).to.have.length(1);
|
|
(0, chai_1.expect)(result1[0].message).to.include('Array has too many items according to schema. Expected 3 or fewer.');
|
|
(0, chai_1.expect)(await parseSetup(toContent(['foo', 42, 42, 'else']))).to.be.empty;
|
|
const result2 = await parseSetup(toContent(['foo', 42, 42, 'else', 42]));
|
|
(0, chai_1.expect)(result2).to.have.length(1);
|
|
(0, chai_1.expect)(result2[0].message).to.include('Array has too many items according to schema. Expected 4 or fewer.');
|
|
});
|
|
it('unevaluatedItems with boolean schemas', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
allOf: [true],
|
|
unevaluatedItems: false,
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent([]))).to.be.empty;
|
|
const result = await parseSetup(toContent(['foo']));
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 0 or fewer.');
|
|
});
|
|
it('unevaluatedItems with $ref', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$ref: '#/$defs/bar',
|
|
prefixItems: [{ type: 'string' }],
|
|
unevaluatedItems: false,
|
|
$defs: {
|
|
bar: {
|
|
prefixItems: [true, { type: 'string' }],
|
|
},
|
|
},
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent(['foo', 'bar']))).to.be.empty;
|
|
const result = await parseSetup(toContent(['foo', 'bar', 'baz']));
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 2 or fewer.');
|
|
});
|
|
it('unevaluatedItems before $ref', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
unevaluatedItems: false,
|
|
prefixItems: [{ type: 'string' }],
|
|
$ref: '#/$defs/bar',
|
|
$defs: {
|
|
bar: {
|
|
prefixItems: [true, { type: 'string' }],
|
|
},
|
|
},
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent(['foo', 'bar']))).to.be.empty;
|
|
const result = await parseSetup(toContent(['foo', 'bar', 'baz']));
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 2 or fewer.');
|
|
});
|
|
it('unevaluatedItems with $dynamicRef', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: 'https://example.com/unevaluated-items-with-dynamic-ref/derived',
|
|
$ref: './baseSchema',
|
|
$defs: {
|
|
derived: {
|
|
$dynamicAnchor: 'addons',
|
|
prefixItems: [true, { type: 'string' }],
|
|
},
|
|
baseSchema: {
|
|
$id: './baseSchema',
|
|
$comment: "unevaluatedItems comes first so it's more likely to catch bugs with implementations that are sensitive to keyword ordering",
|
|
unevaluatedItems: false,
|
|
type: 'array',
|
|
prefixItems: [{ type: 'string' }],
|
|
$dynamicRef: '#addons',
|
|
$defs: {
|
|
defaultAddons: {
|
|
$comment: 'Needed to satisfy the bookending requirement',
|
|
$dynamicAnchor: 'addons',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent(['foo', 'bar']))).to.be.empty;
|
|
const result = await parseSetup(toContent(['foo', 'bar', 'baz']));
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 2 or fewer.');
|
|
});
|
|
it("unevaluatedItems can't see inside cousins", async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
allOf: [
|
|
{
|
|
prefixItems: [true],
|
|
},
|
|
{ unevaluatedItems: false },
|
|
],
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
const result = await parseSetup(toContent([1]));
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 0 or fewer.');
|
|
});
|
|
it('item is evaluated in an uncle schema to unevaluatedItems', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
properties: {
|
|
foo: {
|
|
prefixItems: [{ type: 'string' }],
|
|
unevaluatedItems: false,
|
|
},
|
|
},
|
|
anyOf: [
|
|
{
|
|
properties: {
|
|
foo: {
|
|
prefixItems: [true, { type: 'string' }],
|
|
},
|
|
},
|
|
},
|
|
],
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent({ foo: ['test'] }))).to.be.empty;
|
|
const result = await parseSetup(toContent({ foo: ['test', 'test'] }));
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 1 or fewer.');
|
|
});
|
|
it('unevaluatedItems depends on adjacent contains', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
prefixItems: [true],
|
|
contains: { type: 'string' },
|
|
unevaluatedItems: false,
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent([1, 'foo']))).to.be.empty;
|
|
const result1 = await parseSetup(toContent([1, 2]));
|
|
(0, chai_1.expect)(result1).to.have.length(2);
|
|
(0, chai_1.expect)(result1[0].message).to.include('Array has too few items matching');
|
|
(0, chai_1.expect)(result1[0].message).to.include('Expected 1 or more.');
|
|
(0, chai_1.expect)(result1[1].message).to.include('Array has too many items according to schema. Expected 1 or fewer.');
|
|
const result2 = await parseSetup(toContent([1, 2, 'foo']));
|
|
(0, chai_1.expect)(result2).to.have.length(1);
|
|
(0, chai_1.expect)(result2[0].message).to.include('Array has too many items according to schema. Expected 1 or fewer.');
|
|
});
|
|
it('unevaluatedItems depends on multiple nested contains', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
allOf: [{ contains: { multipleOf: 2 } }, { contains: { multipleOf: 3 } }],
|
|
unevaluatedItems: { multipleOf: 5 },
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent([2, 3, 4, 5, 6]))).to.be.empty;
|
|
const result = await parseSetup(toContent([2, 3, 4, 7, 8]));
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Value is not divisible by 5.');
|
|
});
|
|
it('unevaluatedItems and contains interact to control item dependency relationship', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
if: {
|
|
contains: { const: 'a' },
|
|
},
|
|
then: {
|
|
if: {
|
|
contains: { const: 'b' },
|
|
},
|
|
then: {
|
|
if: {
|
|
contains: { const: 'c' },
|
|
},
|
|
},
|
|
},
|
|
unevaluatedItems: false,
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent([]))).to.be.empty;
|
|
(0, chai_1.expect)(await parseSetup(toContent(['a', 'a']))).to.be.empty;
|
|
(0, chai_1.expect)(await parseSetup(toContent(['a', 'b', 'a', 'b', 'a']))).to.be.empty;
|
|
(0, chai_1.expect)(await parseSetup(toContent(['c', 'a', 'c', 'c', 'b', 'a']))).to.be.empty;
|
|
const result1 = await parseSetup(toContent(['b', 'b']));
|
|
(0, chai_1.expect)(result1).to.have.length(2);
|
|
(0, chai_1.expect)(result1[0].message).to.include('Array has too many items according to schema. Expected 0 or fewer.');
|
|
(0, chai_1.expect)(result1[1].message).to.include('Array has too many items according to schema. Expected 1 or fewer.');
|
|
const result2 = await parseSetup(toContent(['c', 'c']));
|
|
(0, chai_1.expect)(result2).to.have.length(2);
|
|
(0, chai_1.expect)(result2[0].message).to.include('Array has too many items according to schema. Expected 0 or fewer.');
|
|
(0, chai_1.expect)(result2[1].message).to.include('Array has too many items according to schema. Expected 1 or fewer.');
|
|
const result3 = await parseSetup(toContent(['c', 'b', 'c', 'b', 'c']));
|
|
(0, chai_1.expect)(result3).to.have.length(5);
|
|
(0, chai_1.expect)(result3[0].message).to.include('Array has too many items according to schema. Expected 0 or fewer.');
|
|
(0, chai_1.expect)(result3[1].message).to.include('Array has too many items according to schema. Expected 1 or fewer.');
|
|
(0, chai_1.expect)(result3[2].message).to.include('Array has too many items according to schema. Expected 2 or fewer.');
|
|
(0, chai_1.expect)(result3[3].message).to.include('Array has too many items according to schema. Expected 3 or fewer.');
|
|
(0, chai_1.expect)(result3[4].message).to.include('Array has too many items according to schema. Expected 4 or fewer.');
|
|
const result4 = await parseSetup(toContent(['c', 'a', 'c', 'a', 'c']));
|
|
(0, chai_1.expect)(result4).to.have.length(3);
|
|
(0, chai_1.expect)(result4[0].message).to.include('Array has too many items according to schema. Expected 0 or fewer.');
|
|
(0, chai_1.expect)(result4[1].message).to.include('Array has too many items according to schema. Expected 2 or fewer.');
|
|
(0, chai_1.expect)(result4[2].message).to.include('Array has too many items according to schema. Expected 4 or fewer.');
|
|
});
|
|
it('unevaluatedItems with minContains = 0', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
contains: { type: 'string' },
|
|
minContains: 0,
|
|
unevaluatedItems: false,
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent([]))).to.be.empty;
|
|
const result1 = await parseSetup(toContent([0]));
|
|
(0, chai_1.expect)(result1).to.have.length(1);
|
|
(0, chai_1.expect)(result1[0].message).to.include('Array has too many items according to schema. Expected 0 or fewer.');
|
|
const result2 = await parseSetup(toContent(['foo', 0]));
|
|
(0, chai_1.expect)(result2).to.have.length(1);
|
|
(0, chai_1.expect)(result2[0].message).to.include('Array has too many items according to schema. Expected 1 or fewer.');
|
|
(0, chai_1.expect)(await parseSetup(toContent(['foo', 'bar']))).to.be.empty;
|
|
});
|
|
it('non-array instances are valid', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
unevaluatedItems: false,
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent(true))).to.be.empty;
|
|
(0, chai_1.expect)(await parseSetup(toContent(123))).to.be.empty;
|
|
(0, chai_1.expect)(await parseSetup(toContent(1.0))).to.be.empty;
|
|
(0, chai_1.expect)(await parseSetup(toContent({}))).to.be.empty;
|
|
(0, chai_1.expect)(await parseSetup(toContent('foo'))).to.be.empty;
|
|
(0, chai_1.expect)(await parseSetup(toContent(null))).to.be.empty;
|
|
});
|
|
it('unevaluatedItems with null instance elements', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
unevaluatedItems: { type: 'null' },
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent([null]))).to.be.empty;
|
|
});
|
|
it('unevaluatedItems can see annotations from if without then and else', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
if: {
|
|
prefixItems: [{ const: 'a' }],
|
|
},
|
|
unevaluatedItems: false,
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(toContent(['a']))).to.be.empty;
|
|
const result = await parseSetup(toContent(['b']));
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 0 or fewer.');
|
|
});
|
|
it('Evaluated items collection needs to consider instance location', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
prefixItems: [
|
|
{
|
|
prefixItems: [true, { type: 'string' }],
|
|
},
|
|
],
|
|
unevaluatedItems: false,
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
const result = await parseSetup(toContent([['foo', 'bar'], 'bar']));
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 1 or fewer.');
|
|
});
|
|
});
|
|
describe('keyword: contains', () => {
|
|
it('contains keyword validation', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
contains: { minimum: 5 },
|
|
});
|
|
// array with item matching schema (5) is valid
|
|
let content = toContent([3, 4, 5]);
|
|
let result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// array with item matching schema (6) is valid
|
|
content = toContent([3, 4, 6]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// array with two items matching schema (5, 6) is valid
|
|
content = toContent([3, 4, 5, 6]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// array without items matching schema is invalid
|
|
content = toContent([2, 3, 4]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too few items matching');
|
|
// empty array is invalid
|
|
content = toContent([]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too few items matching');
|
|
// not array is valid
|
|
content = toContent({});
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
});
|
|
it('contains keyword with const keyword', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
contains: { const: 5 },
|
|
});
|
|
// array with item 5 is valid
|
|
let content = toContent([3, 4, 5]);
|
|
let result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// array with two items 5 is valid
|
|
content = toContent([3, 4, 5, 5]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// array without item 5 is invalid
|
|
content = toContent([1, 2, 3, 4]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too few items matching');
|
|
});
|
|
it('contains keyword with boolean schema true', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
contains: true,
|
|
});
|
|
// any non-empty array is valid
|
|
let content = toContent(['foo']);
|
|
let result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// empty array is invalid
|
|
content = toContent([]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too few items matching');
|
|
});
|
|
it('contains keyword with boolean schema false', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
contains: false,
|
|
});
|
|
// any non-empty array is invalid
|
|
let content = toContent(['foo']);
|
|
let result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too few items matching');
|
|
// empty array is invalid
|
|
content = toContent([]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too few items matching');
|
|
// non-arrays are valid
|
|
content = toContent('contains does not apply to strings');
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
});
|
|
it('items + contains', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
items: { multipleOf: 2 },
|
|
contains: { multipleOf: 3 },
|
|
});
|
|
// matches items, does not match contains
|
|
let content = toContent([2, 4, 8]);
|
|
let result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too few items matching');
|
|
// does not match items, matches contains
|
|
content = toContent([3, 6, 9]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(2);
|
|
(0, chai_1.expect)(result[0].message).to.include('Value is not divisible by 2.');
|
|
(0, chai_1.expect)(result[1].message).to.include('Value is not divisible by 2.');
|
|
// matches both items and contains
|
|
content = toContent([6, 12]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// matches neither items nor contains
|
|
content = toContent([1, 5]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(3);
|
|
(0, chai_1.expect)(result[0].message).to.include('Value is not divisible by 2.');
|
|
(0, chai_1.expect)(result[1].message).to.include('Value is not divisible by 2.');
|
|
(0, chai_1.expect)(result[2].message).to.include('Array has too few items matching');
|
|
});
|
|
it('contains with false if subschema', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
contains: {
|
|
if: false,
|
|
else: true,
|
|
},
|
|
});
|
|
// any non-empty array is valid
|
|
let content = toContent(['foo']);
|
|
let result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
// empty array is invalid
|
|
content = toContent([]);
|
|
result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too few items matching');
|
|
});
|
|
it('contains with null instance elements', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
contains: {
|
|
type: 'null',
|
|
},
|
|
});
|
|
// allows null items
|
|
const content = toContent([null]);
|
|
const result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
});
|
|
});
|
|
describe('contains and unevaluatedItems tests', () => {
|
|
beforeEach(() => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
type: 'array',
|
|
contains: { type: 'string' },
|
|
unevaluatedItems: { type: 'number' },
|
|
});
|
|
});
|
|
it('passes when there is at least one string and all non-matching items are numbers', async () => {
|
|
const content = `- hello
|
|
- 1
|
|
- 2`;
|
|
const result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
});
|
|
it('passes even if multiple items are strings (strings match contains => evaluated)', async () => {
|
|
const content = `- hello
|
|
- oops
|
|
- 3`;
|
|
const result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
});
|
|
it('fails if a non-matching item is not a number', async () => {
|
|
const content = `- hello\n- a: 1`;
|
|
const result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Incorrect type');
|
|
(0, chai_1.expect)(result[0].message).to.include('number');
|
|
});
|
|
it('fails if no item matches contains (minContains defaults to 1)', async () => {
|
|
const content = `- 1\n- 2`;
|
|
const result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too few items matching');
|
|
(0, chai_1.expect)(result[0].message).to.include('contains');
|
|
(0, chai_1.expect)(result[0].message).to.include('Expected 1 or more');
|
|
});
|
|
});
|
|
it('passes ["a","b","ccc"] because "ccc" is evaluated by contains', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
type: 'array',
|
|
prefixItems: [{ type: 'string' }, { type: 'string' }],
|
|
contains: { type: 'string', minLength: 3 },
|
|
unevaluatedItems: false,
|
|
});
|
|
const content = `- a
|
|
- b
|
|
- ccc`;
|
|
const result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.be.empty;
|
|
});
|
|
it('fails ["a","b","ccc"] using boolean-algebra workaround', async () => {
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
type: 'array',
|
|
prefixItems: [{ type: 'string' }, { type: 'string' }],
|
|
not: {
|
|
items: {
|
|
not: { type: 'string', minLength: 3 },
|
|
},
|
|
},
|
|
unevaluatedItems: false,
|
|
});
|
|
const content = `- a
|
|
- b
|
|
- ccc`;
|
|
const result = await parseSetup(content);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 2 or fewer.');
|
|
});
|
|
describe('Mixed dialect subschema instance validation in Compound Schema Document', () => {
|
|
it('draft-2020 root with draft-04 subschema', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
type: 'object',
|
|
properties: {
|
|
age: {
|
|
allOf: [
|
|
{
|
|
$schema: 'http://json-schema.org/draft-04/schema#',
|
|
type: 'number',
|
|
minimum: 0,
|
|
exclusiveMinimum: true,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
const failResult = await parseSetup('age: 0');
|
|
(0, chai_1.expect)(failResult).to.have.length(1);
|
|
(0, chai_1.expect)(failResult[0].message).to.include('exclusive minimum of 0');
|
|
const passResult = await parseSetup('age: 1');
|
|
(0, chai_1.expect)(passResult).to.be.empty;
|
|
});
|
|
it('draft-2020 root with draft-07 subschema', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
type: 'object',
|
|
properties: {
|
|
score: {
|
|
anyOf: [
|
|
{
|
|
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
type: 'number',
|
|
exclusiveMinimum: 0,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
const failResult = await parseSetup('score: 0');
|
|
(0, chai_1.expect)(failResult).to.have.length(1);
|
|
(0, chai_1.expect)(failResult[0].message).to.include('exclusive minimum of 0');
|
|
const passResult = await parseSetup('score: 1');
|
|
(0, chai_1.expect)(passResult).to.be.empty;
|
|
});
|
|
it('draft-07 root with draft-2019 subschema', async () => {
|
|
const schema = {
|
|
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
type: 'object',
|
|
required: ['productId'],
|
|
properties: {
|
|
productId: { type: 'string' },
|
|
metadata: {
|
|
$schema: 'https://json-schema.org/draft/2019-09/schema',
|
|
type: 'object',
|
|
properties: {
|
|
tags: {
|
|
type: 'array',
|
|
contains: { type: 'string' },
|
|
maxContains: 5,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
const failContent = `productId: "PROD-123"
|
|
metadata:
|
|
tags:
|
|
- "electronics"
|
|
- "sale"
|
|
- "featured"
|
|
- "new"
|
|
- "popular"
|
|
- "trending"`;
|
|
const failResult = await parseSetup(failContent);
|
|
(0, chai_1.expect)(failResult).to.have.length(1);
|
|
(0, chai_1.expect)(failResult[0].message).to.include('too many items matching "contains"');
|
|
(0, chai_1.expect)(failResult[0].message).to.include('5 or fewer');
|
|
const passContent = `productId: "PROD-123"
|
|
metadata:
|
|
tags:
|
|
- "electronics"
|
|
- "sale"
|
|
- "featured"
|
|
- "new"
|
|
- "popular"`;
|
|
const passResult = await parseSetup(passContent);
|
|
(0, chai_1.expect)(passResult).to.be.empty;
|
|
});
|
|
});
|
|
describe('$dynamicAnchor and $dynamicRef resolution', () => {
|
|
const treeSchema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: 'http://localhost:1234/draft2020-12/tree.json',
|
|
$dynamicAnchor: 'node',
|
|
type: 'object',
|
|
properties: {
|
|
data: true,
|
|
children: {
|
|
type: 'array',
|
|
items: { $dynamicRef: '#node' },
|
|
},
|
|
},
|
|
};
|
|
const extendibleDynamicRefSchema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: 'http://localhost:1234/draft2020-12/extendible-dynamic-ref.json',
|
|
type: 'object',
|
|
additionalProperties: false,
|
|
properties: {
|
|
elements: {
|
|
type: 'array',
|
|
items: { $dynamicRef: '#elements' },
|
|
},
|
|
},
|
|
$defs: {
|
|
elements: {
|
|
$comment: 'base dynamic anchor',
|
|
$dynamicAnchor: 'elements',
|
|
},
|
|
},
|
|
};
|
|
const detachedDynamicRefSchema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: 'http://localhost:1234/draft2020-12/detached-dynamicref.json',
|
|
$defs: {
|
|
foo: { $dynamicRef: '#number' },
|
|
number: { $dynamicAnchor: 'number', type: 'number' },
|
|
},
|
|
};
|
|
describe('basic $dynamicRef behavior', () => {
|
|
it('A $dynamicRef to a $dynamicAnchor in the same schema resource behaves like a normal $ref to an $anchor', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: 'https://test.json-schema.org/dynamicRef-dynamicAnchor-same-schema/root',
|
|
type: 'array',
|
|
items: { $dynamicRef: '#items' },
|
|
$defs: {
|
|
foo: {
|
|
$dynamicAnchor: 'items',
|
|
type: 'string',
|
|
},
|
|
},
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup("['foo', 'bar']")).to.be.empty;
|
|
const result = await parseSetup("['foo', 42]");
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Incorrect type.');
|
|
(0, chai_1.expect)(result[0].message).to.include('string');
|
|
});
|
|
it('A $dynamicRef to an $anchor in the same schema resource behaves like a normal $ref to an $anchor', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: 'https://test.json-schema.org/dynamicRef-anchor-same-schema/root',
|
|
type: 'array',
|
|
items: { $dynamicRef: '#items' },
|
|
$defs: {
|
|
foo: {
|
|
$anchor: 'items',
|
|
type: 'string',
|
|
},
|
|
},
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup("['foo', 'bar']")).to.be.empty;
|
|
const result = await parseSetup("['foo', 42]");
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Incorrect type.');
|
|
(0, chai_1.expect)(result[0].message).to.include('string');
|
|
});
|
|
it('A $ref to a $dynamicAnchor in the same schema resource behaves like a normal $ref to an $anchor', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: 'https://test.json-schema.org/ref-dynamicAnchor-same-schema/root',
|
|
type: 'array',
|
|
items: { $ref: '#items' },
|
|
$defs: {
|
|
foo: {
|
|
$dynamicAnchor: 'items',
|
|
type: 'string',
|
|
},
|
|
},
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup("['foo', 'bar']")).to.be.empty;
|
|
const result = await parseSetup("['foo', 42]");
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Incorrect type.');
|
|
(0, chai_1.expect)(result[0].message).to.include('string');
|
|
});
|
|
it('A $dynamicRef resolves to the first $dynamicAnchor still in scope that is encountered when the schema is evaluated', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: 'https://test.json-schema.org/typical-dynamic-resolution/root',
|
|
$ref: 'list',
|
|
$defs: {
|
|
foo: {
|
|
$dynamicAnchor: 'items',
|
|
type: 'string',
|
|
},
|
|
list: {
|
|
$id: 'list',
|
|
type: 'array',
|
|
items: { $dynamicRef: '#items' },
|
|
$defs: {
|
|
items: {
|
|
$comment: 'This is only needed to satisfy the bookending requirement',
|
|
$dynamicAnchor: 'items',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup("['foo', 'bar']")).to.be.empty;
|
|
const result = await parseSetup("['foo', 42]");
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Incorrect type.');
|
|
(0, chai_1.expect)(result[0].message).to.include('string');
|
|
});
|
|
it('A $dynamicRef without anchor in fragment behaves identical to $ref', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: 'https://test.json-schema.org/dynamicRef-without-anchor/root',
|
|
$ref: 'list',
|
|
$defs: {
|
|
foo: {
|
|
$dynamicAnchor: 'items',
|
|
type: 'string',
|
|
},
|
|
list: {
|
|
$id: 'list',
|
|
type: 'array',
|
|
items: { $dynamicRef: '#/$defs/items' },
|
|
$defs: {
|
|
items: {
|
|
$comment: 'This is only needed to satisfy the bookending requirement',
|
|
$dynamicAnchor: 'items',
|
|
type: 'number',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
const invalid = await parseSetup("['foo', 'bar']");
|
|
(0, chai_1.expect)(invalid).to.have.length(1);
|
|
(0, chai_1.expect)(invalid[0].message).to.include('Incorrect type.');
|
|
(0, chai_1.expect)(invalid[0].message).to.include('number');
|
|
(0, chai_1.expect)(await parseSetup('[24, 42]')).to.be.empty;
|
|
});
|
|
it("A $dynamicRef with intermediate scopes that don't include a matching $dynamicAnchor does not affect dynamic scope resolution", async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: 'https://test.json-schema.org/dynamic-resolution-with-intermediate-scopes/root',
|
|
$ref: 'intermediate-scope',
|
|
$defs: {
|
|
foo: {
|
|
$dynamicAnchor: 'items',
|
|
type: 'string',
|
|
},
|
|
'intermediate-scope': {
|
|
$id: 'intermediate-scope',
|
|
$ref: 'list',
|
|
},
|
|
list: {
|
|
$id: 'list',
|
|
type: 'array',
|
|
items: { $dynamicRef: '#items' },
|
|
$defs: {
|
|
items: {
|
|
$comment: 'This is only needed to satisfy the bookending requirement',
|
|
$dynamicAnchor: 'items',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup("['foo', 'bar']")).to.be.empty;
|
|
const result = await parseSetup("['foo', 42]");
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Incorrect type.');
|
|
(0, chai_1.expect)(result[0].message).to.include('string');
|
|
});
|
|
it('An $anchor with the same name as a $dynamicAnchor is not used for dynamic scope resolution', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: 'https://test.json-schema.org/dynamic-resolution-ignores-anchors/root',
|
|
$ref: 'list',
|
|
$defs: {
|
|
foo: {
|
|
$anchor: 'items',
|
|
type: 'string',
|
|
},
|
|
list: {
|
|
$id: 'list',
|
|
type: 'array',
|
|
items: { $dynamicRef: '#items' },
|
|
$defs: {
|
|
items: {
|
|
$comment: 'This is only needed to satisfy the bookending requirement',
|
|
$dynamicAnchor: 'items',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup("['foo', 42]")).to.be.empty;
|
|
});
|
|
it('A $dynamicRef without a matching $dynamicAnchor in the same schema resource behaves like a normal $ref to $anchor', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: 'https://test.json-schema.org/dynamic-resolution-without-bookend/root',
|
|
$ref: 'list',
|
|
$defs: {
|
|
foo: {
|
|
$dynamicAnchor: 'items',
|
|
type: 'string',
|
|
},
|
|
list: {
|
|
$id: 'list',
|
|
type: 'array',
|
|
items: { $dynamicRef: '#items' },
|
|
$defs: {
|
|
items: {
|
|
$comment: 'This is only needed to give the reference somewhere to resolve to when it behaves like $ref',
|
|
$anchor: 'items',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup("['foo', 42]")).to.be.empty;
|
|
});
|
|
it('A $dynamicRef with a non-matching $dynamicAnchor in the same schema resource behaves like a normal $ref to $anchor', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: 'https://test.json-schema.org/unmatched-dynamic-anchor/root',
|
|
$ref: 'list',
|
|
$defs: {
|
|
foo: {
|
|
$dynamicAnchor: 'items',
|
|
type: 'string',
|
|
},
|
|
list: {
|
|
$id: 'list',
|
|
type: 'array',
|
|
items: { $dynamicRef: '#items' },
|
|
$defs: {
|
|
items: {
|
|
$comment: 'This is only needed to give the reference somewhere to resolve to when it behaves like $ref',
|
|
$anchor: 'items',
|
|
$dynamicAnchor: 'foo',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, schema.$id, schema);
|
|
(0, chai_1.expect)(await parseSetup("['foo', 42]")).to.be.empty;
|
|
});
|
|
});
|
|
describe('relative dynamic references', () => {
|
|
it('A $dynamicRef that initially resolves to a schema with a matching $dynamicAnchor resolves to the first $dynamicAnchor in the dynamic scope', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: 'https://test.json-schema.org/relative-dynamic-reference/root',
|
|
$dynamicAnchor: 'meta',
|
|
type: 'object',
|
|
properties: {
|
|
foo: { const: 'pass' },
|
|
},
|
|
$ref: 'extended',
|
|
$defs: {
|
|
extended: {
|
|
$id: 'extended',
|
|
$dynamicAnchor: 'meta',
|
|
type: 'object',
|
|
properties: {
|
|
bar: { $ref: 'bar' },
|
|
},
|
|
},
|
|
bar: {
|
|
$id: 'bar',
|
|
type: 'object',
|
|
properties: {
|
|
baz: { $dynamicRef: 'extended#meta' },
|
|
},
|
|
},
|
|
},
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(`foo: "pass"
|
|
bar:
|
|
baz:
|
|
foo: pass`)).to.be.empty;
|
|
const result = await parseSetup(`foo: "pass"
|
|
bar:
|
|
baz:
|
|
foo: fail`);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Value must be');
|
|
(0, chai_1.expect)(result[0].message).to.include('pass');
|
|
});
|
|
it('A $dynamicRef that initially resolves to a schema without a matching $dynamicAnchor behaves like a normal $ref to $anchor', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: 'https://test.json-schema.org/relative-dynamic-reference-without-bookend/root',
|
|
$dynamicAnchor: 'meta',
|
|
type: 'object',
|
|
properties: {
|
|
foo: { const: 'pass' },
|
|
},
|
|
$ref: 'extended',
|
|
$defs: {
|
|
extended: {
|
|
$id: 'extended',
|
|
$anchor: 'meta',
|
|
type: 'object',
|
|
properties: {
|
|
bar: { $ref: 'bar' },
|
|
},
|
|
},
|
|
bar: {
|
|
$id: 'bar',
|
|
type: 'object',
|
|
properties: {
|
|
baz: { $dynamicRef: 'extended#meta' },
|
|
},
|
|
},
|
|
},
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(`foo: "pass"
|
|
bar:
|
|
baz:
|
|
foo: fail`)).to.be.empty;
|
|
});
|
|
});
|
|
it('multiple dynamic paths to the $dynamicRef keyword', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: 'https://test.json-schema.org/dynamic-ref-with-multiple-paths/main',
|
|
if: {
|
|
properties: {
|
|
kindOfList: { const: 'numbers' },
|
|
},
|
|
required: ['kindOfList'],
|
|
},
|
|
then: { $ref: 'numberList' },
|
|
else: { $ref: 'stringList' },
|
|
$defs: {
|
|
genericList: {
|
|
$id: 'genericList',
|
|
properties: {
|
|
list: {
|
|
items: { $dynamicRef: '#itemType' },
|
|
},
|
|
},
|
|
$defs: {
|
|
defaultItemType: {
|
|
$comment: 'Only needed to satisfy bookending requirement',
|
|
$dynamicAnchor: 'itemType',
|
|
},
|
|
},
|
|
},
|
|
numberList: {
|
|
$id: 'numberList',
|
|
$defs: {
|
|
itemType: {
|
|
$dynamicAnchor: 'itemType',
|
|
type: 'number',
|
|
},
|
|
},
|
|
$ref: 'genericList',
|
|
},
|
|
stringList: {
|
|
$id: 'stringList',
|
|
$defs: {
|
|
itemType: {
|
|
$dynamicAnchor: 'itemType',
|
|
type: 'string',
|
|
},
|
|
},
|
|
$ref: 'genericList',
|
|
},
|
|
},
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
(0, chai_1.expect)(await parseSetup(`kindOfList: "numbers"\nlist: [1.1]`)).to.be.empty;
|
|
const numbersInvalid = await parseSetup(`kindOfList: "numbers"\nlist: ["foo"]`);
|
|
(0, chai_1.expect)(numbersInvalid).to.have.length(1);
|
|
(0, chai_1.expect)(numbersInvalid[0].message).to.include('Incorrect type.');
|
|
(0, chai_1.expect)(numbersInvalid[0].message).to.include('number');
|
|
const stringsInvalid = await parseSetup(`kindOfList: "strings"\nlist: [1.1]`);
|
|
(0, chai_1.expect)(stringsInvalid).to.have.length(1);
|
|
(0, chai_1.expect)(stringsInvalid[0].message).to.include('Incorrect type.');
|
|
(0, chai_1.expect)(stringsInvalid[0].message).to.include('string');
|
|
(0, chai_1.expect)(await parseSetup(`kindOfList: "strings"\nlist: ["foo"]`)).to.be.empty;
|
|
});
|
|
it('after leaving a dynamic scope, it is not used by a $dynamicRef', async () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: 'https://test.json-schema.org/dynamic-ref-leaving-dynamic-scope/main',
|
|
if: {
|
|
$id: 'first_scope',
|
|
$defs: {
|
|
thingy: {
|
|
$comment: 'this is first_scope#thingy',
|
|
$dynamicAnchor: 'thingy',
|
|
type: 'number',
|
|
},
|
|
},
|
|
},
|
|
then: {
|
|
$id: 'second_scope',
|
|
$ref: 'start',
|
|
$defs: {
|
|
thingy: {
|
|
$comment: 'this is second_scope#thingy, the final destination of the $dynamicRef',
|
|
$dynamicAnchor: 'thingy',
|
|
type: 'null',
|
|
},
|
|
},
|
|
},
|
|
$defs: {
|
|
start: {
|
|
$comment: 'this is the landing spot from $ref',
|
|
$id: 'start',
|
|
$dynamicRef: 'inner_scope#thingy',
|
|
},
|
|
thingy: {
|
|
$comment: 'this is the first stop for the $dynamicRef',
|
|
$id: 'inner_scope',
|
|
$dynamicAnchor: 'thingy',
|
|
type: 'string',
|
|
},
|
|
},
|
|
};
|
|
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
|
|
const stringInvalid = await parseSetup('a string');
|
|
(0, chai_1.expect)(stringInvalid).to.have.length(1);
|
|
(0, chai_1.expect)(stringInvalid[0].message).to.include('null');
|
|
const numberInvalid = await parseSetup('42');
|
|
(0, chai_1.expect)(numberInvalid).to.have.length(1);
|
|
(0, chai_1.expect)(stringInvalid[0].message).to.include('null');
|
|
(0, chai_1.expect)(await parseSetup(null)).to.be.empty;
|
|
});
|
|
describe('strict-tree schema, guards against misspelled properties', () => {
|
|
const strictTreeSchema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: 'http://localhost:1234/draft2020-12/strict-tree.json',
|
|
$dynamicAnchor: 'node',
|
|
$ref: 'tree.json',
|
|
unevaluatedProperties: false,
|
|
};
|
|
beforeEach(() => {
|
|
schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, treeSchema.$id, treeSchema);
|
|
schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, strictTreeSchema.$id, strictTreeSchema);
|
|
});
|
|
it('instance with misspelled field', async () => {
|
|
const result = await parseSetup(`# yaml-language-server: $schema=http://localhost:1234/draft2020-12/strict-tree.json
|
|
children:
|
|
- daat: 1`);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Property daat is not allowed.');
|
|
});
|
|
it('instance with correct field', async () => {
|
|
(0, chai_1.expect)(await parseSetup(`# yaml-language-server: $schema=http://localhost:1234/draft2020-12/strict-tree.json
|
|
children:
|
|
- data: 1`)).to.be.empty;
|
|
});
|
|
});
|
|
describe('tests for implementation dynamic anchor and reference link', () => {
|
|
const strictExtendibleSchema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: 'http://localhost:1234/draft2020-12/strict-extendible.json',
|
|
$ref: 'extendible-dynamic-ref.json',
|
|
$defs: {
|
|
elements: {
|
|
$dynamicAnchor: 'elements',
|
|
properties: {
|
|
a: true,
|
|
},
|
|
required: ['a'],
|
|
additionalProperties: false,
|
|
},
|
|
},
|
|
};
|
|
beforeEach(() => {
|
|
schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, extendibleDynamicRefSchema.$id, extendibleDynamicRefSchema);
|
|
schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, strictExtendibleSchema.$id, strictExtendibleSchema);
|
|
});
|
|
it('incorrect parent schema', async () => {
|
|
const result = await parseSetup(`# yaml-language-server: $schema=http://localhost:1234/draft2020-12/strict-extendible.json
|
|
a: true`);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Property a is not allowed.');
|
|
});
|
|
it('incorrect extended schema', async () => {
|
|
const result = await parseSetup(`# yaml-language-server: $schema=http://localhost:1234/draft2020-12/strict-extendible.json
|
|
elements:
|
|
- b: 1`);
|
|
(0, chai_1.expect)(result).to.have.length(2);
|
|
(0, chai_1.expect)(result[0].message).to.include('Missing property');
|
|
(0, chai_1.expect)(result[1].message).to.include('Property b is not allowed.');
|
|
});
|
|
it('correct extended schema', async () => {
|
|
(0, chai_1.expect)(await parseSetup(`# yaml-language-server: $schema=http://localhost:1234/draft2020-12/strict-extendible.json
|
|
elements:
|
|
- a: 1`)).to.be.empty;
|
|
});
|
|
});
|
|
describe('$ref and $dynamicAnchor are independent of order - $defs first', () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: 'http://localhost:1234/draft2020-12/strict-extendible-allof-defs-first.json',
|
|
allOf: [
|
|
{
|
|
$ref: 'extendible-dynamic-ref.json',
|
|
},
|
|
{
|
|
$defs: {
|
|
elements: {
|
|
$dynamicAnchor: 'elements',
|
|
properties: {
|
|
a: true,
|
|
},
|
|
required: ['a'],
|
|
additionalProperties: false,
|
|
},
|
|
},
|
|
},
|
|
],
|
|
};
|
|
beforeEach(() => {
|
|
schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, extendibleDynamicRefSchema.$id, extendibleDynamicRefSchema);
|
|
schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, schema.$id, schema);
|
|
});
|
|
it('incorrect parent schema', async () => {
|
|
const result = await parseSetup(`# yaml-language-server: $schema=http://localhost:1234/draft2020-12/strict-extendible-allof-defs-first.json
|
|
a: true`);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Property a is not allowed.');
|
|
});
|
|
it('incorrect extended schema', async () => {
|
|
const result = await parseSetup(`# yaml-language-server: $schema=http://localhost:1234/draft2020-12/strict-extendible-allof-defs-first.json
|
|
elements:
|
|
- b: 1`);
|
|
(0, chai_1.expect)(result).to.have.length(2);
|
|
(0, chai_1.expect)(result.some((d) => d.message.includes('Missing property') && d.message.includes('a'))).to.eq(true);
|
|
(0, chai_1.expect)(result.some((d) => d.message.includes('Property b is not allowed.'))).to.eq(true);
|
|
});
|
|
it('correct extended schema', async () => {
|
|
(0, chai_1.expect)(await parseSetup(`# yaml-language-server: $schema=http://localhost:1234/draft2020-12/strict-extendible-allof-defs-first.json
|
|
elements:
|
|
- a: 1`)).to.be.empty;
|
|
});
|
|
});
|
|
describe('$ref and $dynamicAnchor are independent of order - $ref first', () => {
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: 'http://localhost:1234/draft2020-12/strict-extendible-allof-ref-first.json',
|
|
allOf: [
|
|
{
|
|
$defs: {
|
|
elements: {
|
|
$dynamicAnchor: 'elements',
|
|
properties: {
|
|
a: true,
|
|
},
|
|
required: ['a'],
|
|
additionalProperties: false,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
$ref: 'extendible-dynamic-ref.json',
|
|
},
|
|
],
|
|
};
|
|
beforeEach(() => {
|
|
schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, extendibleDynamicRefSchema.$id, extendibleDynamicRefSchema);
|
|
schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, schema.$id, schema);
|
|
});
|
|
it('incorrect parent schema', async () => {
|
|
const result = await parseSetup(`# yaml-language-server: $schema=http://localhost:1234/draft2020-12/strict-extendible-allof-ref-first.json
|
|
a: true`);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Property a is not allowed.');
|
|
});
|
|
it('incorrect extended schema', async () => {
|
|
const result = await parseSetup(`# yaml-language-server: $schema=http://localhost:1234/draft2020-12/strict-extendible-allof-ref-first.json
|
|
elements:
|
|
- b: 1`);
|
|
(0, chai_1.expect)(result).to.have.length(2);
|
|
(0, chai_1.expect)(result.some((d) => d.message.includes('Missing property') && d.message.includes('a'))).to.eq(true);
|
|
(0, chai_1.expect)(result.some((d) => d.message.includes('Property b is not allowed.'))).to.eq(true);
|
|
});
|
|
it('correct extended schema', async () => {
|
|
(0, chai_1.expect)(await parseSetup(`# yaml-language-server: $schema=http://localhost:1234/draft2020-12/strict-extendible-allof-ref-first.json
|
|
elements:
|
|
- a: 1`)).to.be.empty;
|
|
});
|
|
});
|
|
it('$ref to $dynamicRef finds detached $dynamicAnchor', async () => {
|
|
const schemaId = 'http://localhost:4242/dynamic-ref-detached.json';
|
|
const schema = {
|
|
$ref: 'http://localhost:1234/draft2020-12/detached-dynamicref.json#/$defs/foo',
|
|
};
|
|
schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, detachedDynamicRefSchema.$id, detachedDynamicRefSchema);
|
|
schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, schemaId, schema);
|
|
(0, chai_1.expect)(await parseSetup(`# yaml-language-server: $schema=${schemaId}
|
|
1`)).to.be.empty;
|
|
const result = await parseSetup(`# yaml-language-server: $schema=${schemaId}
|
|
a`);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Incorrect type.');
|
|
(0, chai_1.expect)(result[0].message).to.include('number');
|
|
});
|
|
it('$dynamicRef points to a boolean schema', async () => {
|
|
const schemaId = 'http://localhost:4242/dynamic-ref-boolean.json';
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$defs: {
|
|
true: true,
|
|
false: false,
|
|
},
|
|
properties: {
|
|
true: {
|
|
$dynamicRef: '#/$defs/true',
|
|
},
|
|
false: {
|
|
$dynamicRef: '#/$defs/false',
|
|
},
|
|
},
|
|
};
|
|
schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, schemaId, schema);
|
|
(0, chai_1.expect)(await parseSetup(`# yaml-language-server: $schema=${schemaId}
|
|
"true": 1`)).to.be.empty;
|
|
const result = await parseSetup(`# yaml-language-server: $schema=${schemaId}
|
|
"false": 1`);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Matches a schema that is not allowed.');
|
|
});
|
|
it('$dynamicRef skips over intermediate resources - direct reference', async () => {
|
|
const schemaId = 'https://test.json-schema.org/dynamic-ref-skips-intermediate-resource/main';
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: schemaId,
|
|
type: 'object',
|
|
properties: {
|
|
'bar-item': {
|
|
$ref: 'item',
|
|
},
|
|
},
|
|
$defs: {
|
|
bar: {
|
|
$id: 'bar',
|
|
type: 'array',
|
|
items: {
|
|
$ref: 'item',
|
|
},
|
|
$defs: {
|
|
item: {
|
|
$id: 'item',
|
|
type: 'object',
|
|
properties: {
|
|
content: {
|
|
$dynamicRef: '#content',
|
|
},
|
|
},
|
|
$defs: {
|
|
defaultContent: {
|
|
$dynamicAnchor: 'content',
|
|
type: 'integer',
|
|
},
|
|
},
|
|
},
|
|
content: {
|
|
$dynamicAnchor: 'content',
|
|
type: 'string',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, schema.$id, schema);
|
|
(0, chai_1.expect)(await parseSetup(`# yaml-language-server: $schema=${schemaId}
|
|
bar-item:
|
|
content: 42`)).to.be.empty;
|
|
const result = await parseSetup(`# yaml-language-server: $schema=${schemaId}
|
|
bar-item:
|
|
content: value`);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('Incorrect type.');
|
|
(0, chai_1.expect)(result[0].message).to.include('integer');
|
|
});
|
|
it('$dynamicRef avoids the root of each schema, but scopes are still registered', async () => {
|
|
const schemaId = 'https://test.json-schema.org/dynamic-ref-avoids-root-of-each-schema/base';
|
|
const schema = {
|
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
$id: schemaId,
|
|
$ref: 'first#/$defs/stuff',
|
|
$defs: {
|
|
first: {
|
|
$id: 'first',
|
|
$defs: {
|
|
stuff: {
|
|
$ref: 'second#/$defs/stuff',
|
|
},
|
|
length: {
|
|
$comment: 'unused, because there is no $dynamicAnchor here',
|
|
maxLength: 1,
|
|
},
|
|
},
|
|
},
|
|
second: {
|
|
$id: 'second',
|
|
$defs: {
|
|
stuff: {
|
|
$ref: 'third#/$defs/stuff',
|
|
},
|
|
length: {
|
|
$dynamicAnchor: 'length',
|
|
maxLength: 2,
|
|
},
|
|
},
|
|
},
|
|
third: {
|
|
$id: 'third',
|
|
$defs: {
|
|
stuff: {
|
|
$dynamicRef: '#length',
|
|
},
|
|
length: {
|
|
$dynamicAnchor: 'length',
|
|
maxLength: 3,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, schema.$id, schema);
|
|
(0, chai_1.expect)(await parseSetup(`# yaml-language-server: $schema=${schemaId}
|
|
hi`)).to.be.empty;
|
|
const result = await parseSetup(`# yaml-language-server: $schema=${schemaId}
|
|
hey`);
|
|
(0, chai_1.expect)(result).to.have.length(1);
|
|
(0, chai_1.expect)(result[0].message).to.include('String is longer than the maximum length of 2.');
|
|
});
|
|
});
|
|
});
|
|
//# sourceMappingURL=schema2020Validation.test.js.map
|