@jirutka/ajv-cli
Version:
CLI for Ajv JSON Schema Validator with human-friendly error messages
117 lines • 4.09 kB
JavaScript
import * as path from 'node:path';
import { Ajv as Ajv7 } from 'ajv';
import { Ajv2019 } from 'ajv/dist/2019.js';
import { Ajv2020 } from 'ajv/dist/2020.js';
import { Ajv as AjvJTD } from 'ajv/dist/jtd.js';
import { injectPathToSchemas } from './ajv-schema-path-workaround.js';
import { ajvOptionNames } from './commands/common.js';
import { ProgramError } from './errors.js';
import { parseFile } from './parsers/index.js';
import jsonSchemaDraft06 from './vendor/json-schema-draft-06.cjs';
import { expandFilePaths } from './utils.js';
const ajvClasses = {
jtd: AjvJTD,
draft7: Ajv7,
draft2019: Ajv2019,
draft2020: Ajv2020,
};
const specUris = {
'https://json-schema.org/draft/2020-12/schema': 'draft2020',
'https://json-schema.org/draft/2019-09/schema': 'draft2019',
'http://json-schema.org/draft-07/schema': 'draft7',
};
export async function initAjv(opts, mode) {
const spec = opts.spec || 'draft7';
const ajvOpts = extractAjvOptions(opts);
if (mode === 'validate') {
// verbose is needed for ajv-schema-path-workaround.
ajvOpts.verbose = true;
}
else {
// This is required by the standalone code generator.
ajvOpts.code.source = true;
}
if (ajvOpts.strict == null) {
// Ignore unknown keywords (specification-compliant behavior).
ajvOpts.strictSchema ??= false;
}
// This number's been pulled out of thin air. The higher the number, the
// longer schema compilation size and memory requirements.
ajvOpts.inlineRefs ??= 8;
const Ajv = ajvClasses[spec];
const ajv = new Ajv(ajvOpts);
let invalid = 0;
if (opts.spec !== 'jtd') {
ajv.addMetaSchema(jsonSchemaDraft06);
}
await addSchemas(opts.metaSchema, 'addMetaSchema', 'meta-schema');
await addSchemas(opts.refSchema, 'addSchema', 'schema');
await customFormatsKeywords(opts.keywords);
if (invalid > 0) {
throw new ProgramError(invalid > 1
? `Found ${invalid} invalid schemas or modules`
: 'Found one invalid schema or module');
}
return ajv;
async function addSchemas(args, method, fileType) {
await Promise.all(expandFilePaths(args).map(async (file) => {
const schema = await parseFile(file);
try {
if (mode === 'validate' && method === 'addSchema') {
injectPathToSchemas(schema);
}
ajv[method](schema);
}
catch (err) {
console.error(`${fileType} ${file} is invalid`);
console.error(`error: ${err.message}`);
invalid++;
}
}));
}
async function customFormatsKeywords(args) {
for (let file of expandFilePaths(args)) {
if (file[0] === '.') {
file = path.resolve(process.cwd(), file);
}
try {
const module = await import(file);
if (typeof module.default === 'function') {
module.default(ajv);
}
else {
module(ajv);
}
}
catch (err) {
console.error(`module ${file} is invalid; it should export function`);
console.error(`error: ${err.message}`);
invalid++;
}
}
}
}
export function resolveSchemaSpec(schema) {
const schemaId = schema.$schema?.replace(/#.*$/, '');
if (schemaId && Object.hasOwn(specUris, schemaId)) {
return specUris[schemaId];
}
}
function extractAjvOptions(opts) {
return Object.entries(opts).reduce((acc, [key, value]) => {
if (!ajvOptionNames.has(key)) {
return acc;
}
if (/^code[A-Z]/.test(key)) {
acc.code[key[4].toLowerCase() + key.slice(5)] = value;
}
else if (key === 'data') {
acc.$data = value;
}
else {
acc[key] = value;
}
return acc;
}, { code: {} });
}
//# sourceMappingURL=ajv.js.map