diff --git a/packages/helix-shared-config/src/ConfigValidator.js b/packages/helix-shared-config/src/ConfigValidator.js index 962a7f17..c2969b8c 100644 --- a/packages/helix-shared-config/src/ConfigValidator.js +++ b/packages/helix-shared-config/src/ConfigValidator.js @@ -14,6 +14,7 @@ const Ajv = require('ajv').default; const ajvFormats = require('ajv-formats'); +const ValidationError = require('./ValidationError.js'); const schemas = [ /* eslint-disable global-require */ @@ -31,40 +32,31 @@ const schemas = [ /* eslint-enable global-require */ ]; -class ValidationError extends Error { +class HelixConfigValidationError extends ValidationError { constructor(msg, errors = []) { - function prettyname(path, schema) { - if (path.startsWith('.strains')) { - return `${schema.title || 'Invalid Strain'} ${path.replace(/\.strains(\.|\[')(.*)/, '$2').replace(/'.*/, '')}`; - } - return `${schema.title || schema.$id} ${path}`; - } + super(msg, errors, HelixConfigValidationError.mapError, HelixConfigValidationError.prettyname); + } - const detail = errors.map(({ - keyword, dataPath, message, data, params, parentSchema, - }) => { - if (keyword === 'additionalProperties') { - return `${prettyname(dataPath, parentSchema)} has unknown property '${params.additionalProperty}'`; - } - if (keyword === 'required' && dataPath === '') { - return 'A set of strains and a default strain are missing.'; - } - if (keyword === 'required' && dataPath === '.strains') { - return 'A default strain is missing.'; - } - if (keyword === 'required') { - return `${prettyname(dataPath, parentSchema)} ${message}`; - } - if (keyword === 'oneOf' && dataPath.startsWith('.strains')) { - return `${prettyname(dataPath, parentSchema)} must be either a Runtime Strain or a Proxy Strain`; - } - return `${prettyname(dataPath, parentSchema)} ${message}: ${keyword}(${JSON.stringify(data)}, ${JSON.stringify(params)})`; - }).join('\n'); - super(`Invalid configuration: -${detail} + static prettyname(path, schema) { + if (path && path.startsWith('.strains')) { + return `${schema.title || 'Invalid Strain'} ${path.replace(/\.strains(\.|\[')(.*)/, '$2').replace(/'.*/, '')}`; + } + return ValidationError.prettyname(path, schema); + } -${msg}`); - this._errors = errors; + static mapError({ + keyword, dataPath, message, data, params, parentSchema, + }, prettyname) { + if (keyword === 'required' && dataPath === '') { + return 'A set of strains and a default strain are missing.'; + } + if (keyword === 'required' && dataPath === '.strains') { + return 'A default strain is missing.'; + } + if (keyword === 'oneOf' && dataPath.startsWith('.strains')) { + return `${prettyname(dataPath, parentSchema)} must be either a Runtime Strain or a Proxy Strain`; + } + return ValidationError.mapError(keyword, dataPath, message, data, params, parentSchema); } } @@ -91,11 +83,11 @@ class ConfigValidator { if (!config.strains || ((config.strains.find && !config.strains.find((s) => s.name === 'default')) && !config.strains.default)) { - throw new ValidationError('A list of strains and a strain with the name "default" is required.'); + throw new HelixConfigValidationError('A list of strains and a strain with the name "default" is required.'); } const valid = this.validate(config); if (!valid) { - throw new ValidationError(this._ajv.errorsText(), this._ajv.errors); + throw new HelixConfigValidationError(this._ajv.errorsText(), this._ajv.errors); } } } diff --git a/packages/helix-shared-config/src/IndexConfig.js b/packages/helix-shared-config/src/IndexConfig.js index 33913d26..37f359a4 100644 --- a/packages/helix-shared-config/src/IndexConfig.js +++ b/packages/helix-shared-config/src/IndexConfig.js @@ -114,6 +114,16 @@ class IndexConfig extends SchemaDerivedConfig { } return undefined; } + + /** + * Initialize the configuration + */ + async init() { + await super.init(); + + this._version = this._cfg.version; + return this; + } } module.exports = IndexConfig; diff --git a/packages/helix-shared-config/src/SchemaDerivedConfig.js b/packages/helix-shared-config/src/SchemaDerivedConfig.js index 884d0f1f..afbe1027 100644 --- a/packages/helix-shared-config/src/SchemaDerivedConfig.js +++ b/packages/helix-shared-config/src/SchemaDerivedConfig.js @@ -12,6 +12,7 @@ const Ajv = require('ajv').default; const ajvFormats = require('ajv-formats'); const BaseConfig = require('./BaseConfig.js'); +const ValidationError = require('./ValidationError.js'); /** * A Helix Config that is based on a (number of) JSON Schema(s). @@ -66,7 +67,7 @@ class SchemaDerivedConfig extends BaseConfig { if (res) { return res; } - throw new Error(ajv.errorsText()); + throw new ValidationError(ajv.errorsText(), ajv.errors); } /** diff --git a/packages/helix-shared-config/src/ValidationError.js b/packages/helix-shared-config/src/ValidationError.js new file mode 100644 index 00000000..203375f4 --- /dev/null +++ b/packages/helix-shared-config/src/ValidationError.js @@ -0,0 +1,43 @@ +/* + * Copyright 2018 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +/* eslint-disable max-classes-per-file */ + +class ValidationError extends Error { + constructor( + msg, errors = [], mapError = ValidationError.mapError, prettyname = ValidationError.prettyname, + ) { + const detail = errors.map((e) => mapError(e, prettyname)).join('\n'); + super(`Invalid configuration: +${detail} + +${msg}`); + this._errors = errors; + } + + static prettyname(path, schema) { + return path ? `${schema.title || schema.$id} ${path}` : `${schema.title || schema.$id}`; + } + + static mapError({ + keyword, dataPath, message, data, params, parentSchema, + }, prettyname) { + if (keyword === 'additionalProperties') { + return `${prettyname(dataPath, parentSchema)} has unknown property '${params.additionalProperty}'`; + } + if (keyword === 'required') { + return `${prettyname(dataPath, parentSchema)} ${message}`; + } + return `${prettyname(dataPath, parentSchema)} ${message}: ${keyword}(${JSON.stringify(data)}, ${JSON.stringify(params)})`; + } +} + +module.exports = ValidationError; diff --git a/packages/helix-shared-config/src/index.js b/packages/helix-shared-config/src/index.js index e6165993..49b636ab 100644 --- a/packages/helix-shared-config/src/index.js +++ b/packages/helix-shared-config/src/index.js @@ -18,6 +18,7 @@ const MarkupConfig = require('./MarkupConfig'); const Condition = require('./Condition.js'); const { optionalConfig, requiredConfig } = require('./config-wrapper'); const DataEmbedValidator = require('./DataEmbedValidator.js'); +const ValidationError = require('./ValidationError.js'); module.exports = { HelixConfig, @@ -30,4 +31,5 @@ module.exports = { optionalConfig, requiredConfig, DataEmbedValidator, + ValidationError, }; diff --git a/packages/helix-shared-config/test/markupconfig.test.js b/packages/helix-shared-config/test/markupconfig.test.js index 296ece02..f42edb64 100644 --- a/packages/helix-shared-config/test/markupconfig.test.js +++ b/packages/helix-shared-config/test/markupconfig.test.js @@ -67,7 +67,7 @@ describe('Markup Config Loading', () => { if (e instanceof AssertionError) { throw e; } - assert.equal(e.message, 'data/markup/images-in-gallery must have required property \'match\', data/markup/last-section must NOT have additional properties'); + assert.equal(e.message, 'Invalid configuration:\nMarkup Mapping must have required property \'match\'\nMarkup Mapping has unknown property \'invalid\'\n\ndata/markup/images-in-gallery must have required property \'match\', data/markup/last-section must NOT have additional properties'); } }); }); diff --git a/packages/helix-shared-config/test/mountpoints.test.js b/packages/helix-shared-config/test/mountpoints.test.js index 2d1c55b4..ba48d8fc 100644 --- a/packages/helix-shared-config/test/mountpoints.test.js +++ b/packages/helix-shared-config/test/mountpoints.test.js @@ -102,7 +102,7 @@ const tests = [ title: 'fails with a broken config', config: 'broken.yaml', result: null, - error: 'Error: data must NOT have additional properties', + error: 'Error: Invalid configuration:\nFSTab (Mount Points) has unknown property \'mounts\'\n\ndata must NOT have additional properties', }, { title: 'loads a theblog example',