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
JavaScript
"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