UNPKG

@salesforce/core

Version:

Core libraries to interact with SFDX projects, orgs, and APIs.

201 lines 8.78 kB
"use strict"; /* * Copyright (c) 2020, salesforce.com, inc. * All rights reserved. * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SchemaValidator = void 0; const path = __importStar(require("node:path")); const fs = __importStar(require("node:fs")); const ajv_1 = __importDefault(require("ajv")); const kit_1 = require("@salesforce/kit"); const sfError_1 = require("../sfError"); /** * Loads a JSON schema and performs validations against JSON objects. */ class SchemaValidator { schemaPath; schemasDir; logger; schema; /** * Creates a new `SchemaValidator` instance given a logger and path to a schema file. * * @param logger An {@link Logger} instance on which to base this class's logger. * @param schemaPath The path to the schema file to load and use for validation. */ constructor(logger, schemaPath) { this.schemaPath = schemaPath; this.logger = logger.child('SchemaValidator'); this.schemasDir = path.dirname(this.schemaPath); } /** * Loads a JSON schema from the `schemaPath` parameter provided at instantiation. */ async load() { if (!this.schema) { this.schema = (0, kit_1.parseJsonMap)(await fs.promises.readFile(this.schemaPath, 'utf8')); this.logger.debug(`Schema loaded for ${this.schemaPath}`); } return this.schema; } /** * Loads a JSON schema from the `schemaPath` parameter provided at instantiation. */ loadSync() { if (!this.schema) { this.schema = (0, kit_1.parseJsonMap)(fs.readFileSync(this.schemaPath, 'utf8')); this.logger.debug(`Schema loaded for ${this.schemaPath}`); } return this.schema; } /** * Performs validation of JSON data against the schema located at the `schemaPath` value provided * at instantiation. * * **Throws** *{@link SfError}{ name: 'ValidationSchemaFieldError' }* If there are known validations errors. * **Throws** *{@link SfError}{ name: 'ValidationSchemaUnknownError' }* If there are unknown validations errors. * * @param json A JSON value to validate against this instance's target schema. * @returns The validated JSON data. */ // eslint-disable-next-line @typescript-eslint/require-await async validate(json) { return this.validateSync(json); } /** * Performs validation of JSON data against the schema located at the `schemaPath` value provided * at instantiation. * * **Throws** *{@link SfError}{ name: 'ValidationSchemaFieldError' }* If there are known validations errors. * **Throws** *{@link SfError}{ name: 'ValidationSchemaUnknownError' }* If there are unknown validations errors. * * @param json A JSON value to validate against this instance's target schema. * @returns The validated JSON data. */ validateSync(json) { const schema = this.loadSync(); const externalSchemas = this.loadExternalSchemas(schema); const ajv = new ajv_1.default({ allErrors: true, allowUnionTypes: true, schemas: externalSchemas, useDefaults: true, // TODO: We may someday want to enable strictSchema. This is disabled for now // because the CLI team does not "own" the @salesforce/schemas repository. // Invalid schema would cause errors wherever SchemaValidator is used. strictSchema: false, // If we end up getting an npm-shrinkwrap working in the future we could turn this back off. // https://github.com/forcedotcom/cli/issues/1493 validateSchema: false, }); // JSEN to AJV migration note - regarding the following "TODO": // I don't think that AJV has a way to throw an error if an additional property exists in the data // It does however have a top level option for `removeAdditional` https://ajv.js.org/options.html#removeadditional // Regardless, this would be a breaking changes and I do not think it should be implemented. // TODO: We should default to throw an error when a property is specified // that is not in the schema, but the only option to do this right now is // to specify "removeAdditional: false" in every object. const validate = ajv.compile(schema); // AJV will modify the original json object. We need to make a clone of the // json to keep this backwards compatible with JSEN functionality const jsonClone = structuredClone(json); const valid = validate(jsonClone); if (!valid) { if (validate.errors) { const errors = this.getErrorsText(validate.errors); throw new sfError_1.SfError(`Validation errors:\n${errors}`, 'ValidationSchemaFieldError'); } else { throw new sfError_1.SfError('Unknown schema validation error', 'ValidationSchemaUnknownError'); } } // We return the cloned JSON because it will have defaults included // This is configured with the 'useDefaults' option above. return jsonClone; } /** * Loads local, external schemas from URIs in the same directory as the local schema file. * Does not support loading from remote URIs. * Returns a map of external schema local URIs to loaded schema JSON objects. * * @param schema The main schema to look up references ($ref) in. * @returns An array of found referenced schemas. */ loadExternalSchemas(schema) { return (0, kit_1.getJsonValuesByName)(schema, '$ref') .map((ref) => ref && RegExp(/([\w\.]+)#/).exec(ref)) // eslint-disable-line no-useless-escape .map((match) => match?.[1]) .filter((uri) => !!uri) .map((uri) => this.loadExternalSchema(uri)); } /** * Load another schema relative to the primary schema when referenced. Only supports local schema URIs. * * @param uri The first segment of the $ref schema. */ loadExternalSchema(uri) { const schemaPath = path.join(this.schemasDir, `${uri}.json`); try { return (0, kit_1.parseJsonMap)(fs.readFileSync(schemaPath, 'utf8')); } catch (err) { if (err.code === 'ENOENT') { throw new sfError_1.SfError(`Schema not found: ${schemaPath}`, 'ValidationSchemaNotFound'); } throw err; } } /** * Get a string representation of the schema validation errors. * Adds additional (human friendly) information to certain errors. * * @param errors An array of AJV (DefinedError) objects. */ // eslint-disable-next-line class-methods-use-this getErrorsText(errors) { return errors .map((error) => { const msg = `${error.schemaPath}: ${error.message}`; switch (error.keyword) { case 'additionalProperties': return `${msg} '${error.params.additionalProperty}'`; case 'enum': return `${msg} '${error.params.allowedValues.join(', ')}'`; default: return msg; } }) .join('\n'); } } exports.SchemaValidator = SchemaValidator; //# sourceMappingURL=validator.js.map