From 4f3ac7fc2ca4166de0a98ae360d481fa5d9a6e27 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Mon, 29 Mar 2021 03:36:29 +0200 Subject: [PATCH 1/7] adds deprecator --- spec/Deprecator.spec.js | 38 ++++++++++++++++++ src/Deprecator/Deprecations.js | 14 +++++++ src/Deprecator/Deprecator.js | 71 ++++++++++++++++++++++++++++++++++ src/ParseServer.js | 4 ++ src/cli/utils/commander.js | 6 ++- 5 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 spec/Deprecator.spec.js create mode 100644 src/Deprecator/Deprecations.js create mode 100644 src/Deprecator/Deprecator.js diff --git a/spec/Deprecator.spec.js b/spec/Deprecator.spec.js new file mode 100644 index 0000000000..8684229781 --- /dev/null +++ b/spec/Deprecator.spec.js @@ -0,0 +1,38 @@ +'use strict'; + +const Deprecator = require('../lib/Deprecator/Deprecator'); + +describe('Deprecator', () => { + let deprecations = []; + + beforeEach(async () => { + deprecations = [{ optionKey: 'exampleKey', changeNewDefault: 'exampleNewDefault' }]; + }); + + it('deprecations are an array', async () => { + expect(Deprecator.getDeprecations()).toBeInstanceOf(Array); + }); + + it('logs deprecation for new default', async () => { + deprecations = [{ optionKey: 'exampleKey', changeNewDefault: 'exampleNewDefault' }]; + + spyOn(Deprecator, '_getDeprecations').and.callFake(() => deprecations); + const logger = require('../lib/logger').logger; + const logSpy = spyOn(logger, 'warn').and.callThrough(); + + await reconfigureServer(); + expect(logSpy.calls.all()[0].args[0]).toContain(deprecations[0].optionKey); + expect(logSpy.calls.all()[0].args[0]).toContain(deprecations[0].changeNewDefault); + }); + + fit('does not log deprecation for new default if option is set manually', async () => { + deprecations = [{ optionKey: 'exampleKey', changeNewDefault: 'exampleNewDefault' }]; + + spyOn(Deprecator, '_getDeprecations').and.callFake(() => deprecations); + const logger = require('../lib/logger').logger; + const logSpy = spyOn(logger, 'warn').and.callThrough(); + + await reconfigureServer({ [deprecations[0].optionKey]: 'manuallySet' }); + expect(logSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/src/Deprecator/Deprecations.js b/src/Deprecator/Deprecations.js new file mode 100644 index 0000000000..f75adcaf49 --- /dev/null +++ b/src/Deprecator/Deprecations.js @@ -0,0 +1,14 @@ +/** + * The deprecations. + * + * Add deprecations to the array using the following keys: + * - `optionKey`: The option key incl. its path, e.g. `security.enableCheck`. + * - `envKey`: The environment key, e.g. `PARSE_SERVER_SECURITY`. + * - `changeNewKey`: Set the new key name if the current key will be replaced, + * or set to an empty string if the current key will be removed without replacement. + * - `changeNewDefault`: Set the new default value if the key's default value + * will change in a future version. + * + * If there are no deprecations this must return an empty array anyway. + */ +module.exports = [{ optionKey: 'directAccess', changeNewDefault: 'true' }]; diff --git a/src/Deprecator/Deprecator.js b/src/Deprecator/Deprecator.js new file mode 100644 index 0000000000..df1710c4ba --- /dev/null +++ b/src/Deprecator/Deprecator.js @@ -0,0 +1,71 @@ +import logger from '../logger'; +import Deprecations from './Deprecations'; + +/** + * The deprecator class. + */ +class Deprecator { + /** + * Scans the Parse Server for deprecated options. + * This needs to be called before setting option defaults, otherwise it + * becomes indistinguishable whether an option has been set manually or + * by default. + * @param {any} options The Parse Server options. + */ + static scanParseServerOptions(options) { + // Scan for deprecations + for (const deprecation of Deprecator._getDeprecations()) { + // Get deprecation properties + const optionKey = deprecation.optionKey; + const changeNewDefault = deprecation.changeNewDefault; + + // If default will change, only throw a warning if option is not set + if (changeNewDefault != null && options[optionKey] == null) { + Deprecator._log({ optionKey, changeNewDefault }); + } + } + } + + /** + * Returns the deprecation definitions. + * @returns {Array} The deprecations. + */ + static _getDeprecations() { + return Deprecations; + } + + /** + * Logs a deprecation warning for a Parse Server option. + * @param {String} optionKey The option key incl. its path, e.g. `security.enableCheck`. + * @param {String} envKey The environment key, e.g. `PARSE_SERVER_SECURITY`. + * @param {String} changeNewKey Set the new key name if the current key will be replaced, + * or set to an empty string if the current key will be removed without replacement. + * @param {String} changeNewDefault Set the new default value if the key's default value + * will change in a future version. + * @param {String} [solution] The instruction to resolve this deprecation warning. This + * message must not include the warning that the parameter is deprecated, that is + * automatically added to the message. It should only contain the instruction on how + * to resolve this warning. + */ + static _log({ optionKey, envKey, changeNewKey, changeNewDefault, solution }) { + const type = optionKey ? 'option' : 'environment key'; + const key = optionKey ? optionKey : envKey; + const keyAction = + changeNewKey == null + ? undefined + : changeNewKey.length > 0 + ? `renamed to '${changeNewKey}'` + : `removed`; + + // Compose message + let output = `⚠️ The Parse Server ${type} '${key}' `; + output += changeNewKey ? `is deprecated and will be ${keyAction} in a future version.` : ''; + output += changeNewDefault + ? `default will change to '${changeNewDefault}' in a future version.` + : ''; + output += solution ? ` ${solution}` : ''; + logger.warn(output); + } +} + +module.exports = Deprecator; diff --git a/src/ParseServer.js b/src/ParseServer.js index 1ee653b62f..43996ac751 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -43,6 +43,7 @@ import * as controllers from './Controllers'; import { ParseGraphQLServer } from './GraphQL/ParseGraphQLServer'; import { SecurityRouter } from './Routers/SecurityRouter'; import CheckRunner from './Security/CheckRunner'; +import Deprecator from './Deprecator/Deprecator'; // Mutate the Parse object to add the Cloud Code handlers addParseCloud(); @@ -55,6 +56,9 @@ class ParseServer { * @param {ParseServerOptions} options the parse server initialization options */ constructor(options: ParseServerOptions) { + // Scan for deprecated Parse Server options + Deprecator.scanParseServerOptions(options); + // Set option defaults injectDefaults(options); const { appId = requiredParameter('You must provide an appId!'), diff --git a/src/cli/utils/commander.js b/src/cli/utils/commander.js index d5a8208253..8b8826fe69 100644 --- a/src/cli/utils/commander.js +++ b/src/cli/utils/commander.js @@ -1,6 +1,8 @@ /* eslint-disable no-console */ import { Command } from 'commander'; import path from 'path'; +import Deprecator from '../../Deprecator/Deprecator'; + let _definitions; let _reverseDefinitions; let _defaults; @@ -40,7 +42,7 @@ Command.prototype.loadDefinitions = function (definitions) { }, {}); _defaults = Object.keys(definitions).reduce((defs, opt) => { - if (_definitions[opt].default) { + if (_definitions[opt].default !== undefined) { defs[opt] = _definitions[opt].default; } return defs; @@ -119,6 +121,8 @@ Command.prototype.parse = function (args, env) { this.setValuesIfNeeded(envOptions); // Load from file to override this.setValuesIfNeeded(fromFile); + // Scan for deprecated Parse Server options + Deprecator.scanParseServerOptions(this); // Last set the defaults this.setValuesIfNeeded(_defaults); }; From bd7aa61ae1b15adfa4cfae37fb785d0e967c881e Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Mon, 29 Mar 2021 04:12:35 +0200 Subject: [PATCH 2/7] un-fit --- spec/Deprecator.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Deprecator.spec.js b/spec/Deprecator.spec.js index 8684229781..95076955fb 100644 --- a/spec/Deprecator.spec.js +++ b/spec/Deprecator.spec.js @@ -25,7 +25,7 @@ describe('Deprecator', () => { expect(logSpy.calls.all()[0].args[0]).toContain(deprecations[0].changeNewDefault); }); - fit('does not log deprecation for new default if option is set manually', async () => { + it('does not log deprecation for new default if option is set manually', async () => { deprecations = [{ optionKey: 'exampleKey', changeNewDefault: 'exampleNewDefault' }]; spyOn(Deprecator, '_getDeprecations').and.callFake(() => deprecations); From 34a52d63ae74edd5cce88f384054ee0c4b409f8c Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Mon, 29 Mar 2021 04:15:16 +0200 Subject: [PATCH 3/7] added changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79cd3add6d..a8b6accab4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -119,6 +119,7 @@ ___ - LDAP: Properly unbind client on group search error (Diamond Lewis) [#7265](https://github.com/parse-community/parse-server/pull/7265) - Improve data consistency in Push and Job Status update (Diamond Lewis) [#7267](https://github.com/parse-community/parse-server/pull/7267) - Excluding keys that have trailing edges.node when performing GraphQL resolver (Chris Bland) [#7273](https://github.com/parse-community/parse-server/pull/7273) +- Added centralized feature deprecation with standardized warning logs (Manuel Trezza) [#7303](https://github.com/parse-community/parse-server/pull/7303) ___ ## 4.5.0 [Full Changelog](https://github.com/parse-community/parse-server/compare/4.4.0...4.5.0) From 642471dfceb59fe26c99ea8cbfa678ef6c3719bc Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Mon, 29 Mar 2021 04:30:22 +0200 Subject: [PATCH 4/7] some fixes --- spec/Deprecator.spec.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/spec/Deprecator.spec.js b/spec/Deprecator.spec.js index 95076955fb..c8b72b0a6d 100644 --- a/spec/Deprecator.spec.js +++ b/spec/Deprecator.spec.js @@ -10,15 +10,15 @@ describe('Deprecator', () => { }); it('deprecations are an array', async () => { - expect(Deprecator.getDeprecations()).toBeInstanceOf(Array); + expect(Deprecator._getDeprecations()).toBeInstanceOf(Array); }); - it('logs deprecation for new default', async () => { + fit('logs deprecation for new default', async () => { deprecations = [{ optionKey: 'exampleKey', changeNewDefault: 'exampleNewDefault' }]; spyOn(Deprecator, '_getDeprecations').and.callFake(() => deprecations); const logger = require('../lib/logger').logger; - const logSpy = spyOn(logger, 'warn').and.callThrough(); + const logSpy = spyOn(logger, 'warn').and.callFake(() => {}); await reconfigureServer(); expect(logSpy.calls.all()[0].args[0]).toContain(deprecations[0].optionKey); @@ -29,9 +29,7 @@ describe('Deprecator', () => { deprecations = [{ optionKey: 'exampleKey', changeNewDefault: 'exampleNewDefault' }]; spyOn(Deprecator, '_getDeprecations').and.callFake(() => deprecations); - const logger = require('../lib/logger').logger; - const logSpy = spyOn(logger, 'warn').and.callThrough(); - + const logSpy = spyOn(Deprecator, '_log').and.callFake(() => {}); await reconfigureServer({ [deprecations[0].optionKey]: 'manuallySet' }); expect(logSpy).not.toHaveBeenCalled(); }); From 030922b66e1bd07d4a33e8b8afd4359395472253 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Mon, 29 Mar 2021 04:38:19 +0200 Subject: [PATCH 5/7] un-fit --- spec/Deprecator.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Deprecator.spec.js b/spec/Deprecator.spec.js index c8b72b0a6d..7e0e28df3d 100644 --- a/spec/Deprecator.spec.js +++ b/spec/Deprecator.spec.js @@ -13,7 +13,7 @@ describe('Deprecator', () => { expect(Deprecator._getDeprecations()).toBeInstanceOf(Array); }); - fit('logs deprecation for new default', async () => { + it('logs deprecation for new default', async () => { deprecations = [{ optionKey: 'exampleKey', changeNewDefault: 'exampleNewDefault' }]; spyOn(Deprecator, '_getDeprecations').and.callFake(() => deprecations); From a61f4c076fe01603a9321158f9ee50dea15dee5b Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Mon, 29 Mar 2021 04:43:49 +0200 Subject: [PATCH 6/7] removed deprecation definition --- src/Deprecator/Deprecations.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Deprecator/Deprecations.js b/src/Deprecator/Deprecations.js index f75adcaf49..5c7cc3630f 100644 --- a/src/Deprecator/Deprecations.js +++ b/src/Deprecator/Deprecations.js @@ -11,4 +11,4 @@ * * If there are no deprecations this must return an empty array anyway. */ -module.exports = [{ optionKey: 'directAccess', changeNewDefault: 'true' }]; +module.exports = []; From 627f66d394c49fd6307a331c907efc7164135b23 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Mon, 29 Mar 2021 16:11:48 +0200 Subject: [PATCH 7/7] changed deprecation log syntax according to Nodejs --- src/Deprecator/Deprecator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Deprecator/Deprecator.js b/src/Deprecator/Deprecator.js index df1710c4ba..53036fcb1f 100644 --- a/src/Deprecator/Deprecator.js +++ b/src/Deprecator/Deprecator.js @@ -58,7 +58,7 @@ class Deprecator { : `removed`; // Compose message - let output = `⚠️ The Parse Server ${type} '${key}' `; + let output = `DeprecationWarning: The Parse Server ${type} '${key}' `; output += changeNewKey ? `is deprecated and will be ${keyAction} in a future version.` : ''; output += changeNewDefault ? `default will change to '${changeNewDefault}' in a future version.`