@itwin/core-backend
Version:
iTwin.js backend components
271 lines • 11.3 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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
;