@apify/input_schema 3.29.1 → 4.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/types.d.ts ADDED
@@ -0,0 +1,128 @@
1
+ type CommonFieldDefinition<T> = {
2
+ title: string;
3
+ description: string;
4
+ default?: T;
5
+ prefill?: T;
6
+ example?: T;
7
+ nullable?: boolean;
8
+ sectionCaption?: string;
9
+ sectionDescription?: string;
10
+ };
11
+ export type StringFieldDefinition = CommonFieldDefinition<string> & {
12
+ type: 'string';
13
+ editor: 'textfield' | 'textarea' | 'javascript' | 'python' | 'select' | 'datepicker' | 'hidden' | 'json' | 'fileupload';
14
+ pattern?: string;
15
+ minLength?: number;
16
+ maxLength?: number;
17
+ enum?: readonly string[];
18
+ enumTitles?: readonly string[];
19
+ enumSuggestedValues?: readonly string[];
20
+ isSecret?: boolean;
21
+ dateType?: 'absolute' | 'relative' | 'absoluteOrRelative';
22
+ };
23
+ export type BooleanFieldDefinition = CommonFieldDefinition<boolean> & {
24
+ type: 'boolean';
25
+ editor?: 'checkbox' | 'hidden';
26
+ groupCaption?: string;
27
+ groupDescription?: string;
28
+ };
29
+ export type IntegerFieldDefinition = CommonFieldDefinition<number> & {
30
+ type: 'integer';
31
+ editor?: 'number' | 'hidden';
32
+ maximum?: number;
33
+ minimum?: number;
34
+ unit?: string;
35
+ };
36
+ export type NumberFieldDefinition = CommonFieldDefinition<number> & {
37
+ type: 'number';
38
+ editor?: 'number' | 'hidden';
39
+ maximum?: number;
40
+ minimum?: number;
41
+ unit?: string;
42
+ };
43
+ export type ObjectFieldDefinition = CommonFieldDefinition<object> & {
44
+ type: 'object';
45
+ editor: 'json' | 'proxy' | 'schemaBased' | 'hidden';
46
+ /** @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. */
47
+ patternKey?: string;
48
+ /** @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. */
49
+ patternValue?: string;
50
+ maxProperties?: number;
51
+ minProperties?: number;
52
+ properties?: Record<string, unknown>;
53
+ required?: string[];
54
+ additionalProperties?: boolean;
55
+ };
56
+ export type ArrayFieldDefinition = CommonFieldDefinition<unknown[]> & {
57
+ type: 'array';
58
+ editor: 'json' | 'requestListSources' | 'pseudoUrls' | 'globs' | 'keyValue' | 'stringList' | 'select' | 'schemaBased' | 'hidden';
59
+ placeholderKey?: string;
60
+ placeholderValue?: string;
61
+ /** @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. */
62
+ patternKey?: string;
63
+ /** @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. */
64
+ patternValue?: string;
65
+ maxItems?: number;
66
+ minItems?: number;
67
+ uniqueItems?: boolean;
68
+ items?: unknown;
69
+ };
70
+ export type CommonResourceFieldDefinition<T> = CommonFieldDefinition<T> & {
71
+ editor?: 'resourcePicker' | 'hidden';
72
+ resourceType: 'dataset' | 'keyValueStore' | 'requestQueue' | 'mcpConnector';
73
+ };
74
+ type StorageResourceFieldDefinition<T> = CommonResourceFieldDefinition<T> & {
75
+ resourceType: 'dataset' | 'keyValueStore' | 'requestQueue';
76
+ resourcePermissions?: ('READ' | 'WRITE')[];
77
+ };
78
+ type McpServerTools = {
79
+ required?: string[];
80
+ readOnly?: boolean;
81
+ destructive?: boolean;
82
+ idempotent?: boolean;
83
+ openWorld?: boolean;
84
+ };
85
+ type McpServer = {
86
+ url: string;
87
+ tools?: McpServerTools;
88
+ };
89
+ type McpConnectorResourceFieldDefinition<T> = CommonResourceFieldDefinition<T> & {
90
+ resourceType: 'mcpConnector';
91
+ mcpServers: McpServer[];
92
+ };
93
+ type AnyResourceFieldDefinition<T> = StorageResourceFieldDefinition<T> | McpConnectorResourceFieldDefinition<T>;
94
+ export type ResourceFieldDefinition = AnyResourceFieldDefinition<string> & {
95
+ type: 'string';
96
+ editor?: CommonResourceFieldDefinition<string>['editor'] | 'textfield';
97
+ };
98
+ export type ResourceArrayFieldDefinition = AnyResourceFieldDefinition<string[]> & {
99
+ type: 'array';
100
+ maxItems?: number;
101
+ minItems?: number;
102
+ uniqueItems?: boolean;
103
+ };
104
+ type AllTypes = StringFieldDefinition['type'] | BooleanFieldDefinition['type'] | IntegerFieldDefinition['type'] | NumberFieldDefinition['type'] | ObjectFieldDefinition['type'] | ArrayFieldDefinition['type'];
105
+ export type MixedFieldDefinition = CommonFieldDefinition<never> & {
106
+ type: readonly AllTypes[];
107
+ editor: 'json';
108
+ };
109
+ export type FieldDefinition = StringFieldDefinition | BooleanFieldDefinition | IntegerFieldDefinition | NumberFieldDefinition | ObjectFieldDefinition | ArrayFieldDefinition | MixedFieldDefinition | ResourceFieldDefinition | ResourceArrayFieldDefinition;
110
+ /**
111
+ * Type with checked base, but not properties
112
+ */
113
+ export type InputSchemaBaseChecked = Omit<InputSchema, 'properties'> & {
114
+ properties: Record<string, Record<string, unknown>>;
115
+ };
116
+ /**
117
+ * Type with checked base & properties
118
+ */
119
+ export type InputSchema = {
120
+ type: 'object';
121
+ title: string;
122
+ description?: string;
123
+ schemaVersion: number;
124
+ properties: Record<string, FieldDefinition>;
125
+ required?: readonly string[];
126
+ $schema?: unknown;
127
+ };
128
+ export {};
package/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
package/types.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["type CommonFieldDefinition<T> = {\n title: string;\n description: string;\n default?: T;\n prefill?: T;\n example?: T;\n nullable?: boolean;\n sectionCaption?: string;\n sectionDescription?: string;\n};\n\nexport type StringFieldDefinition = CommonFieldDefinition<string> & {\n type: 'string';\n editor:\n | 'textfield'\n | 'textarea'\n | 'javascript'\n | 'python'\n | 'select'\n | 'datepicker'\n | 'hidden'\n | 'json'\n | 'fileupload';\n pattern?: string;\n minLength?: number;\n maxLength?: number;\n enum?: readonly string[]; // required if editor is 'select'\n enumTitles?: readonly string[];\n enumSuggestedValues?: readonly string[];\n isSecret?: boolean;\n // Used for 'datepicker' editor, absolute is considered as default value\n dateType?: 'absolute' | 'relative' | 'absoluteOrRelative';\n};\n\nexport type BooleanFieldDefinition = CommonFieldDefinition<boolean> & {\n type: 'boolean';\n editor?: 'checkbox' | 'hidden';\n groupCaption?: string;\n groupDescription?: string;\n};\n\nexport type IntegerFieldDefinition = CommonFieldDefinition<number> & {\n type: 'integer';\n editor?: 'number' | 'hidden';\n maximum?: number;\n minimum?: number;\n unit?: string;\n};\n\nexport type NumberFieldDefinition = CommonFieldDefinition<number> & {\n type: 'number';\n editor?: 'number' | 'hidden';\n maximum?: number;\n minimum?: number;\n unit?: string;\n};\n\nexport type ObjectFieldDefinition = CommonFieldDefinition<object> & {\n type: 'object';\n editor: 'json' | 'proxy' | 'schemaBased' | 'hidden';\n /** @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. */\n patternKey?: string;\n /** @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. */\n patternValue?: string;\n maxProperties?: number;\n minProperties?: number;\n properties?: Record<string, unknown>;\n required?: string[];\n additionalProperties?: boolean;\n};\n\nexport type ArrayFieldDefinition = CommonFieldDefinition<unknown[]> & {\n type: 'array';\n editor:\n | 'json'\n | 'requestListSources'\n | 'pseudoUrls'\n | 'globs'\n | 'keyValue'\n | 'stringList'\n | 'select'\n | 'schemaBased'\n | 'hidden';\n placeholderKey?: string;\n placeholderValue?: string;\n /** @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. */\n patternKey?: string;\n /** @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. */\n patternValue?: string;\n maxItems?: number;\n minItems?: number;\n uniqueItems?: boolean;\n items?: unknown;\n};\n\nexport type CommonResourceFieldDefinition<T> = CommonFieldDefinition<T> & {\n editor?: 'resourcePicker' | 'hidden';\n resourceType: 'dataset' | 'keyValueStore' | 'requestQueue' | 'mcpConnector';\n};\n\ntype StorageResourceFieldDefinition<T> = CommonResourceFieldDefinition<T> & {\n resourceType: 'dataset' | 'keyValueStore' | 'requestQueue';\n resourcePermissions?: ('READ' | 'WRITE')[];\n};\n\ntype McpServerTools = {\n required?: string[];\n readOnly?: boolean;\n destructive?: boolean;\n idempotent?: boolean;\n openWorld?: boolean;\n};\n\ntype McpServer = {\n url: string;\n tools?: McpServerTools;\n};\n\ntype McpConnectorResourceFieldDefinition<T> = CommonResourceFieldDefinition<T> & {\n resourceType: 'mcpConnector';\n mcpServers: McpServer[];\n};\n\ntype AnyResourceFieldDefinition<T> = StorageResourceFieldDefinition<T> | McpConnectorResourceFieldDefinition<T>;\n\nexport type ResourceFieldDefinition = AnyResourceFieldDefinition<string> & {\n type: 'string';\n // Singular resource field also supports 'textfield' editor, unlike the array variant.\n editor?: CommonResourceFieldDefinition<string>['editor'] | 'textfield';\n};\n\nexport type ResourceArrayFieldDefinition = AnyResourceFieldDefinition<string[]> & {\n type: 'array';\n maxItems?: number;\n minItems?: number;\n uniqueItems?: boolean;\n};\n\ntype AllTypes =\n | StringFieldDefinition['type']\n | BooleanFieldDefinition['type']\n | IntegerFieldDefinition['type']\n | NumberFieldDefinition['type']\n | ObjectFieldDefinition['type']\n | ArrayFieldDefinition['type'];\n\nexport type MixedFieldDefinition = CommonFieldDefinition<never> & {\n type: readonly AllTypes[];\n editor: 'json';\n};\n\nexport type FieldDefinition =\n | StringFieldDefinition\n | BooleanFieldDefinition\n | IntegerFieldDefinition\n | NumberFieldDefinition\n | ObjectFieldDefinition\n | ArrayFieldDefinition\n | MixedFieldDefinition\n | ResourceFieldDefinition\n | ResourceArrayFieldDefinition;\n\n/**\n * Type with checked base, but not properties\n */\nexport type InputSchemaBaseChecked = Omit<InputSchema, 'properties'> & {\n properties: Record<string, Record<string, unknown>>;\n};\n\n/**\n * Type with checked base & properties\n */\nexport type InputSchema = {\n type: 'object';\n title: string;\n description?: string;\n schemaVersion: number;\n properties: Record<string, FieldDefinition>;\n required?: readonly string[];\n\n $schema?: unknown;\n};\n"]}
package/utilities.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ import type { ValidateFunction } from 'ajv';
2
+ import type { Ajv2019 as Ajv } from 'ajv/dist/2019.js';
3
+ /**
4
+ * Uses AJV validator to validate input with input schema and then
5
+ * does custom validation for our own properties (nullable, patternKey, patternValue).
6
+ *
7
+ * Note: patternKey/patternValue are deprecated and rejected by `validateInputSchema`,
8
+ * but they are still validated here so that inputs of existing builds keep working.
9
+ * TODO: Remove their validation once stored schemas of existing builds no longer need to be supported.
10
+ * @param validator Initialized AJV validator
11
+ * @param inputSchema Valid input schema in object
12
+ * @param input Input object to be validated
13
+ * @param options (Optional) Additional validation configuration for certain fields
14
+ */
15
+ export declare function validateInputUsingValidator(validator: ValidateFunction, inputSchema: Record<string, any>, input: Record<string, unknown>, options?: Record<string, any>): {
16
+ fieldKey: string;
17
+ message: string;
18
+ }[];
19
+ /**
20
+ * This functions parses all given JSON and then takes each of the jsFields.
21
+ * Then if the field:
22
+ * - is valid JS single function it replaces its single line string with a function delacation.
23
+ * - is valid multiline JS code then replaces its single line string with `multiline` string
24
+ * Then stringifies the code with given number of jsonSpacing spaces and finally prefixes whole
25
+ * stringified JSON except the first line with globalSpacing spaces.
26
+ */
27
+ export declare function makeInputJsFieldsReadable(json: string, jsFields: string[], jsonSpacing?: number, globalSpacing?: number): string;
28
+ export declare function ensureAjvSupportsDraft2019(ajvInstance: Ajv): void;
29
+ /**
30
+ * Validates that the provided pattern is a valid and safe regular expression.
31
+ * @param pattern The regular expression pattern to validate.
32
+ * @param fieldKey The field key where the pattern is used (for error messages).
33
+ */
34
+ export declare function validateRegexpPattern(pattern: string, fieldKey: string): void;
package/utilities.js ADDED
@@ -0,0 +1,366 @@
1
+ import { parse } from 'acorn-loose';
2
+ import { countries } from 'countries-list';
3
+ import { PROXY_URL_REGEX, URL_REGEX } from '@apify/consts';
4
+ import { isEncryptedValueForFieldSchema, isEncryptedValueForFieldType } from '@apify/input_secrets';
5
+ import { getCustomErrorMessage, parseAjvError } from './input_schema.js';
6
+ import { m } from './intl.js';
7
+ /**
8
+ * Validates input field configured with proxy editor
9
+ * @param fieldKey Proxy field value
10
+ * @param value Proxy field value
11
+ * @param [isRequired] Whether the field is required or not
12
+ * @param [options] Information about proxy groups availability
13
+ * @param [options.hasAutoProxyGroups] Informs validation whether user has atleast one proxy group available in auto mode
14
+ * @param [options.availableProxyGroups] List of available proxy groups
15
+ * @param [options.disabledProxyGroups] Object with groupId as key and error message as value (mostly for residential/SERP)
16
+ */
17
+ function validateProxyField(fieldKey, value, isRequired = false, options = null) {
18
+ const fieldErrors = [];
19
+ if (isRequired) {
20
+ // Nullable error is already handled by AJV
21
+ if (value === null)
22
+ return fieldErrors;
23
+ if (!value) {
24
+ const message = m('inputSchema.validation.required', { rootName: 'input', fieldKey });
25
+ fieldErrors.push(message);
26
+ return fieldErrors;
27
+ }
28
+ const { useApifyProxy, proxyUrls } = value;
29
+ if (!useApifyProxy && (!Array.isArray(proxyUrls) || proxyUrls.length === 0)) {
30
+ fieldErrors.push(m('inputSchema.validation.proxyRequired', { rootName: 'input', fieldKey }));
31
+ return fieldErrors;
32
+ }
33
+ }
34
+ // Input is not required, so missing value is valid
35
+ if (!value)
36
+ return fieldErrors;
37
+ const { useApifyProxy, proxyUrls, apifyProxyGroups, apifyProxyCountry } = value;
38
+ if (!useApifyProxy && Array.isArray(proxyUrls)) {
39
+ let invalidUrl = false;
40
+ proxyUrls.forEach((url) => {
41
+ if (!PROXY_URL_REGEX.test(url.trim()))
42
+ invalidUrl = url.trim();
43
+ });
44
+ if (invalidUrl) {
45
+ fieldErrors.push(m('inputSchema.validation.customProxyInvalid', { invalidUrl }));
46
+ }
47
+ }
48
+ // Apify proxy country can be set only when using Apify proxy
49
+ if (!useApifyProxy && apifyProxyCountry) {
50
+ fieldErrors.push(m('inputSchema.validation.apifyProxyCountryWithoutApifyProxyForbidden'));
51
+ }
52
+ // If Apify proxy is not used skip additional checks
53
+ if (!useApifyProxy)
54
+ return fieldErrors;
55
+ // If Apify proxy is used, check if there is a selected country and if so, check that it's valid (empty or a valid country code)
56
+ if (apifyProxyCountry && !countries[apifyProxyCountry]) {
57
+ fieldErrors.push(m('inputSchema.validation.apifyProxyCountryInvalid', { invalidCountry: apifyProxyCountry }));
58
+ }
59
+ // If options are not provided skip additional checks
60
+ if (!options)
61
+ return fieldErrors;
62
+ // if apifyProxyGroups exists it must be an array of strings
63
+ const isStringsArray = (array) => array.every((item) => typeof item === 'string');
64
+ if (apifyProxyGroups && !(Array.isArray(apifyProxyGroups) && isStringsArray(apifyProxyGroups))) {
65
+ fieldErrors.push(m('inputSchema.validation.proxyGroupMustBeArrayOfStrings', { rootName: 'input', fieldKey }));
66
+ return fieldErrors;
67
+ }
68
+ const selectedProxyGroups = apifyProxyGroups || [];
69
+ // Auto mode, check that user has access to alteast one proxy group usable in this mode
70
+ if (!selectedProxyGroups.length && !options.hasAutoProxyGroups) {
71
+ fieldErrors.push(m('inputSchema.validation.noAvailableAutoProxy'));
72
+ return fieldErrors;
73
+ }
74
+ // Check if proxy groups selected by user are available to him
75
+ const availableProxyGroupsById = {};
76
+ (options.availableProxyGroups || []).forEach((group) => {
77
+ availableProxyGroupsById[group] = true;
78
+ });
79
+ const unavailableProxyGroups = selectedProxyGroups.filter((group) => !availableProxyGroupsById[group]);
80
+ if (unavailableProxyGroups.length) {
81
+ fieldErrors.push(m('inputSchema.validation.proxyGroupsNotAvailable', {
82
+ rootName: 'input',
83
+ fieldKey,
84
+ groups: unavailableProxyGroups.join(', '),
85
+ }));
86
+ }
87
+ // Check if any of the proxy groups are blocked and if yes then output the associated message
88
+ const blockedProxyGroupsById = options.disabledProxyGroups || {};
89
+ selectedProxyGroups
90
+ .filter((group) => blockedProxyGroupsById[group])
91
+ .forEach((blockedGroup) => {
92
+ fieldErrors.push(blockedProxyGroupsById[blockedGroup]);
93
+ });
94
+ return fieldErrors;
95
+ }
96
+ /**
97
+ * Uses AJV validator to validate input with input schema and then
98
+ * does custom validation for our own properties (nullable, patternKey, patternValue).
99
+ *
100
+ * Note: patternKey/patternValue are deprecated and rejected by `validateInputSchema`,
101
+ * but they are still validated here so that inputs of existing builds keep working.
102
+ * TODO: Remove their validation once stored schemas of existing builds no longer need to be supported.
103
+ * @param validator Initialized AJV validator
104
+ * @param inputSchema Valid input schema in object
105
+ * @param input Input object to be validated
106
+ * @param options (Optional) Additional validation configuration for certain fields
107
+ */
108
+ export function validateInputUsingValidator(validator, inputSchema, input, options = {}) {
109
+ const isValid = validator(input); // Check if input is valid based on schema values
110
+ const { properties } = inputSchema;
111
+ const required = inputSchema.required || [];
112
+ let errors = [];
113
+ // Process AJV validation errors
114
+ if (!isValid) {
115
+ errors = validator
116
+ .errors.filter((error) => {
117
+ // We are storing encrypted objects/arrays as strings, so AJV will throw type the error here.
118
+ // So we need to skip these errors.
119
+ if (error.keyword === 'type' && error.instancePath) {
120
+ const path = error.instancePath.replace(/^\//, '').split('/')[0];
121
+ const propSchema = inputSchema.properties?.[path];
122
+ const value = input[path];
123
+ // Check if the property is a secret and if the value is an encrypted value.
124
+ // We do additional validation of the field schema in the later part of this function
125
+ if (propSchema?.isSecret &&
126
+ typeof value === 'string' &&
127
+ (propSchema.type === 'object' || propSchema.type === 'array') &&
128
+ isEncryptedValueForFieldType(value, propSchema.type)) {
129
+ return false;
130
+ }
131
+ }
132
+ return true;
133
+ })
134
+ .map((error) => parseAjvError(error, 'input', properties, input))
135
+ .filter((error) => !!error);
136
+ }
137
+ Object.keys(properties).forEach((property) => {
138
+ const value = input[property];
139
+ const { type, editor, patternKey, patternValue, isSecret } = properties[property];
140
+ const fieldErrors = [];
141
+ // Check that proxy is required, if yes, valides that it's correctly setup
142
+ if (type === 'object' && editor === 'proxy') {
143
+ const proxyValidationErrors = validateProxyField(property, value, required.includes(property), options.proxy);
144
+ proxyValidationErrors.forEach((error) => {
145
+ fieldErrors.push(error);
146
+ });
147
+ }
148
+ // Check that array items fit patternKey and patternValue
149
+ if (type === 'array' && value && Array.isArray(value)) {
150
+ if (editor === 'requestListSources') {
151
+ const invalidIndexes = [];
152
+ value.forEach((item, index) => {
153
+ if (!item)
154
+ invalidIndexes.push(index);
155
+ else if (!item.url && !item.requestsFromUrl)
156
+ invalidIndexes.push(index);
157
+ else if (item.url && !URL_REGEX.test(item.url))
158
+ invalidIndexes.push(index);
159
+ else if (item.requestsFromUrl && !URL_REGEX.test(item.requestsFromUrl))
160
+ invalidIndexes.push(index);
161
+ });
162
+ if (invalidIndexes.length) {
163
+ fieldErrors.push(m('inputSchema.validation.requestListSourcesInvalid', {
164
+ rootName: 'input',
165
+ fieldKey: property,
166
+ invalidIndexes: invalidIndexes.join(','),
167
+ }));
168
+ }
169
+ }
170
+ // If patternKey is provided, then validate keys of objects in array
171
+ if (patternKey && editor === 'keyValue') {
172
+ const check = new RegExp(patternKey);
173
+ const invalidIndexes = [];
174
+ value.forEach((item, index) => {
175
+ if (!check.test(item.key))
176
+ invalidIndexes.push(index);
177
+ });
178
+ if (invalidIndexes.length) {
179
+ const customError = getCustomErrorMessage(inputSchema, `properties/${property}/patternKey`);
180
+ fieldErrors.push(customError ??
181
+ m('inputSchema.validation.arrayKeysInvalid', {
182
+ rootName: 'input',
183
+ fieldKey: property,
184
+ invalidIndexes: invalidIndexes.join(','),
185
+ pattern: patternKey,
186
+ }));
187
+ }
188
+ }
189
+ // If patternValue is provided and editor is keyValue, then validate values of objecs in array
190
+ if (patternValue && editor === 'keyValue') {
191
+ const check = new RegExp(patternValue);
192
+ const invalidIndexes = [];
193
+ value.forEach((item, index) => {
194
+ if (!check.test(item.value))
195
+ invalidIndexes.push(index);
196
+ });
197
+ if (invalidIndexes.length) {
198
+ const customError = getCustomErrorMessage(inputSchema, `properties/${property}/patternValue`);
199
+ fieldErrors.push(customError ??
200
+ m('inputSchema.validation.arrayValuesInvalid', {
201
+ rootName: 'input',
202
+ fieldKey: property,
203
+ invalidIndexes: invalidIndexes.join(','),
204
+ pattern: patternValue,
205
+ }));
206
+ }
207
+ // If patternValue is provided and editor is stringList, then validate each item in array
208
+ }
209
+ else if (patternValue && editor === 'stringList') {
210
+ const check = new RegExp(patternValue);
211
+ const invalidIndexes = [];
212
+ value.forEach((item, index) => {
213
+ if (!check.test(item))
214
+ invalidIndexes.push(index);
215
+ });
216
+ if (invalidIndexes.length) {
217
+ const customError = getCustomErrorMessage(inputSchema, `properties/${property}/patternValue`);
218
+ fieldErrors.push(customError ??
219
+ m('inputSchema.validation.arrayValuesInvalid', {
220
+ rootName: 'input',
221
+ fieldKey: property,
222
+ invalidIndexes: invalidIndexes.join(','),
223
+ pattern: patternValue,
224
+ }));
225
+ }
226
+ }
227
+ }
228
+ // Check that object items fit patternKey and patternValue
229
+ if (type === 'object' && value && typeof value === 'object') {
230
+ if (patternKey) {
231
+ const check = new RegExp(patternKey);
232
+ const invalidKeys = [];
233
+ Object.keys(value).forEach((key) => {
234
+ if (!check.test(key))
235
+ invalidKeys.push(key);
236
+ });
237
+ if (invalidKeys.length) {
238
+ const customError = getCustomErrorMessage(inputSchema, `properties/${property}/patternKey`);
239
+ fieldErrors.push(customError ??
240
+ m('inputSchema.validation.objectKeysInvalid', {
241
+ rootName: 'input',
242
+ fieldKey: property,
243
+ invalidKeys: invalidKeys.join(','),
244
+ pattern: patternKey,
245
+ }));
246
+ }
247
+ }
248
+ if (patternValue) {
249
+ const check = new RegExp(patternValue);
250
+ const invalidKeys = [];
251
+ Object.keys(value).forEach((key) => {
252
+ const propertyValue = value[key];
253
+ if (typeof propertyValue !== 'string' || !check.test(propertyValue))
254
+ invalidKeys.push(key);
255
+ });
256
+ if (invalidKeys.length) {
257
+ const customError = getCustomErrorMessage(inputSchema, `properties/${property}/patternValue`);
258
+ fieldErrors.push(customError ??
259
+ m('inputSchema.validation.objectValuesInvalid', {
260
+ rootName: 'input',
261
+ fieldKey: property,
262
+ invalidKeys: invalidKeys.join(','),
263
+ pattern: patternValue,
264
+ }));
265
+ }
266
+ }
267
+ }
268
+ // Additional validation for secret fields
269
+ if (isSecret && value && typeof value === 'string') {
270
+ // If the value is a valid encrypted string for the field type,
271
+ // we check if the field schema is likely to be still valid (is unchanged from the time of encryption).
272
+ if (isEncryptedValueForFieldType(value, type) &&
273
+ !isEncryptedValueForFieldSchema(value, properties[property])) {
274
+ // If not, we add an error message to the field errors and user needs to update the value in the input editor.
275
+ fieldErrors.push(m('inputSchema.validation.secretFieldSchemaChanged', { fieldKey: property }));
276
+ }
277
+ }
278
+ if (fieldErrors.length > 0) {
279
+ const message = fieldErrors.join(', ');
280
+ errors.push({ fieldKey: property, message });
281
+ }
282
+ });
283
+ return errors;
284
+ }
285
+ /**
286
+ * This functions parses all given JSON and then takes each of the jsFields.
287
+ * Then if the field:
288
+ * - is valid JS single function it replaces its single line string with a function delacation.
289
+ * - is valid multiline JS code then replaces its single line string with `multiline` string
290
+ * Then stringifies the code with given number of jsonSpacing spaces and finally prefixes whole
291
+ * stringified JSON except the first line with globalSpacing spaces.
292
+ */
293
+ export function makeInputJsFieldsReadable(json, jsFields, jsonSpacing = 4, globalSpacing = 0) {
294
+ const parsedJson = JSON.parse(json);
295
+ const replacements = {};
296
+ jsFields.forEach((field) => {
297
+ let maybeFunction = parsedJson[field];
298
+ if (!maybeFunction || typeof maybeFunction !== 'string')
299
+ return;
300
+ let ast;
301
+ try {
302
+ ast = parse(maybeFunction, { ecmaVersion: 'latest' });
303
+ }
304
+ catch {
305
+ // Don't do anything in a case of invalid JS code.
306
+ return;
307
+ }
308
+ const isMultiline = maybeFunction.includes('\n');
309
+ const isSingleFunction = ast &&
310
+ ast.body.length === 1 &&
311
+ (ast.body[0].type === 'FunctionDeclaration' ||
312
+ (ast.body[0].type === 'ExpressionStatement' &&
313
+ ast.body[0].expression.type === 'ArrowFunctionExpression'));
314
+ // If it's not a function declaration or multiline JS code then we do nothing.
315
+ if (!isSingleFunction && !isMultiline)
316
+ return;
317
+ const spaces = isSingleFunction ? ' '.repeat(jsonSpacing) : '';
318
+ maybeFunction = maybeFunction
319
+ .split('\n')
320
+ .join(`\n${spaces}`) // This prefixes each line with spaces.
321
+ .trim(); // Trim whitespace on both sides
322
+ const replacementValue = isSingleFunction
323
+ ? maybeFunction.replace(/[;]+$/g, '') // Remove trailing semicolons
324
+ : `\`${maybeFunction}\``;
325
+ const replacementToken = `<<<REPLACEMENT_TOKEN:${Math.random()}>>>`;
326
+ replacements[replacementToken] = replacementValue;
327
+ parsedJson[field] = replacementToken;
328
+ });
329
+ let niceJson = JSON.stringify(parsedJson, null, jsonSpacing);
330
+ Object.entries(replacements).forEach(([replacementToken, replacementValue]) => {
331
+ niceJson = niceJson.replace(`"${replacementToken}"`, replacementValue);
332
+ });
333
+ const globalSpaces = new Array(globalSpacing).fill(' ').join('');
334
+ niceJson = niceJson.split('\n').join(`\n${globalSpaces}`);
335
+ return niceJson;
336
+ }
337
+ const DRAFT_2019_09_META_SCHEMA = 'https://json-schema.org/draft/2019-09/schema';
338
+ export function ensureAjvSupportsDraft2019(ajvInstance) {
339
+ const metaSchema = ajvInstance.getSchema(DRAFT_2019_09_META_SCHEMA);
340
+ if (!metaSchema) {
341
+ throw new Error(`The provided Ajv instance does not support draft-2019-09 (missing meta-schema ${DRAFT_2019_09_META_SCHEMA}).`);
342
+ }
343
+ }
344
+ /**
345
+ * Validates that the provided pattern is a valid and safe regular expression.
346
+ * @param pattern The regular expression pattern to validate.
347
+ * @param fieldKey The field key where the pattern is used (for error messages).
348
+ */
349
+ export function validateRegexpPattern(pattern, fieldKey) {
350
+ try {
351
+ // Validate that the pattern is a valid regular expression
352
+ // eslint-disable-next-line
353
+ new RegExp(pattern);
354
+ }
355
+ catch {
356
+ const message = m('inputSchema.validation.regexpNotValid', { pattern, fieldKey });
357
+ throw new Error(`Input schema is not valid (${message})`);
358
+ }
359
+ // TODO: add check for safe regex but figure out how to avoid false positives with some valid regexes
360
+ // Check if the regex is safe (to avoid ReDoS attacks)
361
+ // if (!safe(regex)) {
362
+ // const message = m('inputSchema.validation.regexpNotSafe', { pattern, fieldKey });
363
+ // throw new Error(`Input schema is not valid (${message})`);
364
+ // }
365
+ }
366
+ //# sourceMappingURL=utilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utilities.js","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"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","sourcesContent":["import { parse } from 'acorn-loose';\nimport type { ValidateFunction } from 'ajv';\nimport type { Ajv2019 as Ajv } from 'ajv/dist/2019.js';\nimport { countries } from 'countries-list';\n\nimport { PROXY_URL_REGEX, URL_REGEX } from '@apify/consts';\nimport { isEncryptedValueForFieldSchema, isEncryptedValueForFieldType } from '@apify/input_secrets';\n\nimport { getCustomErrorMessage, parseAjvError } from './input_schema.js';\nimport { m } from './intl.js';\n\n/**\n * Validates input field configured with proxy editor\n * @param fieldKey Proxy field value\n * @param value Proxy field value\n * @param [isRequired] Whether the field is required or not\n * @param [options] Information about proxy groups availability\n * @param [options.hasAutoProxyGroups] Informs validation whether user has atleast one proxy group available in auto mode\n * @param [options.availableProxyGroups] List of available proxy groups\n * @param [options.disabledProxyGroups] Object with groupId as key and error message as value (mostly for residential/SERP)\n */\nfunction validateProxyField(\n fieldKey: Record<string, unknown>,\n value: Record<string, any>,\n isRequired = false,\n options: {\n hasAutoProxyGroups?: boolean;\n availableProxyGroups?: string[];\n disabledProxyGroups?: Record<string, unknown>;\n } | null = null,\n) {\n const fieldErrors: any[] = [];\n if (isRequired) {\n // Nullable error is already handled by AJV\n if (value === null) return fieldErrors;\n if (!value) {\n const message = m('inputSchema.validation.required', { rootName: 'input', fieldKey });\n fieldErrors.push(message);\n return fieldErrors;\n }\n\n const { useApifyProxy, proxyUrls } = value;\n if (!useApifyProxy && (!Array.isArray(proxyUrls) || proxyUrls.length === 0)) {\n fieldErrors.push(m('inputSchema.validation.proxyRequired', { rootName: 'input', fieldKey }));\n return fieldErrors;\n }\n }\n\n // Input is not required, so missing value is valid\n if (!value) return fieldErrors;\n\n const { useApifyProxy, proxyUrls, apifyProxyGroups, apifyProxyCountry } = value;\n\n if (!useApifyProxy && Array.isArray(proxyUrls)) {\n let invalidUrl = false;\n proxyUrls.forEach((url) => {\n if (!PROXY_URL_REGEX.test(url.trim())) invalidUrl = url.trim();\n });\n if (invalidUrl) {\n fieldErrors.push(m('inputSchema.validation.customProxyInvalid', { invalidUrl }));\n }\n }\n\n // Apify proxy country can be set only when using Apify proxy\n if (!useApifyProxy && apifyProxyCountry) {\n fieldErrors.push(m('inputSchema.validation.apifyProxyCountryWithoutApifyProxyForbidden'));\n }\n\n // If Apify proxy is not used skip additional checks\n if (!useApifyProxy) return fieldErrors;\n\n // If Apify proxy is used, check if there is a selected country and if so, check that it's valid (empty or a valid country code)\n if (apifyProxyCountry && !countries[apifyProxyCountry as keyof typeof countries]) {\n fieldErrors.push(m('inputSchema.validation.apifyProxyCountryInvalid', { invalidCountry: apifyProxyCountry }));\n }\n\n // If options are not provided skip additional checks\n if (!options) return fieldErrors;\n\n // if apifyProxyGroups exists it must be an array of strings\n const isStringsArray = (array: string[]) => array.every((item) => typeof item === 'string');\n if (apifyProxyGroups && !(Array.isArray(apifyProxyGroups) && isStringsArray(apifyProxyGroups))) {\n fieldErrors.push(m('inputSchema.validation.proxyGroupMustBeArrayOfStrings', { rootName: 'input', fieldKey }));\n return fieldErrors;\n }\n\n const selectedProxyGroups = apifyProxyGroups || [];\n\n // Auto mode, check that user has access to alteast one proxy group usable in this mode\n if (!selectedProxyGroups.length && !options.hasAutoProxyGroups) {\n fieldErrors.push(m('inputSchema.validation.noAvailableAutoProxy'));\n return fieldErrors;\n }\n\n // Check if proxy groups selected by user are available to him\n const availableProxyGroupsById = {} as Record<string, boolean>;\n (options.availableProxyGroups || []).forEach((group) => {\n availableProxyGroupsById[group] = true;\n });\n const unavailableProxyGroups = selectedProxyGroups.filter((group: string) => !availableProxyGroupsById[group]);\n\n if (unavailableProxyGroups.length) {\n fieldErrors.push(\n m('inputSchema.validation.proxyGroupsNotAvailable', {\n rootName: 'input',\n fieldKey,\n groups: unavailableProxyGroups.join(', '),\n }),\n );\n }\n\n // Check if any of the proxy groups are blocked and if yes then output the associated message\n const blockedProxyGroupsById = options.disabledProxyGroups || {};\n selectedProxyGroups\n .filter((group: string) => blockedProxyGroupsById[group])\n .forEach((blockedGroup: string) => {\n fieldErrors.push(blockedProxyGroupsById[blockedGroup]);\n });\n\n return fieldErrors;\n}\n\n/**\n * Uses AJV validator to validate input with input schema and then\n * does custom validation for our own properties (nullable, patternKey, patternValue).\n *\n * Note: patternKey/patternValue are deprecated and rejected by `validateInputSchema`,\n * but they are still validated here so that inputs of existing builds keep working.\n * TODO: Remove their validation once stored schemas of existing builds no longer need to be supported.\n * @param validator Initialized AJV validator\n * @param inputSchema Valid input schema in object\n * @param input Input object to be validated\n * @param options (Optional) Additional validation configuration for certain fields\n */\nexport function validateInputUsingValidator(\n validator: ValidateFunction,\n inputSchema: Record<string, any>,\n input: Record<string, unknown>,\n options: Record<string, any> = {},\n) {\n const isValid = validator(input); // Check if input is valid based on schema values\n\n const { properties } = inputSchema;\n const required = inputSchema.required || [];\n\n let errors: { fieldKey: string; message: string }[] = [];\n // Process AJV validation errors\n if (!isValid) {\n errors = validator\n .errors!.filter((error) => {\n // We are storing encrypted objects/arrays as strings, so AJV will throw type the error here.\n // So we need to skip these errors.\n if (error.keyword === 'type' && error.instancePath) {\n const path = error.instancePath.replace(/^\\//, '').split('/')[0];\n const propSchema = inputSchema.properties?.[path];\n const value = input[path];\n\n // Check if the property is a secret and if the value is an encrypted value.\n // We do additional validation of the field schema in the later part of this function\n if (\n propSchema?.isSecret &&\n typeof value === 'string' &&\n (propSchema.type === 'object' || propSchema.type === 'array') &&\n isEncryptedValueForFieldType(value, propSchema.type)\n ) {\n return false;\n }\n }\n return true;\n })\n .map((error) => parseAjvError(error, 'input', properties, input))\n .filter((error) => !!error) as any[];\n }\n\n Object.keys(properties).forEach((property) => {\n const value = input[property];\n const { type, editor, patternKey, patternValue, isSecret } = properties[property];\n const fieldErrors = [];\n // Check that proxy is required, if yes, valides that it's correctly setup\n if (type === 'object' && editor === 'proxy') {\n const proxyValidationErrors = validateProxyField(\n property as any,\n value as Record<string, any>,\n required.includes(property),\n options.proxy,\n );\n proxyValidationErrors.forEach((error) => {\n fieldErrors.push(error);\n });\n }\n // Check that array items fit patternKey and patternValue\n if (type === 'array' && value && Array.isArray(value)) {\n if (editor === 'requestListSources') {\n const invalidIndexes: any[] = [];\n value.forEach((item, index) => {\n if (!item) invalidIndexes.push(index);\n else if (!item.url && !item.requestsFromUrl) invalidIndexes.push(index);\n else if (item.url && !URL_REGEX.test(item.url)) invalidIndexes.push(index);\n else if (item.requestsFromUrl && !URL_REGEX.test(item.requestsFromUrl)) invalidIndexes.push(index);\n });\n if (invalidIndexes.length) {\n fieldErrors.push(\n m('inputSchema.validation.requestListSourcesInvalid', {\n rootName: 'input',\n fieldKey: property,\n invalidIndexes: invalidIndexes.join(','),\n }),\n );\n }\n }\n // If patternKey is provided, then validate keys of objects in array\n if (patternKey && editor === 'keyValue') {\n const check = new RegExp(patternKey);\n const invalidIndexes: any[] = [];\n value.forEach((item, index) => {\n if (!check.test(item.key)) invalidIndexes.push(index);\n });\n if (invalidIndexes.length) {\n const customError = getCustomErrorMessage(inputSchema, `properties/${property}/patternKey`);\n fieldErrors.push(\n customError ??\n m('inputSchema.validation.arrayKeysInvalid', {\n rootName: 'input',\n fieldKey: property,\n invalidIndexes: invalidIndexes.join(','),\n pattern: patternKey,\n }),\n );\n }\n }\n // If patternValue is provided and editor is keyValue, then validate values of objecs in array\n if (patternValue && editor === 'keyValue') {\n const check = new RegExp(patternValue);\n const invalidIndexes: any[] = [];\n value.forEach((item, index) => {\n if (!check.test(item.value)) invalidIndexes.push(index);\n });\n if (invalidIndexes.length) {\n const customError = getCustomErrorMessage(inputSchema, `properties/${property}/patternValue`);\n fieldErrors.push(\n customError ??\n m('inputSchema.validation.arrayValuesInvalid', {\n rootName: 'input',\n fieldKey: property,\n invalidIndexes: invalidIndexes.join(','),\n pattern: patternValue,\n }),\n );\n }\n // If patternValue is provided and editor is stringList, then validate each item in array\n } else if (patternValue && editor === 'stringList') {\n const check = new RegExp(patternValue);\n const invalidIndexes: any[] = [];\n value.forEach((item, index) => {\n if (!check.test(item)) invalidIndexes.push(index);\n });\n if (invalidIndexes.length) {\n const customError = getCustomErrorMessage(inputSchema, `properties/${property}/patternValue`);\n fieldErrors.push(\n customError ??\n m('inputSchema.validation.arrayValuesInvalid', {\n rootName: 'input',\n fieldKey: property,\n invalidIndexes: invalidIndexes.join(','),\n pattern: patternValue,\n }),\n );\n }\n }\n }\n // Check that object items fit patternKey and patternValue\n if (type === 'object' && value && typeof value === 'object') {\n if (patternKey) {\n const check = new RegExp(patternKey);\n const invalidKeys: any[] = [];\n Object.keys(value).forEach((key) => {\n if (!check.test(key)) invalidKeys.push(key);\n });\n if (invalidKeys.length) {\n const customError = getCustomErrorMessage(inputSchema, `properties/${property}/patternKey`);\n fieldErrors.push(\n customError ??\n m('inputSchema.validation.objectKeysInvalid', {\n rootName: 'input',\n fieldKey: property,\n invalidKeys: invalidKeys.join(','),\n pattern: patternKey,\n }),\n );\n }\n }\n if (patternValue) {\n const check = new RegExp(patternValue);\n const invalidKeys: any[] = [];\n Object.keys(value).forEach((key) => {\n const propertyValue = (value as Record<string, any>)[key];\n if (typeof propertyValue !== 'string' || !check.test(propertyValue)) invalidKeys.push(key);\n });\n if (invalidKeys.length) {\n const customError = getCustomErrorMessage(inputSchema, `properties/${property}/patternValue`);\n fieldErrors.push(\n customError ??\n m('inputSchema.validation.objectValuesInvalid', {\n rootName: 'input',\n fieldKey: property,\n invalidKeys: invalidKeys.join(','),\n pattern: patternValue,\n }),\n );\n }\n }\n }\n\n // Additional validation for secret fields\n if (isSecret && value && typeof value === 'string') {\n // If the value is a valid encrypted string for the field type,\n // we check if the field schema is likely to be still valid (is unchanged from the time of encryption).\n if (\n isEncryptedValueForFieldType(value, type) &&\n !isEncryptedValueForFieldSchema(value, properties[property])\n ) {\n // If not, we add an error message to the field errors and user needs to update the value in the input editor.\n fieldErrors.push(m('inputSchema.validation.secretFieldSchemaChanged', { fieldKey: property }));\n }\n }\n\n if (fieldErrors.length > 0) {\n const message = fieldErrors.join(', ');\n errors.push({ fieldKey: property, message });\n }\n });\n\n return errors;\n}\n\n/**\n * This functions parses all given JSON and then takes each of the jsFields.\n * Then if the field:\n * - is valid JS single function it replaces its single line string with a function delacation.\n * - is valid multiline JS code then replaces its single line string with `multiline` string\n * Then stringifies the code with given number of jsonSpacing spaces and finally prefixes whole\n * stringified JSON except the first line with globalSpacing spaces.\n */\nexport function makeInputJsFieldsReadable(\n json: string,\n jsFields: string[],\n jsonSpacing = 4,\n globalSpacing = 0,\n): string {\n const parsedJson = JSON.parse(json);\n const replacements: Record<string, any> = {};\n\n jsFields.forEach((field) => {\n let maybeFunction = parsedJson[field];\n if (!maybeFunction || typeof maybeFunction !== 'string') return;\n\n let ast;\n try {\n ast = parse(maybeFunction, { ecmaVersion: 'latest' });\n } catch {\n // Don't do anything in a case of invalid JS code.\n return;\n }\n\n const isMultiline = maybeFunction.includes('\\n');\n const isSingleFunction =\n ast &&\n ast.body.length === 1 &&\n (ast.body[0].type === 'FunctionDeclaration' ||\n (ast.body[0].type === 'ExpressionStatement' &&\n ast.body[0].expression.type === 'ArrowFunctionExpression'));\n\n // If it's not a function declaration or multiline JS code then we do nothing.\n if (!isSingleFunction && !isMultiline) return;\n\n const spaces = isSingleFunction ? ' '.repeat(jsonSpacing) : '';\n maybeFunction = maybeFunction\n .split('\\n')\n .join(`\\n${spaces}`) // This prefixes each line with spaces.\n .trim(); // Trim whitespace on both sides\n\n const replacementValue = isSingleFunction\n ? maybeFunction.replace(/[;]+$/g, '') // Remove trailing semicolons\n : `\\`${maybeFunction}\\``;\n const replacementToken = `<<<REPLACEMENT_TOKEN:${Math.random()}>>>`;\n replacements[replacementToken] = replacementValue;\n parsedJson[field] = replacementToken;\n });\n\n let niceJson = JSON.stringify(parsedJson, null, jsonSpacing);\n\n Object.entries(replacements).forEach(([replacementToken, replacementValue]) => {\n niceJson = niceJson.replace(`\"${replacementToken}\"`, replacementValue);\n });\n\n const globalSpaces = new Array(globalSpacing).fill(' ').join('');\n niceJson = niceJson.split('\\n').join(`\\n${globalSpaces}`);\n\n return niceJson;\n}\n\nconst DRAFT_2019_09_META_SCHEMA = 'https://json-schema.org/draft/2019-09/schema';\n\nexport function ensureAjvSupportsDraft2019(ajvInstance: Ajv) {\n const metaSchema = ajvInstance.getSchema(DRAFT_2019_09_META_SCHEMA);\n if (!metaSchema) {\n throw new Error(\n `The provided Ajv instance does not support draft-2019-09 (missing meta-schema ${DRAFT_2019_09_META_SCHEMA}).`,\n );\n }\n}\n\n/**\n * Validates that the provided pattern is a valid and safe regular expression.\n * @param pattern The regular expression pattern to validate.\n * @param fieldKey The field key where the pattern is used (for error messages).\n */\nexport function validateRegexpPattern(pattern: string, fieldKey: string) {\n try {\n // Validate that the pattern is a valid regular expression\n // eslint-disable-next-line\n new RegExp(pattern);\n } catch {\n const message = m('inputSchema.validation.regexpNotValid', { pattern, fieldKey });\n throw new Error(`Input schema is not valid (${message})`);\n }\n\n // TODO: add check for safe regex but figure out how to avoid false positives with some valid regexes\n // Check if the regex is safe (to avoid ReDoS attacks)\n // if (!safe(regex)) {\n // const message = m('inputSchema.validation.regexpNotSafe', { pattern, fieldKey });\n // throw new Error(`Input schema is not valid (${message})`);\n // }\n}\n"]}