UNPKG

@itwin/core-backend

Version:
271 lines • 11.3 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module Workspace */ Object.defineProperty(exports, "__esModule", { value: true }); exports.constructSettingsSchemas = constructSettingsSchemas; const fs = require("fs-extra"); const json5_1 = require("json5"); const path_1 = require("path"); const core_bentley_1 = require("@itwin/core-bentley"); const IModelJsFs_1 = require("../../IModelJsFs"); const Symbols_1 = require("../Symbols"); const makeSettingKey = (prefix, key) => `${prefix}/${key}`; class SettingsSchemasImpl { [Symbols_1._implementationProhibited] = undefined; _allGroups = new Map(); /** a map of all registered Setting Definitions */ settingDefs = new Map(); /** a map of all registered TypeDefs */ typeDefs = new Map(); /** event that signals that the values in [[allSchemas]] have changed in some way. */ onSchemaChanged = new core_bentley_1.BeEvent(); verifyType(val, expectedType, path) { if (expectedType === "integer") { if (Number.isInteger(val)) return; } else if (expectedType === "null") { if (val === null || val === undefined) return; } else if (typeof val === expectedType) return; throw new Error(`value for ${path}: "${val}" is wrong type, expected ${expectedType}`); } validateSetting(value, settingName) { const settingDef = this.settingDefs.get(settingName); if (undefined !== settingDef) // if there's no setting definition, there's no rules so just return ok this.validateProperty(value, settingDef, settingName); return value; } /** @internal */ getObjectProperties(propDef, scope) { let required = propDef.required; let properties = propDef.properties; // if this object extends a typeDef, add typeDef's properties and required values, recursively if (propDef.extends !== undefined) { const typeDef = this.typeDefs.get(propDef.extends); if (undefined === typeDef) throw new Error(`typeDef ${propDef.extends} does not exist for ${scope}`); const expanded = this.getObjectProperties(typeDef, `${scope}.${propDef.extends}`); if (expanded.required) required = required ? [...required, ...expanded.required] : expanded.required; if (expanded.properties) { properties = properties ? { ...expanded.properties, ...properties } : expanded.properties; } } properties = properties ?? {}; return { required, properties }; } /** @internal */ getArrayItems(propDef, scope) { let items = propDef.items; if (undefined === items && propDef.extends) { const typeDef = this.typeDefs.get(propDef.extends); if (undefined === typeDef) throw new Error(`typeDef ${propDef.extends} does not exist for ${scope}`); items = typeDef.items; } if (undefined === items) throw new Error(`array ${scope} has no items definition`); return items; } validateProperty(val, propDef, path) { switch (propDef.type) { case "boolean": case "number": case "string": case "integer": case "null": return this.verifyType(val, propDef.type, path); case "array": if (!Array.isArray(val)) throw new Error(`Property ${path} must be an array`); const items = this.getArrayItems(propDef, path); for (let i = 0; i < val.length; ++i) this.validateProperty(val[i], items, `${path}[${i}]`); return; } if (!val || typeof val !== "object") throw new Error(`${path} must be an object`); const { required, properties } = this.getObjectProperties(propDef, path); // first ensure all required properties are present if (undefined !== required) { for (const entry of required) { const value = val[entry]; if (undefined === value) throw new Error(`required value for "${entry}" is missing in "${path}"`); } } // you can supply default values in typeDefs. See if any members are undefined that have a default. if (undefined !== properties) { for (const [key, prop] of Object.entries(properties)) { if (val[key] === undefined && prop.default) val[key] = prop.default; } } // then validate all values in the supplied object are valid for (const key of Object.keys(val)) { const prop = properties[key]; if (prop !== undefined) { // note: extra values are ignored. this.validateProperty(val[key], prop, `${path}.${key}`); } } } /** * Add one or more [[SettingSchemaGroup]]s. `SettingSchemaGroup`s must include a `schemaPrefix` member that is used * to identify the group. If a group with the same name is already registered, the old values are first removed and then the new group is added. */ addGroup(settingsGroup) { if (!Array.isArray(settingsGroup)) settingsGroup = [settingsGroup]; this.doAdd(settingsGroup); this.onSchemaChanged.raiseEvent(); } /** Add a [[SettingSchemaGroup]] from stringified json5. */ addJson(settingSchema) { this.addGroup((0, json5_1.parse)(settingSchema)); } /** Add a [[SettingSchemaGroup]] from a json5 file. */ addFile(fileName) { try { this.addJson(fs.readFileSync(fileName, "utf-8")); } catch (e) { throw new Error(`parsing SettingSchema file "${fileName}": ${e.message}"`); } } /** Add all files with a either ".json" or ".json5" extension from a supplied directory. */ addDirectory(dirName) { for (const fileName of IModelJsFs_1.IModelJsFs.readdirSync(dirName)) { const ext = (0, path_1.extname)(fileName); if (ext === ".json5" || ext === ".json") this.addFile((0, path_1.join)(dirName, fileName)); } } /** Remove a previously added [[SettingSchemaGroup]] by schemaPrefix */ removeGroup(schemaPrefix) { this.doRemove(schemaPrefix); this.onSchemaChanged.raiseEvent(); } doAdd(settingsGroup) { settingsGroup.forEach((group) => { if (undefined === group.schemaPrefix) throw new Error(`settings group has no "schemaPrefix" member`); this.doRemove(group.schemaPrefix); this.validateAndAdd(group); this._allGroups.set(group.schemaPrefix, group); }); } doRemove(schemaPrefix) { const group = this._allGroups.get(schemaPrefix); if (undefined !== group?.settingDefs) { for (const key of Object.keys(group.settingDefs)) this.settingDefs.delete(makeSettingKey(schemaPrefix, key)); } if (undefined !== group?.typeDefs) { for (const key of Object.keys(group.typeDefs)) this.settingDefs.delete(makeSettingKey(schemaPrefix, key)); } this._allGroups.delete(schemaPrefix); } validateName(name) { if (!name.trim()) throw new Error(`empty property name`); } verifyPropertyDef(name, property) { if (!property) throw new Error(`missing required property ${name}`); if (!property.type) throw new Error(`property ${name} has no type`); switch (property.type) { case "boolean": case "integer": case "null": case "number": case "string": return; case "object": const required = property.required; const props = property.properties; if (required && props) { for (const entry of required) { if (undefined === props[entry]) throw new Error(`missing required property of ${name}: "${entry}"`); } } if (props) { for (const key of Object.keys(props)) try { this.verifyPropertyDef(key, props[key]); } catch (e) { throw new Error(`property ${key} of ${name}: ${e.message}`); } } return; case "array": if (typeof property.extends === "string") return; if (typeof property.items !== "object") throw new Error(`array property ${name} has no items member`); try { this.verifyPropertyDef("items", property.items); } catch (e) { throw new Error(`array property ${name}: ${e.message}`); } return; default: throw new Error(`property ${name} has illegal type "${property.type}"`); } } validateAndAdd(group) { const settingDefs = group.settingDefs; if (undefined !== settingDefs) { for (const key of Object.keys(settingDefs)) { this.validateName(key); this.verifyPropertyDef(key, settingDefs[key]); const property = settingDefs[key]; (0, core_bentley_1.assert)(undefined !== property); property.default = property.default ?? this.getDefaultValue(property.type); this.settingDefs.set(makeSettingKey(group.schemaPrefix, key), property); } } const typeDefs = group.typeDefs ?? {}; for (const key of Object.keys(typeDefs)) { this.validateName(key); this.verifyPropertyDef(key, typeDefs[key]); const typeDef = typeDefs[key]; (0, core_bentley_1.assert)(undefined !== typeDef); this.typeDefs.set(makeSettingKey(group.schemaPrefix, key), typeDef); } } getDefaultValue(type) { type = Array.isArray(type) ? type[0] : type; switch (type) { case "boolean": return false; case "integer": case "number": return 0; case "string": return ""; case "array": return []; case "object": return {}; default: return undefined; } } } function constructSettingsSchemas() { return new SettingsSchemasImpl(); } //# sourceMappingURL=SettingsSchemasImpl.js.map