Skip to content
8 changes: 8 additions & 0 deletions packages/input_schema/src/intl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ const intlStrings = {
'Field schema.properties.{fieldKey} does not exist, but it is specified in schema.required. Either define the field or remove it from schema.required.',
'inputSchema.validation.proxyGroupMustBeArrayOfStrings':
'Field {rootName}.{fieldKey}.apifyProxyGroups must be an array of strings.',
'inputSchema.validation.datepickerInvalidFormatAbsolute':
'Field {rootName}.{fieldKey} must be a string in format "YYYY-MM-DD".',
'inputSchema.validation.datepickerInvalidFormatRelative':
'Field {rootName}.{fieldKey} must be a string in format "+/- number unit ". Supported units are "day", "week", "month", "year".',
'inputSchema.validation.datepickerInvalidFormatBoth':
'Field {rootName}.{fieldKey} must be a string in format "YYYY-MM-DD" or "+/- number unit ". Supported units are "day", "week", "month", "year".',
'inputSchema.validation.datepickerInvalidDate':
'Field {rootName}.{fieldKey} must be a valid date.',
};

/**
Expand Down
4 changes: 3 additions & 1 deletion packages/input_schema/src/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@
"editor": { "enum": ["javascript", "python", "textfield", "textarea", "datepicker", "hidden", "dataset", "keyValueStore", "requestQueue"] },
"isSecret": { "type": "boolean" },
"sectionCaption": { "type": "string" },
"sectionDescription": { "type": "string" }
"sectionDescription": { "type": "string" },
"isAbsolute": { "type": "boolean" },
"isRelative": { "type": "boolean" }
}
},
"else": {
Expand Down
3 changes: 3 additions & 0 deletions packages/input_schema/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export type StringFieldDefinition = CommonFieldDefinition<string> & {
enum?: readonly string[]; // required if editor is 'select'
enumTitles?: readonly string[]
isSecret?: boolean;
// used for 'datepicker' editor, isAbsolute is considered with default value true
isAbsolute?: boolean;
isRelative?: boolean;
}

export type BooleanFieldDefinition = CommonFieldDefinition<boolean> & {
Expand Down
52 changes: 48 additions & 4 deletions packages/input_schema/src/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,12 @@ export function validateInputUsingValidator(
}

Object.keys(properties).forEach((property) => {
const value = input[property] as Record<string, any>;
const { type, editor, patternKey, patternValue } = properties[property];
const value = input[property] as Record<string, any> | Array<any> | string | number | boolean | null;
const { type, editor, patternKey, patternValue, isAbsolute, isRelative } = properties[property];
const fieldErrors = [];
// Check that proxy is required, if yes, valides that it's correctly setup
if (type === 'object' && editor === 'proxy') {
const proxyValidationErrors = validateProxyField(property as any, value, required.includes(property), options.proxy);
const proxyValidationErrors = validateProxyField(property as any, value as Record<string, any>, required.includes(property), options.proxy);
proxyValidationErrors.forEach((error) => {
fieldErrors.push(error);
});
Expand Down Expand Up @@ -234,7 +234,7 @@ export function validateInputUsingValidator(
const check = new RegExp(patternValue);
const invalidKeys: any[] = [];
Object.keys(value).forEach((key) => {
const propertyValue = value[key];
const propertyValue = (value as Record<string, any>)[key];
if (typeof propertyValue !== 'string' || !check.test(propertyValue)) invalidKeys.push(key);
});
if (invalidKeys.length) {
Expand All @@ -247,6 +247,50 @@ export function validateInputUsingValidator(
}
}
}

// Check datepicker editor format
if (type === 'string' && editor === 'datepicker' && value && typeof value === 'string') {
const acceptAbsolute = isAbsolute !== false;
const acceptRelative = isRelative === true;
const isValidAbsolute = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(value);
const isValidRelative = /^[+-] [0-9]+ (day|week|month|year)s?$/.test(value);
let isValidDate: boolean | undefined;

if (isValidAbsolute) {
const [year, month, day] = value.split('-').map(Number);
const date = new Date(`${year}-${month}-${day}`);

// Check if the date object is valid and matches the input string
isValidDate = date.getFullYear() === year
&& date.getMonth() + 1 === month
&& date.getDate() === day;
}

if (acceptAbsolute && !acceptRelative && !isValidAbsolute) {
fieldErrors.push(m('inputSchema.validation.datepickerInvalidFormatAbsolute', {
rootName: 'input',
fieldKey: property,
}));
} else if (acceptRelative && !acceptAbsolute && !isValidRelative) {
fieldErrors.push(m('inputSchema.validation.datepickerInvalidFormatRelative', {
rootName: 'input',
fieldKey: property,
}));
} else if ((acceptAbsolute && !acceptRelative && !isValidAbsolute)
|| (acceptRelative && !acceptAbsolute && !isValidRelative)
|| (acceptRelative && acceptAbsolute && !isValidAbsolute && !isValidRelative)) {
fieldErrors.push(m('inputSchema.validation.datepickerInvalidFormatBoth', {
rootName: 'input',
fieldKey: property,
}));
} else if (isValidDate === false && acceptAbsolute) {
fieldErrors.push(m('inputSchema.validation.datepickerInvalidDate', {
rootName: 'input',
fieldKey: property,
}));
}
}

if (fieldErrors.length > 0) {
const message = fieldErrors.join(', ');
errors.push({ fieldKey: property, message });
Expand Down