diff --git a/index.d.ts b/index.d.ts index c6dbcf354..d27b4b799 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,6 +1,7 @@ import type { EventEmitter2 } from "eventemitter2"; import type { BinaryLike, CipherCCMTypes, CipherGCMTypes, CipherKey, CipherOCBTypes } from 'crypto' import type { Worker } from "cluster"; +import { ValidationSchema } from "fastest-validator"; declare namespace Moleculer { /** @@ -628,6 +629,7 @@ declare namespace Moleculer { $dependencyTimeout?: number; $shutdownTimeout?: number; $secureSettings?: string[]; + $validationSchema?: ValidationSchema | GenericObject; [name: string]: any; } diff --git a/src/service.js b/src/service.js index cd430529e..d2ddd34e2 100644 --- a/src/service.js +++ b/src/service.js @@ -252,6 +252,16 @@ class Service { return settings; } + /** + * Validate service settings by provided validation schema. + * + * @param {Object} settings + * @memberof Service + */ + _validateSettings(settings) { + return this.broker.validator.validate(settings, settings.$validationSchema); + } + /** * Initialize service. It called `created` handler in schema * @@ -259,6 +269,15 @@ class Service { * @memberof Service */ _init() { + if (this.settings && this.settings.$validationSchema) { + this.logger.debug("Service settings validation schema found. Try to use it..."); + if (this.broker.validator) { + this._validateSettings(this.settings); + } else { + this.logger.warn("Validator not enabled. Skip service settings validation."); + } + } + this.logger.debug(`Service '${this.fullName}' is creating...`); if (isFunction(this.schema.created)) { this.schema.created.call(this); diff --git a/test/unit/service.spec.js b/test/unit/service.spec.js index d0ef3d170..14d54ed2a 100644 --- a/test/unit/service.spec.js +++ b/test/unit/service.spec.js @@ -3,6 +3,7 @@ const Service = require("../../src/service"); const Context = require("../../src/context"); const ServiceBroker = require("../../src/service-broker"); +const { ValidationError } = require("../../src/errors"); describe("Test Service class", () => { describe("Test constructor", () => { @@ -489,6 +490,74 @@ describe("Test Service class", () => { }); }); + describe("Test service settings validation", () => { + const broker = new ServiceBroker({ logger: false }); + + const svc = new Service(broker); + + jest.spyOn(broker.validator, "validate"); + + const $validationSchema = { + $$strict: false, + someSetting: { + props: { someString: { type: "string" } }, + strict: true, + type: "object" + } + }; + + const validSettings = { + $validationSchema, + someSetting: { + someString: "someString" + } + }; + + const invalidSettings = { + $validationSchema, + someSetting: { + someAnotherString: "someString" + } + }; + + it("should validate service settings correctly", () => { + const validationResult = svc._validateSettings(validSettings); + + expect(validationResult).toEqual(true); + }); + + it("should validate service settings correctly", () => { + const merged = svc.mergeSchemaSettings; + svc.parseServiceSchema({ settings: validSettings, merged, name: "posts" }); + + broker.validator.validate.mockClear(); + + svc._init(); + + expect(broker.validator.validate).toBeCalledTimes(1); + }); + + it("should throw validation error", () => { + const merged = svc.mergeSchemaSettings; + + try { + svc.parseServiceSchema({ settings: invalidSettings, merged, name: "posts" }); + } catch (error) { + expect(error).toBeInstanceOf(ValidationError); + } + }); + + it("should not validate service settings", () => { + broker.validator = null; + + const merged = svc.mergeSchemaSettings; + + expect(() => { + svc.parseServiceSchema({ settings: validSettings, merged, name: "posts" }); + }).not.toThrow(); + }); + }); + describe("Test _init", () => { const broker = new ServiceBroker({ logger: false });