import { TJS, fs, fsPath, value as valueUtil, AJV } from '../common'; import { SchemaDefinition } from './SchemaDefinition'; const defaultValue = valueUtil.defaultValue; /** * Options for initializing a schema. * * For [TJS.PartialArgs] see: * https://github.com/YousefED/typescript-json-schema#usage * * The [TJS.CompilerOptions] is actually a pass-throgh to the typescript compiler. * */ export type SchemaOptions = TJS.PartialArgs & TJS.CompilerOptions & { files: string | string[]; basePath?: string; }; /** * Generates schemas. */ export class Schema { public static compile(args: SchemaOptions) { return new Schema(args); } public readonly basePath: string | undefined; public readonly files: string[]; private _program: TJS.Program; private _generator: TJS.JsonSchemaGenerator; private constructor(args: SchemaOptions) { // Default values. const required = defaultValue(args.required, true); const strictNullChecks = defaultValue(args.strictNullChecks, true); const ignoreErrors = defaultValue(args.ignoreErrors, true); args = { ...args, required, strictNullChecks, ignoreErrors }; // Prepare paths. this.basePath = args.basePath ? fsPath.resolve(args.basePath) : undefined; const toPath = (path: string) => { path = (path || '').trim(); if (path) { path = this.basePath ? fsPath.join(this.basePath, path) : path; path = fsPath.resolve(path); } return path; }; this.files = Array.isArray(args.files) ? args.files : [args.files]; this.files = this.files.map(path => toPath(path)); this.files = this.files.filter(path => Boolean(path.trim())); // Ensure the files exist. if (this.files.length === 0) { throw new Error(`No files were given to the schema.`); } this.files.forEach(path => { if (!fs.pathExistsSync(path)) { throw new Error( `Failed to load schema. The file '${path}' does not exist.`, ); } }); // Create the compiler. this._program = TJS.getProgramFromFiles(this.files, args, this.basePath); const generator = TJS.buildGenerator(this._program, args); if (!generator) { throw new Error(`Failed to create a build-generator for the schema.`); } this._generator = generator; } /** * Generates a JSON-schema for the specifid symbol. */ public forType( type: string | string[], options: { includeReffedDefinitions?: boolean } = {}, ) { try { const g = this._generator; const def = Array.isArray(type) ? g.getSchemaForSymbols(type, options.includeReffedDefinitions) : g.getSchemaForSymbol(type, options.includeReffedDefinitions); return new SchemaDefinition({ type, def }); } catch (error) { throw new Error( `Failed to generate schema for symbol '${type}'. ${error.message}`, ); } } /** * Creates a re-usable schema validator. */ public static async validator(schema: object | string) { const ajv = AJV(); const def = typeof schema === 'string' ? await fs.readJson(fsPath.resolve(schema)) : schema; const func = ajv.compile(def); return (data?: object) => func(data); } /** * Validates an object against a schema. */ public static async validate(schema: object, data?: object) { return (await Schema.validator(schema))(data); } }