From 19a8afe05f1c12f95aeb28eef41284d1c590be6f Mon Sep 17 00:00:00 2001 From: Abdul Ahad Date: Wed, 30 Jul 2025 11:49:48 +0200 Subject: [PATCH 1/6] fix: hidden property without value should be deprecated Closes #181 --- .../src/defs/properties.json | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/packages/zeebe-element-templates-json-schema/src/defs/properties.json b/packages/zeebe-element-templates-json-schema/src/defs/properties.json index 896f0e8..704b386 100644 --- a/packages/zeebe-element-templates-json-schema/src/defs/properties.json +++ b/packages/zeebe-element-templates-json-schema/src/defs/properties.json @@ -627,6 +627,45 @@ }, { "$ref": "./properties/taskSchedule.json" + }, + { + "if": { + "properties": { + "type": { + "const": "Hidden" + }, + "binding": { + "properties": { + "type": { + "not": { + "const": "zeebe:userTask" + } + } + }, + "required": [ + "type" + ] + } + }, + "required": [ + "type" + ] + }, + "then": { + "anyOf": [ + { "required": [ "value" ] }, + { "required": [ "generatedValue" ] }, + { + "not": { + "anyOf": [ + { "required": [ "value" ] }, + { "required": [ "generatedValue" ] } + ] + }, + "deprecated": true + } + ] + } } ], "properties": { From 1fc65b614291140346f7b9ab4d22d9193ffcd124 Mon Sep 17 00:00:00 2001 From: Abdul Ahad Date: Wed, 30 Jul 2025 12:09:38 +0200 Subject: [PATCH 2/6] test: hidden property without value should be deprecated --- .../test/fixtures/hidden-property.js | 29 +++++++++++++++++++ .../test/fixtures/hidden-zeebe-user-task.js | 22 ++++++++++++++ .../test/spec/validationSpec.js | 6 ++++ 3 files changed, 57 insertions(+) create mode 100644 packages/zeebe-element-templates-json-schema/test/fixtures/hidden-property.js create mode 100644 packages/zeebe-element-templates-json-schema/test/fixtures/hidden-zeebe-user-task.js diff --git a/packages/zeebe-element-templates-json-schema/test/fixtures/hidden-property.js b/packages/zeebe-element-templates-json-schema/test/fixtures/hidden-property.js new file mode 100644 index 0000000..1ba89ba --- /dev/null +++ b/packages/zeebe-element-templates-json-schema/test/fixtures/hidden-property.js @@ -0,0 +1,29 @@ +export const template = { + 'name': 'HiddenProperty', + 'id': 'com.camunda.example.HiddenProperty', + 'appliesTo': [ + 'bpmn:Task' + ], + 'elementType': { + 'value': 'bpmn:BusinessRuleTask' + }, + 'properties': [ + { + 'type': 'Hidden', + 'value': 'decision', + 'binding': { + 'type': 'zeebe:calledDecision', + 'property': 'decisionId' + } + }, + { + 'type': 'Hidden', + 'binding': { + 'type': 'zeebe:calledDecision', + 'property': 'resultVariable' + } + } + ] +}; + +export const errors = null; diff --git a/packages/zeebe-element-templates-json-schema/test/fixtures/hidden-zeebe-user-task.js b/packages/zeebe-element-templates-json-schema/test/fixtures/hidden-zeebe-user-task.js new file mode 100644 index 0000000..f0758e2 --- /dev/null +++ b/packages/zeebe-element-templates-json-schema/test/fixtures/hidden-zeebe-user-task.js @@ -0,0 +1,22 @@ +export const template = { + 'name': 'Zeebe User Task Hidden Property', + 'id': 'com.camunda.example.ZeebeUserTaskHiddenProperty', + 'description': 'A template to define a value less hidden property for a Zeebe user task.', + 'version': 1, + 'appliesTo': [ + 'bpmn:Task' + ], + 'elementType': { + 'value': 'bpmn:UserTask' + }, + 'properties': [ + { + 'type': 'Hidden', + 'binding': { + 'type': 'zeebe:userTask', + } + } + ] +}; + +export const errors = null; \ No newline at end of file diff --git a/packages/zeebe-element-templates-json-schema/test/spec/validationSpec.js b/packages/zeebe-element-templates-json-schema/test/spec/validationSpec.js index cf01da4..50fa63a 100644 --- a/packages/zeebe-element-templates-json-schema/test/spec/validationSpec.js +++ b/packages/zeebe-element-templates-json-schema/test/spec/validationSpec.js @@ -199,6 +199,12 @@ describe('validation', function() { it('element-type-invalid'); + it('hidden-property'); + + + it('hidden-zeebe-user-task'); + + describe('element type - event definition', function() { it('element-type-event-definition'); From 9238f24a6cafa08b445981d959e8d7ca3a85f498 Mon Sep 17 00:00:00 2001 From: Abdul Ahad Date: Thu, 14 Aug 2025 16:58:01 +0200 Subject: [PATCH 3/6] test: added a new test setup to verify for warnings - less strict warnings --- .../test/helpers/index.js | 93 ++++++++++++++++++- .../src/defs/properties.json | 2 +- .../test/fixtures/hidden-property.js | 8 ++ .../test/spec/validationSpec.js | 22 ++++- 4 files changed, 116 insertions(+), 9 deletions(-) diff --git a/packages/element-templates-json-schema-shared/test/helpers/index.js b/packages/element-templates-json-schema-shared/test/helpers/index.js index cf5ea81..996059a 100644 --- a/packages/element-templates-json-schema-shared/test/helpers/index.js +++ b/packages/element-templates-json-schema-shared/test/helpers/index.js @@ -10,11 +10,13 @@ const AjvErrors = require('ajv-errors'); module.exports = { createValidator, - withErrorMessages + withErrorMessages, + withDeprecationWarnings }; -function createValidator(schema, errors) { +function createValidator(schema, errors, deprecations) { + let deprecationWarnings = []; const ajv = new Ajv({ allErrors: true, strict: false, @@ -23,7 +25,60 @@ function createValidator(schema, errors) { AjvErrors(ajv); - return ajv.compile(withErrorMessages(schema, errors)); + ajv.addKeyword({ + keyword: 'isDeprecated', + errors: true, + compile(schema, parentSchema) { + return function(data, dataCtx) { + if (schema) { + deprecationWarnings = []; + + // AJV doesn't support real warnings, so this adds a non-strict validation with keyword 'isDeprecated' + const deprecationWarning = { + keyword: 'isDeprecated', + + // TODO: schemaPath + dataPath: dataCtx.dataPath, + message: parentSchema.deprecatedWarning || 'This property is deprecated', + }; + + deprecationWarnings.push({ + id: dataCtx.rootData.id, + warningDescription: deprecationWarning, + }); + + // just return true to not fail validation + return true; + } + return true; + }; + } + }); + + const validator = ajv.compile(withErrorMessages(withDeprecationWarnings(schema, deprecations), errors)); + + + const getDeprecationWarnings = function(data) { + if (deprecationWarnings.length > 0 && data.id === deprecationWarnings[0].id) { + return deprecationWarnings.map(warning => warning.warningDescription); + } + }; + + // wrapper function checks for warnings before each validation + const wrappedValidator = function(data, ...args) { + + // Empty deprecation before each validation + deprecationWarnings = []; + + const result = validator.call(this, data, ...args); + + wrappedValidator.errors = validator.errors; + wrappedValidator.warnings = getDeprecationWarnings(data); + + return result; + }; + + return wrappedValidator; } function withErrorMessages(schema, errors) { @@ -71,4 +126,34 @@ function eqlErrors(chai, utils) { }); } -chai.use(eqlErrors); \ No newline at end of file +chai.use(eqlErrors); + +function withDeprecationWarnings(schema, deprecations) { + if (!deprecations || !deprecations.length) { + return schema; + } + + // clone a new copy + let newSchema = JSON.parse(JSON.stringify(schema)); + + // set deprecation warnings for given paths + forEach(deprecations, function(deprecation) { + newSchema = setDeprecationWarning(newSchema, deprecation); + }); + + return newSchema; +} + +function setDeprecationWarning(schema, deprecation) { + const { + path, + warningMessage + } = deprecation; + + const deprecationPath = [ + ...path, + 'deprecatedWarning' + ]; + + return set(schema, deprecationPath, warningMessage); +} \ No newline at end of file diff --git a/packages/zeebe-element-templates-json-schema/src/defs/properties.json b/packages/zeebe-element-templates-json-schema/src/defs/properties.json index 704b386..0721503 100644 --- a/packages/zeebe-element-templates-json-schema/src/defs/properties.json +++ b/packages/zeebe-element-templates-json-schema/src/defs/properties.json @@ -662,7 +662,7 @@ { "required": [ "generatedValue" ] } ] }, - "deprecated": true + "isDeprecated": true } ] } diff --git a/packages/zeebe-element-templates-json-schema/test/fixtures/hidden-property.js b/packages/zeebe-element-templates-json-schema/test/fixtures/hidden-property.js index 1ba89ba..fecbbc6 100644 --- a/packages/zeebe-element-templates-json-schema/test/fixtures/hidden-property.js +++ b/packages/zeebe-element-templates-json-schema/test/fixtures/hidden-property.js @@ -27,3 +27,11 @@ export const template = { }; export const errors = null; + +export const warnings = [ + { + keyword: 'isDeprecated', + dataPath: '/properties/1', + message: 'Hidden property must specify either "value" or "generatedValue"' + } +]; \ No newline at end of file diff --git a/packages/zeebe-element-templates-json-schema/test/spec/validationSpec.js b/packages/zeebe-element-templates-json-schema/test/spec/validationSpec.js index 50fa63a..56254c2 100644 --- a/packages/zeebe-element-templates-json-schema/test/spec/validationSpec.js +++ b/packages/zeebe-element-templates-json-schema/test/spec/validationSpec.js @@ -5,12 +5,13 @@ const util = require('util'); const schema = require('../../resources/schema.json'); const errorMessages = require('../../resources/error-messages.json'); +const deprecatedWarnings = require('../../resources/deprecated-warnings.json'); const { createValidator } = require('../../../element-templates-json-schema-shared/test/helpers'); -const validator = createValidator(schema, errorMessages); +const validator = createValidator(schema, errorMessages, deprecatedWarnings); // we save this for some other shinanigans const iit = it; @@ -20,10 +21,12 @@ function validateTemplate(template) { const valid = validator(template); const errors = validator.errors; + const warnings = validator.warnings; return { valid, - errors + errors, + warnings }; } @@ -40,16 +43,27 @@ function createTest(name, file, it) { const { errors: expectedErrors, - template + template, + warnings: expectedWarnings } = testDefinition; // when const { - errors + errors, + warnings } = validateTemplate(template); // then expect(errors).to.eqlErrors(expectedErrors); + + // less strict check for warnings + if (expectedWarnings) { + expect(warnings).to.eql(expectedWarnings); + } else if (warnings && warnings.length > 0) { + + // log warnings without failing the test + warnings.forEach(x => console.warn('Deprecation warning:', x.message)); + } }); } From e84f1d692df8cd52b343da8c90f6901f86fe6f20 Mon Sep 17 00:00:00 2001 From: Abdul Ahad Date: Thu, 14 Aug 2025 16:59:28 +0200 Subject: [PATCH 4/6] test(warnings): added expected warnings - added deprecated-warnings task --- .../package.json | 3 +- .../src/deprecated-warnings.json | 17 +++++++ tasks/generate-deprecated-warnings.js | 49 +++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 packages/zeebe-element-templates-json-schema/src/deprecated-warnings.json create mode 100644 tasks/generate-deprecated-warnings.js diff --git a/packages/zeebe-element-templates-json-schema/package.json b/packages/zeebe-element-templates-json-schema/package.json index 1c9fce5..fbc12cb 100644 --- a/packages/zeebe-element-templates-json-schema/package.json +++ b/packages/zeebe-element-templates-json-schema/package.json @@ -10,8 +10,9 @@ "test:integration": "mocha --reporter=spec --recursive test/integration", "dev": "npm run test -- --watch", "all": "run-s build test", - "build": "run-s build:error-messages build:schema", + "build": "run-s build:error-messages build:deprecated-warnings build:schema", "build:error-messages": "node ../../tasks/generate-error-messages.js --input=./src/error-messages.json --output=./resources/error-messages.json", + "build:deprecated-warnings": "node ../../tasks/generate-deprecated-warnings.js --input=./src/deprecated-warnings.json --output=./resources/deprecated-warnings.json", "build:schema": "node ../../tasks/generate-schema.js --input=./src/schema.json --output=./resources/schema.json", "prepare": "run-s build" }, diff --git a/packages/zeebe-element-templates-json-schema/src/deprecated-warnings.json b/packages/zeebe-element-templates-json-schema/src/deprecated-warnings.json new file mode 100644 index 0000000..8106864 --- /dev/null +++ b/packages/zeebe-element-templates-json-schema/src/deprecated-warnings.json @@ -0,0 +1,17 @@ +[ + { + "path": [ + "definitions", + "properties", + "allOf", + 1, + "items", + "allOf", + 23, + "then", + "anyOf", + 2 + ], + "warningMessage": "Hidden property must specify either \"value\" or \"generatedValue\"" + } +] \ No newline at end of file diff --git a/tasks/generate-deprecated-warnings.js b/tasks/generate-deprecated-warnings.js new file mode 100644 index 0000000..32eacf7 --- /dev/null +++ b/tasks/generate-deprecated-warnings.js @@ -0,0 +1,49 @@ +const readFile = require('fs').readFileSync, + writeFile = require('fs').writeFileSync, + mkdir = require('fs').mkdirSync; + +const pathJoin = require('path').join, + dirname = require('path').dirname; + +const mri = require('mri'); + +const argv = process.argv.slice(2); + +async function bundleWarnings(warningMessages, path) { + return writeWarnings(warningMessages, path); +} + + +function writeWarnings(warningMessages, path) { + const filePath = pathJoin(path); + + try { + mkdir(dirname(filePath)); + } catch { + + // directory may already exist + } + + writeFile(filePath, JSON.stringify(warningMessages, 0, 2)); + + return filePath; +} + + +const { + input, + output +} = mri(argv, { + alias: { + i: 'input', + o: 'output' + } +}); + +if (!input || !output) { + console.error('Arguments missing.'); + console.error('Example: node tasks/generate-error-messages.js --input=./src/error-messages.json --output=./resources/error-messages.json'); + process.exit(1); +} + +bundleWarnings(JSON.parse(readFile(input)), output); From ff0237f7a172fffa4a68b76c7d81d38ce3ed81db Mon Sep 17 00:00:00 2001 From: Abdul Ahad Date: Thu, 14 Aug 2025 17:21:38 +0200 Subject: [PATCH 5/6] chore: cleanup --- .../test/helpers/index.js | 99 +------------------ .../test/helpers/utils.js | 97 ++++++++++++++++++ 2 files changed, 101 insertions(+), 95 deletions(-) create mode 100644 packages/element-templates-json-schema-shared/test/helpers/utils.js diff --git a/packages/element-templates-json-schema-shared/test/helpers/index.js b/packages/element-templates-json-schema-shared/test/helpers/index.js index 996059a..910a80c 100644 --- a/packages/element-templates-json-schema-shared/test/helpers/index.js +++ b/packages/element-templates-json-schema-shared/test/helpers/index.js @@ -1,17 +1,10 @@ -const { - forEach, - set -} = require('min-dash'); - -const chai = require('chai'); - const { default: Ajv } = require('ajv'); const AjvErrors = require('ajv-errors'); +const { withErrorMessages, withDeprecationWarnings, getDeprecationWarnings } = require('./utils'); + module.exports = { - createValidator, - withErrorMessages, - withDeprecationWarnings + createValidator }; function createValidator(schema, errors, deprecations) { @@ -57,13 +50,6 @@ function createValidator(schema, errors, deprecations) { const validator = ajv.compile(withErrorMessages(withDeprecationWarnings(schema, deprecations), errors)); - - const getDeprecationWarnings = function(data) { - if (deprecationWarnings.length > 0 && data.id === deprecationWarnings[0].id) { - return deprecationWarnings.map(warning => warning.warningDescription); - } - }; - // wrapper function checks for warnings before each validation const wrappedValidator = function(data, ...args) { @@ -73,87 +59,10 @@ function createValidator(schema, errors, deprecations) { const result = validator.call(this, data, ...args); wrappedValidator.errors = validator.errors; - wrappedValidator.warnings = getDeprecationWarnings(data); + wrappedValidator.warnings = getDeprecationWarnings(deprecationWarnings, data); return result; }; return wrappedValidator; -} - -function withErrorMessages(schema, errors) { - - if (!errors || !errors.length) { - return schema; - } - - // clone a new copy - let newSchema = JSON.parse(JSON.stringify(schema)); - - // set keyword for given path - forEach(errors, function(error) { - newSchema = setErrorMessage(newSchema, error); - }); - - return newSchema; -} - -function setErrorMessage(schema, error) { - const { - path, - errorMessage - } = error; - - const errorMessagePath = [ - ...path, - 'errorMessage' - ]; - - return set(schema, errorMessagePath, errorMessage); -} - -function eqlErrors(chai, utils) { - - const Assertion = chai.Assertion; - - Assertion.addMethod('eqlErrors', function(expectedErrors, filter) { - - const actualErrors = this._obj; - - // formats the validation errors, so that they can be used directly in the fixture files. - this.eql(expectedErrors, - `Errors from validation do not match expected.\n\tValidation returned this error (you can use it in the fixture):\n\t${JSON.stringify(actualErrors, null, 2).replace(/"([^"]+)":/g, '$1:')}\n`); - }); -} - -chai.use(eqlErrors); - -function withDeprecationWarnings(schema, deprecations) { - if (!deprecations || !deprecations.length) { - return schema; - } - - // clone a new copy - let newSchema = JSON.parse(JSON.stringify(schema)); - - // set deprecation warnings for given paths - forEach(deprecations, function(deprecation) { - newSchema = setDeprecationWarning(newSchema, deprecation); - }); - - return newSchema; -} - -function setDeprecationWarning(schema, deprecation) { - const { - path, - warningMessage - } = deprecation; - - const deprecationPath = [ - ...path, - 'deprecatedWarning' - ]; - - return set(schema, deprecationPath, warningMessage); } \ No newline at end of file diff --git a/packages/element-templates-json-schema-shared/test/helpers/utils.js b/packages/element-templates-json-schema-shared/test/helpers/utils.js new file mode 100644 index 0000000..83704bc --- /dev/null +++ b/packages/element-templates-json-schema-shared/test/helpers/utils.js @@ -0,0 +1,97 @@ +const chai = require('chai'); + +const { + forEach, + set +} = require('min-dash'); + +function withErrorMessages(schema, errors) { + + if (!errors || !errors.length) { + return schema; + } + + // clone a new copy + let newSchema = JSON.parse(JSON.stringify(schema)); + + // set keyword for given path + forEach(errors, function(error) { + newSchema = setErrorMessage(newSchema, error); + }); + + return newSchema; +} + +function setErrorMessage(schema, error) { + const { + path, + errorMessage + } = error; + + const errorMessagePath = [ + ...path, + 'errorMessage' + ]; + + return set(schema, errorMessagePath, errorMessage); +} + + +function withDeprecationWarnings(schema, deprecations) { + if (!deprecations || !deprecations.length) { + return schema; + } + + // clone a new copy + let newSchema = JSON.parse(JSON.stringify(schema)); + + // set deprecation warnings for given paths + forEach(deprecations, function(deprecation) { + newSchema = setDeprecationWarning(newSchema, deprecation); + }); + + return newSchema; +} + +function setDeprecationWarning(schema, deprecation) { + const { + path, + warningMessage + } = deprecation; + + const deprecationPath = [ + ...path, + 'deprecatedWarning' + ]; + + return set(schema, deprecationPath, warningMessage); +} + +function getDeprecationWarnings(deprecationWarnings, data) { + if (deprecationWarnings.length > 0 && data.id === deprecationWarnings[0].id) { + return deprecationWarnings.map(warning => warning.warningDescription); + } +}; + +function eqlErrors(chai, utils) { + + const Assertion = chai.Assertion; + + Assertion.addMethod('eqlErrors', function(expectedErrors, filter) { + + const actualErrors = this._obj; + + // formats the validation errors, so that they can be used directly in the fixture files. + this.eql(expectedErrors, + `Errors from validation do not match expected.\n\tValidation returned this error (you can use it in the fixture):\n\t${JSON.stringify(actualErrors, null, 2).replace(/"([^"]+)":/g, '$1:')}\n`); + }); +} + +chai.use(eqlErrors); + +module.exports = { + withErrorMessages, + withDeprecationWarnings, + getDeprecationWarnings +}; + From bbf8065c7d046cb335a313488dda75516a7f86c9 Mon Sep 17 00:00:00 2001 From: Abdul Ahad Date: Tue, 19 Aug 2025 17:10:17 +0200 Subject: [PATCH 6/6] chore: rebase adjusted --- .../src/deprecated-warnings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/zeebe-element-templates-json-schema/src/deprecated-warnings.json b/packages/zeebe-element-templates-json-schema/src/deprecated-warnings.json index 8106864..4743ec0 100644 --- a/packages/zeebe-element-templates-json-schema/src/deprecated-warnings.json +++ b/packages/zeebe-element-templates-json-schema/src/deprecated-warnings.json @@ -7,7 +7,7 @@ 1, "items", "allOf", - 23, + 25, "then", "anyOf", 2