diff --git a/packages/input_schema/src/input_schema.ts b/packages/input_schema/src/input_schema.ts index 428fdabcb..a72eaea93 100644 --- a/packages/input_schema/src/input_schema.ts +++ b/packages/input_schema/src/input_schema.ts @@ -5,12 +5,10 @@ import { inputSchema as schema } from '@apify/json_schemas'; import { m } from './intl'; import type { - ArrayFieldDefinition, CommonResourceFieldDefinition, FieldDefinition, InputSchema, InputSchemaBaseChecked, - ObjectFieldDefinition, StringFieldDefinition, } from './types'; import { ensureAjvSupportsDraft2019, validateRegexpPattern } from './utilities'; @@ -313,16 +311,24 @@ function validateField( fieldKey: string, isSubField = false, ): asserts fieldSchema is FieldDefinition { + // The deprecated patternKey/patternValue properties are no longer supported. They would fail + // the schema definition validation below anyway, but this check gives a clear error message + // with a link to migration instructions. + // TODO: Remove this check (and the deprecatedProperty message) once schemas have had enough + // time to migrate and the generic "property is not allowed" error is a good enough response. + for (const property of ['patternKey', 'patternValue']) { + if (property in fieldSchema) { + const message = m('inputSchema.validation.deprecatedProperty', { fieldKey, property }); + throw new Error(`Input schema is not valid (${message})`); + } + } + // Validate against schema definition first. validateFieldAgainstSchemaDefinition(validator, fieldSchema, fieldKey, isSubField); - // Validate regex patterns if defined. + // Validate regex pattern if defined. const { pattern } = fieldSchema as Partial; - const { patternKey, patternValue } = fieldSchema as Partial; - if (pattern) validateRegexpPattern(pattern, `${fieldKey}.pattern`); - if (patternKey) validateRegexpPattern(patternKey, `${fieldKey}.patternKey`); - if (patternValue) validateRegexpPattern(patternValue, `${fieldKey}.patternValue`); } /** diff --git a/packages/input_schema/src/intl.ts b/packages/input_schema/src/intl.ts index b01682f43..0c090c368 100644 --- a/packages/input_schema/src/intl.ts +++ b/packages/input_schema/src/intl.ts @@ -31,6 +31,9 @@ const intlStrings = { 'Field {rootName}.{fieldKey}.apifyProxyGroups must be an array of strings.', 'inputSchema.validation.secretFieldSchemaChanged': 'The field schema.properties.{fieldKey} is a secret field, but its schema has changed. Please update the value in the input editor.', + 'inputSchema.validation.deprecatedProperty': + 'Property schema.properties.{fieldKey}.{property} is deprecated and no longer supported. Please remove it from the input schema. ' + + 'See https://docs.apify.com/platform/actors/development/actor-definition/input-schema/specification/v1#deprecation-of-patternkey-and-patternvalue for migration instructions.', 'inputSchema.validation.regexpNotValid': 'The regular expression "{pattern}" in field schema.properties.{fieldKey} must be valid.', 'inputSchema.validation.regexpNotSafe': diff --git a/packages/input_schema/src/types.ts b/packages/input_schema/src/types.ts index 321d8167e..c17d11399 100644 --- a/packages/input_schema/src/types.ts +++ b/packages/input_schema/src/types.ts @@ -58,7 +58,9 @@ export type NumberFieldDefinition = CommonFieldDefinition & { export type ObjectFieldDefinition = CommonFieldDefinition & { type: 'object'; editor: 'json' | 'proxy' | 'schemaBased' | 'hidden'; + /** @deprecated Rejected in new input schemas, but may still be present in stored schemas of existing builds. TODO: Remove once those no longer need to be supported. */ patternKey?: string; + /** @deprecated Rejected in new input schemas, but may still be present in stored schemas of existing builds. TODO: Remove once those no longer need to be supported. */ patternValue?: string; maxProperties?: number; minProperties?: number; @@ -81,7 +83,9 @@ export type ArrayFieldDefinition = CommonFieldDefinition & { | 'hidden'; placeholderKey?: string; placeholderValue?: string; + /** @deprecated Rejected in new input schemas, but may still be present in stored schemas of existing builds. TODO: Remove once those no longer need to be supported. */ patternKey?: string; + /** @deprecated Rejected in new input schemas, but may still be present in stored schemas of existing builds. TODO: Remove once those no longer need to be supported. */ patternValue?: string; maxItems?: number; minItems?: number; diff --git a/packages/input_schema/src/utilities.ts b/packages/input_schema/src/utilities.ts index 391a65c0b..822352095 100644 --- a/packages/input_schema/src/utilities.ts +++ b/packages/input_schema/src/utilities.ts @@ -122,7 +122,11 @@ function validateProxyField( /** * Uses AJV validator to validate input with input schema and then - * does custom validation for our own properties (nullable, patternKey, patternValue) + * does custom validation for our own properties (nullable, patternKey, patternValue). + * + * Note: patternKey/patternValue are deprecated and rejected by `validateInputSchema`, + * but they are still validated here so that inputs of existing builds keep working. + * TODO: Remove their validation once stored schemas of existing builds no longer need to be supported. * @param validator Initialized AJV validator * @param inputSchema Valid input schema in object * @param input Input object to be validated diff --git a/packages/json_schemas/output/actor.ide.json b/packages/json_schemas/output/actor.ide.json index e15c75740..b1cfa1b7d 100644 --- a/packages/json_schemas/output/actor.ide.json +++ b/packages/json_schemas/output/actor.ide.json @@ -984,12 +984,6 @@ "placeholderValue": { "type": "string" }, - "patternKey": { - "type": "string" - }, - "patternValue": { - "type": "string" - }, "isSecret": { "enum": [ false @@ -1355,12 +1349,6 @@ "x-intellij-html-description": "

Sample value of this field for the Actor to be displayed when Actor is published in Apify Store.

", "markdownDescription": "Sample value of this field for the Actor to be displayed when Actor is published in Apify Store." }, - "patternKey": { - "type": "string" - }, - "patternValue": { - "type": "string" - }, "nullable": { "type": "boolean", "description": "Specifies whether null is an allowed value.", @@ -1514,12 +1502,6 @@ "x-intellij-html-description": "

Sample value of this field for the Actor to be displayed when Actor is published in Apify Store.

", "markdownDescription": "Sample value of this field for the Actor to be displayed when Actor is published in Apify Store." }, - "patternKey": { - "type": "string" - }, - "patternValue": { - "type": "string" - }, "nullable": { "type": "boolean", "description": "Specifies whether null is an allowed value.", @@ -2904,12 +2886,6 @@ "placeholderValue": { "type": "string" }, - "patternKey": { - "type": "string" - }, - "patternValue": { - "type": "string" - }, "errorMessage": { "$ref": "#/definitions/input-ide-json-2212166aa408babb6120b85f1282521a/definitions/errorMessage" } @@ -3421,12 +3397,6 @@ "x-intellij-html-description": "

Sample value of this field for the Actor to be displayed when Actor is published in Apify Store.

", "markdownDescription": "Sample value of this field for the Actor to be displayed when Actor is published in Apify Store." }, - "patternKey": { - "type": "string" - }, - "patternValue": { - "type": "string" - }, "nullable": { "type": "boolean", "description": "Specifies whether null is an allowed value.", @@ -4995,12 +4965,6 @@ }, "maxProperties": { "type": "string" - }, - "patternKey": { - "type": "string" - }, - "patternValue": { - "type": "string" } }, "additionalProperties": false diff --git a/packages/json_schemas/output/input.ide.json b/packages/json_schemas/output/input.ide.json index a892601ff..585bfbb26 100644 --- a/packages/json_schemas/output/input.ide.json +++ b/packages/json_schemas/output/input.ide.json @@ -684,12 +684,6 @@ "placeholderValue": { "type": "string" }, - "patternKey": { - "type": "string" - }, - "patternValue": { - "type": "string" - }, "isSecret": { "enum": [ false @@ -1055,12 +1049,6 @@ "x-intellij-html-description": "

Sample value of this field for the Actor to be displayed when Actor is published in Apify Store.

", "markdownDescription": "Sample value of this field for the Actor to be displayed when Actor is published in Apify Store." }, - "patternKey": { - "type": "string" - }, - "patternValue": { - "type": "string" - }, "nullable": { "type": "boolean", "description": "Specifies whether null is an allowed value.", @@ -1214,12 +1202,6 @@ "x-intellij-html-description": "

Sample value of this field for the Actor to be displayed when Actor is published in Apify Store.

", "markdownDescription": "Sample value of this field for the Actor to be displayed when Actor is published in Apify Store." }, - "patternKey": { - "type": "string" - }, - "patternValue": { - "type": "string" - }, "nullable": { "type": "boolean", "description": "Specifies whether null is an allowed value.", @@ -2604,12 +2586,6 @@ "placeholderValue": { "type": "string" }, - "patternKey": { - "type": "string" - }, - "patternValue": { - "type": "string" - }, "errorMessage": { "$ref": "#/definitions/errorMessage" } @@ -3121,12 +3097,6 @@ "x-intellij-html-description": "

Sample value of this field for the Actor to be displayed when Actor is published in Apify Store.

", "markdownDescription": "Sample value of this field for the Actor to be displayed when Actor is published in Apify Store." }, - "patternKey": { - "type": "string" - }, - "patternValue": { - "type": "string" - }, "nullable": { "type": "boolean", "description": "Specifies whether null is an allowed value.", @@ -4695,12 +4665,6 @@ }, "maxProperties": { "type": "string" - }, - "patternKey": { - "type": "string" - }, - "patternValue": { - "type": "string" } }, "additionalProperties": false diff --git a/packages/json_schemas/output/input.json b/packages/json_schemas/output/input.json index 235408852..787b554c0 100644 --- a/packages/json_schemas/output/input.json +++ b/packages/json_schemas/output/input.json @@ -685,12 +685,6 @@ "placeholderValue": { "type": "string" }, - "patternKey": { - "type": "string" - }, - "patternValue": { - "type": "string" - }, "isSecret": { "enum": [ false @@ -1056,12 +1050,6 @@ "x-intellij-html-description": "

Sample value of this field for the Actor to be displayed when Actor is published in Apify Store.

", "markdownDescription": "Sample value of this field for the Actor to be displayed when Actor is published in Apify Store." }, - "patternKey": { - "type": "string" - }, - "patternValue": { - "type": "string" - }, "nullable": { "type": "boolean", "description": "Specifies whether null is an allowed value.", @@ -1215,12 +1203,6 @@ "x-intellij-html-description": "

Sample value of this field for the Actor to be displayed when Actor is published in Apify Store.

", "markdownDescription": "Sample value of this field for the Actor to be displayed when Actor is published in Apify Store." }, - "patternKey": { - "type": "string" - }, - "patternValue": { - "type": "string" - }, "nullable": { "type": "boolean", "description": "Specifies whether null is an allowed value.", @@ -2605,12 +2587,6 @@ "placeholderValue": { "type": "string" }, - "patternKey": { - "type": "string" - }, - "patternValue": { - "type": "string" - }, "errorMessage": { "$ref": "#/definitions/errorMessage" } @@ -3122,12 +3098,6 @@ "x-intellij-html-description": "

Sample value of this field for the Actor to be displayed when Actor is published in Apify Store.

", "markdownDescription": "Sample value of this field for the Actor to be displayed when Actor is published in Apify Store." }, - "patternKey": { - "type": "string" - }, - "patternValue": { - "type": "string" - }, "nullable": { "type": "boolean", "description": "Specifies whether null is an allowed value.", @@ -4696,12 +4666,6 @@ }, "maxProperties": { "type": "string" - }, - "patternKey": { - "type": "string" - }, - "patternValue": { - "type": "string" } }, "additionalProperties": false diff --git a/packages/json_schemas/rules/add-description/input.description-rules.xml b/packages/json_schemas/rules/add-description/input.description-rules.xml index cb8308421..caf8d8633 100644 --- a/packages/json_schemas/rules/add-description/input.description-rules.xml +++ b/packages/json_schemas/rules/add-description/input.description-rules.xml @@ -366,14 +366,6 @@ Placeholder displayed in value field when no value is provided. Works only with `keyValue` and `stringList` editors. - - Regular expression that will be used to validate the keys of items in the array. Works only with `keyValue` - editor. - - - Regular expression that will be used to validate the values of items in the array. Works only with `keyValue` - and `stringList` editors. - Allowed type for the input value. Cannot be mixed. @@ -392,12 +384,6 @@ Sample value of this field for the Actor to be displayed when Actor is published in Apify Store. - - Regular expression that will be used to validate the keys of the object. - - - Regular expression that will be used to validate the values of object. - Specifies whether null is an allowed value. diff --git a/packages/json_schemas/schemas/input.schema.json b/packages/json_schemas/schemas/input.schema.json index 49b1326d1..e0741e6fa 100644 --- a/packages/json_schemas/schemas/input.schema.json +++ b/packages/json_schemas/schemas/input.schema.json @@ -227,8 +227,6 @@ "sectionDescription": { "type": "string" }, "placeholderKey": { "type": "string" }, "placeholderValue": { "type": "string" }, - "patternKey": { "type": "string" }, - "patternValue": { "type": "string" }, "isSecret": { "enum": [false] }, "errorMessage": { "$ref": "#/definitions/errorMessage" } }, @@ -355,8 +353,6 @@ "description": { "type": "string" }, "prefill": { "type": "object" }, "example": { "type": "object" }, - "patternKey": { "type": "string" }, - "patternValue": { "type": "string" }, "nullable": { "type": "boolean" }, "minProperties": { "type": "integer" }, "maxProperties": { "type": "integer" }, @@ -410,8 +406,6 @@ "description": { "type": "string" }, "prefill": { "type": "object" }, "example": { "type": "object" }, - "patternKey": { "type": "string" }, - "patternValue": { "type": "string" }, "nullable": { "type": "boolean" }, "minProperties": { "type": "integer" }, "maxProperties": { "type": "integer" }, @@ -864,8 +858,6 @@ "uniqueItems": { "type": "boolean" }, "placeholderKey": { "type": "string" }, "placeholderValue": { "type": "string" }, - "patternKey": { "type": "string" }, - "patternValue": { "type": "string" }, "errorMessage": { "$ref": "#/definitions/errorMessage" } }, "required": ["type", "title", "description"], @@ -1041,8 +1033,6 @@ "description": { "type": "string" }, "prefill": { "type": "object" }, "example": { "type": "object" }, - "patternKey": { "type": "string" }, - "patternValue": { "type": "string" }, "nullable": { "type": "boolean" }, "minProperties": { "type": "integer" }, "maxProperties": { "type": "integer" }, @@ -1725,9 +1715,7 @@ "maxItems": { "type": "string" }, "uniqueItems": { "type": "string" }, "minProperties": { "type": "string" }, - "maxProperties": { "type": "string" }, - "patternKey": { "type": "string" }, - "patternValue": { "type": "string" } + "maxProperties": { "type": "string" } }, "additionalProperties": false } diff --git a/test/input_schema.test.ts b/test/input_schema.test.ts index 20ecd360c..f79adcd8f 100644 --- a/test/input_schema.test.ts +++ b/test/input_schema.test.ts @@ -1677,22 +1677,6 @@ describe('input_schema.json', () => { editor: 'textfield', pattern: '^[A-Z]{3}$', }, - objectField: { - title: 'Object field', - type: 'object', - description: 'Some description ...', - editor: 'json', - patternKey: '^[a-z]+$', - patternValue: '^[0-9]+$', - }, - arrayField: { - title: 'Array field', - type: 'array', - description: 'Some description ...', - editor: 'json', - patternKey: '^[a-z]+$', - patternValue: '^[0-9]+$', - }, }, }; @@ -1719,8 +1703,15 @@ describe('input_schema.json', () => { 'Input schema is not valid (The regular expression "[A-Z{3}" in field schema.properties.myField.pattern must be valid.)', ); }); + }); - it('should throw error on invalid patternKey regexp', () => { + describe('deprecated patternKey/patternValue', () => { + const deprecationMessage = (fieldKey: string, property: string) => + `Input schema is not valid (Property schema.properties.${fieldKey}.${property} is deprecated and no longer supported. ` + + 'Please remove it from the input schema. ' + + 'See https://docs.apify.com/platform/actors/development/actor-definition/input-schema/specification/v1#deprecation-of-patternkey-and-patternvalue for migration instructions.)'; + + it('should throw error on patternKey in an object field', () => { const schema = { title: 'Test input schema', type: 'object', @@ -1731,18 +1722,38 @@ describe('input_schema.json', () => { type: 'object', description: 'Some description ...', editor: 'json', - patternKey: '[a-z+$', // invalid regexp + patternKey: '^[a-z]+$', + }, + }, + }; + + expect(() => validateInputSchema(validator, schema)).toThrow( + deprecationMessage('objectField', 'patternKey'), + ); + }); + + it('should throw error on patternValue in an array field', () => { + const schema = { + title: 'Test input schema', + type: 'object', + schemaVersion: 1, + properties: { + arrayField: { + title: 'Array field', + type: 'array', + description: 'Some description ...', + editor: 'stringList', patternValue: '^[0-9]+$', }, }, }; expect(() => validateInputSchema(validator, schema)).toThrow( - 'Input schema is not valid (The regular expression "[a-z+$" in field schema.properties.objectField.patternKey must be valid.)', + deprecationMessage('arrayField', 'patternValue'), ); }); - it('should throw error on invalid patternValue regexp', () => { + it('should throw error on patternKey in a sub-field', () => { const schema = { title: 'Test input schema', type: 'object', @@ -1752,15 +1763,22 @@ describe('input_schema.json', () => { title: 'Object field', type: 'object', description: 'Some description ...', - editor: 'json', - patternKey: '^[a-z]+$', - patternValue: '^[0-9+$', // invalid regexp + editor: 'schemaBased', + properties: { + subObject: { + title: 'Sub-object', + type: 'object', + description: 'Some description ...', + editor: 'json', + patternValue: '^[a-z]+$', + }, + }, }, }, }; expect(() => validateInputSchema(validator, schema)).toThrow( - 'Input schema is not valid (The regular expression "^[0-9+$" in field schema.properties.objectField.patternValue must be valid.)', + deprecationMessage('objectField.subObject', 'patternValue'), ); }); }); diff --git a/test/input_schema_definition.test.ts b/test/input_schema_definition.test.ts index f2b21c9da..493375196 100644 --- a/test/input_schema_definition.test.ts +++ b/test/input_schema_definition.test.ts @@ -388,7 +388,7 @@ describe('input_schema.json', () => { ['minProperties', 'maxProperties'].forEach((intField) => { expect(isSchemaValid({ [intField]: 10 }, true)).toBe(false); }); - ['patternKey', 'patternValue', 'prefill', 'example'].forEach((stringField) => { + ['prefill', 'example'].forEach((stringField) => { expect(isSchemaValid({ [stringField]: 'bla' }, true)).toBe(false); }); });