UNPKG

gpii-universal

Version:

Cross platform, core components of the GPII personalization infrastructure.

229 lines (204 loc) 9.47 kB
/* kettle.middleware "validators" designed to validate preference and settings payloads. */ /* eslint-env node */ "use strict"; var fluid = require("infusion"); var gpii = fluid.registerNamespace("gpii"); // Add the ability to require JSON5 files, the default usage of these grades will need to have this or they will fail to // expand the base schemas. require("json5/lib/register"); fluid.registerNamespace("gpii.universal.solutionsRegistry.validators"); fluid.require("%gpii-json-schema"); require("../../../transformer/src/js/Transformer"); require("../../../settingsHandlers/"); require("../../../flowManager/src/SystemUtils"); /** * * A replacement for the default kettle validation middleware handle invokers backing function, which is more suitable * for working with a schema that is generated based on the payload. * * @param {gpii.universal.solutionsRegistry.validators.base} that - The validation middleware component. * @param {gpii.schema.validator} globalValidator - The persistent global validator component. * @param {kettle.request.http} requestHandler - The component fielding the individual request. * @return {Promise} - A `fluid.promise` that will resolve if the payload is valid or reject with errors if the payload is invalid. * */ gpii.universal.solutionsRegistry.validators.validateRequest = function (that, globalValidator, requestHandler) { var transformationRules = that.options.rules.outerPayload; var outerPayload = fluid.model.transformWithRules(requestHandler.req, transformationRules); var validationStages = []; // "gross" validation based on the overall structure validationStages.push(gpii.universal.solutionsRegistry.validators.singleValidation(that, globalValidator, that.options.baseSchema, outerPayload, that.options.baseSchemaHash)); // "deep" validation of individual settings, per solution. var innerPayload = fluid.model.transformWithRules(requestHandler.req, that.options.rules.innerPayload); fluid.each(innerPayload, function (solutionSettings, settingURI) { var solutionID = gpii.universal.solutionsRegistry.validators.solutionIdFromURI(settingURI); fluid.each(fluid.get(that.options.sr, [solutionID, "settingsHandlers"]), function (settingsHandler) { var transformed = gpii.transformer.transformOneSettingsHandler(fluid.copy(settingsHandler), { settings: innerPayload }, solutionID); if (Object.keys(transformed.settings).length) { innerPayload[settingURI] = transformed.settings; } }); }); fluid.each(innerPayload, function (solutionSettings, settingURI) { var solutionSchema = that.options.codex[settingURI]; if (solutionSchema) { if (!that.schemaHashesByUri[settingURI]) { that.schemaHashesByUri[settingURI] = gpii.schema.hashSchema(solutionSchema); } validationStages.push(gpii.universal.solutionsRegistry.validators.singleValidation(that, globalValidator, solutionSchema, solutionSettings, that.schemaHashesByUri[settingURI], [settingURI])); } }); // Although our validation is synchronous, we use this pattern so that processing will be cleanly interrupted on the // first validation error. return fluid.promise.sequence(validationStages); }; /** * * Extract the solution ID (such as `com.microsoft.windows.desktopBackground`) from a solution URI, such as * `http://registry.gpii.net/applications/com.microsoft.windows.desktopBackground`. * * @param {String} solutionURI - The solution URI. * @return {String} - The solution ID. * */ gpii.universal.solutionsRegistry.validators.solutionIdFromURI = function (solutionURI) { var segments = solutionURI.split("/"); return segments[segments.length - 1]; }; /** * * Return a promise-returning function that represents a single attempt at validating a given payload. Meant to be used * in a multi-pass validation approach with `fluid.promise.sequence`. * * @param {gpii.universal.solutionsRegistry.validators.base} validationMiddleware - The validation middleware component. * @param {gpii.schema.validator} globalValidator - The persistent global validator component. * @param {GssSchema} schema - A GSS schema. * @param {Any} toValidate - The payload to validate. * @param {String} schemaHash - A hash of the schema to use in * @param {Array<String>} [dataPathPrefix] - A path to prepend to the `dataPath` reported in any validation errors. * @return {Function} - A `fluid.promise` returning function. * */ gpii.universal.solutionsRegistry.validators.singleValidation = function (validationMiddleware, globalValidator, schema, toValidate, schemaHash, dataPathPrefix) { return function () { var validationPromise = fluid.promise(); var validationResults = globalValidator.validate(schema, toValidate, schemaHash); if (validationResults.isValid) { validationPromise.resolve(); } else { // TODO: We have to reconstruct the original path somehow if (dataPathPrefix) { fluid.each(validationResults.errors, function (singleError) { singleError.dataPath = fluid.copy(dataPathPrefix).concat(singleError.dataPath); }); } var localisedErrors = gpii.schema.validator.localiseErrors(validationResults.errors, toValidate, validationMiddleware.model.messages, validationMiddleware.options.rules.localisation); var localisedPayload = fluid.copy(validationResults); localisedPayload.errors = localisedErrors; var failurePayload = fluid.extend({}, validationMiddleware.options.errorTemplate, localisedPayload); validationPromise.reject(failurePayload); } return validationPromise; }; }; fluid.defaults("gpii.universal.solutionsRegistry.validators.base", { gradeNames: ["kettle.middleware", "fluid.modelComponent"], members: { schemaHashesByUri: {} }, model: { messages: gpii.schema.messages.validationErrors }, errorTemplate: { // "Bad Request": https://developer.mozilla.org/nl/docs/Web/HTTP/Status/400 statusCode: 400, message: "Your request was invalid. See the errors for details." }, mergePolicy: { "rules.localisation": "nomerge", "rules.innerPayload": "nomerge", "rules.outerPayload": "nomerge", "rules.transformedPayload": "nomerge" }, pathToSettings: "", codexPath: "%gpii-universal/build/schemas/solution-schema-codex.json", codex: "@expand:fluid.require({that}.options.codexPath)", // TODO: Discuss combining the "codex" and "solutions registry" in the upcoming LSR work. srPath: "%gpii-universal/testData/solutions/win32.json5", sr: "@expand:fluid.require({that}.options.srPath)", baseSchemaPath: "%gpii-universal/gpii/node_modules/solutionsRegistry/src/schemas/settings-base-schema.json5", baseSchema: "@expand:fluid.require({that}.options.baseSchemaPath)", baseSchemaHash: "@expand:gpii.schema.hashSchema({that}.options.requestSchema)", rules: { localisation: { "": "" }, outerPayload: { "": "body" }, innerPayload: { "": "" }, transformedPayload: { "": "toValidate", flat: { contexts: { "gpii-default": { "preferences": "transformedSettings" } } } } }, invokers: { handle: { funcName: "gpii.universal.solutionsRegistry.validators.validateRequest", args: ["{that}", "{gpii.schema.validator}", "{arguments}.0"] // kettleValidator, globalValidator, request } } }); // TODO: discuss how to handle contexts other than gpii-default in both of these stacks. fluid.defaults("gpii.universal.solutionsRegistry.validators.settings", { gradeNames: ["gpii.universal.solutionsRegistry.validators.base"], rules: { innerPayload: { "": "body.flat.contexts.gpii-default.preferences" } } }); fluid.defaults("gpii.universal.solutionsRegistry.requestHandlers.settings", { gradeNames: ["kettle.request.http"], requestMiddleware: { validate: { middleware: "{gpii.universal.solutionsRegistry.validators.settings}" } } }); fluid.defaults("gpii.universal.solutionsRegistry.validators.preferences", { gradeNames: ["gpii.universal.solutionsRegistry.validators.base"], baseSchemaPath: "%gpii-universal/gpii/node_modules/solutionsRegistry/src/schemas/prefs-base-schema.json5", rules: { innerPayload: { "": { transform: { type: "fluid.transforms.firstValue", values: [ "body.contexts.gpii-default.preferences.application", "body.contexts.gpii-default.preferences" ] } } } } }); fluid.defaults("gpii.universal.solutionsRegistry.requestHandlers.preferences", { gradeNames: ["kettle.request.http"], requestMiddleware: { validate: { middleware: "{gpii.universal.solutionsRegistry.validators.preferences}" } } });