UNPKG

pcf-scripts

Version:

This package contains a module for building PowerApps Component Framework (PCF) controls. See project homepage how to install.

393 lines (391 loc) 20.5 kB
"use strict"; // Copyright (C) Microsoft Corporation. All rights reserved. Object.defineProperty(exports, "__esModule", { value: true }); exports.ManifestSchemaValidator = void 0; const jsonschema_1 = require("jsonschema"); const constants = require("./constants"); const diagnosticMessages_generated_1 = require("./diagnosticMessages.generated"); const featureManager_1 = require("./featureManager"); const platformLibrariesHandler_1 = require("./platformLibrariesHandler"); // eslint-disable-next-line @typescript-eslint/no-require-imports,@typescript-eslint/no-unsafe-assignment const schema = require("./ManifestSchema.json"); class ManifestSchemaValidator { constructor(data, diag) { this.parsedJson = data; this.diag = diag; this.featureManager = new featureManager_1.FeatureManager(); } validateManifest() { if (!this.parsedJson) { this.logValidationError(diagnosticMessages_generated_1.strings.manifest_empty, [constants.MANIFEST_INPUT_FILE_NAME]); return; } // schema validation const result = (0, jsonschema_1.validate)(this.parsedJson, schema); if (!result.valid) { result.errors.forEach((err) => this.logValidationError(diagnosticMessages_generated_1.strings.manifest_validation_error, [err.stack])); return; } const controlNode = this.parsedJson.manifest.control[0]; // At least one <data-set> node or <property> should be present if (!controlNode["data-set"] && !controlNode.property) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_noexistent_dataset_property); } // json schema doesn't support the verification of uniqueness of a specific identifier among an array of objects // hence manually validating that properties, values, datasets and type-groups all have unique "name" attributes const typeGroupNames = this.validateTypeGroupNodes(controlNode["type-group"]); this.validatePropertyNodes(controlNode.property, typeGroupNames); this.validateSubscribedFunctionalities(controlNode); this.validateEventNodes(controlNode.event); this.validateCommonEventNodes(controlNode["common-event"]); this.validateExternalServiceUsageNode(controlNode["external-service-usage"]); this.validateDatasetNodes(controlNode["data-set"], controlNode.property, typeGroupNames); this.validateFeatureUsageNodes(controlNode["feature-usage"]); this.validateConnectorUsageNodes(controlNode["feature-usage"]); this.validatePropertyDependencyNodes(controlNode["property-dependencies"], controlNode.property); this.validateResourceNodes(controlNode.resources[0]); this.validateSupportedPlatformNodes(controlNode["supported-platforms"]); this.validateActionNodes(controlNode.action); this.validatePlatformActionNodes(controlNode["platform-action"]); } validateExternalServiceUsageNode(externalServiceUsageNode) { const externalServiceUsageDomains = new Set(); if (!externalServiceUsageNode) { return externalServiceUsageDomains; } if (externalServiceUsageNode[0].domain) { externalServiceUsageNode[0].domain.forEach((domain) => { if (externalServiceUsageDomains.has(domain)) { this.diag.pushA(diagnosticMessages_generated_1.strings.validation_duplicate_element_name, ["domain", domain]); } else { externalServiceUsageDomains.add(domain); } }); } return externalServiceUsageDomains; } validateSubscribedFunctionalities(controlNode) { if (controlNode[constants.SUBSCRIBED_FUNCTIONALITY_SECTION_NODE]) { const functionalities = controlNode[constants.SUBSCRIBED_FUNCTIONALITY_SECTION_NODE][0]["subscribed-functionality"]; if (functionalities.some((func) => func.$.name === constants.SF_SUPPORT_RESETTABLE_PROPS && func.$.value === "true") && controlNode.property && !controlNode.property.some((prop) => prop.$.resettable && prop.$.resettable === "true")) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_support_resettable_with_no_resettable_props); } } return; } validateResourceNodes(resources) { const codeOrder = resources[constants.CODE_ELEM_NAME] ? Number(resources[constants.CODE_ELEM_NAME][0].$.order) : undefined; Object.entries(resources).forEach(([resourceType, resourceList]) => { if (resourceType === "library") { const libOrder = Number(resourceList[0].$.order); if (!this.featureManager.isFeatureEnabled("pcfAllowLibraryResources")) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_invalid_resource_error, [resourceType]); } else if (typeof codeOrder !== "undefined" && codeOrder <= libOrder) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_invalid_library_order_error, [resourceList[0].$.name, libOrder, codeOrder]); } } if (resourceType === "dependency") { if (!this.featureManager.isFeatureEnabled("pcfResourceDependency")) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_invalid_resource_error, [resourceType]); } else { const depNames = new Set(); resourceList.forEach((dep) => { if (!dep.$.name) { this.logValidationError(diagnosticMessages_generated_1.strings.manifest_validation_error, "resource dependency with no name"); return; } if (depNames.has(dep.$.name)) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_duplicate_element_name, ["dependency", dep.$.name]); } else { depNames.add(dep.$.name); } if (!dep.$.type || dep.$.type !== "control") { this.logValidationError(diagnosticMessages_generated_1.strings.manifest_validation_error, "resource dependency with invalid type"); } if (typeof dep.$.order === "number" && dep.$.order < 0) { this.logValidationError(diagnosticMessages_generated_1.strings.manifest_validation_error, "resource dependency with invalid order"); } }); } } if (resourceType === "platform-library") { const platformHelper = new platformLibrariesHandler_1.PlatformLibrariesHandler(); resourceList .filter((lib) => !platformHelper.getSupportedVersion(lib.$.name?.toLowerCase(), lib.$.version)) .forEach((lib) => { this.logValidationError(diagnosticMessages_generated_1.strings.validation_property_platformlibrary_range_error, [lib.$.name, lib.$.version]); }); } }); } validatePropertyNodes(propertyNodes, typeGroupNames, isPropertySet) { const key = isPropertySet ? "property-set" : "property"; const propNames = new Set(); if (propertyNodes) { propertyNodes.forEach((prop) => { if (propNames.has(prop.$.name)) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_duplicate_element_name, [key, prop.$.name]); } else { propNames.add(prop.$.name); } if (prop.$["of-type-group"] && !typeGroupNames.has(prop.$["of-type-group"])) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_nonexistent_typegroup, [key, prop.$.name]); } if (prop.$["of-type"] && prop.$["of-type"] === "Object") { if (!this.featureManager.isFeatureEnabled("pcfObjectType")) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_object_type_error); } } else if (prop.$["of-type"]?.startsWith("Lookup")) { if (!this.featureManager.isFeatureEnabled("pcfAllowLookup")) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_lookup_type_error); } } this.validateValueNodes(prop.value, prop.$.name); }); } } validateEventNodes(eventNodes) { if (!this.featureManager.isFeatureEnabled("pcfAllowEvents") && eventNodes) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_events_not_allowed); } if (eventNodes) { const eventNames = new Set(); eventNodes.forEach((event) => { if (eventNames.has(event.$.name)) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_duplicate_element_name, ["event", event.$.name]); } else { eventNames.add(event.$.name); } const reservedCommonEvents = ["OnSelect", "OnChange"]; reservedCommonEvents.forEach((commonEvent) => { if (event.$.name.toUpperCase() === commonEvent.toUpperCase()) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_event_name_reserved, [event.$.name, commonEvent]); } }); }); } } validateCommonEventNodes(commonEventNodes) { if (!this.featureManager.isFeatureEnabled("pcfAllowEvents") && commonEventNodes) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_events_not_allowed); } if (commonEventNodes) { const commonEventNames = new Set(); commonEventNodes.forEach((event) => { if (commonEventNames.has(event.$.name)) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_duplicate_element_name, ["common-event", event.$.name]); } else { commonEventNames.add(event.$.name); } }); } } validateDatasetNodes(datasetNodes, _propertyNodes, typeGroupNames) { const datasetNames = new Set(); if (datasetNodes) { datasetNodes.forEach((dataset) => { if (dataset.$["allow-default-selected-items"] && this.featureManager.isFeatureEnabled("pcfAllowDefaultSelectedItemsForDataSet")) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_invalid_dataset_error, ["allow-default-selected-items"]); } if (datasetNames.has(dataset.$.name)) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_duplicate_element_name, ["data-set", dataset.$.name]); } else { datasetNames.add(dataset.$.name); } this.validatePropertyNodes(dataset["property-set"], typeGroupNames, true); }); } } validateTypeGroupNodes(typeGroups) { const typeGroupNames = new Set(); if (typeGroups) { typeGroups.forEach((tg) => { if (typeGroupNames.has(tg.$.name)) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_duplicate_element_name, ["type-group", tg.$.name]); } else { typeGroupNames.add(tg.$.name); if (tg.type.includes("Object")) { if (!this.featureManager.isFeatureEnabled("pcfObjectType")) { this.diag.push(diagnosticMessages_generated_1.strings.validation_object_type_error); } } } }); } return typeGroupNames; } validateValueNodes(values, propName) { const enumValueNames = new Set(); const enumValues = new Set(); if (values) { values.forEach((val) => { if (enumValueNames.has(val.$.name)) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_duplicate_child_element_name, ["value", val.$.name, "property", propName]); } else { enumValueNames.add(val.$.name); } if (enumValues.has(val._)) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_duplicate_enum_value, [val._, "property", propName]); } else { enumValues.add(val._); } }); } } validateFeatureUsageNodes(featureUsageNode) { const usesFeatureNames = new Set(); if (featureUsageNode?.[0]?.["uses-feature"]) { featureUsageNode[0]["uses-feature"].forEach((usesFeature) => { if (usesFeatureNames.has(usesFeature.$.name)) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_duplicate_element_name, ["uses-feature", usesFeature.$.name]); } else { usesFeatureNames.add(usesFeature.$.name); } }); } return usesFeatureNames; } validateConnectorUsageNodes(featureUsageNode) { const usesConnectorNames = new Set(); const usesConnectorIds = new Set(); if (featureUsageNode?.[0]?.["uses-connector"]) { if (!this.featureManager.isFeatureEnabled("pcfCanvasDataConnector")) { this.diag.push(diagnosticMessages_generated_1.strings.validation_property_dependency_error); } featureUsageNode[0]["uses-connector"].forEach((useConnector) => { if (usesConnectorNames.has(useConnector.$.name)) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_duplicate_element_name, ["uses-connector", useConnector.$.name]); } else { usesConnectorNames.add(useConnector.$.name); } if (usesConnectorIds.has(useConnector.$.id)) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_duplicate_element_id, ["uses-connector", useConnector.$.id]); } else { usesConnectorIds.add(useConnector.$.id); } }); } } validatePropertyDependencyNodes(propertyDependencyNodes, propertyNodes) { if (propertyDependencyNodes?.[0]?.["property-dependency"]) { if (!this.featureManager.isFeatureEnabled("pcfPropertyDependencies")) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_property_dependency_error); } const propNames = new Set(); if (propertyNodes) { propertyNodes.forEach((prop) => { propNames.add(prop.$.name); }); } propertyDependencyNodes[0]["property-dependency"].forEach((propertyDependency) => { const input = propertyDependency.$.input; const output = propertyDependency.$.output; const requiredFor = propertyDependency.$["required-for"]; if (!propNames.has(input) || !propNames.has(output)) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_property_dependency_error); } if (requiredFor !== "schema") { this.logValidationError(diagnosticMessages_generated_1.strings.validation_property_dependency_error); } }); } } validateSupportedPlatformNodes(supportedPlatformsNode) { const supportedPlatformNodes = new Set(); if (!supportedPlatformsNode?.[0]?.["supported-platform"]) { return supportedPlatformNodes; } const availablePlatforms = ["Model", "Canvas", "CustomPage", "Portal"]; supportedPlatformsNode[0]["supported-platform"].forEach((supportedPlatform) => { const name = supportedPlatform.$.name; if (supportedPlatformNodes.has(name)) { this.diag.pushA(diagnosticMessages_generated_1.strings.validation_duplicate_element_name, ["supported-platform", name]); } else { if (!availablePlatforms.includes(name)) { this.diag.pushA(diagnosticMessages_generated_1.strings.validation_property_supportedplatform_error, [name]); } supportedPlatformNodes.add(name); } }); return supportedPlatformNodes; } validateActionNodes(actionNodes) { if (!this.featureManager.isFeatureEnabled("pcfAllowActions") && actionNodes) { this.diag.push(diagnosticMessages_generated_1.strings.manifest_validation_error); } if (actionNodes) { const actionNames = new Set(); actionNodes.forEach((action) => { if (!action.$.name) { this.logValidationError(diagnosticMessages_generated_1.strings.manifest_validation_error); } if (actionNames.has(action.$.name)) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_duplicate_element_name, ["action", action.$.name]); } else { actionNames.add(action.$.name); } if (action.parameter) { const parameterNames = new Set(); action.parameter.forEach((param) => { if (!param.$.name) { this.logValidationError(diagnosticMessages_generated_1.strings.manifest_validation_error); } if (parameterNames.has(param.$.name)) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_duplicate_element_name, ["parameter", param.$.name]); } else { parameterNames.add(param.$.name); } }); } }); } } validatePlatformActionNodes(platformActionNodes) { const platformActionTypes = new Set(); const supportedPlatformActions = ["afterPageLoad"]; platformActionNodes?.forEach((platformAction) => { const actionType = platformAction.$["action-type"]; if (platformActionTypes.has(actionType)) { this.logValidationError(diagnosticMessages_generated_1.strings.validation_duplicate_element_name, ["parameter", actionType]); } else if (!supportedPlatformActions.includes(actionType)) { this.logValidationError(diagnosticMessages_generated_1.strings.unsupported_platform_action, actionType); } else { platformActionTypes.add(actionType); } }); } logValidationError(diagError, args) { const warningCategoryDiag = { category: "Warning" }; const diag = this.featureManager.isFeatureEnabled("treatValidationErrorsAsWarnings") ? { ...diagError, ...warningCategoryDiag } : diagError; if (args) { this.diag.pushA(diag, args); } else { this.diag.push(diag); } } } exports.ManifestSchemaValidator = ManifestSchemaValidator; //# sourceMappingURL=manifestSchemaValidator.js.map