project/lib/validation/validator.js

const path = require("path");
const {promisify} = require("util");
const readFile = promisify(require("fs").readFile);

async function loadSchema(schemaPath) {
	const filePath = schemaPath.replace("http://ui5.sap/schema/", "");
	const schemaFile = await readFile(path.join(__dirname, "schema", filePath), {encoding: "utf8"});
	return JSON.parse(schemaFile);
}

/**
 * @private
 * @memberof module:@ui5/project.validation
 */
class Validator {
	constructor() {
		const Ajv = require("ajv");
		this.ajv = new Ajv({
			allErrors: true,
			jsonPointers: true,
			loadSchema
		});
		const ajvErrors = require("ajv-errors");
		ajvErrors(this.ajv);
	}

	_compileSchema() {
		if (!this._compiling) {
			this._compiling = Promise.resolve().then(async () => {
				const schema = await loadSchema("ui5.json");
				const validate = await this.ajv.compileAsync(schema);
				return validate;
			});
		}
		return this._compiling;
	}

	async validate({config, project, yaml}) {
		const fnValidate = await this._compileSchema();
		const valid = fnValidate(config);
		if (!valid) {
			const ValidationError = require("./ValidationError");
			throw new ValidationError({
				errors: fnValidate.errors,
				schema: fnValidate.schema,
				project,
				yaml
			});
		}
	}
}

let validator;

/**
 * @public
 * @namespace
 * @alias module:@ui5/project.validation.validator
 */
module.exports = {
	/**
	 * Validates the given configuration.
	 *
	 * @param {object} options
	 * @param {object} options.config UI5 Configuration to validate
	 * @param {object} options.project Project information
	 * @param {string} options.project.id ID of the project
	 * @param {object} [options.yaml] YAML information
	 * @param {string} options.yaml.path Path of the YAML file
	 * @param {string} options.yaml.source Content of the YAML file
	 * @param {number} [options.yaml.documentIndex=0] Document index in case the YAML file contains multiple documents
	 * @throws {module:@ui5/project.validation.ValidationError}
	 *   Rejects with a {@link module:@ui5/project.validation.ValidationError ValidationError}
	 *   when the validation fails.
	 * @returns {Promise<undefined>} Returns a Promise that resolves when the validation succeeds
	 * @public
	 */
	validate: async (options) => {
		if (!validator) {
			validator = new Validator();
		}
		await validator.validate(options);
	},
	_Validator: Validator // For testing only
};