Improve userFriendlyMessage devX (#16815)

Two challenges with error messages
- always provide a useful/meaningful error message for the end user
instead of the generic one. eg: show "Wrong password" and not "An error
occured"
- avoid technical details unless error regards a technical feature. eg:
show "An error occured" and not "Invalid post-hook payload."; but do
show "Invalid issuer URL." as it occurs while configuring SSO

What this PR does
- Make userFriendlyMessage mandatory for widely used
GraphqlQueryRunnerException and CommonQueryRunnerException, so that
developers are forced to ask themselves what the error message should
be, and as it contains very wide error codes (eg: "Bad request") which
should not be mapped to just one default message
- Keep userFriendlyMessage optional for service-specific exceptions (eg:
workflowStepExecutorException), but convert the error code to
userFriendlyMessage mapper to a switch case function with a typecheck
ensuring that all codes are mapped to a message. These default messages
are still overridable where they are thrown.
This commit is contained in:
Marie
2025-12-30 10:08:43 +01:00
committed by GitHub
parent 7522ff6675
commit 19c9f957b1
135 changed files with 1672 additions and 845 deletions
@@ -7,6 +7,7 @@ import {
CommonQueryRunnerException,
CommonQueryRunnerExceptionCode,
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import { CommonSelectedFieldsResult } from 'src/engine/api/common/types/common-selected-fields-result.type';
import { getAllSelectableFields } from 'src/engine/api/rest/core/rest-to-common-args-handlers/utils/get-all-selectable-fields.util';
import { MAX_DEPTH } from 'src/engine/api/rest/input-request-parsers/constants/max-depth.constant';
@@ -106,6 +107,7 @@ export class CommonSelectedFieldsHandler {
throw new CommonQueryRunnerException(
`Object metadata relation target not found for relation creation payload`,
CommonQueryRunnerExceptionCode.BAD_REQUEST,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
const relationFieldSelectFields = getAllSelectableFields({
@@ -1,5 +1,6 @@
import { Injectable } from '@nestjs/common';
import { msg } from '@lingui/core/macro';
import { isNull, isUndefined } from '@sniptt/guards';
import {
FieldMetadataRelationSettings,
@@ -44,6 +45,7 @@ import {
CommonQueryRunnerException,
CommonQueryRunnerExceptionCode,
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
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';
@@ -112,6 +114,7 @@ export class DataArgProcessor {
throw new CommonQueryRunnerException(
`Object ${flatObjectMetadata.nameSingular} doesn't have any "${key}" field.`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -121,6 +124,7 @@ export class DataArgProcessor {
throw new CommonQueryRunnerException(
`Field metadata not found for field ${key}`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -132,6 +136,7 @@ export class DataArgProcessor {
throw new CommonQueryRunnerException(
`Field ${key} is not nullable and has no default value.`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: msg`A required field is missing.` },
);
}
@@ -221,6 +226,7 @@ export class DataArgProcessor {
throw new CommonQueryRunnerException(
`One-to-many relation ${key} field does not support write operations.`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -276,6 +282,7 @@ export class DataArgProcessor {
throw new CommonQueryRunnerException(
`${key} ${fieldMetadata.type}-typed field does not support write operations`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
default:
assertUnreachable(
@@ -1,3 +1,4 @@
import { msg } from '@lingui/core/macro';
import { isNull } from '@sniptt/guards';
import { FieldActorSource } from 'twenty-shared/types';
@@ -40,6 +41,7 @@ export const validateActorFieldOrThrow = (
throw new CommonQueryRunnerException(
`Invalid subfield ${subField} for actor field "${fieldName}"`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: msg`Invalid value for actor.` },
);
}
}
@@ -1,3 +1,4 @@
import { msg } from '@lingui/core/macro';
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';
@@ -55,6 +56,7 @@ export const validateAddressFieldOrThrow = (
throw new CommonQueryRunnerException(
`Invalid subfield ${subField} for address field "${fieldName}"`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: msg`Invalid value for address.` },
);
}
}
@@ -1,5 +1,6 @@
import { inspect } from 'util';
import { msg } from '@lingui/core/macro';
import { isNull } from '@sniptt/guards';
import {
@@ -16,9 +17,12 @@ export const validateArrayFieldOrThrow = (
if (typeof value === 'string') return value;
if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
const inspectedValue = inspect(value);
throw new CommonQueryRunnerException(
`Invalid value ${inspect(value)} for field "${fieldName} - Array values need to be string"`,
`Invalid value ${inspectedValue} for field "${fieldName} - Array values need to be string"`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: msg`Invalid value: "${inspectedValue}"` },
);
}
@@ -1,5 +1,6 @@
import { inspect } from 'util';
import { msg } from '@lingui/core/macro';
import { isNull } from '@sniptt/guards';
import {
@@ -11,11 +12,15 @@ export const validateBooleanFieldOrThrow = (
value: unknown,
fieldName: string,
): boolean | null => {
if (typeof value !== 'boolean' && !isNull(value))
if (typeof value !== 'boolean' && !isNull(value)) {
const inspectedValue = inspect(value);
throw new CommonQueryRunnerException(
`Invalid boolean value ${inspect(value)} for field "${fieldName}"`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: msg`Invalid value: "${inspectedValue}"` },
);
}
return value;
};
@@ -1,3 +1,4 @@
import { msg } from '@lingui/core/macro';
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';
@@ -32,6 +33,7 @@ export const validateCurrencyFieldOrThrow = (
throw new CommonQueryRunnerException(
`Invalid subfield ${subField} for currency field "${fieldName}"`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: msg`Invalid value for currency.` },
);
}
}
@@ -1,5 +1,6 @@
import { inspect } from 'util';
import { msg } from '@lingui/core/macro';
import { isDate, isNull, isNumber, isString } from '@sniptt/guards';
import {
@@ -20,8 +21,11 @@ export const validateDateAndDateTimeFieldOrThrow = (
if (!isNaN(date.getTime())) return value;
}
const inspectedValue = inspect(value);
throw new CommonQueryRunnerException(
`Invalid value ${inspect(value)} for date or date-time field "${fieldName}"`,
`Invalid value ${inspectedValue} for date or date-time field "${fieldName}"`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: msg`Invalid value for date: "${inspectedValue}"` },
);
};
@@ -1,3 +1,4 @@
import { msg } from '@lingui/core/macro';
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';
@@ -31,6 +32,7 @@ export const validateEmailsFieldOrThrow = (
throw new CommonQueryRunnerException(
`Invalid subfield ${subField} for emails field "${fieldName}"`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: msg`Invalid value for emails.` },
);
}
}
@@ -1,3 +1,4 @@
import { msg } from '@lingui/core/macro';
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';
@@ -30,6 +31,7 @@ export const validateFullNameFieldOrThrow = (
throw new CommonQueryRunnerException(
`Invalid subfield ${subField} for full name field "${fieldName}"`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: msg`Invalid value for full name.` },
);
}
}
@@ -1,3 +1,4 @@
import { msg } from '@lingui/core/macro';
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';
@@ -31,6 +32,7 @@ export const validateLinksFieldOrThrow = (
throw new CommonQueryRunnerException(
`Invalid subfield ${subField} for links field "${fieldName}"`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: msg`Invalid value for links.` },
);
}
}
@@ -1,5 +1,6 @@
import { inspect } from 'util';
import { msg } from '@lingui/core/macro';
import { isNull } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
@@ -8,6 +9,7 @@ import {
CommonQueryRunnerException,
CommonQueryRunnerExceptionCode,
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
export const validateMultiSelectFieldOrThrow = (
value: unknown,
@@ -22,6 +24,7 @@ export const validateMultiSelectFieldOrThrow = (
throw new CommonQueryRunnerException(
`Invalid options for field "${fieldName}"`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -31,9 +34,14 @@ export const validateMultiSelectFieldOrThrow = (
: [preValidatedValue]
).some((item) => !options.includes(item))
) {
const inspectedValue = inspect(value);
throw new CommonQueryRunnerException(
`Invalid value ${inspect(value)} for multi select field "${fieldName}"`,
`Invalid value ${inspectedValue} for multi select field "${fieldName}"`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{
userFriendlyMessage: msg`Invalid value for multi-select: "${inspectedValue}"`,
},
);
}
@@ -1,5 +1,6 @@
import { inspect } from 'util';
import { msg } from '@lingui/core/macro';
import { isNull } from '@sniptt/guards';
import {
@@ -15,11 +16,17 @@ export const validateNumberFieldOrThrow = (
(typeof value !== 'number' && !isNull(value)) ||
(typeof value === 'number' &&
(isNaN(value) || value === Infinity || value === -Infinity))
)
) {
const inspectedValue = inspect(value);
throw new CommonQueryRunnerException(
`Invalid number value ${inspect(value)} for field "${fieldName}"`,
`Invalid number value ${inspectedValue} for field "${fieldName}"`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{
userFriendlyMessage: msg`Invalid value for number: "${inspectedValue}"`,
},
);
}
return value;
};
@@ -1,5 +1,7 @@
import { inspect } from 'util';
import { msg } from '@lingui/core/macro';
import {
CommonQueryRunnerException,
CommonQueryRunnerExceptionCode,
@@ -13,11 +15,17 @@ export const validateOverriddenPositionFieldOrThrow = (
typeof value !== 'number' ||
(typeof value === 'number' &&
(isNaN(value) || value === Infinity || value === -Infinity))
)
) {
const inspectedValue = inspect(value);
throw new CommonQueryRunnerException(
`Invalid position value ${inspect(value)} for field "${fieldName}"`,
`Invalid position value ${inspectedValue} for field "${fieldName}"`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{
userFriendlyMessage: msg`Invalid value for position: "${inspectedValue}"`,
},
);
}
return value;
};
@@ -1,3 +1,4 @@
import { msg } from '@lingui/core/macro';
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';
@@ -35,6 +36,7 @@ export const validatePhonesFieldOrThrow = (
throw new CommonQueryRunnerException(
`Invalid subfield ${subField} for phones field "${fieldName}"`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: msg`Invalid value for phones.` },
);
}
}
@@ -1,5 +1,6 @@
import { inspect } from 'util';
import { msg } from '@lingui/core/macro';
import { isNull } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
@@ -8,6 +9,7 @@ import {
CommonQueryRunnerException,
CommonQueryRunnerExceptionCode,
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
export const validateRatingAndSelectFieldOrThrow = (
value: unknown,
@@ -20,13 +22,19 @@ export const validateRatingAndSelectFieldOrThrow = (
throw new CommonQueryRunnerException(
`Invalid options for field "${fieldName}"`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
if (!isNull(preValidatedValue) && !options.includes(preValidatedValue)) {
const inspectedValue = inspect(value);
throw new CommonQueryRunnerException(
`Invalid value ${inspect(value)} for field "${fieldName}"`,
`Invalid value ${inspectedValue} for field "${fieldName}"`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{
userFriendlyMessage: msg`Invalid value for select: "${inspectedValue}"`,
},
);
}
@@ -1,5 +1,6 @@
import { inspect } from 'util';
import { msg } from '@lingui/core/macro';
import { isNull, isObject } from '@sniptt/guards';
import {
@@ -20,6 +21,7 @@ export const validateRawJsonFieldOrThrow = (
throw new CommonQueryRunnerException(
`Invalid object value ${inspect(value)} for field "${fieldName}"`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: msg`Invalid value for JSON.` },
);
}
@@ -27,9 +29,12 @@ export const validateRawJsonFieldOrThrow = (
}
if (!isObject(value)) {
const inspectedValue = inspect(value);
throw new CommonQueryRunnerException(
`Invalid object value ${inspect(value)} for field "${fieldName}"`,
`Invalid object value ${inspectedValue} for field "${fieldName}"`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: msg`Invalid value for JSON: "${inspectedValue}"` },
);
}
@@ -1,5 +1,6 @@
import { inspect } from 'util';
import { msg } from '@lingui/core/macro';
import { isNull, isObject } from '@sniptt/guards';
import {
compositeTypeDefinitions,
@@ -48,6 +49,7 @@ export const validateRichTextV2FieldOrThrow = (
throw new CommonQueryRunnerException(
`Invalid rich text v2 value ${inspect(value)} for field "${fieldName}" - ${error.message}`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: msg`Invalid value for rich text.` },
);
}
};
@@ -1,5 +1,6 @@
import { inspect } from 'util';
import { msg } from '@lingui/core/macro';
import { isNull } from '@sniptt/guards';
import {
@@ -11,11 +12,15 @@ export const validateTextFieldOrThrow = (
value: unknown,
fieldName: string,
): string | null => {
if (typeof value !== 'string' && !isNull(value))
if (typeof value !== 'string' && !isNull(value)) {
const inspectedValue = inspect(value);
throw new CommonQueryRunnerException(
`Invalid string value ${inspect(value)} for text field "${fieldName}"`,
`Invalid string value ${inspectedValue} for text field "${fieldName}"`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: msg`Invalid value: "${inspectedValue}"` },
);
}
return value;
};
@@ -1,5 +1,6 @@
import { inspect } from 'util';
import { msg } from '@lingui/core/macro';
import { isNull } from '@sniptt/guards';
import { isValidUuid } from 'twenty-shared/utils';
@@ -12,11 +13,15 @@ export const validateUUIDFieldOrThrow = (
value: unknown,
fieldName: string,
): string | null => {
if (!isValidUuid(value as string) && !isNull(value))
if (!isValidUuid(value as string) && !isNull(value)) {
const inspectedValue = inspect(value);
throw new CommonQueryRunnerException(
`Invalid UUID value ${inspect(value)} for field "${fieldName}"`,
`Invalid UUID value ${inspectedValue} for field "${fieldName}"`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: msg`Invalid value for UUID: "${inspectedValue}"` },
);
}
return value as string;
};
@@ -1,6 +1,5 @@
import { Inject, Injectable } from '@nestjs/common';
import { msg } from '@lingui/core/macro';
import { type PermissionFlagType } from 'twenty-shared/constants';
import { isDefined } from 'twenty-shared/utils';
@@ -13,6 +12,7 @@ import {
CommonQueryRunnerException,
CommonQueryRunnerExceptionCode,
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import { CommonResultGettersService } from 'src/engine/api/common/common-result-getters/common-result-getters.service';
import { CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type';
import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/common-extended-query-runner-context.type';
@@ -104,6 +104,7 @@ export abstract class CommonBaseQueryRunnerService<
throw new CommonQueryRunnerException(
'Invalid auth context',
CommonQueryRunnerExceptionCode.INVALID_AUTH_CONTEXT,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -315,6 +316,7 @@ export abstract class CommonBaseQueryRunnerService<
throw new CommonQueryRunnerException(
'Invalid auth context',
CommonQueryRunnerExceptionCode.INVALID_AUTH_CONTEXT,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -414,7 +416,7 @@ export abstract class CommonBaseQueryRunnerService<
`Query complexity is too high. One-to-Many relation cannot be nested in another One-to-Many relation.`,
CommonQueryRunnerExceptionCode.TOO_COMPLEX_QUERY,
{
userFriendlyMessage: msg`Query complexity is too high. One-to-Many relation cannot be nested in another One-to-Many relation.`,
userFriendlyMessage: STANDARD_ERROR_MESSAGE,
},
);
}
@@ -429,7 +431,7 @@ export abstract class CommonBaseQueryRunnerService<
`Query complexity is too high. Please, reduce the amount of relation fields requested. Query complexity: ${queryComplexity}. Maximum complexity: ${maximumComplexity}.`,
CommonQueryRunnerExceptionCode.TOO_COMPLEX_QUERY,
{
userFriendlyMessage: msg`Query complexity is too high. Please, reduce the amount of relation fields requested. Query complexity: ${queryComplexity}. Maximum complexity: ${maximumComplexity}.`,
userFriendlyMessage: STANDARD_ERROR_MESSAGE,
},
);
}
@@ -17,6 +17,7 @@ import {
CommonQueryRunnerException,
CommonQueryRunnerExceptionCode,
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import { CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type';
import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/common-extended-query-runner-context.type';
import {
@@ -467,6 +468,7 @@ export class CommonCreateManyQueryRunnerService extends CommonBaseQueryRunnerSer
throw new CommonQueryRunnerException(
`Missing createdBy field metadata for object ${flatObjectMetadata.nameSingular}`,
CommonQueryRunnerExceptionCode.MISSING_SYSTEM_FIELD,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -12,6 +12,7 @@ import {
CommonQueryRunnerException,
CommonQueryRunnerExceptionCode,
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import { CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type';
import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/common-extended-query-runner-context.type';
import {
@@ -139,6 +140,7 @@ export class CommonDeleteManyQueryRunnerService extends CommonBaseQueryRunnerSer
throw new CommonQueryRunnerException(
'Filter is required',
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -1,5 +1,6 @@
import { Injectable } from '@nestjs/common';
import { msg } from '@lingui/core/macro';
import { type ObjectRecord } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
@@ -54,6 +55,9 @@ export class CommonDeleteOneQueryRunnerService extends CommonBaseQueryRunnerServ
throw new CommonQueryRunnerException(
'Record not found',
CommonQueryRunnerExceptionCode.RECORD_NOT_FOUND,
{
userFriendlyMessage: msg`This record does not exist or has been deleted.`,
},
);
}
@@ -12,6 +12,7 @@ import {
CommonQueryRunnerException,
CommonQueryRunnerExceptionCode,
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import { CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type';
import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/common-extended-query-runner-context.type';
import {
@@ -140,6 +141,7 @@ export class CommonDestroyManyQueryRunnerService extends CommonBaseQueryRunnerSe
throw new CommonQueryRunnerException(
'Filter is required',
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -1,5 +1,6 @@
import { Injectable } from '@nestjs/common';
import { msg } from '@lingui/core/macro';
import { type ObjectRecord } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
@@ -11,6 +12,7 @@ import {
CommonQueryRunnerException,
CommonQueryRunnerExceptionCode,
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import { CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type';
import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/common-extended-query-runner-context.type';
import {
@@ -52,6 +54,9 @@ export class CommonDestroyOneQueryRunnerService extends CommonBaseQueryRunnerSer
throw new CommonQueryRunnerException(
'Record not found',
CommonQueryRunnerExceptionCode.RECORD_NOT_FOUND,
{
userFriendlyMessage: msg`This record does not exist or has been deleted.`,
},
);
}
@@ -89,6 +94,7 @@ export class CommonDestroyOneQueryRunnerService extends CommonBaseQueryRunnerSer
throw new CommonQueryRunnerException(
'Missing id',
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
}
@@ -16,6 +16,7 @@ import {
CommonQueryRunnerException,
CommonQueryRunnerExceptionCode,
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import { CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type';
import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/common-extended-query-runner-context.type';
import { CommonFindDuplicatesOutputItem } from 'src/engine/api/common/types/common-find-duplicates-output-item.type';
@@ -232,6 +233,7 @@ export class CommonFindDuplicatesQueryRunnerService extends CommonBaseQueryRunne
throw new CommonQueryRunnerException(
'You have to provide either "data" or "ids" argument',
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -239,6 +241,7 @@ export class CommonFindDuplicatesQueryRunnerService extends CommonBaseQueryRunne
throw new CommonQueryRunnerException(
'You cannot provide both "data" and "ids" arguments',
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -246,6 +249,7 @@ export class CommonFindDuplicatesQueryRunnerService extends CommonBaseQueryRunne
throw new CommonQueryRunnerException(
'The "data" condition can not be empty when "ids" input not provided',
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
}
@@ -19,6 +19,7 @@ import {
CommonQueryRunnerException,
CommonQueryRunnerExceptionCode,
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import { CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type';
import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/common-extended-query-runner-context.type';
import { CommonFindManyOutput } from 'src/engine/api/common/types/common-find-many-output.type';
@@ -235,36 +236,42 @@ export class CommonFindManyQueryRunnerService extends CommonBaseQueryRunnerServi
throw new CommonQueryRunnerException(
'Cannot provide both first and last',
CommonQueryRunnerExceptionCode.ARGS_CONFLICT,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
if (args.before && args.after) {
throw new CommonQueryRunnerException(
'Cannot provide both before and after',
CommonQueryRunnerExceptionCode.ARGS_CONFLICT,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
if (args.before && args.first) {
throw new CommonQueryRunnerException(
'Cannot provide both before and first',
CommonQueryRunnerExceptionCode.ARGS_CONFLICT,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
if (args.after && args.last) {
throw new CommonQueryRunnerException(
'Cannot provide both after and last',
CommonQueryRunnerExceptionCode.ARGS_CONFLICT,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
if (args.first !== undefined && args.first < 0) {
throw new CommonQueryRunnerException(
'First argument must be non-negative',
CommonQueryRunnerExceptionCode.INVALID_ARGS_FIRST,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
if (args.last !== undefined && args.last < 0) {
throw new CommonQueryRunnerException(
'Last argument must be non-negative',
CommonQueryRunnerExceptionCode.INVALID_ARGS_LAST,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
}
@@ -1,5 +1,6 @@
import { Injectable } from '@nestjs/common';
import { msg } from '@lingui/core/macro';
import { QUERY_MAX_RECORDS_FROM_RELATION } from 'twenty-shared/constants';
import { ObjectRecord } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
@@ -13,6 +14,7 @@ import {
CommonQueryRunnerException,
CommonQueryRunnerExceptionCode,
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import { CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type';
import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/common-extended-query-runner-context.type';
import {
@@ -81,6 +83,9 @@ export class CommonFindOneQueryRunnerService extends CommonBaseQueryRunnerServic
throw new CommonQueryRunnerException(
'Record not found',
CommonQueryRunnerExceptionCode.RECORD_NOT_FOUND,
{
userFriendlyMessage: msg`This record does not exist or has been deleted.`,
},
);
}
@@ -147,6 +152,7 @@ export class CommonFindOneQueryRunnerService extends CommonBaseQueryRunnerServic
throw new CommonQueryRunnerException(
'Missing filter argument',
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
}
@@ -23,6 +23,7 @@ import {
CommonQueryRunnerException,
CommonQueryRunnerExceptionCode,
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import { getGroupByDefinitions } from 'src/engine/api/common/common-query-runners/utils/get-group-by-definitions.util';
import { getObjectAlias } from 'src/engine/api/common/common-query-runners/utils/get-object-alias-for-group-by.util';
import { CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type';
@@ -214,6 +215,7 @@ export class CommonGroupByQueryRunnerService extends CommonBaseQueryRunnerServic
throw new CommonQueryRunnerException(
`Field metadata not found for field ${viewFilter.fieldMetadataId}`,
CommonQueryRunnerExceptionCode.INTERNAL_SERVER_ERROR,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -363,6 +365,7 @@ export class CommonGroupByQueryRunnerService extends CommonBaseQueryRunnerServic
throw new CommonQueryRunnerException(
`Field metadata settings are missing or invalid for field ${groupByField.fieldMetadata.name}`,
CommonQueryRunnerExceptionCode.INTERNAL_SERVER_ERROR,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -1,5 +1,6 @@
import { Injectable, Logger } from '@nestjs/common';
import { msg } from '@lingui/core/macro';
import {
MUTATION_MAX_MERGE_RECORDS,
QUERY_MAX_RECORDS_FROM_RELATION,
@@ -21,6 +22,7 @@ import {
CommonQueryRunnerException,
CommonQueryRunnerExceptionCode,
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import { CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type';
import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/common-extended-query-runner-context.type';
import {
@@ -144,6 +146,7 @@ export class CommonMergeManyQueryRunnerService extends CommonBaseQueryRunnerServ
throw new CommonQueryRunnerException(
'One or more records not found',
CommonQueryRunnerExceptionCode.RECORD_NOT_FOUND,
{ userFriendlyMessage: msg`One or more records were not found.` },
);
}
@@ -182,6 +185,9 @@ export class CommonMergeManyQueryRunnerService extends CommonBaseQueryRunnerServ
throw new CommonQueryRunnerException(
'Priority record not found',
CommonQueryRunnerExceptionCode.RECORD_NOT_FOUND,
{
userFriendlyMessage: msg`This record does not exist or has been deleted.`,
},
);
}
@@ -323,6 +329,7 @@ export class CommonMergeManyQueryRunnerService extends CommonBaseQueryRunnerServ
throw new CommonQueryRunnerException(
'Failed to update record',
CommonQueryRunnerExceptionCode.RECORD_NOT_FOUND,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -482,6 +489,7 @@ export class CommonMergeManyQueryRunnerService extends CommonBaseQueryRunnerServ
throw new CommonQueryRunnerException(
`Merge is only available for objects with duplicate criteria. Object '${flatObjectMetadata.nameSingular}' does not have duplicate criteria defined.`,
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{ userFriendlyMessage: msg`This type of record cannot be merged.` },
);
}
@@ -491,6 +499,9 @@ export class CommonMergeManyQueryRunnerService extends CommonBaseQueryRunnerServ
throw new CommonQueryRunnerException(
'At least 2 record IDs are required for merge',
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{
userFriendlyMessage: msg`Please select at least 2 records to merge.`,
},
);
}
@@ -498,6 +509,9 @@ export class CommonMergeManyQueryRunnerService extends CommonBaseQueryRunnerServ
throw new CommonQueryRunnerException(
`Maximum ${MUTATION_MAX_MERGE_RECORDS} records can be merged at once`,
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{
userFriendlyMessage: msg`You can merge up to ${MUTATION_MAX_MERGE_RECORDS} records at once.`,
},
);
}
@@ -505,6 +519,7 @@ export class CommonMergeManyQueryRunnerService extends CommonBaseQueryRunnerServ
throw new CommonQueryRunnerException(
`Invalid conflict priority '${conflictPriorityIndex}'. Valid options for ${ids.length} records: 0-${ids.length - 1}`,
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
}
@@ -12,6 +12,7 @@ import {
CommonQueryRunnerException,
CommonQueryRunnerExceptionCode,
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import { CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type';
import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/common-extended-query-runner-context.type';
import {
@@ -139,6 +140,7 @@ export class CommonRestoreManyQueryRunnerService extends CommonBaseQueryRunnerSe
throw new CommonQueryRunnerException(
'Filter is required',
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -1,5 +1,6 @@
import { Injectable } from '@nestjs/common';
import { msg } from '@lingui/core/macro';
import { type ObjectRecord } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
@@ -54,6 +55,9 @@ export class CommonRestoreOneQueryRunnerService extends CommonBaseQueryRunnerSer
throw new CommonQueryRunnerException(
'Record not found',
CommonQueryRunnerExceptionCode.RECORD_NOT_FOUND,
{
userFriendlyMessage: msg`This record does not exist or has been deleted.`,
},
);
}
@@ -12,6 +12,7 @@ import {
CommonQueryRunnerException,
CommonQueryRunnerExceptionCode,
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import { CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type';
import { CommonExtendedQueryRunnerContext } from 'src/engine/api/common/types/common-extended-query-runner-context.type';
import {
@@ -133,6 +134,7 @@ export class CommonUpdateManyQueryRunnerService extends CommonBaseQueryRunnerSer
throw new CommonQueryRunnerException(
'Filter is required',
CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -1,5 +1,6 @@
import { Injectable } from '@nestjs/common';
import { msg } from '@lingui/core/macro';
import { type ObjectRecord } from 'twenty-shared/types';
import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
@@ -52,6 +53,9 @@ export class CommonUpdateOneQueryRunnerService extends CommonBaseQueryRunnerServ
throw new CommonQueryRunnerException(
'Record not found',
CommonQueryRunnerExceptionCode.RECORD_NOT_FOUND,
{
userFriendlyMessage: msg`This record does not exist or has been deleted.`,
},
);
}
@@ -1,5 +1,4 @@
import { type MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { CustomException } from 'src/utils/custom-exception';
@@ -21,37 +20,14 @@ export enum CommonQueryRunnerExceptionCode {
MISSING_TIMEZONE_FOR_DATE_GROUP_BY = 'MISSING_TIMEZONE_FOR_DATE_GROUP_BY',
}
const commonQueryRunnerExceptionUserFriendlyMessages: Record<
CommonQueryRunnerExceptionCode,
MessageDescriptor
> = {
[CommonQueryRunnerExceptionCode.RECORD_NOT_FOUND]: msg`Record not found.`,
[CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT]: msg`Invalid query input.`,
[CommonQueryRunnerExceptionCode.INVALID_AUTH_CONTEXT]: msg`Invalid authentication context.`,
[CommonQueryRunnerExceptionCode.ARGS_CONFLICT]: msg`Conflicting arguments provided.`,
[CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA]: msg`Invalid data provided.`,
[CommonQueryRunnerExceptionCode.INVALID_ARGS_FIRST]: msg`Invalid 'first' argument.`,
[CommonQueryRunnerExceptionCode.INVALID_ARGS_LAST]: msg`Invalid 'last' argument.`,
[CommonQueryRunnerExceptionCode.UPSERT_MULTIPLE_MATCHING_RECORDS_CONFLICT]: msg`Multiple matching records found during upsert.`,
[CommonQueryRunnerExceptionCode.MISSING_SYSTEM_FIELD]: msg`Missing required system field.`,
[CommonQueryRunnerExceptionCode.INVALID_CURSOR]: msg`Invalid cursor provided.`,
[CommonQueryRunnerExceptionCode.TOO_MANY_RECORDS_TO_UPDATE]: msg`Too many records to update at once.`,
[CommonQueryRunnerExceptionCode.BAD_REQUEST]: msg`Bad request.`,
[CommonQueryRunnerExceptionCode.INTERNAL_SERVER_ERROR]: msg`An unexpected error occurred.`,
[CommonQueryRunnerExceptionCode.TOO_COMPLEX_QUERY]: msg`Query is too complex.`,
[CommonQueryRunnerExceptionCode.MISSING_TIMEZONE_FOR_DATE_GROUP_BY]: msg`Missing time zone for date group by.`,
};
export class CommonQueryRunnerException extends CustomException<CommonQueryRunnerExceptionCode> {
constructor(
message: string,
code: CommonQueryRunnerExceptionCode,
{ userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {},
{ userFriendlyMessage }: { userFriendlyMessage: MessageDescriptor },
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
commonQueryRunnerExceptionUserFriendlyMessages[code],
userFriendlyMessage,
});
}
}
@@ -0,0 +1,3 @@
import { msg } from '@lingui/core/macro';
export const STANDARD_ERROR_MESSAGE = msg`An error occurred.`;
@@ -1,5 +1,4 @@
import { type MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { CustomException } from 'src/utils/custom-exception';
@@ -24,40 +23,14 @@ export enum GraphqlQueryRunnerExceptionCode {
UPSERT_MAX_RECORDS_EXCEEDED = 'UPSERT_MAX_RECORDS_EXCEEDED',
}
const graphqlQueryRunnerExceptionUserFriendlyMessages: Record<
GraphqlQueryRunnerExceptionCode,
MessageDescriptor
> = {
[GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT]: msg`Invalid query input.`,
[GraphqlQueryRunnerExceptionCode.MAX_DEPTH_REACHED]: msg`Maximum query depth reached.`,
[GraphqlQueryRunnerExceptionCode.INVALID_CURSOR]: msg`Invalid cursor provided.`,
[GraphqlQueryRunnerExceptionCode.INVALID_DIRECTION]: msg`Invalid direction provided.`,
[GraphqlQueryRunnerExceptionCode.UNSUPPORTED_OPERATOR]: msg`Unsupported operator.`,
[GraphqlQueryRunnerExceptionCode.ARGS_CONFLICT]: msg`Conflicting arguments provided.`,
[GraphqlQueryRunnerExceptionCode.FIELD_NOT_FOUND]: msg`Field not found.`,
[GraphqlQueryRunnerExceptionCode.MISSING_SYSTEM_FIELD]: msg`Missing required system field.`,
[GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND]: msg`Object not found.`,
[GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND]: msg`Record not found.`,
[GraphqlQueryRunnerExceptionCode.INVALID_ARGS_FIRST]: msg`Invalid 'first' argument.`,
[GraphqlQueryRunnerExceptionCode.INVALID_ARGS_LAST]: msg`Invalid 'last' argument.`,
[GraphqlQueryRunnerExceptionCode.RELATION_SETTINGS_NOT_FOUND]: msg`Relation settings not found.`,
[GraphqlQueryRunnerExceptionCode.RELATION_TARGET_OBJECT_METADATA_NOT_FOUND]: msg`Relation target not found.`,
[GraphqlQueryRunnerExceptionCode.NOT_IMPLEMENTED]: msg`This feature is not implemented.`,
[GraphqlQueryRunnerExceptionCode.INVALID_POST_HOOK_PAYLOAD]: msg`Invalid post-hook payload.`,
[GraphqlQueryRunnerExceptionCode.UPSERT_MULTIPLE_MATCHING_RECORDS_CONFLICT]: msg`Multiple matching records found during upsert.`,
[GraphqlQueryRunnerExceptionCode.UPSERT_MAX_RECORDS_EXCEEDED]: msg`Maximum records exceeded for upsert.`,
};
export class GraphqlQueryRunnerException extends CustomException<GraphqlQueryRunnerExceptionCode> {
constructor(
message: string,
code: GraphqlQueryRunnerExceptionCode,
{ userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {},
{ userFriendlyMessage }: { userFriendlyMessage: MessageDescriptor },
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
graphqlQueryRunnerExceptionUserFriendlyMessages[code],
userFriendlyMessage,
});
}
}
@@ -1,3 +1,4 @@
import { msg } from '@lingui/core/macro';
import { compositeTypeDefinitions } from 'twenty-shared/types';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { type WhereExpressionBuilder } from 'typeorm';
@@ -73,6 +74,7 @@ export class GraphqlQueryFilterFieldParser {
throw new GraphqlQueryRunnerException(
`Invalid filter value for field ${key}. Expected non-empty array`,
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{ userFriendlyMessage: msg`Invalid filter value: "${value}"` },
);
}
const { sql, params } = computeWhereConditionParts({
@@ -133,6 +135,7 @@ export class GraphqlQueryFilterFieldParser {
throw new GraphqlQueryRunnerException(
`Invalid filter value for field ${subFieldKey}. Expected non-empty array`,
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{ userFriendlyMessage: msg`Invalid filter value: "${value}"` },
);
}
@@ -14,6 +14,7 @@ import { isDefined } from 'twenty-shared/utils';
import { type ObjectRecordOrderBy } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
@@ -90,6 +91,7 @@ export class GraphqlQueryOrderFieldParser {
throw new GraphqlQueryRunnerException(
`Field "${fieldName}" does not exist or is not sortable`,
GraphqlQueryRunnerExceptionCode.FIELD_NOT_FOUND,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -1,5 +1,6 @@
import { OrderByDirection } from 'twenty-shared/types';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
@@ -35,6 +36,7 @@ export const convertOrderByToFindOptionsOrder = (
throw new GraphqlQueryRunnerException(
`Invalid direction: ${direction}`,
GraphqlQueryRunnerExceptionCode.INVALID_DIRECTION,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
};
@@ -10,6 +10,7 @@ import {
CommonQueryRunnerException,
CommonQueryRunnerExceptionCode,
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import { type GroupByField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/types/group-by-field.types';
import { isGroupByDateField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/is-group-by-date-field.util';
import { isGroupByRelationField } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/is-group-by-relation-field.util';
@@ -44,6 +45,7 @@ export const getGroupByExpression = ({
throw new CommonQueryRunnerException(
'Time zone should be specified for a group by date on Day, Week, Month, Quarter or Year',
CommonQueryRunnerExceptionCode.MISSING_TIMEZONE_FOR_DATE_GROUP_BY,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -1,6 +1,7 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
@@ -68,6 +69,7 @@ const getNestedFieldMetadataDetails = ({
throw new GraphqlQueryRunnerException(
`Nested field "${nestedFieldName}" not found in target object "${targetObjectMetadata.nameSingular}"`,
GraphqlQueryRunnerExceptionCode.FIELD_NOT_FOUND,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -133,6 +135,7 @@ const handleNestedCompositeField = ({
throw new GraphqlQueryRunnerException(
`Composite field "${nestedFieldName}" requires a subfield to be specified`,
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
};
@@ -1,3 +1,4 @@
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
@@ -14,6 +15,7 @@ export const validateSingleKeyForGroupByOrThrow = ({
throw new GraphqlQueryRunnerException(
errorMessage,
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
};
@@ -8,6 +8,7 @@ import { isDefined } from 'twenty-shared/utils';
import { type ObjectRecordOrderBy } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import { CONNECTION_MAX_DEPTH } from 'src/engine/api/graphql/graphql-query-runner/constants/connection-max-depth.constant';
import {
GraphqlQueryRunnerException,
@@ -159,6 +160,7 @@ export class ObjectRecordsToGraphqlConnectionHelper {
throw new GraphqlQueryRunnerException(
`Maximum depth of ${CONNECTION_MAX_DEPTH} reached`,
GraphqlQueryRunnerExceptionCode.MAX_DEPTH_REACHED,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -5,6 +5,7 @@ import { type FindOptionsRelations, type ObjectLiteral } from 'typeorm';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
@@ -147,6 +148,7 @@ export class ProcessNestedRelationsV2Helper {
throw new GraphqlQueryRunnerException(
`Relation settings not found for field ${sourceFieldName}`,
GraphqlQueryRunnerExceptionCode.RELATION_SETTINGS_NOT_FOUND,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -271,6 +273,7 @@ export class ProcessNestedRelationsV2Helper {
throw new GraphqlQueryRunnerException(
`Field ${sourceFieldName} not found on object ${parentObjectMetadataItem.nameSingular}`,
GraphqlQueryRunnerExceptionCode.FIELD_NOT_FOUND,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -286,6 +289,7 @@ export class ProcessNestedRelationsV2Helper {
throw new GraphqlQueryRunnerException(
`Relation target object metadata id or field metadata id not found for field ${sourceFieldName}`,
GraphqlQueryRunnerExceptionCode.RELATION_TARGET_OBJECT_METADATA_NOT_FOUND,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -3,6 +3,7 @@ import { isDefined } from 'twenty-shared/utils';
import { type ObjectLiteral } from 'typeorm';
import { findPostgresDefaultNullEquivalentValue } from 'src/engine/api/common/common-args-processors/data-arg-processor/utils/find-postgres-default-null-equivalent-value.util';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
@@ -149,6 +150,7 @@ export const computeWhereConditionParts = ({
throw new GraphqlQueryRunnerException(
`Operator "${operator}" is not supported`,
GraphqlQueryRunnerExceptionCode.UNSUPPORTED_OPERATOR,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
};
@@ -7,6 +7,7 @@ import {
CommonQueryRunnerException,
CommonQueryRunnerExceptionCode,
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
export interface CursorData {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -20,6 +21,7 @@ export const decodeCursor = <T = CursorData>(cursor: string): T => {
throw new CommonQueryRunnerException(
`Invalid cursor: ${cursor}`,
CommonQueryRunnerExceptionCode.INVALID_CURSOR,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
};
@@ -1,3 +1,4 @@
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
@@ -14,6 +15,7 @@ export const getTargetObjectMetadataOrThrow = (
throw new GraphqlQueryRunnerException(
`Relation target object metadata id not found for field ${fieldMetadata.name}`,
GraphqlQueryRunnerExceptionCode.RELATION_TARGET_OBJECT_METADATA_NOT_FOUND,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -24,6 +26,7 @@ export const getTargetObjectMetadataOrThrow = (
throw new GraphqlQueryRunnerException(
`Target object metadata not found for field ${fieldMetadata.name}`,
GraphqlQueryRunnerExceptionCode.RELATION_TARGET_OBJECT_METADATA_NOT_FOUND,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -1,3 +1,4 @@
import { msg } from '@lingui/core/macro';
import { isValidUuid } from 'twenty-shared/utils';
import {
@@ -10,6 +11,7 @@ export const assertIsValidUuid = (value: string) => {
throw new WorkspaceQueryRunnerException(
`Value "${value}" is not a valid UUID`,
WorkspaceQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{ userFriendlyMessage: msg`Invalid UUID format.` },
);
}
};
@@ -12,6 +12,7 @@ import {
type WorkspacePreQueryHookInstance,
} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
@@ -148,6 +149,7 @@ export class WorkspaceQueryHookExplorer implements OnModuleInit {
throw new GraphqlQueryRunnerException(
`Unsupported payload type: ${payload}`,
GraphqlQueryRunnerExceptionCode.INVALID_POST_HOOK_PAYLOAD,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -1,6 +1,8 @@
import { type MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { assertUnreachable } from 'twenty-shared/utils';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import {
appendCommonExceptionCode,
CustomException,
@@ -17,18 +19,26 @@ export const WorkspaceQueryRunnerExceptionCode = appendCommonExceptionCode({
NO_ROWS_AFFECTED: 'NO_ROWS_AFFECTED',
} as const);
const workspaceQueryRunnerExceptionUserFriendlyMessages: Record<
keyof typeof WorkspaceQueryRunnerExceptionCode,
MessageDescriptor
> = {
INVALID_QUERY_INPUT: msg`Invalid query input.`,
DATA_NOT_FOUND: msg`Data not found.`,
QUERY_TIMEOUT: msg`Query timed out.`,
QUERY_VIOLATES_UNIQUE_CONSTRAINT: msg`A record with this value already exists.`,
QUERY_VIOLATES_FOREIGN_KEY_CONSTRAINT: msg`Cannot complete operation due to related records.`,
TOO_MANY_ROWS_AFFECTED: msg`Too many records affected.`,
NO_ROWS_AFFECTED: msg`No records were affected.`,
INTERNAL_SERVER_ERROR: msg`An unexpected error occurred.`,
const getWorkspaceQueryRunnerExceptionUserFriendlyMessage = (
code: keyof typeof WorkspaceQueryRunnerExceptionCode,
) => {
switch (code) {
case WorkspaceQueryRunnerExceptionCode.QUERY_VIOLATES_UNIQUE_CONSTRAINT:
return msg`A record with this value already exists.`;
case WorkspaceQueryRunnerExceptionCode.QUERY_VIOLATES_FOREIGN_KEY_CONSTRAINT:
return msg`Cannot complete operation due to related records.`;
case WorkspaceQueryRunnerExceptionCode.TOO_MANY_ROWS_AFFECTED:
return msg`Too many records affected.`;
case WorkspaceQueryRunnerExceptionCode.NO_ROWS_AFFECTED:
return msg`No records were affected.`;
case WorkspaceQueryRunnerExceptionCode.QUERY_TIMEOUT:
case WorkspaceQueryRunnerExceptionCode.DATA_NOT_FOUND:
case WorkspaceQueryRunnerExceptionCode.INVALID_QUERY_INPUT:
case WorkspaceQueryRunnerExceptionCode.INTERNAL_SERVER_ERROR:
return STANDARD_ERROR_MESSAGE;
default:
assertUnreachable(code);
}
};
export class WorkspaceQueryRunnerException extends CustomException<
@@ -42,7 +52,7 @@ export class WorkspaceQueryRunnerException extends CustomException<
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
workspaceQueryRunnerExceptionUserFriendlyMessages[code],
getWorkspaceQueryRunnerExceptionUserFriendlyMessage(code),
});
}
}
@@ -1,5 +1,6 @@
import { isDefined } from 'twenty-shared/utils';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import { type CommonSelectedFields } from 'src/engine/api/common/types/common-selected-fields-result.type';
import {
RestInputRequestParserException,
@@ -18,6 +19,7 @@ export const parseAggregateFieldsRestRequest = (
throw new RestInputRequestParserException(
`Invalid aggregate query parameter - should be a valid array of string - ex: ["countNotEmptyId", "countEmptyField"]`,
RestInputRequestParserExceptionCode.INVALID_AGGREGATE_FIELDS_QUERY_PARAM,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -36,6 +38,7 @@ export const parseAggregateFieldsRestRequest = (
throw new RestInputRequestParserException(
`Invalid aggregate query parameter - should be a valid array of string - ex: ["countNotEmptyId", "countEmptyField"]`,
RestInputRequestParserExceptionCode.INVALID_AGGREGATE_FIELDS_QUERY_PARAM,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
};
@@ -1,3 +1,4 @@
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import {
RestInputRequestParserException,
RestInputRequestParserExceptionCode,
@@ -22,6 +23,7 @@ export const parseDepthRestRequest = (request: AuthenticatedRequest): Depth => {
', ',
)}`,
RestInputRequestParserExceptionCode.INVALID_DEPTH_QUERY_PARAM,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -1,3 +1,5 @@
import { msg } from '@lingui/core/macro';
import {
RestInputRequestParserException,
RestInputRequestParserExceptionCode,
@@ -19,6 +21,7 @@ export const checkFilterQuery = (filterQuery: string): void => {
throw new RestInputRequestParserException(
`'filter' invalid. ${hint} missing in the query`,
RestInputRequestParserExceptionCode.INVALID_FILTER_QUERY_PARAM,
{ userFriendlyMessage: msg`Invalid filter parameter.` },
);
}
@@ -1,5 +1,7 @@
import { BadRequestException } from '@nestjs/common';
import { msg } from '@lingui/core/macro';
import { type FieldValue } from 'src/engine/api/rest/core/types/field-value.type';
import { formatFieldValue } from 'src/engine/api/rest/input-request-parsers/filter-parser-utils/format-field-values.util';
import { parseBaseFilter } from 'src/engine/api/rest/input-request-parsers/filter-parser-utils/parse-base-filter.util';
@@ -40,6 +42,7 @@ export const parseFilter = (
throw new RestInputRequestParserException(
`'filter' invalid. 'not' conjunction should contain only 1 condition. eg: not(field[eq]:1)`,
RestInputRequestParserExceptionCode.INVALID_FILTER_QUERY_PARAM,
{ userFriendlyMessage: msg`Invalid filter parameter.` },
);
}
// @ts-expect-error legacy noImplicitAny
@@ -1,5 +1,6 @@
import { type MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { assertUnreachable } from 'twenty-shared/utils';
import { CustomException } from 'src/utils/custom-exception';
@@ -13,17 +14,27 @@ export enum RestInputRequestParserExceptionCode {
INVALID_FILTER_QUERY_PARAM = 'INVALID_FILTER_QUERY_PARAM',
}
const restInputRequestParserExceptionUserFriendlyMessages: Record<
RestInputRequestParserExceptionCode,
MessageDescriptor
> = {
[RestInputRequestParserExceptionCode.INVALID_AGGREGATE_FIELDS_QUERY_PARAM]: msg`Invalid aggregate fields parameter.`,
[RestInputRequestParserExceptionCode.INVALID_GROUP_BY_QUERY_PARAM]: msg`Invalid group by parameter.`,
[RestInputRequestParserExceptionCode.INVALID_ORDER_BY_WITH_GROUP_BY_QUERY_PARAM]: msg`Invalid order by with group by parameter.`,
[RestInputRequestParserExceptionCode.INVALID_ORDER_BY_QUERY_PARAM]: msg`Invalid order by parameter.`,
[RestInputRequestParserExceptionCode.INVALID_DEPTH_QUERY_PARAM]: msg`Invalid depth parameter.`,
[RestInputRequestParserExceptionCode.INVALID_LIMIT_QUERY_PARAM]: msg`Invalid limit parameter.`,
[RestInputRequestParserExceptionCode.INVALID_FILTER_QUERY_PARAM]: msg`Invalid filter parameter.`,
const getRestInputRequestParserExceptionUserFriendlyMessage = (
code: RestInputRequestParserExceptionCode,
) => {
switch (code) {
case RestInputRequestParserExceptionCode.INVALID_AGGREGATE_FIELDS_QUERY_PARAM:
return msg`Invalid aggregate fields parameter.`;
case RestInputRequestParserExceptionCode.INVALID_GROUP_BY_QUERY_PARAM:
return msg`Invalid group by parameter.`;
case RestInputRequestParserExceptionCode.INVALID_ORDER_BY_WITH_GROUP_BY_QUERY_PARAM:
return msg`Invalid order by with group by parameter.`;
case RestInputRequestParserExceptionCode.INVALID_ORDER_BY_QUERY_PARAM:
return msg`Invalid order by parameter.`;
case RestInputRequestParserExceptionCode.INVALID_DEPTH_QUERY_PARAM:
return msg`Invalid depth parameter.`;
case RestInputRequestParserExceptionCode.INVALID_LIMIT_QUERY_PARAM:
return msg`Invalid limit parameter.`;
case RestInputRequestParserExceptionCode.INVALID_FILTER_QUERY_PARAM:
return msg`Invalid filter parameter.`;
default:
assertUnreachable(code);
}
};
export class RestInputRequestParserException extends CustomException<RestInputRequestParserExceptionCode> {
@@ -35,7 +46,7 @@ export class RestInputRequestParserException extends CustomException<RestInputRe
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
restInputRequestParserExceptionUserFriendlyMessages[code],
getRestInputRequestParserExceptionUserFriendlyMessage(code),
});
}
}
@@ -11,6 +11,7 @@ import {
type ObjectRecordOrderBy,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
@@ -46,6 +47,7 @@ export const buildCursorCompositeFieldWhereCondition = ({
throw new GraphqlQueryRunnerException(
`Composite type definition not found for type: ${fieldType}`,
GraphqlQueryRunnerExceptionCode.INVALID_CURSOR,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -112,6 +114,7 @@ export const buildCursorCompositeFieldWhereCondition = ({
throw new GraphqlQueryRunnerException(
'Invalid cursor',
GraphqlQueryRunnerExceptionCode.INVALID_CURSOR,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -7,6 +7,7 @@ import {
type ObjectRecordOrderBy,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
@@ -59,6 +60,7 @@ export const buildCursorWhereCondition = ({
throw new GraphqlQueryRunnerException(
`Field metadata not found for key: ${cursorKey}`,
GraphqlQueryRunnerExceptionCode.INVALID_CURSOR,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -84,6 +86,7 @@ export const buildCursorWhereCondition = ({
throw new GraphqlQueryRunnerException(
'Invalid cursor',
GraphqlQueryRunnerExceptionCode.INVALID_CURSOR,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -8,6 +8,7 @@ import { isDefined } from 'twenty-shared/utils';
import { type ObjectRecordOrderBy } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
@@ -51,6 +52,7 @@ export const validateAndGetOrderByForScalarField = (
throw new GraphqlQueryRunnerException(
'Invalid cursor',
GraphqlQueryRunnerExceptionCode.INVALID_CURSOR,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -58,6 +60,7 @@ export const validateAndGetOrderByForScalarField = (
throw new GraphqlQueryRunnerException(
'Expected non-composite field order by',
GraphqlQueryRunnerExceptionCode.INVALID_CURSOR,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -74,6 +77,7 @@ export const validateAndGetOrderByForCompositeField = (
throw new GraphqlQueryRunnerException(
'Invalid cursor',
GraphqlQueryRunnerExceptionCode.INVALID_CURSOR,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -81,6 +85,7 @@ export const validateAndGetOrderByForCompositeField = (
throw new GraphqlQueryRunnerException(
'Expected composite field order by',
GraphqlQueryRunnerExceptionCode.INVALID_CURSOR,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}