Common - Field validation (#15491)
Closes : https://github.com/twentyhq/core-team-issues/issues/1622 To do in other PR : - Add migration command for non nullable text, raw_json & array fields - Add null transformation --------- Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
-3
@@ -1,3 +0,0 @@
|
||||
import { CommonSelectedFieldsHandler } from 'src/engine/api/common/common-args-handlers/common-query-selected-fields/common-selected-fields.handler';
|
||||
|
||||
export const CommonArgsHandlers = [CommonSelectedFieldsHandler];
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import { DataArgProcessor } from 'src/engine/api/common/common-args-processors/data-arg-processor/data-arg.processor';
|
||||
import { QueryRunnerArgsFactory } from 'src/engine/api/common/common-args-processors/query-runner-args.factory';
|
||||
|
||||
export const CommonArgsProcessors = [DataArgProcessor, QueryRunnerArgsFactory]; // TODO: Refacto-common Remove QueryRunnerArgsFactory
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isNull, isUndefined } from '@sniptt/guards';
|
||||
import {
|
||||
FieldMetadataRelationSettings,
|
||||
FieldMetadataType,
|
||||
ObjectRecord,
|
||||
RelationType,
|
||||
} from 'twenty-shared/types';
|
||||
import {
|
||||
assertIsDefinedOrThrow,
|
||||
assertUnreachable,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
import { transformActorField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-actor-field.util';
|
||||
import { transformAddressField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-address-field.util';
|
||||
import { transformArrayField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-array-field.util';
|
||||
import { transformCurrencyField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-currency-field.util';
|
||||
import { transformFullNameField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-full-name-field.util';
|
||||
import { transformNumericField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-numeric-field.util';
|
||||
import { transformRawJsonField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-raw-json-field.util';
|
||||
import { transformTextField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-text-field.util';
|
||||
import { validateActorFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-actor-field-or-throw.util';
|
||||
import { validateAddressFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-address-field-or-throw.util';
|
||||
import { validateArrayFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-array-field-or-throw.util';
|
||||
import { validateBooleanFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-boolean-field-or-throw.util';
|
||||
import { validateCurrencyFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-currency-field-or-throw.util';
|
||||
import { validateDateAndDateTimeFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-date-and-date-time-field-or-throw.util';
|
||||
import { validateEmailsFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-emails-field-or-throw.util';
|
||||
import { validateFullNameFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-full-name-field-or-throw.util';
|
||||
import { validateLinksFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-links-field-or-throw.util';
|
||||
import { validateMultiSelectFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-multi-select-field-or-throw.util';
|
||||
import { validateNumberFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-number-field-or-throw.util';
|
||||
import { validateNumericFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-numeric-field-or-throw.util';
|
||||
import { validatePhonesFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-phones-field-or-throw.util';
|
||||
import { validateRatingAndSelectFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-rating-and-select-field-or-throw.util';
|
||||
import { validateRawJsonFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-raw-json-field-or-throw.util';
|
||||
import { validateRichTextV2FieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-rich-text-v2-field-or-throw.util';
|
||||
import { validateTextFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-text-field-or-throw.util';
|
||||
import { validateUUIDFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-uuid-field-or-throw.util';
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||
import { transformEmailsValue } from 'src/engine/core-modules/record-transformer/utils/transform-emails-value.util';
|
||||
import { transformLinksValue } from 'src/engine/core-modules/record-transformer/utils/transform-links-value.util';
|
||||
import { transformPhonesValue } from 'src/engine/core-modules/record-transformer/utils/transform-phones-value.util';
|
||||
import { transformRichTextV2Value } from 'src/engine/core-modules/record-transformer/utils/transform-rich-text-v2.util';
|
||||
import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
|
||||
@Injectable()
|
||||
export class DataArgProcessor {
|
||||
constructor(private readonly recordPositionService: RecordPositionService) {}
|
||||
|
||||
async process({
|
||||
partialRecordInputs,
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
shouldBackfillPositionIfUndefined = true,
|
||||
}: {
|
||||
partialRecordInputs: Partial<ObjectRecord>[] | undefined;
|
||||
authContext: AuthContext;
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
|
||||
shouldBackfillPositionIfUndefined?: boolean;
|
||||
}): Promise<Partial<ObjectRecord>[]> {
|
||||
if (!isDefined(partialRecordInputs)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const workspace = authContext.workspace;
|
||||
|
||||
assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError);
|
||||
|
||||
const processedRecords: Partial<ObjectRecord>[] = [];
|
||||
|
||||
for (const record of partialRecordInputs) {
|
||||
const processedRecord: Partial<ObjectRecord> = {};
|
||||
|
||||
for (const [key, value] of Object.entries(record)) {
|
||||
const fieldMetadataId =
|
||||
objectMetadataItemWithFieldMaps.fieldIdByName[key] ||
|
||||
objectMetadataItemWithFieldMaps.fieldIdByJoinColumnName[key];
|
||||
|
||||
if (!isDefined(fieldMetadataId)) {
|
||||
throw new CommonQueryRunnerException(
|
||||
`Object ${objectMetadataItemWithFieldMaps.nameSingular} doesn't have any "${key}" field.`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
const fieldMetadata =
|
||||
objectMetadataItemWithFieldMaps.fieldsById[fieldMetadataId];
|
||||
|
||||
if (
|
||||
!isDefined(fieldMetadata.defaultValue) &&
|
||||
!fieldMetadata.isNullable &&
|
||||
isNull(value)
|
||||
) {
|
||||
throw new CommonQueryRunnerException(
|
||||
`Field ${key} is not nullable and has no default value.`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
if (isUndefined(value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
processedRecord[key] = await this.processField(
|
||||
fieldMetadata,
|
||||
key,
|
||||
value,
|
||||
);
|
||||
}
|
||||
processedRecords.push(processedRecord);
|
||||
}
|
||||
|
||||
const overriddenPositionRecords =
|
||||
await this.recordPositionService.overridePositionOnRecords({
|
||||
partialRecordInputs: processedRecords,
|
||||
workspaceId: workspace.id,
|
||||
objectMetadata: {
|
||||
isCustom: objectMetadataItemWithFieldMaps.isCustom,
|
||||
nameSingular: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
fieldIdByName: objectMetadataItemWithFieldMaps.fieldIdByName,
|
||||
},
|
||||
shouldBackfillPositionIfUndefined,
|
||||
});
|
||||
|
||||
return overriddenPositionRecords;
|
||||
}
|
||||
|
||||
private async processField(
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
key: string,
|
||||
value: unknown,
|
||||
): Promise<unknown> {
|
||||
switch (fieldMetadata.type) {
|
||||
case FieldMetadataType.POSITION:
|
||||
return value;
|
||||
case FieldMetadataType.NUMERIC: {
|
||||
const validatedValue = validateNumericFieldOrThrow(value, key);
|
||||
|
||||
return transformNumericField(validatedValue);
|
||||
}
|
||||
case FieldMetadataType.NUMBER: {
|
||||
return validateNumberFieldOrThrow(value, key);
|
||||
}
|
||||
case FieldMetadataType.TEXT: {
|
||||
const validatedValue = validateTextFieldOrThrow(value, key);
|
||||
|
||||
return transformTextField(validatedValue);
|
||||
}
|
||||
case FieldMetadataType.DATE_TIME:
|
||||
case FieldMetadataType.DATE:
|
||||
return validateDateAndDateTimeFieldOrThrow(value, key);
|
||||
case FieldMetadataType.BOOLEAN:
|
||||
return validateBooleanFieldOrThrow(value, key);
|
||||
case FieldMetadataType.RATING:
|
||||
case FieldMetadataType.SELECT: {
|
||||
validateRatingAndSelectFieldOrThrow(
|
||||
value,
|
||||
key,
|
||||
fieldMetadata.options?.map((option) => option.value),
|
||||
);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
case FieldMetadataType.MULTI_SELECT: {
|
||||
const validatedValue = validateMultiSelectFieldOrThrow(
|
||||
value,
|
||||
key,
|
||||
fieldMetadata.options?.map((option) => option.value),
|
||||
);
|
||||
|
||||
return transformArrayField(validatedValue);
|
||||
}
|
||||
case FieldMetadataType.UUID:
|
||||
return validateUUIDFieldOrThrow(value, key);
|
||||
case FieldMetadataType.ARRAY: {
|
||||
const validatedValue = validateArrayFieldOrThrow(value, key);
|
||||
|
||||
return transformArrayField(validatedValue);
|
||||
}
|
||||
case FieldMetadataType.RAW_JSON: {
|
||||
const validatedValue = validateRawJsonFieldOrThrow(value, key);
|
||||
|
||||
return transformRawJsonField(validatedValue);
|
||||
}
|
||||
case FieldMetadataType.RELATION:
|
||||
case FieldMetadataType.MORPH_RELATION: {
|
||||
const fieldMetadataRelationSettings =
|
||||
fieldMetadata.settings as FieldMetadataRelationSettings;
|
||||
|
||||
if (
|
||||
fieldMetadataRelationSettings.relationType ===
|
||||
RelationType.ONE_TO_MANY
|
||||
) {
|
||||
throw new CommonQueryRunnerException(
|
||||
`One-to-many relation ${key} field does not support write operations.`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
if (key === fieldMetadataRelationSettings.joinColumnName) {
|
||||
return validateUUIDFieldOrThrow(value, key);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
case FieldMetadataType.PHONES: {
|
||||
const validatedValue = validatePhonesFieldOrThrow(value, key);
|
||||
|
||||
return transformPhonesValue({ input: validatedValue });
|
||||
}
|
||||
case FieldMetadataType.EMAILS: {
|
||||
const validatedValue = validateEmailsFieldOrThrow(value, key);
|
||||
|
||||
return transformEmailsValue(validatedValue);
|
||||
}
|
||||
case FieldMetadataType.FULL_NAME: {
|
||||
const validatedValue = validateFullNameFieldOrThrow(value, key);
|
||||
|
||||
return transformFullNameField(validatedValue);
|
||||
}
|
||||
|
||||
case FieldMetadataType.ADDRESS: {
|
||||
const validatedValue = validateAddressFieldOrThrow(value, key);
|
||||
|
||||
return transformAddressField(validatedValue);
|
||||
}
|
||||
case FieldMetadataType.CURRENCY: {
|
||||
const validatedValue = validateCurrencyFieldOrThrow(value, key);
|
||||
|
||||
return transformCurrencyField(validatedValue);
|
||||
}
|
||||
case FieldMetadataType.ACTOR: {
|
||||
const validatedValue = validateActorFieldOrThrow(value, key);
|
||||
|
||||
return transformActorField(validatedValue);
|
||||
}
|
||||
case FieldMetadataType.RICH_TEXT_V2: {
|
||||
const validatedValue = validateRichTextV2FieldOrThrow(value, key);
|
||||
|
||||
return await transformRichTextV2Value(validatedValue);
|
||||
}
|
||||
case FieldMetadataType.LINKS: {
|
||||
const validatedValue = validateLinksFieldOrThrow(value, key);
|
||||
|
||||
return transformLinksValue(validatedValue);
|
||||
}
|
||||
case FieldMetadataType.RICH_TEXT:
|
||||
case FieldMetadataType.TS_VECTOR:
|
||||
throw new CommonQueryRunnerException(
|
||||
`${key} ${fieldMetadata.type}-typed field does not support write operations`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
default:
|
||||
assertUnreachable(
|
||||
fieldMetadata.type,
|
||||
'Should never occur, add validator for new field type',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
import { FieldActorSource } from 'twenty-shared/types';
|
||||
|
||||
import { transformActorField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-actor-field.util';
|
||||
|
||||
describe('transformActorField', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = transformActorField(null, true);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should transform actor with source only', () => {
|
||||
const result = transformActorField(
|
||||
{
|
||||
source: FieldActorSource.EMAIL,
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
source: FieldActorSource.EMAIL,
|
||||
});
|
||||
});
|
||||
|
||||
it('should transform actor with source and context', () => {
|
||||
const result = transformActorField(
|
||||
{
|
||||
source: FieldActorSource.WORKFLOW,
|
||||
context: { workflowId: '123', stepId: 'step-1' },
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
source: FieldActorSource.WORKFLOW,
|
||||
context: { workflowId: '123', stepId: 'step-1' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should transform actor with null source', () => {
|
||||
const result = transformActorField(
|
||||
{
|
||||
source: null,
|
||||
context: { userId: '456' },
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
source: null,
|
||||
context: { userId: '456' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should transform actor with null context', () => {
|
||||
const result = transformActorField(
|
||||
{
|
||||
source: FieldActorSource.API,
|
||||
context: null,
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
source: FieldActorSource.API,
|
||||
context: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should transform empty context object to null', () => {
|
||||
const result = transformActorField(
|
||||
{
|
||||
source: FieldActorSource.EMAIL,
|
||||
context: {},
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
source: FieldActorSource.EMAIL,
|
||||
context: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import { transformAddressField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-address-field.util';
|
||||
|
||||
describe('transformAddressField', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = transformAddressField(null);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return an empty object when value is an empty object', () => {
|
||||
const result = transformAddressField({});
|
||||
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
it('should preserve undefined for fields that are not provided', () => {
|
||||
const result = transformAddressField({
|
||||
addressStreet1: '123 Main St',
|
||||
addressCity: 'San Francisco',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
addressStreet1: '123 Main St',
|
||||
addressCity: 'San Francisco',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle mixed null, undefined, and valid values', () => {
|
||||
const result = transformAddressField({
|
||||
addressStreet1: '123 Main St',
|
||||
addressStreet2: null,
|
||||
addressCity: 'San Francisco',
|
||||
addressLat: 37.7749,
|
||||
addressLng: null,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
addressStreet1: '123 Main St',
|
||||
addressStreet2: null,
|
||||
addressCity: 'San Francisco',
|
||||
addressLat: 37.7749,
|
||||
addressLng: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import { transformArrayField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-array-field.util';
|
||||
|
||||
describe('transformArrayField', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = transformArrayField(null, true);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null when value is an empty array', () => {
|
||||
const result = transformArrayField([], true);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return an array when value is a string', () => {
|
||||
const result = transformArrayField('singleString', true);
|
||||
|
||||
expect(result).toEqual(['singleString']);
|
||||
});
|
||||
|
||||
it('should return an array when value is an array of strings', () => {
|
||||
const result = transformArrayField(['string1', 'string2', 'string3'], true);
|
||||
|
||||
expect(result).toEqual(['string1', 'string2', 'string3']);
|
||||
});
|
||||
});
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import { transformCurrencyField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-currency-field.util';
|
||||
|
||||
describe('transformCurrencyField', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = transformCurrencyField(null);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return empty object when value is empty object', () => {
|
||||
const result = transformCurrencyField({});
|
||||
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
it('should transform amountMicros from number', () => {
|
||||
const result = transformCurrencyField({ amountMicros: 1000 });
|
||||
|
||||
expect(result).toEqual({ amountMicros: 1000 });
|
||||
});
|
||||
|
||||
it('should transform amountMicros from string to number', () => {
|
||||
const result = transformCurrencyField({ amountMicros: '1000' });
|
||||
|
||||
expect(result).toEqual({ amountMicros: 1000 });
|
||||
});
|
||||
|
||||
it('should transform currencyCode', () => {
|
||||
const result = transformCurrencyField({ currencyCode: 'USD' });
|
||||
|
||||
expect(result).toEqual({ currencyCode: 'USD' });
|
||||
});
|
||||
|
||||
it('should transform both amountMicros and currencyCode', () => {
|
||||
const result = transformCurrencyField({
|
||||
amountMicros: '2500',
|
||||
currencyCode: 'EUR',
|
||||
});
|
||||
|
||||
expect(result).toEqual({ amountMicros: 2500, currencyCode: 'EUR' });
|
||||
});
|
||||
|
||||
it('should handle null amountMicros', () => {
|
||||
const result = transformCurrencyField({ amountMicros: null });
|
||||
|
||||
expect(result).toEqual({ amountMicros: null });
|
||||
});
|
||||
|
||||
it('should handle null currencyCode', () => {
|
||||
const result = transformCurrencyField({ currencyCode: null });
|
||||
|
||||
expect(result).toEqual({ currencyCode: null });
|
||||
});
|
||||
});
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { transformFullNameField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-full-name-field.util';
|
||||
|
||||
describe('transformFullNameField', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = transformFullNameField(null, true);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return full name object with both fields', () => {
|
||||
const value = {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
};
|
||||
const result = transformFullNameField(value, true);
|
||||
|
||||
expect(result).toEqual({
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return full name object with only lastName', () => {
|
||||
const value = {
|
||||
lastName: '',
|
||||
};
|
||||
const result = transformFullNameField(value, true);
|
||||
|
||||
expect(result).toEqual({
|
||||
lastName: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { transformNumericField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-numeric-field.util';
|
||||
|
||||
describe('transformNumericField', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = transformNumericField(null);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return the number when value is a float', () => {
|
||||
const result = transformNumericField(3.14159);
|
||||
|
||||
expect(result).toBe(3.14159);
|
||||
});
|
||||
|
||||
it('should transform a numeric string with decimals to a number', () => {
|
||||
const result = transformNumericField('123.456');
|
||||
|
||||
expect(result).toBe(123.456);
|
||||
});
|
||||
});
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { transformRawJsonField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-raw-json-field.util';
|
||||
|
||||
describe('transformRawJsonField', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = transformRawJsonField(null, true);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null when value is empty object', () => {
|
||||
const result = transformRawJsonField({}, true);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return the string when value is empty array', () => {
|
||||
const result = transformRawJsonField([], true);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { transformTextField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-text-field.util';
|
||||
|
||||
describe('transformTextField', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = transformTextField(null, true);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null when value is empty string', () => {
|
||||
const result = transformTextField('', true);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return the string when value is a non-empty string', () => {
|
||||
const result = transformTextField('hello world', true);
|
||||
|
||||
expect(result).toBe('hello world');
|
||||
});
|
||||
});
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { isNull, isUndefined } from '@sniptt/guards';
|
||||
import { type FieldActorSource } from 'twenty-shared/types';
|
||||
|
||||
import { transformRawJsonField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-raw-json-field.util';
|
||||
|
||||
export const transformActorField = (
|
||||
value: {
|
||||
source?: FieldActorSource | null;
|
||||
context?: object | null;
|
||||
} | null,
|
||||
isNullEquivalenceEnabled: boolean = false,
|
||||
): {
|
||||
source?: FieldActorSource | null;
|
||||
context?: object | null;
|
||||
} | null => {
|
||||
if (isNull(value)) return null;
|
||||
|
||||
return {
|
||||
source: value.source,
|
||||
context: isUndefined(value.context)
|
||||
? undefined
|
||||
: transformRawJsonField(value.context, isNullEquivalenceEnabled),
|
||||
};
|
||||
};
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
import { isNull, isUndefined } from '@sniptt/guards';
|
||||
|
||||
import { transformNumericField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-numeric-field.util';
|
||||
import { transformTextField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-text-field.util';
|
||||
|
||||
export const transformAddressField = (
|
||||
value: {
|
||||
addressStreet1?: string | null;
|
||||
addressStreet2?: string | null;
|
||||
addressCity?: string | null;
|
||||
addressState?: string | null;
|
||||
addressPostcode?: string | null;
|
||||
addressCountry?: string | null;
|
||||
addressLat?: number | null;
|
||||
addressLng?: number | null;
|
||||
} | null,
|
||||
isNullEquivalenceEnabled: boolean = false,
|
||||
): {
|
||||
addressStreet1?: string | null;
|
||||
addressStreet2?: string | null;
|
||||
addressCity?: string | null;
|
||||
addressState?: string | null;
|
||||
addressPostcode?: string | null;
|
||||
addressCountry?: string | null;
|
||||
addressLat?: number | null;
|
||||
addressLng?: number | null;
|
||||
} | null => {
|
||||
if (isNull(value)) return null;
|
||||
|
||||
return {
|
||||
addressStreet1: isUndefined(value.addressStreet1)
|
||||
? undefined
|
||||
: transformTextField(value.addressStreet1, isNullEquivalenceEnabled),
|
||||
addressStreet2: isUndefined(value.addressStreet2)
|
||||
? undefined
|
||||
: transformTextField(value.addressStreet2, isNullEquivalenceEnabled),
|
||||
addressCity: isUndefined(value.addressCity)
|
||||
? undefined
|
||||
: transformTextField(value.addressCity, isNullEquivalenceEnabled),
|
||||
addressState: isUndefined(value.addressState)
|
||||
? undefined
|
||||
: transformTextField(value.addressState, isNullEquivalenceEnabled),
|
||||
addressPostcode: isUndefined(value.addressPostcode)
|
||||
? undefined
|
||||
: transformTextField(value.addressPostcode, isNullEquivalenceEnabled),
|
||||
addressCountry: isUndefined(value.addressCountry)
|
||||
? undefined
|
||||
: transformTextField(value.addressCountry, isNullEquivalenceEnabled),
|
||||
addressLat: isUndefined(value.addressLat)
|
||||
? undefined
|
||||
: transformNumericField(value.addressLat),
|
||||
addressLng: isUndefined(value.addressLng)
|
||||
? undefined
|
||||
: transformNumericField(value.addressLng),
|
||||
};
|
||||
};
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { isNull } from '@sniptt/guards';
|
||||
|
||||
export const transformArrayField = (
|
||||
value: string | string[] | null,
|
||||
isNullEquivalenceEnabled: boolean = false,
|
||||
): string[] | null => {
|
||||
if (typeof value === 'string') return [value];
|
||||
|
||||
return isNullEquivalenceEnabled &&
|
||||
!isNull(value) &&
|
||||
Object.keys(value).length === 0
|
||||
? null
|
||||
: value;
|
||||
};
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { isNull, isUndefined } from '@sniptt/guards';
|
||||
|
||||
import { transformNumericField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-numeric-field.util';
|
||||
import { transformTextField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-text-field.util';
|
||||
|
||||
export const transformCurrencyField = (
|
||||
value: {
|
||||
amountMicros?: number | string | null;
|
||||
currencyCode?: string | null;
|
||||
} | null,
|
||||
isNullEquivalenceEnabled: boolean = false,
|
||||
): {
|
||||
amountMicros?: number | null;
|
||||
currencyCode?: string | null;
|
||||
} | null => {
|
||||
if (isNull(value)) return null;
|
||||
|
||||
return {
|
||||
amountMicros: isUndefined(value.amountMicros)
|
||||
? undefined
|
||||
: transformNumericField(value.amountMicros),
|
||||
currencyCode: isUndefined(value.currencyCode)
|
||||
? undefined
|
||||
: transformTextField(value.currencyCode, isNullEquivalenceEnabled),
|
||||
};
|
||||
};
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { isNull, isUndefined } from '@sniptt/guards';
|
||||
|
||||
import { transformTextField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-text-field.util';
|
||||
|
||||
export const transformFullNameField = (
|
||||
value: {
|
||||
firstName?: string | null;
|
||||
lastName?: string | null;
|
||||
} | null,
|
||||
isNullEquivalenceEnabled: boolean = false,
|
||||
): {
|
||||
firstName?: string | null;
|
||||
lastName?: string | null;
|
||||
} | null => {
|
||||
if (isNull(value)) return null;
|
||||
|
||||
return {
|
||||
firstName: isUndefined(value.firstName)
|
||||
? undefined
|
||||
: transformTextField(value.firstName, isNullEquivalenceEnabled),
|
||||
lastName: isUndefined(value.lastName)
|
||||
? undefined
|
||||
: transformTextField(value.lastName, isNullEquivalenceEnabled),
|
||||
};
|
||||
};
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { isNull } from '@sniptt/guards';
|
||||
|
||||
export const transformNumericField = (
|
||||
value: number | string | null,
|
||||
): number | null => {
|
||||
return isNull(value) ? null : Number(value);
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { isNull } from '@sniptt/guards';
|
||||
|
||||
export const transformRawJsonField = (
|
||||
value: object | null,
|
||||
isNullEquivalenceEnabled: boolean = false,
|
||||
): object | null => {
|
||||
return isNullEquivalenceEnabled &&
|
||||
!isNull(value) &&
|
||||
Object.keys(value).length === 0
|
||||
? null
|
||||
: value;
|
||||
};
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
export const transformTextField = (
|
||||
value: string | null,
|
||||
isNullEquivalenceEnabled: boolean = false,
|
||||
): string | null => {
|
||||
return isNullEquivalenceEnabled && !isNonEmptyString(value) ? null : value;
|
||||
};
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
import { FieldActorSource } from 'twenty-shared/types';
|
||||
|
||||
import { validateActorFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-actor-field-or-throw.util';
|
||||
import { CommonQueryRunnerException } from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
describe('validateActorFieldOrThrow', () => {
|
||||
describe('valid inputs', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = validateActorFieldOrThrow(null, 'testField');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return valid actor object with source and context', () => {
|
||||
const validActor = {
|
||||
source: FieldActorSource.EMAIL,
|
||||
context: { userId: '123', email: 'test@example.com' },
|
||||
};
|
||||
|
||||
const result = validateActorFieldOrThrow(validActor, 'testField');
|
||||
|
||||
expect(result).toEqual(validActor);
|
||||
});
|
||||
|
||||
it('should accept empty context object', () => {
|
||||
const validActor = {
|
||||
source: FieldActorSource.EMAIL,
|
||||
context: {},
|
||||
};
|
||||
|
||||
const result = validateActorFieldOrThrow(validActor, 'testField');
|
||||
|
||||
expect(result).toEqual(validActor);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid inputs', () => {
|
||||
it('should throw when value is undefined', () => {
|
||||
expect(() => validateActorFieldOrThrow(undefined, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is a string', () => {
|
||||
expect(() => validateActorFieldOrThrow('invalid', 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when source is invalid', () => {
|
||||
const invalidActor = {
|
||||
source: 'INVALID_SOURCE',
|
||||
context: {},
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
validateActorFieldOrThrow(invalidActor, 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when context is a string', () => {
|
||||
const invalidActor = {
|
||||
source: FieldActorSource.EMAIL,
|
||||
context: 'invalid',
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
validateActorFieldOrThrow(invalidActor, 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when actor has invalid subfield', () => {
|
||||
const invalidActor = {
|
||||
source: FieldActorSource.EMAIL,
|
||||
context: {},
|
||||
invalidField: 'invalid',
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
validateActorFieldOrThrow(invalidActor, 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
});
|
||||
});
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
import { validateAddressFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-address-field-or-throw.util';
|
||||
import { CommonQueryRunnerException } from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
describe('validateAddressFieldOrThrow', () => {
|
||||
describe('valid inputs', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = validateAddressFieldOrThrow(null, 'testField');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return valid address object with all text fields', () => {
|
||||
const value = {
|
||||
addressStreet1: '123 Main St',
|
||||
addressStreet2: 'Apt 4B',
|
||||
addressCity: 'New York',
|
||||
addressState: 'NY',
|
||||
addressPostcode: '10001',
|
||||
addressCountry: 'USA',
|
||||
};
|
||||
const result = validateAddressFieldOrThrow(value, 'testField');
|
||||
|
||||
expect(result).toEqual(value);
|
||||
});
|
||||
|
||||
it('should return valid address object with coordinates', () => {
|
||||
const value = {
|
||||
addressStreet1: '123 Main St',
|
||||
addressCity: 'New York',
|
||||
addressLat: 40.7128,
|
||||
addressLng: -74.006,
|
||||
};
|
||||
const result = validateAddressFieldOrThrow(value, 'testField');
|
||||
|
||||
expect(result).toEqual(value);
|
||||
});
|
||||
|
||||
it('should return valid address object with null subfields', () => {
|
||||
const value = {
|
||||
addressStreet1: '123 Main St',
|
||||
addressStreet2: null,
|
||||
addressCity: 'New York',
|
||||
addressState: null,
|
||||
addressPostcode: null,
|
||||
addressCountry: null,
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
};
|
||||
const result = validateAddressFieldOrThrow(value, 'testField');
|
||||
|
||||
expect(result).toEqual(value);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid inputs', () => {
|
||||
it('should throw when value is not an object', () => {
|
||||
expect(() => validateAddressFieldOrThrow('invalid', 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is an array', () => {
|
||||
expect(() =>
|
||||
validateAddressFieldOrThrow(['123 Main St'], 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when addressStreet1 is not a string or number', () => {
|
||||
const value = {
|
||||
addressStreet1: { invalid: 'object' },
|
||||
};
|
||||
|
||||
expect(() => validateAddressFieldOrThrow(value, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when addressStreet2 is not a string', () => {
|
||||
const value = {
|
||||
addressStreet2: 123,
|
||||
};
|
||||
|
||||
expect(() => validateAddressFieldOrThrow(value, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when addressCity is not a string', () => {
|
||||
const value = {
|
||||
addressCity: true,
|
||||
};
|
||||
|
||||
expect(() => validateAddressFieldOrThrow(value, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when addressState is not a string', () => {
|
||||
const value = {
|
||||
addressState: ['NY'],
|
||||
};
|
||||
|
||||
expect(() => validateAddressFieldOrThrow(value, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when addressPostcode is not a string', () => {
|
||||
const value = {
|
||||
addressPostcode: 10001,
|
||||
};
|
||||
|
||||
expect(() => validateAddressFieldOrThrow(value, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when addressCountry is not a string', () => {
|
||||
const value = {
|
||||
addressCountry: { code: 'US' },
|
||||
};
|
||||
|
||||
expect(() => validateAddressFieldOrThrow(value, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when addressLat is not a number', () => {
|
||||
const value = {
|
||||
addressLat: 'not a number',
|
||||
};
|
||||
|
||||
expect(() => validateAddressFieldOrThrow(value, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when addressLng is not a number', () => {
|
||||
const value = {
|
||||
addressLng: 'not a number',
|
||||
};
|
||||
|
||||
expect(() => validateAddressFieldOrThrow(value, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when an invalid subfield is provided', () => {
|
||||
const value = {
|
||||
addressStreet1: '123 Main St',
|
||||
invalidSubField: 'invalid',
|
||||
};
|
||||
|
||||
expect(() => validateAddressFieldOrThrow(value, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
import { validateArrayFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-array-field-or-throw.util';
|
||||
import { CommonQueryRunnerException } from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
describe('validateArrayFieldOrThrow', () => {
|
||||
describe('valid inputs', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = validateArrayFieldOrThrow(null, 'testField');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return the string when value is a string', () => {
|
||||
const result = validateArrayFieldOrThrow('singleString', 'testField');
|
||||
|
||||
expect(result).toBe('singleString');
|
||||
});
|
||||
|
||||
it('should return the array when value is an empty array', () => {
|
||||
const result = validateArrayFieldOrThrow([], 'testField');
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return the array when value is an array of strings', () => {
|
||||
const stringArray = ['string1', 'string2', 'string3'];
|
||||
const result = validateArrayFieldOrThrow(stringArray, 'testField');
|
||||
|
||||
expect(result).toEqual(stringArray);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid inputs', () => {
|
||||
it('should throw when value is a number', () => {
|
||||
expect(() => validateArrayFieldOrThrow(123, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is an object', () => {
|
||||
const objectValue = { key: 'value' };
|
||||
|
||||
expect(() => validateArrayFieldOrThrow(objectValue, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is an array containing objects', () => {
|
||||
const arrayWithObjects = ['string1', { key: 'value' }, 'string2'];
|
||||
|
||||
expect(() =>
|
||||
validateArrayFieldOrThrow(arrayWithObjects, 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
});
|
||||
});
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
import { validateBooleanFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-boolean-field-or-throw.util';
|
||||
import { CommonQueryRunnerException } from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
describe('validateBooleanFieldOrThrow', () => {
|
||||
describe('valid inputs', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = validateBooleanFieldOrThrow(null, 'testField');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return true when value is true', () => {
|
||||
const result = validateBooleanFieldOrThrow(true, 'testField');
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when value is false', () => {
|
||||
const result = validateBooleanFieldOrThrow(false, 'testField');
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid inputs', () => {
|
||||
it('should throw when value is undefined', () => {
|
||||
expect(() => validateBooleanFieldOrThrow(undefined, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is a string "true"', () => {
|
||||
expect(() => validateBooleanFieldOrThrow('true', 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is an empty string', () => {
|
||||
expect(() => validateBooleanFieldOrThrow('', 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is a number 1', () => {
|
||||
expect(() => validateBooleanFieldOrThrow(1, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is a number 0', () => {
|
||||
expect(() => validateBooleanFieldOrThrow(0, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is an object', () => {
|
||||
expect(() => validateBooleanFieldOrThrow({}, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is a function', () => {
|
||||
const functionValue = () => true;
|
||||
|
||||
expect(() =>
|
||||
validateBooleanFieldOrThrow(functionValue, 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
});
|
||||
});
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
import { validateCurrencyFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-currency-field-or-throw.util';
|
||||
import { CommonQueryRunnerException } from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
describe('validateCurrencyFieldOrThrow', () => {
|
||||
describe('valid inputs', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = validateCurrencyFieldOrThrow(null, 'testField');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return the currency object when both amountMicros and currencyCode are valid', () => {
|
||||
const currencyValue = {
|
||||
amountMicros: 1000000,
|
||||
currencyCode: 'USD',
|
||||
};
|
||||
const result = validateCurrencyFieldOrThrow(currencyValue, 'testField');
|
||||
|
||||
expect(result).toEqual(currencyValue);
|
||||
});
|
||||
|
||||
it('should return the currency object when only amountMicros is provided', () => {
|
||||
const currencyValue = {
|
||||
amountMicros: 5000000,
|
||||
};
|
||||
const result = validateCurrencyFieldOrThrow(currencyValue, 'testField');
|
||||
|
||||
expect(result).toEqual(currencyValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid inputs', () => {
|
||||
it('should throw when value is not an object', () => {
|
||||
expect(() =>
|
||||
validateCurrencyFieldOrThrow('not an object', 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when amountMicros is not a valid numeric value', () => {
|
||||
const currencyValue = {
|
||||
amountMicros: 'not a number',
|
||||
currencyCode: 'USD',
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
validateCurrencyFieldOrThrow(currencyValue, 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when currencyCode is not a string', () => {
|
||||
const currencyValue = {
|
||||
amountMicros: 1000000,
|
||||
currencyCode: 123,
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
validateCurrencyFieldOrThrow(currencyValue, 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when an invalid subfield is present', () => {
|
||||
const currencyValue = {
|
||||
amountMicros: 1000000,
|
||||
currencyCode: 'USD',
|
||||
invalidField: 'invalid',
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
validateCurrencyFieldOrThrow(currencyValue, 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
});
|
||||
});
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
import { validateDateAndDateTimeFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-date-and-date-time-field-or-throw.util';
|
||||
import { CommonQueryRunnerException } from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
describe('validateDateAndDateTimeFieldOrThrow', () => {
|
||||
describe('valid inputs', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = validateDateAndDateTimeFieldOrThrow(null, 'testField');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return the value when it is a valid ISO date string', () => {
|
||||
const dateString = '2024-01-15';
|
||||
const result = validateDateAndDateTimeFieldOrThrow(
|
||||
dateString,
|
||||
'testField',
|
||||
);
|
||||
|
||||
expect(result).toBe(dateString);
|
||||
});
|
||||
|
||||
it('should return the value when it is a valid ISO datetime string', () => {
|
||||
const datetimeString = '2024-01-15T10:30:00Z';
|
||||
const result = validateDateAndDateTimeFieldOrThrow(
|
||||
datetimeString,
|
||||
'testField',
|
||||
);
|
||||
|
||||
expect(result).toBe(datetimeString);
|
||||
});
|
||||
|
||||
it('should return the value when it is a Date object', () => {
|
||||
const dateObject = new Date('2024-01-15');
|
||||
const result = validateDateAndDateTimeFieldOrThrow(
|
||||
dateObject,
|
||||
'testField',
|
||||
);
|
||||
|
||||
expect(result).toBe(dateObject);
|
||||
});
|
||||
|
||||
it('should return the value when it is a timestamp number', () => {
|
||||
const timestamp = Date.now();
|
||||
const result = validateDateAndDateTimeFieldOrThrow(
|
||||
timestamp,
|
||||
'testField',
|
||||
);
|
||||
|
||||
expect(result).toBe(timestamp);
|
||||
});
|
||||
|
||||
it('should return the value when it is a Date', () => {
|
||||
const date = new Date();
|
||||
const result = validateDateAndDateTimeFieldOrThrow(date, 'testField');
|
||||
|
||||
expect(result).toBe(date);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid inputs', () => {
|
||||
it('should throw when value is an invalid date string', () => {
|
||||
expect(() =>
|
||||
validateDateAndDateTimeFieldOrThrow('invalid-date', 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when value is an empty string', () => {
|
||||
expect(() =>
|
||||
validateDateAndDateTimeFieldOrThrow('', 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when value is a boolean', () => {
|
||||
expect(() =>
|
||||
validateDateAndDateTimeFieldOrThrow(true, 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when value is an array', () => {
|
||||
expect(() =>
|
||||
validateDateAndDateTimeFieldOrThrow([], 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when value is an object', () => {
|
||||
expect(() =>
|
||||
validateDateAndDateTimeFieldOrThrow({}, 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when value is undefined', () => {
|
||||
expect(() =>
|
||||
validateDateAndDateTimeFieldOrThrow(undefined, 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
});
|
||||
});
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
import { validateEmailsFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-emails-field-or-throw.util';
|
||||
import { CommonQueryRunnerException } from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
describe('validateEmailsFieldOrThrow', () => {
|
||||
describe('valid inputs', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = validateEmailsFieldOrThrow(null, 'testField');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return the emails object when all fields are valid', () => {
|
||||
const emailsValue = {
|
||||
primaryEmail: 'primary@example.com',
|
||||
additionalEmails: ['secondary1@example.com', 'secondary2@example.com'],
|
||||
};
|
||||
const result = validateEmailsFieldOrThrow(emailsValue, 'testField');
|
||||
|
||||
expect(result).toEqual(emailsValue);
|
||||
});
|
||||
|
||||
it('should return the emails object when only primaryEmail is provided', () => {
|
||||
const emailsValue = {
|
||||
primaryEmail: 'primary@example.com',
|
||||
};
|
||||
const result = validateEmailsFieldOrThrow(emailsValue, 'testField');
|
||||
|
||||
expect(result).toEqual(emailsValue);
|
||||
});
|
||||
|
||||
it('should accept empty additionalEmails array', () => {
|
||||
const emailsValue = {
|
||||
primaryEmail: 'primary@example.com',
|
||||
additionalEmails: [],
|
||||
};
|
||||
const result = validateEmailsFieldOrThrow(emailsValue, 'testField');
|
||||
|
||||
expect(result).toEqual(emailsValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid inputs', () => {
|
||||
it('should throw when value is not an object', () => {
|
||||
expect(() =>
|
||||
validateEmailsFieldOrThrow('not an object', 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when value is undefined', () => {
|
||||
expect(() => validateEmailsFieldOrThrow(undefined, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when primaryEmail is not a string', () => {
|
||||
const emailsValue = {
|
||||
primaryEmail: 12345,
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
validateEmailsFieldOrThrow(emailsValue, 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when additionalEmails is not an array', () => {
|
||||
const emailsValue = {
|
||||
additionalEmails: { key: 'not an array' },
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
validateEmailsFieldOrThrow(emailsValue, 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when invalid subfields are present', () => {
|
||||
const emailsValue = {
|
||||
primaryEmail: 'primary@example.com',
|
||||
invalidField1: 'invalid',
|
||||
invalidField2: 'invalid',
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
validateEmailsFieldOrThrow(emailsValue, 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
});
|
||||
});
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
import { validateFullNameFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-full-name-field-or-throw.util';
|
||||
import { CommonQueryRunnerException } from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
describe('validateFullNameFieldOrThrow', () => {
|
||||
describe('valid inputs', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = validateFullNameFieldOrThrow(null, 'testField');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return valid full name object with both fields', () => {
|
||||
const value = {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
};
|
||||
const result = validateFullNameFieldOrThrow(value, 'testField');
|
||||
|
||||
expect(result).toEqual(value);
|
||||
});
|
||||
|
||||
it('should return valid full name object with only firstName', () => {
|
||||
const value = {
|
||||
firstName: 'John',
|
||||
};
|
||||
const result = validateFullNameFieldOrThrow(value, 'testField');
|
||||
|
||||
expect(result).toEqual(value);
|
||||
});
|
||||
|
||||
it('should return valid full name object with only lastName', () => {
|
||||
const value = {
|
||||
lastName: 'Doe',
|
||||
};
|
||||
const result = validateFullNameFieldOrThrow(value, 'testField');
|
||||
|
||||
expect(result).toEqual(value);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid inputs', () => {
|
||||
it('should throw when value is not an object', () => {
|
||||
expect(() =>
|
||||
validateFullNameFieldOrThrow('invalid', 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when firstName is not a string', () => {
|
||||
const value = {
|
||||
firstName: { invalid: 'object' },
|
||||
};
|
||||
|
||||
expect(() => validateFullNameFieldOrThrow(value, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when an invalid subfield is provided', () => {
|
||||
const value = {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
invalidSubField: 'invalid',
|
||||
};
|
||||
|
||||
expect(() => validateFullNameFieldOrThrow(value, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
import { validateLinksFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-links-field-or-throw.util';
|
||||
import { CommonQueryRunnerException } from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
describe('validateLinksFieldOrThrow', () => {
|
||||
describe('valid inputs', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = validateLinksFieldOrThrow(null, 'testField');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return the links object when all fields are valid', () => {
|
||||
const linksValue = {
|
||||
primaryLinkUrl: 'https://example.com',
|
||||
primaryLinkLabel: 'Example Website',
|
||||
secondaryLinks: [{ url: 'https://secondary.com', label: 'Secondary' }],
|
||||
};
|
||||
const result = validateLinksFieldOrThrow(linksValue, 'testField');
|
||||
|
||||
expect(result).toEqual(linksValue);
|
||||
});
|
||||
|
||||
it('should return the links object when only primaryLinkUrl is provided', () => {
|
||||
const linksValue = {
|
||||
primaryLinkUrl: 'https://example.com',
|
||||
};
|
||||
const result = validateLinksFieldOrThrow(linksValue, 'testField');
|
||||
|
||||
expect(result).toEqual(linksValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid inputs', () => {
|
||||
it('should throw when value is not an object', () => {
|
||||
expect(() =>
|
||||
validateLinksFieldOrThrow('not an object', 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when value is undefined', () => {
|
||||
expect(() => validateLinksFieldOrThrow(undefined, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when primaryLinkUrl is not a string', () => {
|
||||
const linksValue = {
|
||||
primaryLinkUrl: 12345,
|
||||
};
|
||||
|
||||
expect(() => validateLinksFieldOrThrow(linksValue, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when primaryLinkLabel is not a string', () => {
|
||||
const linksValue = {
|
||||
primaryLinkLabel: ['not', 'a', 'string'],
|
||||
};
|
||||
|
||||
expect(() => validateLinksFieldOrThrow(linksValue, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when secondaryLinks is not an object or null', () => {
|
||||
const linksValue = {
|
||||
secondaryLinks: 'not an object',
|
||||
};
|
||||
|
||||
expect(() => validateLinksFieldOrThrow(linksValue, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when invalid subfields are present', () => {
|
||||
const linksValue = {
|
||||
primaryLinkUrl: 'https://example.com',
|
||||
invalidField1: 'invalid',
|
||||
invalidField2: 'invalid',
|
||||
};
|
||||
|
||||
expect(() => validateLinksFieldOrThrow(linksValue, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
import { validateMultiSelectFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-multi-select-field-or-throw.util';
|
||||
import { CommonQueryRunnerException } from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
describe('validateMultiSelectFieldOrThrow', () => {
|
||||
const validOptions = ['option1', 'option2', 'option3'];
|
||||
|
||||
describe('valid inputs', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = validateMultiSelectFieldOrThrow(
|
||||
null,
|
||||
'testField',
|
||||
validOptions,
|
||||
);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return array when all values are in options', () => {
|
||||
const value = ['option1', 'option2'];
|
||||
const result = validateMultiSelectFieldOrThrow(
|
||||
value,
|
||||
'testField',
|
||||
validOptions,
|
||||
);
|
||||
|
||||
expect(result).toEqual(value);
|
||||
});
|
||||
|
||||
it('should return string when value is a single string in options', () => {
|
||||
const value = 'option1';
|
||||
const result = validateMultiSelectFieldOrThrow(
|
||||
value,
|
||||
'testField',
|
||||
validOptions,
|
||||
);
|
||||
|
||||
expect(result).toEqual(value);
|
||||
});
|
||||
|
||||
it('should return empty array when value is empty array', () => {
|
||||
const value: string[] = [];
|
||||
const result = validateMultiSelectFieldOrThrow(
|
||||
value,
|
||||
'testField',
|
||||
validOptions,
|
||||
);
|
||||
|
||||
expect(result).toEqual(value);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid inputs', () => {
|
||||
it('should throw when options are undefined', () => {
|
||||
expect(() =>
|
||||
validateMultiSelectFieldOrThrow(['option1'], 'testField', undefined),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when options are not defined', () => {
|
||||
expect(() =>
|
||||
validateMultiSelectFieldOrThrow(['option1'], 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when value contains option not in the options list', () => {
|
||||
const value = ['option1', 'invalidOption'];
|
||||
|
||||
expect(() =>
|
||||
validateMultiSelectFieldOrThrow(value, 'testField', validOptions),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
});
|
||||
});
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
import { validateNumberFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-number-field-or-throw.util';
|
||||
|
||||
describe('validateNumberFieldOrThrow', () => {
|
||||
describe('valid inputs', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = validateNumberFieldOrThrow(null, 'testField');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return the number when value is a positive integer', () => {
|
||||
const result = validateNumberFieldOrThrow(123, 'testField');
|
||||
|
||||
expect(result).toBe(123);
|
||||
});
|
||||
|
||||
it('should return the number when value is a negative integer', () => {
|
||||
const result = validateNumberFieldOrThrow(-456, 'testField');
|
||||
|
||||
expect(result).toBe(-456);
|
||||
});
|
||||
|
||||
it('should return the number when value is a positive float', () => {
|
||||
const result = validateNumberFieldOrThrow(123.45, 'testField');
|
||||
|
||||
expect(result).toBe(123.45);
|
||||
});
|
||||
|
||||
it('should return the number when value is zero', () => {
|
||||
const result = validateNumberFieldOrThrow(0, 'testField');
|
||||
|
||||
expect(result).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid inputs', () => {
|
||||
it('should throw when value is undefined', () => {
|
||||
expect(() => validateNumberFieldOrThrow(undefined, 'testField')).toThrow(
|
||||
'Invalid number value undefined for field "testField"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is a string with a number', () => {
|
||||
expect(() => validateNumberFieldOrThrow('123', 'testField')).toThrow(
|
||||
'Invalid number value \'123\' for field "testField"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is an empty string', () => {
|
||||
expect(() => validateNumberFieldOrThrow('', 'testField')).toThrow(
|
||||
'Invalid number value \'\' for field "testField"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is a boolean (true)', () => {
|
||||
expect(() => validateNumberFieldOrThrow(true, 'testField')).toThrow(
|
||||
'Invalid number value true for field "testField"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is a boolean (false)', () => {
|
||||
expect(() => validateNumberFieldOrThrow(false, 'testField')).toThrow(
|
||||
'Invalid number value false for field "testField"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is an array', () => {
|
||||
expect(() => validateNumberFieldOrThrow([1, 2, 3], 'testField')).toThrow(
|
||||
'Invalid number value [ 1, 2, 3 ] for field "testField"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is an object', () => {
|
||||
expect(() =>
|
||||
validateNumberFieldOrThrow({ key: 'value' }, 'testField'),
|
||||
).toThrow(
|
||||
'Invalid number value { key: \'value\' } for field "testField"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is NaN', () => {
|
||||
expect(() => validateNumberFieldOrThrow(NaN, 'testField')).toThrow(
|
||||
'Invalid number value NaN for field "testField"',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import { validateNumericFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-numeric-field-or-throw.util';
|
||||
import { CommonQueryRunnerException } from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
describe('validateNumericFieldOrThrow', () => {
|
||||
describe('valid inputs', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = validateNumericFieldOrThrow(null, 'testField');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null when value is an empty string', () => {
|
||||
const result = validateNumericFieldOrThrow('', 'testField');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return the number when value is a float', () => {
|
||||
const result = validateNumericFieldOrThrow(3.14159, 'testField');
|
||||
|
||||
expect(result).toBe(3.14159);
|
||||
});
|
||||
});
|
||||
describe('invalid inputs', () => {
|
||||
it('should throw when value is NaN', () => {
|
||||
expect(() => validateNumericFieldOrThrow(NaN, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is a non-numeric string', () => {
|
||||
expect(() =>
|
||||
validateNumericFieldOrThrow('not a number', 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when value is undefined', () => {
|
||||
expect(() => validateNumericFieldOrThrow(undefined, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is an array', () => {
|
||||
expect(() => validateNumericFieldOrThrow([1, 2, 3], 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
import { validatePhonesFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-phones-field-or-throw.util';
|
||||
import { CommonQueryRunnerException } from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
describe('validatePhonesFieldOrThrow', () => {
|
||||
describe('valid inputs', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = validatePhonesFieldOrThrow(null, 'testField');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return the phones object when all fields are valid', () => {
|
||||
const phonesValue = {
|
||||
primaryPhoneNumber: '+1234567890',
|
||||
primaryPhoneCountryCode: 'US',
|
||||
primaryPhoneCallingCode: '+1',
|
||||
additionalPhones: null,
|
||||
};
|
||||
const result = validatePhonesFieldOrThrow(phonesValue, 'testField');
|
||||
|
||||
expect(result).toEqual(phonesValue);
|
||||
});
|
||||
|
||||
it('should return the phones object when only primaryPhoneNumber is provided', () => {
|
||||
const phonesValue = {
|
||||
primaryPhoneNumber: '+1234567890',
|
||||
};
|
||||
const result = validatePhonesFieldOrThrow(phonesValue, 'testField');
|
||||
|
||||
expect(result).toEqual(phonesValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid inputs', () => {
|
||||
it('should throw when value is not an object', () => {
|
||||
expect(() =>
|
||||
validatePhonesFieldOrThrow('not an object', 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when value is undefined', () => {
|
||||
expect(() => validatePhonesFieldOrThrow(undefined, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when primaryPhoneNumber is not a string', () => {
|
||||
const phonesValue = {
|
||||
primaryPhoneNumber: 123456,
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
validatePhonesFieldOrThrow(phonesValue, 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when primaryPhoneCountryCode is not a string', () => {
|
||||
const phonesValue = {
|
||||
primaryPhoneCountryCode: 123,
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
validatePhonesFieldOrThrow(phonesValue, 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when primaryPhoneCallingCode is not a string', () => {
|
||||
const phonesValue = {
|
||||
primaryPhoneCallingCode: 1,
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
validatePhonesFieldOrThrow(phonesValue, 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when an invalid subfield is present', () => {
|
||||
const phonesValue = {
|
||||
primaryPhoneNumber: '+1234567890',
|
||||
invalidField: 'invalid',
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
validatePhonesFieldOrThrow(phonesValue, 'testField'),
|
||||
).toThrow('Invalid subfield invalidField for phones field "testField"');
|
||||
});
|
||||
});
|
||||
});
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
import { validateRatingAndSelectFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-rating-and-select-field-or-throw.util';
|
||||
|
||||
describe('validateRatingAndSelectFieldOrThrow', () => {
|
||||
const validOptions = ['option1', 'option2', 'option3'];
|
||||
|
||||
describe('valid inputs', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = validateRatingAndSelectFieldOrThrow(
|
||||
null,
|
||||
'testField',
|
||||
validOptions,
|
||||
);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return the string when value is a valid option', () => {
|
||||
const result = validateRatingAndSelectFieldOrThrow(
|
||||
'option1',
|
||||
'testField',
|
||||
validOptions,
|
||||
);
|
||||
|
||||
expect(result).toBe('option1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid inputs', () => {
|
||||
it('should throw when options is undefined', () => {
|
||||
expect(() =>
|
||||
validateRatingAndSelectFieldOrThrow('option1', 'testField', undefined),
|
||||
).toThrow('Invalid options for field "testField"');
|
||||
});
|
||||
|
||||
it('should throw when value is not in the options list', () => {
|
||||
expect(() =>
|
||||
validateRatingAndSelectFieldOrThrow(
|
||||
'invalidOption',
|
||||
'testField',
|
||||
validOptions,
|
||||
),
|
||||
).toThrow('Invalid value \'invalidOption\' for field "testField"');
|
||||
});
|
||||
|
||||
it('should throw when value is a number', () => {
|
||||
expect(() =>
|
||||
validateRatingAndSelectFieldOrThrow(123, 'testField', validOptions),
|
||||
).toThrow('Invalid string value 123 for text field "testField"');
|
||||
});
|
||||
});
|
||||
});
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
import { validateRawJsonFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-raw-json-field-or-throw.util';
|
||||
import { CommonQueryRunnerException } from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
describe('validateRawJsonFieldOrThrow', () => {
|
||||
describe('valid inputs', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = validateRawJsonFieldOrThrow(null, 'testField');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return empty object when value is an empty object', () => {
|
||||
const result = validateRawJsonFieldOrThrow({}, 'testField');
|
||||
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
it('should return the value when it is a valid JSON object', () => {
|
||||
const jsonObject = { key: 'value', nested: { prop: 123 } };
|
||||
const result = validateRawJsonFieldOrThrow(jsonObject, 'testField');
|
||||
|
||||
expect(result).toEqual(jsonObject);
|
||||
});
|
||||
|
||||
it('should return the value when it is a valid JSON array', () => {
|
||||
const jsonArray = [1, 2, 3, 'test', { key: 'value' }];
|
||||
const result = validateRawJsonFieldOrThrow(jsonArray, 'testField');
|
||||
|
||||
expect(result).toEqual(jsonArray);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid inputs', () => {
|
||||
it('should throw when value is undefined', () => {
|
||||
expect(() => validateRawJsonFieldOrThrow(undefined, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is a function', () => {
|
||||
const functionValue = () => 'test';
|
||||
|
||||
expect(() =>
|
||||
validateRawJsonFieldOrThrow(functionValue, 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when value is a number', () => {
|
||||
expect(() => validateRawJsonFieldOrThrow(42, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is a string', () => {
|
||||
expect(() =>
|
||||
validateRawJsonFieldOrThrow('string value', 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
});
|
||||
});
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import { validateRichTextV2FieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-rich-text-v2-field-or-throw.util';
|
||||
import { CommonQueryRunnerException } from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
describe('validateRichTextV2FieldOrThrow', () => {
|
||||
describe('valid inputs', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = validateRichTextV2FieldOrThrow(null, 'testField');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null when value is an empty object', () => {
|
||||
const result = validateRichTextV2FieldOrThrow({}, 'testField');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return the value when it has valid subfields', () => {
|
||||
const value = {
|
||||
blocknote: 'some blocknote content',
|
||||
markdown: '# Heading\nContent',
|
||||
};
|
||||
const result = validateRichTextV2FieldOrThrow(value, 'testField');
|
||||
|
||||
expect(result).toEqual(value);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid inputs', () => {
|
||||
it('should throw when value is undefined', () => {
|
||||
expect(() =>
|
||||
validateRichTextV2FieldOrThrow(undefined, 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when value is a string', () => {
|
||||
expect(() =>
|
||||
validateRichTextV2FieldOrThrow('not an object', 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
|
||||
it('should throw when value has invalid subfields', () => {
|
||||
const value = { invalidField: 'value' };
|
||||
|
||||
expect(() => validateRichTextV2FieldOrThrow(value, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
expect(() => validateRichTextV2FieldOrThrow(value, 'testField')).toThrow(
|
||||
/Should have only blocknote, markdown subfields/,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
import { validateTextFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-text-field-or-throw.util';
|
||||
|
||||
describe('validateTextFieldOrThrow', () => {
|
||||
describe('valid inputs', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = validateTextFieldOrThrow(null, 'testField');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return empty string when value is an empty string', () => {
|
||||
const result = validateTextFieldOrThrow('', 'testField');
|
||||
|
||||
expect(result).toEqual('');
|
||||
});
|
||||
|
||||
it('should return the string when value is a regular string', () => {
|
||||
const result = validateTextFieldOrThrow('hello world', 'testField');
|
||||
|
||||
expect(result).toBe('hello world');
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid inputs', () => {
|
||||
it('should throw when value is undefined', () => {
|
||||
expect(() => validateTextFieldOrThrow(undefined, 'testField')).toThrow(
|
||||
'Invalid string value undefined for text field "testField"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is a number', () => {
|
||||
expect(() => validateTextFieldOrThrow(123, 'testField')).toThrow(
|
||||
'Invalid string value 123 for text field "testField"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is a float number', () => {
|
||||
expect(() => validateTextFieldOrThrow(123.45, 'testField')).toThrow(
|
||||
'Invalid string value 123.45 for text field "testField"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is a boolean (true)', () => {
|
||||
expect(() => validateTextFieldOrThrow(true, 'testField')).toThrow(
|
||||
'Invalid string value true for text field "testField"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is a boolean (false)', () => {
|
||||
expect(() => validateTextFieldOrThrow(false, 'testField')).toThrow(
|
||||
'Invalid string value false for text field "testField"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is an array', () => {
|
||||
expect(() => validateTextFieldOrThrow([1, 2, 3], 'testField')).toThrow(
|
||||
'Invalid string value [ 1, 2, 3 ] for text field "testField"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is an object', () => {
|
||||
expect(() =>
|
||||
validateTextFieldOrThrow({ key: 'value' }, 'testField'),
|
||||
).toThrow(
|
||||
'Invalid string value { key: \'value\' } for text field "testField"',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import { validateUUIDFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-uuid-field-or-throw.util';
|
||||
import { CommonQueryRunnerException } from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
describe('validateUUIDFieldOrThrow', () => {
|
||||
describe('valid inputs', () => {
|
||||
it('should return null when value is null', () => {
|
||||
const result = validateUUIDFieldOrThrow(null, 'testField');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return the value when it is a valid UUID v4', () => {
|
||||
const validUuid = '550e8400-e29b-41d4-a716-446655440000';
|
||||
const result = validateUUIDFieldOrThrow(validUuid, 'testField');
|
||||
|
||||
expect(result).toBe(validUuid);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid inputs', () => {
|
||||
it('should throw when value is undefined', () => {
|
||||
expect(() => validateUUIDFieldOrThrow(undefined, 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is an empty string', () => {
|
||||
expect(() => validateUUIDFieldOrThrow('', 'testField')).toThrow(
|
||||
CommonQueryRunnerException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when value is an invalid UUID format', () => {
|
||||
expect(() =>
|
||||
validateUUIDFieldOrThrow('invalid-uuid', 'testField'),
|
||||
).toThrow(CommonQueryRunnerException);
|
||||
});
|
||||
});
|
||||
});
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import { isNull } from '@sniptt/guards';
|
||||
import { FieldActorSource } from 'twenty-shared/types';
|
||||
|
||||
import { validateRatingAndSelectFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-rating-and-select-field-or-throw.util';
|
||||
import { validateRawJsonFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-raw-json-field-or-throw.util';
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
export const validateActorFieldOrThrow = (
|
||||
value: unknown,
|
||||
fieldName: string,
|
||||
): { source: FieldActorSource; context: Record<string, unknown> } | null => {
|
||||
const preValidatedValue = validateRawJsonFieldOrThrow(value, fieldName);
|
||||
|
||||
if (isNull(preValidatedValue)) return null;
|
||||
|
||||
for (const [subField, subFieldValue] of Object.entries(preValidatedValue)) {
|
||||
switch (subField) {
|
||||
case 'source':
|
||||
validateRatingAndSelectFieldOrThrow(
|
||||
subFieldValue,
|
||||
`${fieldName}.${subField}`,
|
||||
Object.keys(FieldActorSource),
|
||||
);
|
||||
break;
|
||||
case 'context':
|
||||
validateRawJsonFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
|
||||
break;
|
||||
default:
|
||||
throw new CommonQueryRunnerException(
|
||||
`Invalid subfield ${subField} for actor field "${fieldName}"`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return value as {
|
||||
source: FieldActorSource;
|
||||
context: Record<string, unknown>;
|
||||
};
|
||||
};
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
import { isNull } from '@sniptt/guards';
|
||||
|
||||
import { validateNumericFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-numeric-field-or-throw.util';
|
||||
import { validateRawJsonFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-raw-json-field-or-throw.util';
|
||||
import { validateTextFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-text-field-or-throw.util';
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
export const validateAddressFieldOrThrow = (
|
||||
value: unknown,
|
||||
fieldName: string,
|
||||
): {
|
||||
addressStreet1?: string | null;
|
||||
addressStreet2?: string | null;
|
||||
addressCity?: string | null;
|
||||
addressState?: string | null;
|
||||
addressPostcode?: string | null;
|
||||
addressCountry?: string | null;
|
||||
addressLat?: number | null;
|
||||
addressLng?: number | null;
|
||||
} | null => {
|
||||
const preValidatedValue = validateRawJsonFieldOrThrow(value, fieldName);
|
||||
|
||||
if (isNull(preValidatedValue)) return null;
|
||||
|
||||
for (const [subField, subFieldValue] of Object.entries(preValidatedValue)) {
|
||||
switch (subField) {
|
||||
case 'addressStreet1':
|
||||
validateTextFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
|
||||
break;
|
||||
case 'addressStreet2':
|
||||
validateTextFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
|
||||
break;
|
||||
case 'addressCity':
|
||||
validateTextFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
|
||||
break;
|
||||
case 'addressState':
|
||||
validateTextFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
|
||||
break;
|
||||
case 'addressPostcode':
|
||||
validateTextFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
|
||||
break;
|
||||
case 'addressCountry':
|
||||
validateTextFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
|
||||
break;
|
||||
case 'addressLat':
|
||||
validateNumericFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
|
||||
break;
|
||||
case 'addressLng':
|
||||
validateNumericFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
|
||||
break;
|
||||
default:
|
||||
throw new CommonQueryRunnerException(
|
||||
`Invalid subfield ${subField} for address field "${fieldName}"`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return value as {
|
||||
addressStreet1?: string | null;
|
||||
addressStreet2?: string | null;
|
||||
addressCity?: string | null;
|
||||
addressState?: string | null;
|
||||
addressPostcode?: string | null;
|
||||
addressCountry?: string | null;
|
||||
addressLat?: number | null;
|
||||
addressLng?: number | null;
|
||||
};
|
||||
};
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { inspect } from 'util';
|
||||
|
||||
import { isNull } from '@sniptt/guards';
|
||||
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
export const validateArrayFieldOrThrow = (
|
||||
value: unknown,
|
||||
fieldName: string,
|
||||
): string | string[] | null => {
|
||||
if (isNull(value)) return null;
|
||||
|
||||
if (typeof value === 'string') return value;
|
||||
|
||||
if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
|
||||
throw new CommonQueryRunnerException(
|
||||
`Invalid value ${inspect(value)} for field "${fieldName} - Array values need to be string"`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { inspect } from 'util';
|
||||
|
||||
import { isNull } from '@sniptt/guards';
|
||||
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
export const validateBooleanFieldOrThrow = (
|
||||
value: unknown,
|
||||
fieldName: string,
|
||||
): boolean | null => {
|
||||
if (typeof value !== 'boolean' && !isNull(value))
|
||||
throw new CommonQueryRunnerException(
|
||||
`Invalid boolean value ${inspect(value)} for field "${fieldName}"`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
|
||||
return value;
|
||||
};
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import { isNull } from '@sniptt/guards';
|
||||
|
||||
import { validateNumericFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-numeric-field-or-throw.util';
|
||||
import { validateRawJsonFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-raw-json-field-or-throw.util';
|
||||
import { validateTextFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-text-field-or-throw.util';
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
export const validateCurrencyFieldOrThrow = (
|
||||
value: unknown,
|
||||
fieldName: string,
|
||||
): {
|
||||
amountMicros?: number | string | null;
|
||||
currencyCode?: string | null;
|
||||
} | null => {
|
||||
const preValidatedValue = validateRawJsonFieldOrThrow(value, fieldName);
|
||||
|
||||
if (isNull(preValidatedValue)) return null;
|
||||
|
||||
for (const [subField, subFieldValue] of Object.entries(preValidatedValue)) {
|
||||
switch (subField) {
|
||||
case 'amountMicros':
|
||||
validateNumericFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
|
||||
break;
|
||||
case 'currencyCode':
|
||||
validateTextFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new CommonQueryRunnerException(
|
||||
`Invalid subfield ${subField} for currency field "${fieldName}"`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return value as {
|
||||
amountMicros?: number | string | null;
|
||||
currencyCode?: string | null;
|
||||
};
|
||||
};
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { inspect } from 'util';
|
||||
|
||||
import { isDate, isNull, isNumber, isString } from '@sniptt/guards';
|
||||
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
export const validateDateAndDateTimeFieldOrThrow = (
|
||||
value: unknown,
|
||||
fieldName: string,
|
||||
) => {
|
||||
if (isNull(value)) return null;
|
||||
|
||||
if (isString(value) || isNumber(value) || isDate(value)) {
|
||||
const date = new Date(value);
|
||||
|
||||
if (!isNaN(date.getTime())) return value;
|
||||
}
|
||||
|
||||
throw new CommonQueryRunnerException(
|
||||
`Invalid value ${inspect(value)} for date or date-time field "${fieldName}"`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
};
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { isNull } from '@sniptt/guards';
|
||||
|
||||
import { validateArrayFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-array-field-or-throw.util';
|
||||
import { validateRawJsonFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-raw-json-field-or-throw.util';
|
||||
import { validateTextFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-text-field-or-throw.util';
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
export const validateEmailsFieldOrThrow = (
|
||||
value: unknown,
|
||||
fieldName: string,
|
||||
): {
|
||||
primaryEmail?: string | null;
|
||||
additionalEmails?: string[] | null;
|
||||
} | null => {
|
||||
const preValidatedValue = validateRawJsonFieldOrThrow(value, fieldName);
|
||||
|
||||
if (isNull(preValidatedValue)) return null;
|
||||
|
||||
for (const [subField, subFieldValue] of Object.entries(preValidatedValue)) {
|
||||
switch (subField) {
|
||||
case 'primaryEmail':
|
||||
validateTextFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
|
||||
break;
|
||||
case 'additionalEmails':
|
||||
validateArrayFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
|
||||
break;
|
||||
default:
|
||||
throw new CommonQueryRunnerException(
|
||||
`Invalid subfield ${subField} for emails field "${fieldName}"`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return value as {
|
||||
primaryEmail?: string | null;
|
||||
additionalEmails?: string[] | null;
|
||||
};
|
||||
};
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import { isNull } from '@sniptt/guards';
|
||||
|
||||
import { validateRawJsonFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-raw-json-field-or-throw.util';
|
||||
import { validateTextFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-text-field-or-throw.util';
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
export const validateFullNameFieldOrThrow = (
|
||||
value: unknown,
|
||||
fieldName: string,
|
||||
): {
|
||||
firstName?: string | null;
|
||||
lastName?: string | null;
|
||||
} | null => {
|
||||
const preValidatedValue = validateRawJsonFieldOrThrow(value, fieldName);
|
||||
|
||||
if (isNull(preValidatedValue)) return null;
|
||||
|
||||
for (const [subField, subFieldValue] of Object.entries(preValidatedValue)) {
|
||||
switch (subField) {
|
||||
case 'firstName':
|
||||
validateTextFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
|
||||
break;
|
||||
case 'lastName':
|
||||
validateTextFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
|
||||
break;
|
||||
default:
|
||||
throw new CommonQueryRunnerException(
|
||||
`Invalid subfield ${subField} for full name field "${fieldName}"`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return value as {
|
||||
firstName?: string | null;
|
||||
lastName?: string | null;
|
||||
};
|
||||
};
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import { isNull } from '@sniptt/guards';
|
||||
|
||||
import { validateRawJsonFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-raw-json-field-or-throw.util';
|
||||
import { validateTextFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-text-field-or-throw.util';
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
import { type LinksFieldGraphQLInput } from 'src/engine/core-modules/record-transformer/utils/transform-links-value.util';
|
||||
|
||||
export const validateLinksFieldOrThrow = (
|
||||
value: unknown,
|
||||
fieldName: string,
|
||||
): LinksFieldGraphQLInput | null => {
|
||||
const preValidatedValue = validateRawJsonFieldOrThrow(value, fieldName);
|
||||
|
||||
if (isNull(preValidatedValue)) return null;
|
||||
|
||||
for (const [subField, subFieldValue] of Object.entries(preValidatedValue)) {
|
||||
switch (subField) {
|
||||
case 'primaryLinkUrl':
|
||||
validateTextFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
|
||||
break;
|
||||
case 'primaryLinkLabel':
|
||||
validateTextFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
|
||||
break;
|
||||
case 'secondaryLinks':
|
||||
validateRawJsonFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
|
||||
break;
|
||||
default:
|
||||
throw new CommonQueryRunnerException(
|
||||
`Invalid subfield ${subField} for links field "${fieldName}"`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return value as LinksFieldGraphQLInput;
|
||||
};
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import { inspect } from 'util';
|
||||
|
||||
import { isNull } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { validateArrayFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-array-field-or-throw.util';
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
export const validateMultiSelectFieldOrThrow = (
|
||||
value: unknown,
|
||||
fieldName: string,
|
||||
options?: string[],
|
||||
): string | string[] | null => {
|
||||
const preValidatedValue = validateArrayFieldOrThrow(value, fieldName);
|
||||
|
||||
if (isNull(preValidatedValue)) return null;
|
||||
|
||||
if (!isDefined(options)) {
|
||||
throw new CommonQueryRunnerException(
|
||||
`Invalid options for field "${fieldName}"`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
(Array.isArray(preValidatedValue)
|
||||
? preValidatedValue
|
||||
: [preValidatedValue]
|
||||
).some((item) => !options.includes(item))
|
||||
) {
|
||||
throw new CommonQueryRunnerException(
|
||||
`Invalid value ${inspect(value)} for multi select field "${fieldName}"`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
return value as string | string[];
|
||||
};
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { inspect } from 'util';
|
||||
|
||||
import { isNull } from '@sniptt/guards';
|
||||
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
export const validateNumberFieldOrThrow = (
|
||||
value: unknown,
|
||||
fieldName: string,
|
||||
): number | null => {
|
||||
if (
|
||||
(typeof value !== 'number' && !isNull(value)) ||
|
||||
(typeof value === 'number' &&
|
||||
(isNaN(value) || value === Infinity || value === -Infinity))
|
||||
)
|
||||
throw new CommonQueryRunnerException(
|
||||
`Invalid number value ${inspect(value)} for field "${fieldName}"`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
|
||||
return value;
|
||||
};
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import { isNull } from '@sniptt/guards';
|
||||
|
||||
import { validateNumberFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-number-field-or-throw.util';
|
||||
|
||||
//Need to handle stringified numbers because of BigFloatScalarType custom gql type
|
||||
|
||||
export const validateNumericFieldOrThrow = (
|
||||
value: unknown,
|
||||
fieldName: string,
|
||||
): number | string | null => {
|
||||
if (value === '' || isNull(value)) return null;
|
||||
|
||||
const numberValue = Number(value);
|
||||
|
||||
validateNumberFieldOrThrow(numberValue, fieldName);
|
||||
|
||||
return value as number | string;
|
||||
};
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import { isNull } from '@sniptt/guards';
|
||||
|
||||
import { validateRawJsonFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-raw-json-field-or-throw.util';
|
||||
import { validateTextFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-text-field-or-throw.util';
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
import { type PhonesFieldGraphQLInput } from 'src/engine/core-modules/record-transformer/utils/transform-phones-value.util';
|
||||
|
||||
export const validatePhonesFieldOrThrow = (
|
||||
value: unknown,
|
||||
fieldName: string,
|
||||
): PhonesFieldGraphQLInput => {
|
||||
const preValidatedValue = validateRawJsonFieldOrThrow(value, fieldName);
|
||||
|
||||
if (isNull(preValidatedValue)) return null;
|
||||
|
||||
for (const [subField, subFieldValue] of Object.entries(preValidatedValue)) {
|
||||
switch (subField) {
|
||||
case 'primaryPhoneNumber':
|
||||
validateTextFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
|
||||
break;
|
||||
case 'primaryPhoneCountryCode':
|
||||
validateTextFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
|
||||
break;
|
||||
case 'primaryPhoneCallingCode':
|
||||
validateTextFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
|
||||
break;
|
||||
case 'additionalPhones':
|
||||
validateRawJsonFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new CommonQueryRunnerException(
|
||||
`Invalid subfield ${subField} for phones field "${fieldName}"`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return value as PhonesFieldGraphQLInput;
|
||||
};
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import { inspect } from 'util';
|
||||
|
||||
import { isNull } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { validateTextFieldOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-text-field-or-throw.util';
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
export const validateRatingAndSelectFieldOrThrow = (
|
||||
value: unknown,
|
||||
fieldName: string,
|
||||
options?: string[],
|
||||
): string | null => {
|
||||
const preValidatedValue = validateTextFieldOrThrow(value, fieldName);
|
||||
|
||||
if (!isDefined(options)) {
|
||||
throw new CommonQueryRunnerException(
|
||||
`Invalid options for field "${fieldName}"`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isNull(preValidatedValue) && !options.includes(preValidatedValue)) {
|
||||
throw new CommonQueryRunnerException(
|
||||
`Invalid value ${inspect(value)} for field "${fieldName}"`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
return preValidatedValue;
|
||||
};
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { inspect } from 'util';
|
||||
|
||||
import { isNull, isObject } from '@sniptt/guards';
|
||||
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
export const validateRawJsonFieldOrThrow = (
|
||||
value: unknown,
|
||||
fieldName: string,
|
||||
): object | null => {
|
||||
if (isNull(value)) return null;
|
||||
|
||||
if (!isObject(value)) {
|
||||
throw new CommonQueryRunnerException(
|
||||
`Invalid object value ${inspect(value)} for field "${fieldName}"`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import { inspect } from 'util';
|
||||
|
||||
import { isNull, isObject } from '@sniptt/guards';
|
||||
import {
|
||||
compositeTypeDefinitions,
|
||||
FieldMetadataType,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
export const validateRichTextV2FieldOrThrow = (
|
||||
value: unknown,
|
||||
fieldName: string,
|
||||
): {
|
||||
blocknote: string | null | undefined;
|
||||
markdown: string | null | undefined;
|
||||
} | null => {
|
||||
if (isNull(value)) return null;
|
||||
|
||||
try {
|
||||
const parsedValue = JSON.parse(JSON.stringify(value));
|
||||
|
||||
if (!isObject(parsedValue)) throw new Error('Should be an object');
|
||||
|
||||
if (Object.keys(parsedValue).length === 0) return null;
|
||||
|
||||
const subfields = Object.keys(parsedValue);
|
||||
const richTextV2Subfields = compositeTypeDefinitions
|
||||
.get(FieldMetadataType.RICH_TEXT_V2)
|
||||
?.properties.filter(
|
||||
(prop) => prop.hidden !== true && prop.hidden !== 'input',
|
||||
)
|
||||
.map((prop) => prop.name);
|
||||
|
||||
if (!subfields.every((subfield) => richTextV2Subfields?.includes(subfield)))
|
||||
throw new Error(
|
||||
`Should have only ${richTextV2Subfields?.join(', ')} subfields`,
|
||||
);
|
||||
|
||||
return value as {
|
||||
blocknote: string | null | undefined;
|
||||
markdown: string | null | undefined;
|
||||
};
|
||||
} catch (error) {
|
||||
throw new CommonQueryRunnerException(
|
||||
`Invalid rich text v2 value ${inspect(value)} for field "${fieldName}" - ${error.message}`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
}
|
||||
};
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { inspect } from 'util';
|
||||
|
||||
import { isNull } from '@sniptt/guards';
|
||||
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
export const validateTextFieldOrThrow = (
|
||||
value: unknown,
|
||||
fieldName: string,
|
||||
): string | null => {
|
||||
if (typeof value !== 'string' && !isNull(value))
|
||||
throw new CommonQueryRunnerException(
|
||||
`Invalid string value ${inspect(value)} for text field "${fieldName}"`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
|
||||
return value;
|
||||
};
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { inspect } from 'util';
|
||||
|
||||
import { isNull } from '@sniptt/guards';
|
||||
import { isValidUuid } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
|
||||
export const validateUUIDFieldOrThrow = (
|
||||
value: unknown,
|
||||
fieldName: string,
|
||||
): string | null => {
|
||||
if (!isValidUuid(value as string) && !isNull(value))
|
||||
throw new CommonQueryRunnerException(
|
||||
`Invalid UUID value ${inspect(value)} for field "${fieldName}"`,
|
||||
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
|
||||
);
|
||||
|
||||
return value as string;
|
||||
};
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||
import { RecordInputTransformerService } from 'src/engine/core-modules/record-transformer/services/record-input-transformer.service';
|
||||
import { type FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
|
||||
@Injectable()
|
||||
export class QueryRunnerArgsFactory {
|
||||
constructor(
|
||||
private readonly recordPositionService: RecordPositionService,
|
||||
private readonly recordInputTransformerService: RecordInputTransformerService,
|
||||
) {}
|
||||
|
||||
public overrideFilterByFieldMetadata<
|
||||
T extends ObjectRecordFilter | undefined,
|
||||
>(
|
||||
filter: T,
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||
): T {
|
||||
if (!isDefined(filter)) {
|
||||
return filter;
|
||||
}
|
||||
|
||||
const overrideFilter = (filterObject: ObjectRecordFilter) => {
|
||||
return Object.entries(filterObject).reduce((acc, [key, value]) => {
|
||||
if (key === 'and' || key === 'or') {
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
acc[key] = value.map((nestedFilter: ObjectRecordFilter) =>
|
||||
overrideFilter(nestedFilter),
|
||||
);
|
||||
} else if (key === 'not') {
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
acc[key] = overrideFilter(value);
|
||||
} else {
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
acc[key] = this.transformFilterValueByType(
|
||||
key,
|
||||
value,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
||||
return overrideFilter(filter) as T;
|
||||
}
|
||||
|
||||
private transformFilterValueByType(
|
||||
key: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
value: any,
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||
) {
|
||||
const fieldMetadataId = objectMetadataItemWithFieldMaps.fieldIdByName[key];
|
||||
const fieldMetadata =
|
||||
objectMetadataItemWithFieldMaps.fieldsById[fieldMetadataId];
|
||||
|
||||
if (!fieldMetadata) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// Special handling for filter values, which have a specific structure
|
||||
switch (fieldMetadata.type) {
|
||||
case FieldMetadataType.NUMBER: {
|
||||
if (value?.is === 'NULL') {
|
||||
return value;
|
||||
} else {
|
||||
return Object.fromEntries(
|
||||
Object.entries(value).map(([filterKey, filterValue]) => [
|
||||
filterKey,
|
||||
Number(filterValue),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
async overrideValueByFieldMetadata(
|
||||
key: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
value: any,
|
||||
fieldMetadataMapByName: FieldMetadataMap,
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||
) {
|
||||
const fieldMetadata = fieldMetadataMapByName[key];
|
||||
|
||||
if (!fieldMetadata) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return this.recordInputTransformerService.process({
|
||||
recordInput: { [key]: value },
|
||||
objectMetadataMapItem: objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
}
|
||||
}
|
||||
+8
-10
@@ -6,7 +6,8 @@ import { Omit } from 'zod/v4/core/util.cjs';
|
||||
import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
|
||||
import { QueryResultFieldValue } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-field-value';
|
||||
|
||||
import { CommonSelectedFieldsHandler } from 'src/engine/api/common/common-args-handlers/common-query-selected-fields/common-selected-fields.handler';
|
||||
import { DataArgProcessor } from 'src/engine/api/common/common-args-processors/data-arg-processor/data-arg.processor';
|
||||
import { QueryRunnerArgsFactory } from 'src/engine/api/common/common-args-processors/query-runner-args.factory';
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
@@ -25,8 +26,6 @@ import { isWorkspaceAuthContext } from 'src/engine/api/common/utils/is-workspace
|
||||
import { OBJECTS_WITH_SETTINGS_PERMISSIONS_REQUIREMENTS } from 'src/engine/api/graphql/graphql-query-runner/constants/objects-with-settings-permissions-requirements';
|
||||
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
||||
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
|
||||
import { QueryResultGettersFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/query-result-getters.factory';
|
||||
import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory';
|
||||
import { WorkspacePreQueryHookPayload } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type';
|
||||
import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service';
|
||||
import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/api-key-role.service';
|
||||
@@ -62,7 +61,7 @@ export abstract class CommonBaseQueryRunnerService<
|
||||
@Inject()
|
||||
protected readonly queryRunnerArgsFactory: QueryRunnerArgsFactory;
|
||||
@Inject()
|
||||
protected readonly queryResultGettersFactory: QueryResultGettersFactory;
|
||||
protected readonly dataArgProcessor: DataArgProcessor;
|
||||
@Inject()
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager;
|
||||
@Inject()
|
||||
@@ -76,8 +75,6 @@ export abstract class CommonBaseQueryRunnerService<
|
||||
@Inject()
|
||||
protected readonly apiKeyRoleService: ApiKeyRoleService;
|
||||
@Inject()
|
||||
protected readonly selectedFieldsHandler: CommonSelectedFieldsHandler;
|
||||
@Inject()
|
||||
protected readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService;
|
||||
@Inject()
|
||||
protected readonly commonResultGettersService: CommonResultGettersService;
|
||||
@@ -191,18 +188,19 @@ export abstract class CommonBaseQueryRunnerService<
|
||||
);
|
||||
|
||||
const { authContext, objectMetadataItemWithFieldMaps } = queryRunnerContext;
|
||||
|
||||
const computedArgs = await this.computeArgs(args, queryRunnerContext);
|
||||
|
||||
const hookedArgs =
|
||||
(await this.workspaceQueryHookService.executePreQueryHooks(
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
operationName,
|
||||
args as WorkspacePreQueryHookPayload<CommonQueryNames>,
|
||||
computedArgs as WorkspacePreQueryHookPayload<CommonQueryNames>,
|
||||
)) as CommonInput<Args>;
|
||||
|
||||
const computedArgs = await this.computeArgs(hookedArgs, queryRunnerContext);
|
||||
|
||||
return {
|
||||
...computedArgs,
|
||||
...hookedArgs,
|
||||
selectedFieldsResult,
|
||||
};
|
||||
}
|
||||
|
||||
+1
-1
@@ -140,7 +140,7 @@ export class CommonCreateManyQueryRunnerService extends CommonBaseQueryRunnerSer
|
||||
|
||||
return {
|
||||
...args,
|
||||
data: await this.queryRunnerArgsFactory.overrideDataByFieldMetadata({
|
||||
data: await this.dataArgProcessor.process({
|
||||
partialRecordInputs: args.data,
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
|
||||
+7
-7
@@ -53,15 +53,15 @@ export class CommonCreateOneQueryRunnerService extends CommonBaseQueryRunnerServ
|
||||
): Promise<CommonInput<CreateOneQueryArgs>> {
|
||||
const { authContext, objectMetadataItemWithFieldMaps } = queryRunnerContext;
|
||||
|
||||
const coercedData = await this.dataArgProcessor.process({
|
||||
partialRecordInputs: [args.data],
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
|
||||
return {
|
||||
...args,
|
||||
data: (
|
||||
await this.queryRunnerArgsFactory.overrideDataByFieldMetadata({
|
||||
partialRecordInputs: [args.data],
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
})
|
||||
)[0],
|
||||
data: coercedData[0],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -171,7 +171,7 @@ export class CommonFindDuplicatesQueryRunnerService extends CommonBaseQueryRunne
|
||||
),
|
||||
) ?? [],
|
||||
),
|
||||
data: await this.queryRunnerArgsFactory.overrideDataByFieldMetadata({
|
||||
data: await this.dataArgProcessor.process({
|
||||
partialRecordInputs: args.data,
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
|
||||
+2
-6
@@ -29,10 +29,6 @@ import {
|
||||
CommonQueryNames,
|
||||
MergeManyQueryArgs,
|
||||
} from 'src/engine/api/common/types/common-query-args.type';
|
||||
import {
|
||||
GraphqlQueryRunnerException,
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
|
||||
import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
|
||||
import { hasRecordFieldValue } from 'src/engine/api/graphql/graphql-query-runner/utils/has-record-field-value.util';
|
||||
@@ -174,9 +170,9 @@ export class CommonMergeManyQueryRunnerService extends CommonBaseQueryRunnerServ
|
||||
);
|
||||
|
||||
if (!priorityRecord) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
throw new CommonQueryRunnerException(
|
||||
'Priority record not found',
|
||||
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
|
||||
CommonQueryRunnerExceptionCode.RECORD_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -106,7 +106,7 @@ export class CommonUpdateManyQueryRunnerService extends CommonBaseQueryRunnerSer
|
||||
objectMetadataItemWithFieldMaps,
|
||||
) || {},
|
||||
data: (
|
||||
await this.queryRunnerArgsFactory.overrideDataByFieldMetadata({
|
||||
await this.dataArgProcessor.process({
|
||||
partialRecordInputs: [args.data],
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ export class CommonUpdateOneQueryRunnerService extends CommonBaseQueryRunnerServ
|
||||
return {
|
||||
...args,
|
||||
data: (
|
||||
await this.queryRunnerArgsFactory.overrideDataByFieldMetadata({
|
||||
await this.dataArgProcessor.process({
|
||||
partialRecordInputs: [args.data],
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
|
||||
+1
@@ -7,6 +7,7 @@ export enum CommonQueryRunnerExceptionCode {
|
||||
INVALID_QUERY_INPUT = 'INVALID_QUERY_INPUT',
|
||||
INVALID_AUTH_CONTEXT = 'INVALID_AUTH_CONTEXT',
|
||||
ARGS_CONFLICT = 'ARGS_CONFLICT',
|
||||
INVALID_ARGS_DATA = 'INVALID_ARGS_DATA',
|
||||
INVALID_ARGS_FIRST = 'INVALID_ARGS_FIRST',
|
||||
INVALID_ARGS_LAST = 'INVALID_ARGS_LAST',
|
||||
UPSERT_MULTIPLE_MATCHING_RECORDS_CONFLICT = 'UPSERT_MULTIPLE_MATCHING_RECORDS_CONFLICT',
|
||||
|
||||
+1
@@ -20,6 +20,7 @@ export const commonQueryRunnerToGraphqlApiExceptionHandler = (
|
||||
case CommonQueryRunnerExceptionCode.INVALID_ARGS_FIRST:
|
||||
case CommonQueryRunnerExceptionCode.INVALID_ARGS_LAST:
|
||||
case CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT:
|
||||
case CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA:
|
||||
case CommonQueryRunnerExceptionCode.UPSERT_MULTIPLE_MATCHING_RECORDS_CONFLICT:
|
||||
case CommonQueryRunnerExceptionCode.INVALID_CURSOR:
|
||||
case CommonQueryRunnerExceptionCode.UPSERT_MAX_RECORDS_EXCEEDED:
|
||||
|
||||
+1
@@ -19,6 +19,7 @@ export const commonQueryRunnerToRestApiExceptionHandler = (
|
||||
case CommonQueryRunnerExceptionCode.INVALID_ARGS_FIRST:
|
||||
case CommonQueryRunnerExceptionCode.INVALID_ARGS_LAST:
|
||||
case CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT:
|
||||
case CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA:
|
||||
case CommonQueryRunnerExceptionCode.UPSERT_MULTIPLE_MATCHING_RECORDS_CONFLICT:
|
||||
case CommonQueryRunnerExceptionCode.INVALID_CURSOR:
|
||||
case CommonQueryRunnerExceptionCode.UPSERT_MAX_RECORDS_EXCEEDED:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { CommonArgsHandlers } from 'src/engine/api/common/common-args-handlers/common-query-selected-fields/common-arg-handlers';
|
||||
import { CommonArgsProcessors } from 'src/engine/api/common/common-args-processors/common-args-processors';
|
||||
import { CommonQueryRunners } from 'src/engine/api/common/common-query-runners/common-query-runners';
|
||||
import { CommonResultGettersService } from 'src/engine/api/common/common-result-getters/common-result-getters.service';
|
||||
import { GroupByWithRecordsService } from 'src/engine/api/graphql/graphql-query-runner/group-by/services/group-by-with-records.service';
|
||||
@@ -14,6 +14,8 @@ import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module';
|
||||
import { RecordPositionModule } from 'src/engine/core-modules/record-position/record-position.module';
|
||||
import { RecordTransformerModule } from 'src/engine/core-modules/record-transformer/record-transformer.module';
|
||||
import { ThrottlerModule } from 'src/engine/core-modules/throttler/throttler.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity';
|
||||
@@ -39,13 +41,15 @@ import { GlobalWorkspaceDataSourceModule } from 'src/engine/twenty-orm/global-wo
|
||||
ViewFilterGroupModule,
|
||||
ThrottlerModule,
|
||||
MetricsModule,
|
||||
RecordPositionModule,
|
||||
RecordTransformerModule,
|
||||
GlobalWorkspaceDataSourceModule,
|
||||
FeatureFlagModule,
|
||||
],
|
||||
providers: [
|
||||
ProcessNestedRelationsHelper,
|
||||
ProcessNestedRelationsV2Helper,
|
||||
...CommonArgsHandlers,
|
||||
...CommonArgsProcessors,
|
||||
ProcessAggregateHelper,
|
||||
...CommonQueryRunners,
|
||||
CommonResultGettersService,
|
||||
|
||||
-1
@@ -7,6 +7,5 @@ export interface CommonSelectedFields {
|
||||
export type CommonSelectedFieldsResult = {
|
||||
select: CommonSelectedFields;
|
||||
relations: CommonSelectedFields;
|
||||
//TODO = Refacto-common - to update when rest api will handle aggregates
|
||||
aggregate: Record<string, AggregationField>;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user