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 },
);
}
@@ -3,6 +3,7 @@ import { isDefined } from 'twenty-shared/utils';
import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
import { type CreateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
@@ -31,6 +32,7 @@ export class CreatedByCreateManyPreQueryHook
throw new GraphqlQueryRunnerException(
'Payload data is required',
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -3,6 +3,7 @@ import { isDefined } from 'twenty-shared/utils';
import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
import { type CreateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
@@ -31,6 +32,7 @@ export class CreatedByCreateOnePreQueryHook
throw new GraphqlQueryRunnerException(
'Payload data is required',
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -3,6 +3,7 @@ import { isDefined } from 'twenty-shared/utils';
import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
import { type UpdateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
@@ -31,6 +32,7 @@ export class UpdatedByUpdateManyPreQueryHook
throw new GraphqlQueryRunnerException(
'Payload data is required',
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -3,6 +3,7 @@ import { isDefined } from 'twenty-shared/utils';
import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
import { type UpdateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
@@ -31,6 +32,7 @@ export class UpdatedByUpdateOnePreQueryHook
throw new GraphqlQueryRunnerException(
'Payload data is required',
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
}
@@ -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';
@@ -11,15 +12,21 @@ export enum ApiKeyExceptionCode {
ROLE_CANNOT_BE_ASSIGNED_TO_API_KEYS = 'ROLE_CANNOT_BE_ASSIGNED_TO_API_KEYS',
}
const apiKeyExceptionUserFriendlyMessages: Record<
ApiKeyExceptionCode,
MessageDescriptor
> = {
[ApiKeyExceptionCode.API_KEY_NOT_FOUND]: msg`API key not found.`,
[ApiKeyExceptionCode.API_KEY_REVOKED]: msg`This API key has been revoked.`,
[ApiKeyExceptionCode.API_KEY_EXPIRED]: msg`This API key has expired.`,
[ApiKeyExceptionCode.API_KEY_NO_ROLE_ASSIGNED]: msg`This API key has no role assigned.`,
[ApiKeyExceptionCode.ROLE_CANNOT_BE_ASSIGNED_TO_API_KEYS]: msg`This role cannot be assigned to API keys.`,
const getApiKeyExceptionUserFriendlyMessage = (code: ApiKeyExceptionCode) => {
switch (code) {
case ApiKeyExceptionCode.API_KEY_NOT_FOUND:
return msg`API key not found.`;
case ApiKeyExceptionCode.API_KEY_REVOKED:
return msg`This API key has been revoked.`;
case ApiKeyExceptionCode.API_KEY_EXPIRED:
return msg`This API key has expired.`;
case ApiKeyExceptionCode.API_KEY_NO_ROLE_ASSIGNED:
return msg`This API key has no role assigned.`;
case ApiKeyExceptionCode.ROLE_CANNOT_BE_ASSIGNED_TO_API_KEYS:
return msg`This role cannot be assigned to API keys.`;
default:
assertUnreachable(code);
}
};
export class ApiKeyException extends CustomException<ApiKeyExceptionCode> {
@@ -30,7 +37,7 @@ export class ApiKeyException extends CustomException<ApiKeyExceptionCode> {
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? apiKeyExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getApiKeyExceptionUserFriendlyMessage(code),
});
}
}
@@ -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';
@@ -12,16 +13,25 @@ export enum ApplicationExceptionCode {
FORBIDDEN = 'FORBIDDEN',
}
const applicationExceptionUserFriendlyMessages: Record<
ApplicationExceptionCode,
MessageDescriptor
> = {
[ApplicationExceptionCode.OBJECT_NOT_FOUND]: msg`Object not found.`,
[ApplicationExceptionCode.FIELD_NOT_FOUND]: msg`Field not found.`,
[ApplicationExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND]: msg`Serverless function not found.`,
[ApplicationExceptionCode.ENTITY_NOT_FOUND]: msg`Entity not found.`,
[ApplicationExceptionCode.APPLICATION_NOT_FOUND]: msg`Application not found.`,
[ApplicationExceptionCode.FORBIDDEN]: msg`You do not have permission to perform this action.`,
const getApplicationExceptionUserFriendlyMessage = (
code: ApplicationExceptionCode,
) => {
switch (code) {
case ApplicationExceptionCode.OBJECT_NOT_FOUND:
return msg`Object not found.`;
case ApplicationExceptionCode.FIELD_NOT_FOUND:
return msg`Field not found.`;
case ApplicationExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND:
return msg`Serverless function not found.`;
case ApplicationExceptionCode.ENTITY_NOT_FOUND:
return msg`Entity not found.`;
case ApplicationExceptionCode.APPLICATION_NOT_FOUND:
return msg`Application not found.`;
case ApplicationExceptionCode.FORBIDDEN:
return msg`You do not have permission to perform this action.`;
default:
assertUnreachable(code);
}
};
export class ApplicationException extends CustomException<ApplicationExceptionCode> {
@@ -32,7 +42,7 @@ export class ApplicationException extends CustomException<ApplicationExceptionCo
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? applicationExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getApplicationExceptionUserFriendlyMessage(code),
});
}
}
@@ -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';
@@ -7,11 +8,15 @@ export enum ApplicationVariableEntityExceptionCode {
APPLICATION_VARIABLE_NOT_FOUND = 'APPLICATION_VARIABLE_NOT_FOUND',
}
const applicationVariableEntityExceptionUserFriendlyMessages: Record<
ApplicationVariableEntityExceptionCode,
MessageDescriptor
> = {
[ApplicationVariableEntityExceptionCode.APPLICATION_VARIABLE_NOT_FOUND]: msg`Application variable not found.`,
const getApplicationVariableEntityExceptionUserFriendlyMessage = (
code: ApplicationVariableEntityExceptionCode,
) => {
switch (code) {
case ApplicationVariableEntityExceptionCode.APPLICATION_VARIABLE_NOT_FOUND:
return msg`Application variable not found.`;
default:
assertUnreachable(code);
}
};
export class ApplicationVariableEntityException extends CustomException<ApplicationVariableEntityExceptionCode> {
@@ -23,7 +28,7 @@ export class ApplicationVariableEntityException extends CustomException<Applicat
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
applicationVariableEntityExceptionUserFriendlyMessages[code],
getApplicationVariableEntityExceptionUserFriendlyMessage(code),
});
}
}
@@ -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 ApprovedAccessDomainExceptionCode {
APPROVED_ACCESS_DOMAIN_MUST_BE_A_COMPANY_DOMAIN = 'APPROVED_ACCESS_DOMAIN_MUST_BE_A_COMPANY_DOMAIN',
}
const approvedAccessDomainExceptionUserFriendlyMessages: Record<
ApprovedAccessDomainExceptionCode,
MessageDescriptor
> = {
[ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_NOT_FOUND]: msg`Approved access domain not found.`,
[ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_ALREADY_VERIFIED]: msg`This domain has already been verified.`,
[ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_ALREADY_REGISTERED]: msg`This domain is already registered.`,
[ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_DOES_NOT_MATCH_DOMAIN_EMAIL]: msg`The domain does not match your email domain.`,
[ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_VALIDATION_TOKEN_INVALID]: msg`Invalid validation token.`,
[ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_ALREADY_VALIDATED]: msg`This domain has already been validated.`,
[ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_MUST_BE_A_COMPANY_DOMAIN]: msg`Please use a company email domain.`,
const getApprovedAccessDomainExceptionUserFriendlyMessage = (
code: ApprovedAccessDomainExceptionCode,
) => {
switch (code) {
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_NOT_FOUND:
return msg`Approved access domain not found.`;
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_ALREADY_VERIFIED:
return msg`This domain has already been verified.`;
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_ALREADY_REGISTERED:
return msg`This domain is already registered.`;
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_DOES_NOT_MATCH_DOMAIN_EMAIL:
return msg`The domain does not match your email domain.`;
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_VALIDATION_TOKEN_INVALID:
return msg`Invalid validation token.`;
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_ALREADY_VALIDATED:
return msg`This domain has already been validated.`;
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_MUST_BE_A_COMPANY_DOMAIN:
return msg`Please use a company email domain.`;
default:
assertUnreachable(code);
}
};
export class ApprovedAccessDomainException extends CustomException<ApprovedAccessDomainExceptionCode> {
@@ -35,7 +46,7 @@ export class ApprovedAccessDomainException extends CustomException<ApprovedAcces
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
approvedAccessDomainExceptionUserFriendlyMessages[code],
getApprovedAccessDomainExceptionUserFriendlyMessage(code),
});
}
}
@@ -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';
@@ -8,12 +9,15 @@ export enum AuditExceptionCode {
INVALID_INPUT = 'INVALID_INPUT',
}
const auditExceptionUserFriendlyMessages: Record<
AuditExceptionCode,
MessageDescriptor
> = {
[AuditExceptionCode.INVALID_TYPE]: msg`Invalid audit type.`,
[AuditExceptionCode.INVALID_INPUT]: msg`Invalid audit input.`,
const getAuditExceptionUserFriendlyMessage = (code: AuditExceptionCode) => {
switch (code) {
case AuditExceptionCode.INVALID_TYPE:
return msg`Invalid audit type.`;
case AuditExceptionCode.INVALID_INPUT:
return msg`Invalid audit input.`;
default:
assertUnreachable(code);
}
};
export class AuditException extends CustomException<AuditExceptionCode> {
@@ -24,7 +28,7 @@ export class AuditException extends CustomException<AuditExceptionCode> {
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? auditExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getAuditExceptionUserFriendlyMessage(code),
});
}
}
@@ -1,55 +1,13 @@
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,
} from 'src/utils/custom-exception';
const authExceptionUserFriendlyMessages: Record<
keyof typeof AuthExceptionCode,
MessageDescriptor
> = {
USER_NOT_FOUND: msg`User not found.`,
USER_WORKSPACE_NOT_FOUND: msg`User workspace not found.`,
EMAIL_NOT_VERIFIED: msg`Email is not verified.`,
CLIENT_NOT_FOUND: msg`Client not found.`,
WORKSPACE_NOT_FOUND: msg`Workspace not found.`,
APPLICATION_NOT_FOUND: msg`Application not found.`,
INVALID_INPUT: msg`Invalid input provided.`,
FORBIDDEN_EXCEPTION: msg`You do not have permission to perform this action.`,
INSUFFICIENT_SCOPES: msg`Insufficient permissions.`,
UNAUTHENTICATED: msg`You must be authenticated to perform this action.`,
INVALID_DATA: msg`Invalid data provided.`,
OAUTH_ACCESS_DENIED: msg`OAuth access was denied.`,
SSO_AUTH_FAILED: msg`Single sign-on authentication failed.`,
USE_SSO_AUTH: msg`Please use single sign-on to authenticate.`,
SIGNUP_DISABLED: msg`Sign up is disabled.`,
GOOGLE_API_AUTH_DISABLED: msg`Google API authentication is disabled.`,
MICROSOFT_API_AUTH_DISABLED: msg`Microsoft API authentication is disabled.`,
MISSING_ENVIRONMENT_VARIABLE: msg`A required configuration is missing.`,
INVALID_JWT_TOKEN_TYPE: msg`Invalid authentication token.`,
TWO_FACTOR_AUTHENTICATION_PROVISION_REQUIRED: msg`Two-factor authentication setup is required.`,
TWO_FACTOR_AUTHENTICATION_VERIFICATION_REQUIRED: msg`Two-factor authentication verification is required.`,
USER_ALREADY_EXISTS: msg`A user with this email already exists.`,
INTERNAL_SERVER_ERROR: msg`An unexpected error occurred.`,
};
export class AuthException extends CustomException<
keyof typeof AuthExceptionCode
> {
constructor(
message: string,
code: keyof typeof AuthExceptionCode,
{ userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {},
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? authExceptionUserFriendlyMessages[code],
});
}
}
export const AuthExceptionCode = appendCommonExceptionCode({
USER_NOT_FOUND: 'USER_NOT_FOUND',
USER_WORKSPACE_NOT_FOUND: 'USER_WORKSPACE_NOT_FOUND',
@@ -76,3 +34,70 @@ export const AuthExceptionCode = appendCommonExceptionCode({
'TWO_FACTOR_AUTHENTICATION_VERIFICATION_REQUIRED',
USER_ALREADY_EXISTS: 'USER_ALREADY_EXISTS',
} as const);
const getAuthExceptionUserFriendlyMessage = (
code: keyof typeof AuthExceptionCode,
) => {
switch (code) {
case AuthExceptionCode.USER_NOT_FOUND:
return msg`User not found.`;
case AuthExceptionCode.USER_WORKSPACE_NOT_FOUND:
return msg`User workspace not found.`;
case AuthExceptionCode.EMAIL_NOT_VERIFIED:
return msg`Email is not verified.`;
case AuthExceptionCode.WORKSPACE_NOT_FOUND:
return msg`Workspace not found.`;
case AuthExceptionCode.APPLICATION_NOT_FOUND:
return msg`Application not found.`;
case AuthExceptionCode.INVALID_INPUT:
return msg`Invalid input provided.`;
case AuthExceptionCode.FORBIDDEN_EXCEPTION:
return msg`You do not have permission to perform this action.`;
case AuthExceptionCode.INSUFFICIENT_SCOPES:
return msg`Insufficient permissions.`;
case AuthExceptionCode.UNAUTHENTICATED:
return msg`You must be authenticated to perform this action.`;
case AuthExceptionCode.OAUTH_ACCESS_DENIED:
return msg`OAuth access was denied.`;
case AuthExceptionCode.SSO_AUTH_FAILED:
return msg`Single sign-on authentication failed.`;
case AuthExceptionCode.USE_SSO_AUTH:
return msg`Please use single sign-on to authenticate.`;
case AuthExceptionCode.SIGNUP_DISABLED:
return msg`Sign up is disabled.`;
case AuthExceptionCode.GOOGLE_API_AUTH_DISABLED:
return msg`Google API authentication is disabled.`;
case AuthExceptionCode.MICROSOFT_API_AUTH_DISABLED:
return msg`Microsoft API authentication is disabled.`;
case AuthExceptionCode.MISSING_ENVIRONMENT_VARIABLE:
return msg`A required configuration is missing.`;
case AuthExceptionCode.TWO_FACTOR_AUTHENTICATION_PROVISION_REQUIRED:
return msg`Two-factor authentication setup is required.`;
case AuthExceptionCode.TWO_FACTOR_AUTHENTICATION_VERIFICATION_REQUIRED:
return msg`Two-factor authentication verification is required.`;
case AuthExceptionCode.USER_ALREADY_EXISTS:
return msg`A user with this email already exists.`;
case AuthExceptionCode.INTERNAL_SERVER_ERROR:
case AuthExceptionCode.INVALID_DATA:
case AuthExceptionCode.CLIENT_NOT_FOUND:
case AuthExceptionCode.INVALID_JWT_TOKEN_TYPE:
return STANDARD_ERROR_MESSAGE;
default:
assertUnreachable(code);
}
};
export class AuthException extends CustomException<
keyof typeof AuthExceptionCode
> {
constructor(
message: string,
code: keyof typeof AuthExceptionCode,
{ userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {},
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? getAuthExceptionUserFriendlyMessage(code),
});
}
}
@@ -8,10 +8,10 @@ import { render } from '@react-email/render';
import { addMilliseconds } from 'date-fns';
import ms from 'ms';
import { PasswordUpdateNotifyEmail } from 'twenty-emails';
import { PermissionFlagType } from 'twenty-shared/constants';
import { AppPath } from 'twenty-shared/types';
import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { PermissionFlagType } from 'twenty-shared/constants';
import { NodeEnvironment } from 'src/engine/core-modules/twenty-config/interfaces/node-environment.interface';
@@ -191,7 +191,7 @@ export class AuthService {
'Wrong password',
AuthExceptionCode.FORBIDDEN_EXCEPTION,
{
userFriendlyMessage: msg`Wrong password`,
userFriendlyMessage: msg`Wrong password.`,
},
);
}
@@ -357,6 +357,9 @@ export class AuthService {
throw new AuthException(
'Email is required',
AuthExceptionCode.INVALID_INPUT,
{
userFriendlyMessage: msg`Email is required.`,
},
);
}
@@ -603,6 +606,9 @@ export class AuthService {
throw new AuthException(
'Password is too weak',
AuthExceptionCode.INVALID_INPUT,
{
userFriendlyMessage: msg`Password is too weak.`,
},
);
}
@@ -650,6 +656,9 @@ export class AuthService {
throw new AuthException(
'Workspace does not exist',
AuthExceptionCode.INVALID_INPUT,
{
userFriendlyMessage: msg`Workspace does not exist.`,
},
);
}
@@ -1,5 +1,7 @@
import { randomUUID } from 'crypto';
import { msg } from '@lingui/core/macro';
import {
AuthException,
AuthExceptionCode,
@@ -12,15 +14,6 @@ import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.ent
import { JwtAuthStrategy } from './jwt.auth.strategy';
jest.mock('twenty-shared/utils', () => ({
...jest.requireActual('twenty-shared/utils'),
assertIsDefinedOrThrow: jest.fn((value, error) => {
if (value === null || value === undefined) {
throw error;
}
}),
}));
describe('JwtAuthStrategy', () => {
let strategy: JwtAuthStrategy;
let workspaceRepository: any;
@@ -230,7 +223,9 @@ describe('JwtAuthStrategy', () => {
);
await expect(strategy.validate(payload as JwtPayload)).rejects.toThrow(
new AuthException('UserWorkspaceEntity not found', expect.any(String)),
new AuthException('UserWorkspaceEntity not found', expect.any(String), {
userFriendlyMessage: msg`User does not have access to this workspace.`,
}),
);
try {
@@ -269,7 +264,9 @@ describe('JwtAuthStrategy', () => {
);
await expect(strategy.validate(payload as JwtPayload)).rejects.toThrow(
new AuthException('UserWorkspaceEntity not found', expect.any(String)),
new AuthException('UserWorkspaceEntity not found', expect.any(String), {
userFriendlyMessage: msg`User does not have access to this workspace.`,
}),
);
try {
@@ -345,7 +342,9 @@ describe('JwtAuthStrategy', () => {
);
await expect(strategy.validate(payload as JwtPayload)).rejects.toThrow(
new AuthException('Application not found', expect.any(String)),
new AuthException('Application not found', expect.any(String), {
userFriendlyMessage: msg`Application not found.`,
}),
);
try {
@@ -552,6 +551,7 @@ describe('JwtAuthStrategy', () => {
new AuthException(
'Invalid impersonation token, cannot find impersonator or impersonated user workspace',
AuthExceptionCode.USER_WORKSPACE_NOT_FOUND,
{ userFriendlyMessage: msg`User workspace not found.` },
),
);
});
@@ -2,6 +2,7 @@
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';
@@ -33,35 +34,61 @@ export enum BillingExceptionCode {
BILLING_CREDITS_EXHAUSTED = 'BILLING_CREDITS_EXHAUSTED',
}
const billingExceptionUserFriendlyMessages: Record<
BillingExceptionCode,
MessageDescriptor
> = {
[BillingExceptionCode.BILLING_CUSTOMER_NOT_FOUND]: msg`Billing customer not found.`,
[BillingExceptionCode.BILLING_PLAN_NOT_FOUND]: msg`Billing plan not found.`,
[BillingExceptionCode.BILLING_PRODUCT_NOT_FOUND]: msg`Billing product not found.`,
[BillingExceptionCode.BILLING_PRICE_NOT_FOUND]: msg`Billing price not found.`,
[BillingExceptionCode.BILLING_METER_NOT_FOUND]: msg`Billing meter not found.`,
[BillingExceptionCode.BILLING_SUBSCRIPTION_NOT_FOUND]: msg`Subscription not found.`,
[BillingExceptionCode.BILLING_SUBSCRIPTION_ITEM_NOT_FOUND]: msg`Subscription item not found.`,
[BillingExceptionCode.BILLING_SUBSCRIPTION_INVALID]: msg`Invalid subscription.`,
[BillingExceptionCode.BILLING_SUBSCRIPTION_EVENT_WORKSPACE_NOT_FOUND]: msg`Workspace not found for subscription event.`,
[BillingExceptionCode.BILLING_CUSTOMER_EVENT_WORKSPACE_NOT_FOUND]: msg`Workspace not found for customer event.`,
[BillingExceptionCode.BILLING_ACTIVE_SUBSCRIPTION_NOT_FOUND]: msg`No active subscription found.`,
[BillingExceptionCode.BILLING_METER_EVENT_FAILED]: msg`Failed to record billing event.`,
[BillingExceptionCode.BILLING_MISSING_REQUEST_BODY]: msg`Missing request body.`,
[BillingExceptionCode.BILLING_UNHANDLED_ERROR]: msg`An unexpected billing error occurred.`,
[BillingExceptionCode.BILLING_STRIPE_ERROR]: msg`A payment processing error occurred.`,
[BillingExceptionCode.BILLING_SUBSCRIPTION_NOT_IN_TRIAL_PERIOD]: msg`Subscription is not in trial period.`,
[BillingExceptionCode.BILLING_SUBSCRIPTION_INTERVAL_NOT_SWITCHABLE]: msg`Cannot switch subscription interval.`,
[BillingExceptionCode.BILLING_SUBSCRIPTION_INTERVAL_INVALID]: msg`Invalid subscription interval.`,
[BillingExceptionCode.BILLING_SUBSCRIPTION_PLAN_NOT_SWITCHABLE]: msg`Cannot switch subscription plan.`,
[BillingExceptionCode.BILLING_SUBSCRIPTION_ITEM_INVALID]: msg`Invalid subscription item.`,
[BillingExceptionCode.BILLING_PRICE_INVALID_TIERS]: msg`Invalid pricing tiers.`,
[BillingExceptionCode.BILLING_PRICE_INVALID]: msg`Invalid price.`,
[BillingExceptionCode.BILLING_SUBSCRIPTION_PHASE_NOT_FOUND]: msg`Subscription phase not found.`,
[BillingExceptionCode.BILLING_TOO_MUCH_SUBSCRIPTIONS_FOUND]: msg`Multiple subscriptions found where one was expected.`,
[BillingExceptionCode.BILLING_CREDITS_EXHAUSTED]: msg`You have exhausted your credits. Please upgrade your plan to continue.`,
const getBillingExceptionUserFriendlyMessage = (code: BillingExceptionCode) => {
switch (code) {
case BillingExceptionCode.BILLING_CUSTOMER_NOT_FOUND:
return msg`Billing customer not found.`;
case BillingExceptionCode.BILLING_PLAN_NOT_FOUND:
return msg`Billing plan not found.`;
case BillingExceptionCode.BILLING_PRODUCT_NOT_FOUND:
return msg`Billing product not found.`;
case BillingExceptionCode.BILLING_PRICE_NOT_FOUND:
return msg`Billing price not found.`;
case BillingExceptionCode.BILLING_METER_NOT_FOUND:
return msg`Billing meter not found.`;
case BillingExceptionCode.BILLING_SUBSCRIPTION_NOT_FOUND:
return msg`Subscription not found.`;
case BillingExceptionCode.BILLING_SUBSCRIPTION_ITEM_NOT_FOUND:
return msg`Subscription item not found.`;
case BillingExceptionCode.BILLING_SUBSCRIPTION_INVALID:
return msg`Invalid subscription.`;
case BillingExceptionCode.BILLING_SUBSCRIPTION_EVENT_WORKSPACE_NOT_FOUND:
return msg`Workspace not found for subscription event.`;
case BillingExceptionCode.BILLING_CUSTOMER_EVENT_WORKSPACE_NOT_FOUND:
return msg`Workspace not found for customer event.`;
case BillingExceptionCode.BILLING_ACTIVE_SUBSCRIPTION_NOT_FOUND:
return msg`No active subscription found.`;
case BillingExceptionCode.BILLING_METER_EVENT_FAILED:
return msg`Failed to record billing event.`;
case BillingExceptionCode.BILLING_MISSING_REQUEST_BODY:
return msg`Missing request body.`;
case BillingExceptionCode.BILLING_UNHANDLED_ERROR:
return msg`An unexpected billing error occurred.`;
case BillingExceptionCode.BILLING_STRIPE_ERROR:
return msg`A payment processing error occurred.`;
case BillingExceptionCode.BILLING_SUBSCRIPTION_NOT_IN_TRIAL_PERIOD:
return msg`Subscription is not in trial period.`;
case BillingExceptionCode.BILLING_SUBSCRIPTION_INTERVAL_NOT_SWITCHABLE:
return msg`Cannot switch subscription interval.`;
case BillingExceptionCode.BILLING_SUBSCRIPTION_INTERVAL_INVALID:
return msg`Invalid subscription interval.`;
case BillingExceptionCode.BILLING_SUBSCRIPTION_PLAN_NOT_SWITCHABLE:
return msg`Cannot switch subscription plan.`;
case BillingExceptionCode.BILLING_SUBSCRIPTION_ITEM_INVALID:
return msg`Invalid subscription item.`;
case BillingExceptionCode.BILLING_PRICE_INVALID_TIERS:
return msg`Invalid pricing tiers.`;
case BillingExceptionCode.BILLING_PRICE_INVALID:
return msg`Invalid price.`;
case BillingExceptionCode.BILLING_SUBSCRIPTION_PHASE_NOT_FOUND:
return msg`Subscription phase not found.`;
case BillingExceptionCode.BILLING_TOO_MUCH_SUBSCRIPTIONS_FOUND:
return msg`Multiple subscriptions found where one was expected.`;
case BillingExceptionCode.BILLING_CREDITS_EXHAUSTED:
return msg`You have exhausted your credits. Please upgrade your plan to continue.`;
default:
assertUnreachable(code);
}
};
export class BillingException extends CustomException<BillingExceptionCode> {
@@ -72,7 +99,7 @@ export class BillingException extends CustomException<BillingExceptionCode> {
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? billingExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getBillingExceptionUserFriendlyMessage(code),
});
}
}
@@ -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';
@@ -7,11 +8,13 @@ export enum CaptchaExceptionCode {
INVALID_CAPTCHA = 'INVALID_CAPTCHA',
}
const captchaExceptionUserFriendlyMessages: Record<
CaptchaExceptionCode,
MessageDescriptor
> = {
[CaptchaExceptionCode.INVALID_CAPTCHA]: msg`Invalid captcha. Please try again.`,
const getCaptchaExceptionUserFriendlyMessage = (code: CaptchaExceptionCode) => {
switch (code) {
case CaptchaExceptionCode.INVALID_CAPTCHA:
return msg`Invalid captcha. Please try again.`;
default:
assertUnreachable(code);
}
};
export class CaptchaException extends CustomException<CaptchaExceptionCode> {
@@ -22,7 +25,7 @@ export class CaptchaException extends CustomException<CaptchaExceptionCode> {
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? captchaExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getCaptchaExceptionUserFriendlyMessage(code),
});
}
}
@@ -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';
@@ -14,18 +15,27 @@ export enum EmailVerificationExceptionCode {
RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED',
}
const emailVerificationExceptionUserFriendlyMessages: Record<
EmailVerificationExceptionCode,
MessageDescriptor
> = {
[EmailVerificationExceptionCode.EMAIL_VERIFICATION_NOT_REQUIRED]: msg`Email verification is not required.`,
[EmailVerificationExceptionCode.INVALID_TOKEN]: msg`Invalid verification token.`,
[EmailVerificationExceptionCode.INVALID_APP_TOKEN_TYPE]: msg`Invalid token type.`,
[EmailVerificationExceptionCode.TOKEN_EXPIRED]: msg`Verification token has expired.`,
[EmailVerificationExceptionCode.EMAIL_MISSING]: msg`Email is required.`,
[EmailVerificationExceptionCode.EMAIL_ALREADY_VERIFIED]: msg`Email is already verified.`,
[EmailVerificationExceptionCode.INVALID_EMAIL]: msg`Invalid email address.`,
[EmailVerificationExceptionCode.RATE_LIMIT_EXCEEDED]: msg`Too many requests. Please try again later.`,
const getEmailVerificationExceptionUserFriendlyMessage = (
code: EmailVerificationExceptionCode,
) => {
switch (code) {
case EmailVerificationExceptionCode.EMAIL_VERIFICATION_NOT_REQUIRED:
return msg`Email verification is not required.`;
case EmailVerificationExceptionCode.INVALID_TOKEN:
case EmailVerificationExceptionCode.INVALID_APP_TOKEN_TYPE:
case EmailVerificationExceptionCode.TOKEN_EXPIRED:
return msg`There is an issue with your token. Please try again.`;
case EmailVerificationExceptionCode.EMAIL_MISSING:
return msg`Email is required.`;
case EmailVerificationExceptionCode.EMAIL_ALREADY_VERIFIED:
return msg`Email is already verified.`;
case EmailVerificationExceptionCode.INVALID_EMAIL:
return msg`Invalid email address.`;
case EmailVerificationExceptionCode.RATE_LIMIT_EXCEEDED:
return msg`Too many requests. Please try again later.`;
default:
assertUnreachable(code);
}
};
export class EmailVerificationException extends CustomException<EmailVerificationExceptionCode> {
@@ -37,7 +47,7 @@ export class EmailVerificationException extends CustomException<EmailVerificatio
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
emailVerificationExceptionUserFriendlyMessages[code],
getEmailVerificationExceptionUserFriendlyMessage(code),
});
}
}
@@ -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 { CustomException } from 'src/utils/custom-exception';
export enum EmailingDomainDriverExceptionCode {
@@ -11,15 +13,22 @@ export enum EmailingDomainDriverExceptionCode {
UNKNOWN = 'UNKNOWN',
}
const emailingDomainDriverExceptionUserFriendlyMessages: Record<
EmailingDomainDriverExceptionCode,
MessageDescriptor
> = {
[EmailingDomainDriverExceptionCode.NOT_FOUND]: msg`Email domain not found.`,
[EmailingDomainDriverExceptionCode.TEMPORARY_ERROR]: msg`A temporary error occurred. Please try again.`,
[EmailingDomainDriverExceptionCode.INSUFFICIENT_PERMISSIONS]: msg`Insufficient permissions for email domain.`,
[EmailingDomainDriverExceptionCode.CONFIGURATION_ERROR]: msg`Email domain configuration error.`,
[EmailingDomainDriverExceptionCode.UNKNOWN]: msg`An unknown email error occurred.`,
const getEmailingDomainDriverExceptionUserFriendlyMessage = (
code: EmailingDomainDriverExceptionCode,
) => {
switch (code) {
case EmailingDomainDriverExceptionCode.NOT_FOUND:
return msg`Email domain not found.`;
case EmailingDomainDriverExceptionCode.INSUFFICIENT_PERMISSIONS:
return msg`Insufficient permissions for email domain.`;
case EmailingDomainDriverExceptionCode.CONFIGURATION_ERROR:
return msg`Email domain configuration error.`;
case EmailingDomainDriverExceptionCode.TEMPORARY_ERROR:
case EmailingDomainDriverExceptionCode.UNKNOWN:
return STANDARD_ERROR_MESSAGE;
default:
assertUnreachable(code);
}
};
export class EmailingDomainDriverException extends CustomException<EmailingDomainDriverExceptionCode> {
@@ -31,7 +40,7 @@ export class EmailingDomainDriverException extends CustomException<EmailingDomai
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
emailingDomainDriverExceptionUserFriendlyMessages[code],
getEmailingDomainDriverExceptionUserFriendlyMessage(code),
});
}
}
@@ -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';
@@ -7,11 +8,15 @@ export enum FeatureFlagExceptionCode {
INVALID_FEATURE_FLAG_KEY = 'INVALID_FEATURE_FLAG_KEY',
}
const featureFlagExceptionUserFriendlyMessages: Record<
FeatureFlagExceptionCode,
MessageDescriptor
> = {
[FeatureFlagExceptionCode.INVALID_FEATURE_FLAG_KEY]: msg`Invalid feature flag key.`,
const getFeatureFlagExceptionUserFriendlyMessage = (
code: FeatureFlagExceptionCode,
) => {
switch (code) {
case FeatureFlagExceptionCode.INVALID_FEATURE_FLAG_KEY:
return msg`Invalid feature flag key.`;
default:
assertUnreachable(code);
}
};
export class FeatureFlagException extends CustomException<FeatureFlagExceptionCode> {
@@ -22,7 +27,7 @@ export class FeatureFlagException extends CustomException<FeatureFlagExceptionCo
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? featureFlagExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getFeatureFlagExceptionUserFriendlyMessage(code),
});
}
}
@@ -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';
@@ -7,11 +8,15 @@ export enum FileStorageExceptionCode {
FILE_NOT_FOUND = 'FILE_NOT_FOUND',
}
const fileStorageExceptionUserFriendlyMessages: Record<
FileStorageExceptionCode,
MessageDescriptor
> = {
[FileStorageExceptionCode.FILE_NOT_FOUND]: msg`File not found.`,
const getFileStorageExceptionUserFriendlyMessage = (
code: FileStorageExceptionCode,
) => {
switch (code) {
case FileStorageExceptionCode.FILE_NOT_FOUND:
return msg`File not found.`;
default:
assertUnreachable(code);
}
};
export class FileStorageException extends CustomException<FileStorageExceptionCode> {
@@ -22,7 +27,7 @@ export class FileStorageException extends CustomException<FileStorageExceptionCo
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? fileStorageExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getFileStorageExceptionUserFriendlyMessage(code),
});
}
}
@@ -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,
@@ -11,13 +13,19 @@ export const FileExceptionCode = appendCommonExceptionCode({
FILE_NOT_FOUND: 'FILE_NOT_FOUND',
} as const);
const fileExceptionUserFriendlyMessages: Record<
keyof typeof FileExceptionCode,
MessageDescriptor
> = {
UNAUTHENTICATED: msg`Authentication is required.`,
FILE_NOT_FOUND: msg`File not found.`,
INTERNAL_SERVER_ERROR: msg`An unexpected error occurred.`,
const getFileExceptionUserFriendlyMessage = (
code: keyof typeof FileExceptionCode,
) => {
switch (code) {
case FileExceptionCode.UNAUTHENTICATED:
return msg`Authentication is required.`;
case FileExceptionCode.FILE_NOT_FOUND:
return msg`File not found.`;
case FileExceptionCode.INTERNAL_SERVER_ERROR:
return STANDARD_ERROR_MESSAGE;
default:
assertUnreachable(code);
}
};
export class FileException extends CustomException<
@@ -30,7 +38,7 @@ export class FileException extends CustomException<
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? fileExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getFileExceptionUserFriendlyMessage(code),
});
}
}
@@ -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';
@@ -9,13 +10,19 @@ export enum PublicDomainExceptionCode {
PUBLIC_DOMAIN_NOT_FOUND = 'PUBLIC_DOMAIN_NOT_FOUND',
}
const publicDomainExceptionUserFriendlyMessages: Record<
PublicDomainExceptionCode,
MessageDescriptor
> = {
[PublicDomainExceptionCode.PUBLIC_DOMAIN_ALREADY_REGISTERED]: msg`This public domain is already registered.`,
[PublicDomainExceptionCode.DOMAIN_ALREADY_REGISTERED_AS_CUSTOM_DOMAIN]: msg`This domain is already registered as a custom domain.`,
[PublicDomainExceptionCode.PUBLIC_DOMAIN_NOT_FOUND]: msg`Public domain not found.`,
const getPublicDomainExceptionUserFriendlyMessage = (
code: PublicDomainExceptionCode,
) => {
switch (code) {
case PublicDomainExceptionCode.PUBLIC_DOMAIN_ALREADY_REGISTERED:
return msg`This public domain is already registered.`;
case PublicDomainExceptionCode.DOMAIN_ALREADY_REGISTERED_AS_CUSTOM_DOMAIN:
return msg`This domain is already registered as a custom domain.`;
case PublicDomainExceptionCode.PUBLIC_DOMAIN_NOT_FOUND:
return msg`Public domain not found.`;
default:
assertUnreachable(code);
}
};
export class PublicDomainException extends CustomException<PublicDomainExceptionCode> {
@@ -26,7 +33,8 @@ export class PublicDomainException extends CustomException<PublicDomainException
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? publicDomainExceptionUserFriendlyMessages[code],
userFriendlyMessage ??
getPublicDomainExceptionUserFriendlyMessage(code),
});
}
}
@@ -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';
@@ -15,19 +16,31 @@ export enum RecordCrudExceptionCode {
QUERY_FAILED = 'QUERY_FAILED',
}
const recordCrudExceptionUserFriendlyMessages: Record<
RecordCrudExceptionCode,
MessageDescriptor
> = {
[RecordCrudExceptionCode.INVALID_REQUEST]: msg`Invalid request.`,
[RecordCrudExceptionCode.WORKSPACE_ID_NOT_FOUND]: msg`Workspace not found.`,
[RecordCrudExceptionCode.OBJECT_NOT_FOUND]: msg`Object not found.`,
[RecordCrudExceptionCode.RECORD_NOT_FOUND]: msg`Record not found.`,
[RecordCrudExceptionCode.RECORD_CREATION_FAILED]: msg`Failed to create record.`,
[RecordCrudExceptionCode.RECORD_UPDATE_FAILED]: msg`Failed to update record.`,
[RecordCrudExceptionCode.RECORD_DELETION_FAILED]: msg`Failed to delete record.`,
[RecordCrudExceptionCode.RECORD_UPSERT_FAILED]: msg`Failed to upsert record.`,
[RecordCrudExceptionCode.QUERY_FAILED]: msg`Query failed.`,
const getRecordCrudExceptionUserFriendlyMessage = (
code: RecordCrudExceptionCode,
) => {
switch (code) {
case RecordCrudExceptionCode.INVALID_REQUEST:
return msg`Invalid request.`;
case RecordCrudExceptionCode.WORKSPACE_ID_NOT_FOUND:
return msg`Workspace not found.`;
case RecordCrudExceptionCode.OBJECT_NOT_FOUND:
return msg`Object not found.`;
case RecordCrudExceptionCode.RECORD_NOT_FOUND:
return msg`Record not found.`;
case RecordCrudExceptionCode.RECORD_CREATION_FAILED:
return msg`Failed to create record.`;
case RecordCrudExceptionCode.RECORD_UPDATE_FAILED:
return msg`Failed to update record.`;
case RecordCrudExceptionCode.RECORD_DELETION_FAILED:
return msg`Failed to delete record.`;
case RecordCrudExceptionCode.RECORD_UPSERT_FAILED:
return msg`Failed to upsert record.`;
case RecordCrudExceptionCode.QUERY_FAILED:
return msg`Query failed.`;
default:
assertUnreachable(code);
}
};
export class RecordCrudException extends CustomException<RecordCrudExceptionCode> {
@@ -38,7 +51,7 @@ export class RecordCrudException extends CustomException<RecordCrudExceptionCode
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? recordCrudExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getRecordCrudExceptionUserFriendlyMessage(code),
});
}
}
@@ -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 RecordTransformerExceptionCode {
CONFLICTING_PHONE_CALLING_CODE_AND_COUNTRY_CODE = 'CONFLICTING_PHONE_CALLING_CODE_AND_COUNTRY_CODE',
}
const recordTransformerExceptionUserFriendlyMessages: Record<
RecordTransformerExceptionCode,
MessageDescriptor
> = {
[RecordTransformerExceptionCode.INVALID_URL]: msg`Invalid URL format.`,
[RecordTransformerExceptionCode.INVALID_PHONE_NUMBER]: msg`Invalid phone number.`,
[RecordTransformerExceptionCode.INVALID_PHONE_COUNTRY_CODE]: msg`Invalid phone country code.`,
[RecordTransformerExceptionCode.INVALID_PHONE_CALLING_CODE]: msg`Invalid phone calling code.`,
[RecordTransformerExceptionCode.CONFLICTING_PHONE_COUNTRY_CODE]: msg`Conflicting phone country code.`,
[RecordTransformerExceptionCode.CONFLICTING_PHONE_CALLING_CODE]: msg`Conflicting phone calling code.`,
[RecordTransformerExceptionCode.CONFLICTING_PHONE_CALLING_CODE_AND_COUNTRY_CODE]: msg`Conflicting phone calling code and country code.`,
const getRecordTransformerExceptionUserFriendlyMessage = (
code: RecordTransformerExceptionCode,
) => {
switch (code) {
case RecordTransformerExceptionCode.INVALID_URL:
return msg`Invalid URL format.`;
case RecordTransformerExceptionCode.INVALID_PHONE_NUMBER:
return msg`Invalid phone number.`;
case RecordTransformerExceptionCode.INVALID_PHONE_COUNTRY_CODE:
return msg`Invalid phone country code.`;
case RecordTransformerExceptionCode.INVALID_PHONE_CALLING_CODE:
return msg`Invalid phone calling code.`;
case RecordTransformerExceptionCode.CONFLICTING_PHONE_COUNTRY_CODE:
return msg`Conflicting phone country code.`;
case RecordTransformerExceptionCode.CONFLICTING_PHONE_CALLING_CODE:
return msg`Conflicting phone calling code.`;
case RecordTransformerExceptionCode.CONFLICTING_PHONE_CALLING_CODE_AND_COUNTRY_CODE:
return msg`Conflicting phone calling code and country code.`;
default:
assertUnreachable(code);
}
};
export class RecordTransformerException extends CustomException<RecordTransformerExceptionCode> {
@@ -35,7 +46,7 @@ export class RecordTransformerException extends CustomException<RecordTransforme
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
recordTransformerExceptionUserFriendlyMessages[code],
getRecordTransformerExceptionUserFriendlyMessage(code),
});
}
}
@@ -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';
@@ -8,12 +9,15 @@ export enum SearchExceptionCode {
OBJECT_METADATA_NOT_FOUND = 'OBJECT_METADATA_NOT_FOUND',
}
const searchExceptionUserFriendlyMessages: Record<
SearchExceptionCode,
MessageDescriptor
> = {
[SearchExceptionCode.LABEL_IDENTIFIER_FIELD_NOT_FOUND]: msg`Label identifier field not found.`,
[SearchExceptionCode.OBJECT_METADATA_NOT_FOUND]: msg`Object not found.`,
const getSearchExceptionUserFriendlyMessage = (code: SearchExceptionCode) => {
switch (code) {
case SearchExceptionCode.LABEL_IDENTIFIER_FIELD_NOT_FOUND:
return msg`No identifier to search by was found.`;
case SearchExceptionCode.OBJECT_METADATA_NOT_FOUND:
return msg`Object not found.`;
default:
assertUnreachable(code);
}
};
export class SearchException extends CustomException<SearchExceptionCode> {
@@ -24,7 +28,7 @@ export class SearchException extends CustomException<SearchExceptionCode> {
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? searchExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getSearchExceptionUserFriendlyMessage(code),
});
}
}
@@ -2,6 +2,7 @@
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';
@@ -14,16 +15,23 @@ export enum SSOExceptionCode {
SSO_DISABLE = 'SSO_DISABLE',
}
const ssoExceptionUserFriendlyMessages: Record<
SSOExceptionCode,
MessageDescriptor
> = {
[SSOExceptionCode.USER_NOT_FOUND]: msg`User not found.`,
[SSOExceptionCode.IDENTITY_PROVIDER_NOT_FOUND]: msg`Identity provider not found.`,
[SSOExceptionCode.INVALID_ISSUER_URL]: msg`Invalid issuer URL.`,
[SSOExceptionCode.INVALID_IDP_TYPE]: msg`Invalid identity provider type.`,
[SSOExceptionCode.UNKNOWN_SSO_CONFIGURATION_ERROR]: msg`SSO configuration error.`,
[SSOExceptionCode.SSO_DISABLE]: msg`SSO is disabled.`,
const getSSOExceptionUserFriendlyMessage = (code: SSOExceptionCode) => {
switch (code) {
case SSOExceptionCode.USER_NOT_FOUND:
return msg`User not found.`;
case SSOExceptionCode.IDENTITY_PROVIDER_NOT_FOUND:
return msg`Identity provider not found.`;
case SSOExceptionCode.INVALID_ISSUER_URL:
return msg`Invalid issuer URL.`;
case SSOExceptionCode.INVALID_IDP_TYPE:
return msg`Invalid identity provider type.`;
case SSOExceptionCode.UNKNOWN_SSO_CONFIGURATION_ERROR:
return msg`SSO configuration error.`;
case SSOExceptionCode.SSO_DISABLE:
return msg`SSO is disabled.`;
default:
assertUnreachable(code);
}
};
export class SSOException extends CustomException<SSOExceptionCode> {
@@ -34,7 +42,7 @@ export class SSOException extends CustomException<SSOExceptionCode> {
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? ssoExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getSSOExceptionUserFriendlyMessage(code),
});
}
}
@@ -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';
@@ -7,11 +8,15 @@ export enum ThrottlerExceptionCode {
LIMIT_REACHED = 'LIMIT_REACHED',
}
const throttlerExceptionUserFriendlyMessages: Record<
ThrottlerExceptionCode,
MessageDescriptor
> = {
[ThrottlerExceptionCode.LIMIT_REACHED]: msg`Rate limit reached. Please try again later.`,
const getThrottlerExceptionUserFriendlyMessage = (
code: ThrottlerExceptionCode,
) => {
switch (code) {
case ThrottlerExceptionCode.LIMIT_REACHED:
return msg`Rate limit reached. Please try again later.`;
default:
assertUnreachable(code);
}
};
export class ThrottlerException extends CustomException<ThrottlerExceptionCode> {
@@ -22,7 +27,7 @@ export class ThrottlerException extends CustomException<ThrottlerExceptionCode>
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? throttlerExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getThrottlerExceptionUserFriendlyMessage(code),
});
}
}
@@ -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';
@@ -12,16 +13,25 @@ export enum SendEmailToolExceptionCode {
INVALID_FILE_ID = 'INVALID_FILE_ID',
}
const sendEmailToolExceptionUserFriendlyMessages: Record<
SendEmailToolExceptionCode,
MessageDescriptor
> = {
[SendEmailToolExceptionCode.INVALID_CONNECTED_ACCOUNT_ID]: msg`Invalid connected account ID.`,
[SendEmailToolExceptionCode.CONNECTED_ACCOUNT_NOT_FOUND]: msg`Connected account not found.`,
[SendEmailToolExceptionCode.INVALID_EMAIL]: msg`Invalid email address.`,
[SendEmailToolExceptionCode.WORKSPACE_ID_NOT_FOUND]: msg`Workspace not found.`,
[SendEmailToolExceptionCode.FILE_NOT_FOUND]: msg`File not found.`,
[SendEmailToolExceptionCode.INVALID_FILE_ID]: msg`Invalid file ID.`,
const getSendEmailToolExceptionUserFriendlyMessage = (
code: SendEmailToolExceptionCode,
) => {
switch (code) {
case SendEmailToolExceptionCode.INVALID_CONNECTED_ACCOUNT_ID:
return msg`Invalid connected account ID.`;
case SendEmailToolExceptionCode.CONNECTED_ACCOUNT_NOT_FOUND:
return msg`Connected account not found.`;
case SendEmailToolExceptionCode.INVALID_EMAIL:
return msg`Invalid email address.`;
case SendEmailToolExceptionCode.WORKSPACE_ID_NOT_FOUND:
return msg`Workspace not found.`;
case SendEmailToolExceptionCode.FILE_NOT_FOUND:
return msg`File not found.`;
case SendEmailToolExceptionCode.INVALID_FILE_ID:
return msg`Invalid file ID.`;
default:
assertUnreachable(code);
}
};
export class SendEmailToolException extends CustomException<SendEmailToolExceptionCode> {
@@ -32,7 +42,8 @@ export class SendEmailToolException extends CustomException<SendEmailToolExcepti
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? sendEmailToolExceptionUserFriendlyMessages[code],
userFriendlyMessage ??
getSendEmailToolExceptionUserFriendlyMessage(code),
});
}
}
@@ -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';
@@ -12,16 +13,25 @@ export enum ConfigVariableExceptionCode {
INTERNAL_ERROR = 'INTERNAL_ERROR',
}
const configVariableExceptionUserFriendlyMessages: Record<
ConfigVariableExceptionCode,
MessageDescriptor
> = {
[ConfigVariableExceptionCode.DATABASE_CONFIG_DISABLED]: msg`Database configuration is disabled.`,
[ConfigVariableExceptionCode.ENVIRONMENT_ONLY_VARIABLE]: msg`This variable can only be set via environment.`,
[ConfigVariableExceptionCode.VARIABLE_NOT_FOUND]: msg`Configuration variable not found.`,
[ConfigVariableExceptionCode.VALIDATION_FAILED]: msg`Configuration validation failed.`,
[ConfigVariableExceptionCode.UNSUPPORTED_CONFIG_TYPE]: msg`Unsupported configuration type.`,
[ConfigVariableExceptionCode.INTERNAL_ERROR]: msg`An unexpected configuration error occurred.`,
const getConfigVariableExceptionUserFriendlyMessage = (
code: ConfigVariableExceptionCode,
) => {
switch (code) {
case ConfigVariableExceptionCode.DATABASE_CONFIG_DISABLED:
return msg`Database configuration is disabled.`;
case ConfigVariableExceptionCode.ENVIRONMENT_ONLY_VARIABLE:
return msg`This variable can only be set via environment.`;
case ConfigVariableExceptionCode.VARIABLE_NOT_FOUND:
return msg`Configuration variable not found.`;
case ConfigVariableExceptionCode.VALIDATION_FAILED:
return msg`Configuration validation failed.`;
case ConfigVariableExceptionCode.UNSUPPORTED_CONFIG_TYPE:
return msg`Unsupported configuration type.`;
case ConfigVariableExceptionCode.INTERNAL_ERROR:
return msg`An unexpected configuration error occurred.`;
default:
assertUnreachable(code);
}
};
export class ConfigVariableException extends CustomException<ConfigVariableExceptionCode> {
@@ -33,7 +43,7 @@ export class ConfigVariableException extends CustomException<ConfigVariableExcep
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
configVariableExceptionUserFriendlyMessages[code],
getConfigVariableExceptionUserFriendlyMessage(code),
});
}
}
@@ -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';
@@ -11,15 +12,23 @@ export enum TwoFactorAuthenticationExceptionCode {
MALFORMED_DATABASE_OBJECT = 'MALFORMED_DATABASE_OBJECT',
}
const twoFactorAuthenticationExceptionUserFriendlyMessages: Record<
TwoFactorAuthenticationExceptionCode,
MessageDescriptor
> = {
[TwoFactorAuthenticationExceptionCode.INVALID_CONFIGURATION]: msg`Invalid two-factor authentication configuration.`,
[TwoFactorAuthenticationExceptionCode.TWO_FACTOR_AUTHENTICATION_METHOD_NOT_FOUND]: msg`Two-factor authentication method not found.`,
[TwoFactorAuthenticationExceptionCode.INVALID_OTP]: msg`Invalid verification code.`,
[TwoFactorAuthenticationExceptionCode.TWO_FACTOR_AUTHENTICATION_METHOD_ALREADY_PROVISIONED]: msg`Two-factor authentication is already set up.`,
[TwoFactorAuthenticationExceptionCode.MALFORMED_DATABASE_OBJECT]: msg`An error occurred with two-factor authentication data.`,
const getTwoFactorAuthenticationExceptionUserFriendlyMessage = (
code: TwoFactorAuthenticationExceptionCode,
) => {
switch (code) {
case TwoFactorAuthenticationExceptionCode.INVALID_CONFIGURATION:
return msg`Invalid two-factor authentication configuration.`;
case TwoFactorAuthenticationExceptionCode.TWO_FACTOR_AUTHENTICATION_METHOD_NOT_FOUND:
return msg`Two-factor authentication method not found.`;
case TwoFactorAuthenticationExceptionCode.INVALID_OTP:
return msg`Invalid verification code.`;
case TwoFactorAuthenticationExceptionCode.TWO_FACTOR_AUTHENTICATION_METHOD_ALREADY_PROVISIONED:
return msg`Two-factor authentication is already set up.`;
case TwoFactorAuthenticationExceptionCode.MALFORMED_DATABASE_OBJECT:
return msg`An error occurred with two-factor authentication data.`;
default:
assertUnreachable(code);
}
};
export class TwoFactorAuthenticationException extends CustomException<TwoFactorAuthenticationExceptionCode> {
@@ -31,7 +40,7 @@ export class TwoFactorAuthenticationException extends CustomException<TwoFactorA
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
twoFactorAuthenticationExceptionUserFriendlyMessages[code],
getTwoFactorAuthenticationExceptionUserFriendlyMessage(code),
});
}
}
@@ -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';
@@ -7,11 +8,15 @@ export enum UserWorkspaceExceptionCode {
USER_WORKSPACE_NOT_FOUND = 'WORKSPACE_NOT_FOUND',
}
const userWorkspaceExceptionUserFriendlyMessages: Record<
UserWorkspaceExceptionCode,
MessageDescriptor
> = {
[UserWorkspaceExceptionCode.USER_WORKSPACE_NOT_FOUND]: msg`User workspace not found.`,
const getUserWorkspaceExceptionUserFriendlyMessage = (
code: UserWorkspaceExceptionCode,
) => {
switch (code) {
case UserWorkspaceExceptionCode.USER_WORKSPACE_NOT_FOUND:
return msg`User workspace not found.`;
default:
assertUnreachable(code);
}
};
export class UserWorkspaceException extends CustomException<UserWorkspaceExceptionCode> {
@@ -22,7 +27,8 @@ export class UserWorkspaceException extends CustomException<UserWorkspaceExcepti
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? userWorkspaceExceptionUserFriendlyMessages[code],
userFriendlyMessage ??
getUserWorkspaceExceptionUserFriendlyMessage(code),
});
}
}
@@ -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';
@@ -10,14 +11,19 @@ export enum UserExceptionCode {
EMAIL_UPDATE_RESTRICTED_TO_SINGLE_WORKSPACE = 'EMAIL_UPDATE_RESTRICTED_TO_SINGLE_WORKSPACE',
}
const userExceptionUserFriendlyMessages: Record<
UserExceptionCode,
MessageDescriptor
> = {
[UserExceptionCode.USER_NOT_FOUND]: msg`User not found.`,
[UserExceptionCode.EMAIL_ALREADY_IN_USE]: msg`This email is already in use.`,
[UserExceptionCode.EMAIL_UNCHANGED]: msg`Email is unchanged.`,
[UserExceptionCode.EMAIL_UPDATE_RESTRICTED_TO_SINGLE_WORKSPACE]: msg`Email update is restricted to single workspace users.`,
const getUserExceptionUserFriendlyMessage = (code: UserExceptionCode) => {
switch (code) {
case UserExceptionCode.USER_NOT_FOUND:
return msg`User not found.`;
case UserExceptionCode.EMAIL_ALREADY_IN_USE:
return msg`This email is already in use.`;
case UserExceptionCode.EMAIL_UNCHANGED:
return msg`Email is unchanged.`;
case UserExceptionCode.EMAIL_UPDATE_RESTRICTED_TO_SINGLE_WORKSPACE:
return msg`Email update is restricted to single workspace users.`;
default:
assertUnreachable(code);
}
};
export class UserException extends CustomException<UserExceptionCode> {
@@ -28,7 +34,7 @@ export class UserException extends CustomException<UserExceptionCode> {
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? userExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getUserExceptionUserFriendlyMessage(code),
});
}
}
@@ -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';
@@ -8,12 +9,15 @@ export enum WebhookExceptionCode {
INVALID_TARGET_URL = 'INVALID_TARGET_URL',
}
const webhookExceptionUserFriendlyMessages: Record<
WebhookExceptionCode,
MessageDescriptor
> = {
[WebhookExceptionCode.WEBHOOK_NOT_FOUND]: msg`Webhook not found.`,
[WebhookExceptionCode.INVALID_TARGET_URL]: msg`Invalid target URL.`,
const getWebhookExceptionUserFriendlyMessage = (code: WebhookExceptionCode) => {
switch (code) {
case WebhookExceptionCode.WEBHOOK_NOT_FOUND:
return msg`Webhook not found.`;
case WebhookExceptionCode.INVALID_TARGET_URL:
return msg`Invalid target URL.`;
default:
assertUnreachable(code);
}
};
export class WebhookException extends CustomException<WebhookExceptionCode> {
@@ -24,7 +28,7 @@ export class WebhookException extends CustomException<WebhookExceptionCode> {
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? webhookExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getWebhookExceptionUserFriendlyMessage(code),
});
}
}
@@ -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';
@@ -12,16 +13,23 @@ export enum WorkspaceInvitationExceptionCode {
EMAIL_MISSING = 'EMAIL_MISSING',
}
const workspaceInvitationExceptionUserFriendlyMessages: Record<
WorkspaceInvitationExceptionCode,
MessageDescriptor
> = {
[WorkspaceInvitationExceptionCode.INVALID_APP_TOKEN_TYPE]: msg`Invalid token type.`,
[WorkspaceInvitationExceptionCode.INVITATION_CORRUPTED]: msg`Invitation is corrupted.`,
[WorkspaceInvitationExceptionCode.INVITATION_ALREADY_EXIST]: msg`An invitation has already been sent to this email.`,
[WorkspaceInvitationExceptionCode.USER_ALREADY_EXIST]: msg`This user is already a member of the workspace.`,
[WorkspaceInvitationExceptionCode.INVALID_INVITATION]: msg`Invalid invitation.`,
[WorkspaceInvitationExceptionCode.EMAIL_MISSING]: msg`Email is required.`,
const getWorkspaceInvitationExceptionUserFriendlyMessage = (
code: WorkspaceInvitationExceptionCode,
) => {
switch (code) {
case WorkspaceInvitationExceptionCode.INVALID_APP_TOKEN_TYPE:
case WorkspaceInvitationExceptionCode.INVITATION_CORRUPTED:
case WorkspaceInvitationExceptionCode.INVALID_INVITATION:
return msg`There is an issue with your invitation. Please try again.`;
case WorkspaceInvitationExceptionCode.INVITATION_ALREADY_EXIST:
return msg`An invitation has already been sent to this email.`;
case WorkspaceInvitationExceptionCode.USER_ALREADY_EXIST:
return msg`This user is already a member of the workspace.`;
case WorkspaceInvitationExceptionCode.EMAIL_MISSING:
return msg`Email is required.`;
default:
assertUnreachable(code);
}
};
export class WorkspaceInvitationException extends CustomException<WorkspaceInvitationExceptionCode> {
@@ -33,7 +41,7 @@ export class WorkspaceInvitationException extends CustomException<WorkspaceInvit
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
workspaceInvitationExceptionUserFriendlyMessages[code],
getWorkspaceInvitationExceptionUserFriendlyMessage(code),
});
}
}
@@ -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';
@@ -14,18 +15,29 @@ export enum WorkspaceExceptionCode {
CUSTOM_DOMAIN_NOT_FOUND = 'CUSTOM_DOMAIN_NOT_FOUND',
}
const workspaceExceptionUserFriendlyMessages: Record<
WorkspaceExceptionCode,
MessageDescriptor
> = {
[WorkspaceExceptionCode.SUBDOMAIN_NOT_FOUND]: msg`Subdomain not found.`,
[WorkspaceExceptionCode.SUBDOMAIN_ALREADY_TAKEN]: msg`This subdomain is already taken.`,
[WorkspaceExceptionCode.SUBDOMAIN_NOT_VALID]: msg`Invalid subdomain.`,
[WorkspaceExceptionCode.DOMAIN_ALREADY_TAKEN]: msg`This domain is already taken.`,
[WorkspaceExceptionCode.WORKSPACE_NOT_FOUND]: msg`Workspace not found.`,
[WorkspaceExceptionCode.WORKSPACE_CUSTOM_DOMAIN_DISABLED]: msg`Custom domains are disabled for this workspace.`,
[WorkspaceExceptionCode.ENVIRONMENT_VAR_NOT_ENABLED]: msg`This feature is not enabled.`,
[WorkspaceExceptionCode.CUSTOM_DOMAIN_NOT_FOUND]: msg`Custom domain not found.`,
const getWorkspaceExceptionUserFriendlyMessage = (
code: WorkspaceExceptionCode,
) => {
switch (code) {
case WorkspaceExceptionCode.SUBDOMAIN_NOT_FOUND:
return msg`Subdomain not found.`;
case WorkspaceExceptionCode.SUBDOMAIN_ALREADY_TAKEN:
return msg`This subdomain is already taken.`;
case WorkspaceExceptionCode.SUBDOMAIN_NOT_VALID:
return msg`Invalid subdomain.`;
case WorkspaceExceptionCode.DOMAIN_ALREADY_TAKEN:
return msg`This domain is already taken.`;
case WorkspaceExceptionCode.WORKSPACE_NOT_FOUND:
return msg`Workspace not found.`;
case WorkspaceExceptionCode.WORKSPACE_CUSTOM_DOMAIN_DISABLED:
return msg`Custom domains are disabled for this workspace.`;
case WorkspaceExceptionCode.ENVIRONMENT_VAR_NOT_ENABLED:
return msg`This feature is not enabled.`;
case WorkspaceExceptionCode.CUSTOM_DOMAIN_NOT_FOUND:
return msg`Custom domain not found.`;
default:
assertUnreachable(code);
}
};
export class WorkspaceException extends CustomException<WorkspaceExceptionCode> {
@@ -36,7 +48,7 @@ export class WorkspaceException extends CustomException<WorkspaceExceptionCode>
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? workspaceExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getWorkspaceExceptionUserFriendlyMessage(code),
});
}
}
@@ -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';
@@ -15,19 +16,29 @@ export enum AgentExceptionCode {
AGENT_IS_STANDARD = 'AGENT_IS_STANDARD',
}
const agentExceptionUserFriendlyMessages: Record<
AgentExceptionCode,
MessageDescriptor
> = {
[AgentExceptionCode.AGENT_NOT_FOUND]: msg`Agent not found.`,
[AgentExceptionCode.AGENT_EXECUTION_FAILED]: msg`Agent execution failed.`,
[AgentExceptionCode.API_KEY_NOT_CONFIGURED]: msg`API key is not configured.`,
[AgentExceptionCode.USER_WORKSPACE_ID_NOT_FOUND]: msg`User workspace not found.`,
[AgentExceptionCode.ROLE_NOT_FOUND]: msg`Role not found.`,
[AgentExceptionCode.ROLE_CANNOT_BE_ASSIGNED_TO_AGENTS]: msg`This role cannot be assigned to agents.`,
[AgentExceptionCode.INVALID_AGENT_INPUT]: msg`Invalid agent input.`,
[AgentExceptionCode.AGENT_ALREADY_EXISTS]: msg`An agent with this name already exists.`,
[AgentExceptionCode.AGENT_IS_STANDARD]: msg`Standard agents cannot be modified.`,
const getAgentExceptionUserFriendlyMessage = (code: AgentExceptionCode) => {
switch (code) {
case AgentExceptionCode.AGENT_NOT_FOUND:
return msg`Agent not found.`;
case AgentExceptionCode.AGENT_EXECUTION_FAILED:
return msg`Agent execution failed.`;
case AgentExceptionCode.API_KEY_NOT_CONFIGURED:
return msg`API key is not configured.`;
case AgentExceptionCode.USER_WORKSPACE_ID_NOT_FOUND:
return msg`User workspace not found.`;
case AgentExceptionCode.ROLE_NOT_FOUND:
return msg`Role not found.`;
case AgentExceptionCode.ROLE_CANNOT_BE_ASSIGNED_TO_AGENTS:
return msg`This role cannot be assigned to agents.`;
case AgentExceptionCode.INVALID_AGENT_INPUT:
return msg`Invalid agent input.`;
case AgentExceptionCode.AGENT_ALREADY_EXISTS:
return msg`An agent with this name already exists.`;
case AgentExceptionCode.AGENT_IS_STANDARD:
return msg`Standard agents cannot be modified.`;
default:
assertUnreachable(code);
}
};
export class AgentException extends CustomException<AgentExceptionCode> {
@@ -38,7 +49,7 @@ export class AgentException extends CustomException<AgentExceptionCode> {
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? agentExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getAgentExceptionUserFriendlyMessage(code),
});
}
}
@@ -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';
@@ -9,13 +10,19 @@ export enum CronTriggerExceptionCode {
SERVERLESS_FUNCTION_NOT_FOUND = 'SERVERLESS_FUNCTION_NOT_FOUND',
}
const cronTriggerExceptionUserFriendlyMessages: Record<
CronTriggerExceptionCode,
MessageDescriptor
> = {
[CronTriggerExceptionCode.CRON_TRIGGER_NOT_FOUND]: msg`Cron trigger not found.`,
[CronTriggerExceptionCode.CRON_TRIGGER_ALREADY_EXIST]: msg`Cron trigger already exists.`,
[CronTriggerExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND]: msg`Serverless function not found.`,
const getCronTriggerExceptionUserFriendlyMessage = (
code: CronTriggerExceptionCode,
) => {
switch (code) {
case CronTriggerExceptionCode.CRON_TRIGGER_NOT_FOUND:
return msg`Cron trigger not found.`;
case CronTriggerExceptionCode.CRON_TRIGGER_ALREADY_EXIST:
return msg`Cron trigger already exists.`;
case CronTriggerExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND:
return msg`Serverless function not found.`;
default:
assertUnreachable(code);
}
};
export class CronTriggerException extends CustomException<CronTriggerExceptionCode> {
@@ -26,7 +33,7 @@ export class CronTriggerException extends CustomException<CronTriggerExceptionCo
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? cronTriggerExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getCronTriggerExceptionUserFriendlyMessage(code),
});
}
}
@@ -1,17 +1,22 @@
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 { CustomException } from 'src/utils/custom-exception';
export enum DataSourceExceptionCode {
DATA_SOURCE_NOT_FOUND = 'DATA_SOURCE_NOT_FOUND',
}
const dataSourceExceptionUserFriendlyMessages: Record<
DataSourceExceptionCode,
MessageDescriptor
> = {
[DataSourceExceptionCode.DATA_SOURCE_NOT_FOUND]: msg`Data source not found.`,
const getDataSourceExceptionUserFriendlyMessage = (
code: DataSourceExceptionCode,
) => {
switch (code) {
case DataSourceExceptionCode.DATA_SOURCE_NOT_FOUND:
return STANDARD_ERROR_MESSAGE;
default:
assertUnreachable(code);
}
};
export class DataSourceException extends CustomException<DataSourceExceptionCode> {
@@ -22,7 +27,7 @@ export class DataSourceException extends CustomException<DataSourceExceptionCode
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? dataSourceExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getDataSourceExceptionUserFriendlyMessage(code),
});
}
}
@@ -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';
@@ -10,14 +11,21 @@ export enum DatabaseEventTriggerExceptionCode {
SERVERLESS_FUNCTION_NOT_FOUND = 'SERVERLESS_FUNCTION_NOT_FOUND',
}
const databaseEventTriggerExceptionUserFriendlyMessages: Record<
DatabaseEventTriggerExceptionCode,
MessageDescriptor
> = {
[DatabaseEventTriggerExceptionCode.DATABASE_EVENT_TRIGGER_NOT_FOUND]: msg`Database event trigger not found.`,
[DatabaseEventTriggerExceptionCode.DATABASE_EVENT_TRIGGER_ALREADY_EXIST]: msg`Database event trigger already exists.`,
[DatabaseEventTriggerExceptionCode.DATABASE_EVENT_TRIGGER_INVALID]: msg`Invalid database event trigger.`,
[DatabaseEventTriggerExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND]: msg`Serverless function not found.`,
const getDatabaseEventTriggerExceptionUserFriendlyMessage = (
code: DatabaseEventTriggerExceptionCode,
) => {
switch (code) {
case DatabaseEventTriggerExceptionCode.DATABASE_EVENT_TRIGGER_NOT_FOUND:
return msg`Database event trigger not found.`;
case DatabaseEventTriggerExceptionCode.DATABASE_EVENT_TRIGGER_ALREADY_EXIST:
return msg`Database event trigger already exists.`;
case DatabaseEventTriggerExceptionCode.DATABASE_EVENT_TRIGGER_INVALID:
return msg`Invalid database event trigger.`;
case DatabaseEventTriggerExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND:
return msg`Serverless function not found.`;
default:
assertUnreachable(code);
}
};
export class DatabaseEventTriggerException extends CustomException<DatabaseEventTriggerExceptionCode> {
@@ -29,7 +37,7 @@ export class DatabaseEventTriggerException extends CustomException<DatabaseEvent
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
databaseEventTriggerExceptionUserFriendlyMessages[code],
getDatabaseEventTriggerExceptionUserFriendlyMessage(code),
});
}
}
@@ -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,
@@ -27,23 +29,39 @@ export const FieldMetadataExceptionCode = appendCommonExceptionCode({
export type FieldMetadataExceptionCode =
(typeof FieldMetadataExceptionCode)[keyof typeof FieldMetadataExceptionCode];
const fieldMetadataExceptionUserFriendlyMessages: Record<
keyof typeof FieldMetadataExceptionCode,
MessageDescriptor
> = {
FIELD_METADATA_NOT_FOUND: msg`Field not found.`,
INVALID_FIELD_INPUT: msg`Invalid field input.`,
FIELD_MUTATION_NOT_ALLOWED: msg`This field cannot be modified.`,
FIELD_ALREADY_EXISTS: msg`A field with this name already exists.`,
OBJECT_METADATA_NOT_FOUND: msg`Object not found.`,
FIELD_METADATA_RELATION_NOT_ENABLED: msg`Relation is not enabled for this field.`,
FIELD_METADATA_RELATION_MALFORMED: msg`Relation configuration is invalid.`,
LABEL_IDENTIFIER_FIELD_METADATA_ID_NOT_FOUND: msg`Label identifier field not found.`,
UNCOVERED_FIELD_METADATA_TYPE_VALIDATION: msg`Field type validation error.`,
RESERVED_KEYWORD: msg`This name is a reserved keyword.`,
NOT_AVAILABLE: msg`This field name is not available.`,
NAME_NOT_SYNCED_WITH_LABEL: msg`Field name is not synced with label.`,
INTERNAL_SERVER_ERROR: msg`An unexpected error occurred.`,
const getFieldMetadataExceptionUserFriendlyMessage = (
code: keyof typeof FieldMetadataExceptionCode,
) => {
switch (code) {
case FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND:
return msg`Field not found.`;
case FieldMetadataExceptionCode.INVALID_FIELD_INPUT:
return msg`Invalid field input.`;
case FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED:
return msg`This field cannot be modified.`;
case FieldMetadataExceptionCode.FIELD_ALREADY_EXISTS:
return msg`A field with this name already exists.`;
case FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND:
return msg`Object not found.`;
case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_NOT_ENABLED:
return msg`Relation is not enabled for this field.`;
case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED:
return msg`Relation configuration is invalid.`;
case FieldMetadataExceptionCode.LABEL_IDENTIFIER_FIELD_METADATA_ID_NOT_FOUND:
return msg`Label identifier field not found.`;
case FieldMetadataExceptionCode.UNCOVERED_FIELD_METADATA_TYPE_VALIDATION:
return msg`Field type validation error.`;
case FieldMetadataExceptionCode.RESERVED_KEYWORD:
return msg`This name is a reserved keyword.`;
case FieldMetadataExceptionCode.NOT_AVAILABLE:
return msg`This field name is not available.`;
case FieldMetadataExceptionCode.NAME_NOT_SYNCED_WITH_LABEL:
return msg`Field name is not synced with label.`;
case FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR:
return STANDARD_ERROR_MESSAGE;
default:
assertUnreachable(code);
}
};
export class FieldMetadataException extends CustomException<
@@ -56,7 +74,8 @@ export class FieldMetadataException extends CustomException<
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? fieldMetadataExceptionUserFriendlyMessages[code],
userFriendlyMessage ??
getFieldMetadataExceptionUserFriendlyMessage(code),
});
}
}
@@ -1,6 +1,7 @@
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,
@@ -12,14 +13,18 @@ export const FlatEntityMapsExceptionCode = appendCommonExceptionCode({
ENTITY_MALFORMED: 'ENTITY_MALFORMED',
} as const);
const flatEntityMapsExceptionUserFriendlyMessages: Record<
keyof typeof FlatEntityMapsExceptionCode,
MessageDescriptor
> = {
ENTITY_ALREADY_EXISTS: msg`Entity already exists.`,
ENTITY_NOT_FOUND: msg`Entity not found.`,
ENTITY_MALFORMED: msg`Entity data is malformed.`,
INTERNAL_SERVER_ERROR: msg`An unexpected error occurred.`,
const getFlatEntityMapsExceptionUserFriendlyMessage = (
code: keyof typeof FlatEntityMapsExceptionCode,
) => {
switch (code) {
case FlatEntityMapsExceptionCode.ENTITY_ALREADY_EXISTS:
case FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND:
case FlatEntityMapsExceptionCode.ENTITY_MALFORMED:
case FlatEntityMapsExceptionCode.INTERNAL_SERVER_ERROR:
return STANDARD_ERROR_MESSAGE;
default:
assertUnreachable(code);
}
};
export class FlatEntityMapsException extends CustomException<
@@ -33,7 +38,7 @@ export class FlatEntityMapsException extends CustomException<
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
flatEntityMapsExceptionUserFriendlyMessages[code],
getFlatEntityMapsExceptionUserFriendlyMessage(code),
});
}
}
@@ -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 { CustomException } from 'src/utils/custom-exception';
export enum ObjectMetadataExceptionCode {
@@ -14,18 +16,29 @@ export enum ObjectMetadataExceptionCode {
NAME_CONFLICT = 'NAME_CONFLICT',
}
const objectMetadataExceptionUserFriendlyMessages: Record<
ObjectMetadataExceptionCode,
MessageDescriptor
> = {
[ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND]: msg`Object not found.`,
[ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT]: msg`Invalid object input.`,
[ObjectMetadataExceptionCode.OBJECT_MUTATION_NOT_ALLOWED]: msg`This object cannot be modified.`,
[ObjectMetadataExceptionCode.OBJECT_ALREADY_EXISTS]: msg`An object with this name already exists.`,
[ObjectMetadataExceptionCode.MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD]: msg`Custom object is missing a label identifier field.`,
[ObjectMetadataExceptionCode.INVALID_ORM_OUTPUT]: msg`Invalid data format.`,
[ObjectMetadataExceptionCode.INTERNAL_SERVER_ERROR]: msg`An unexpected error occurred.`,
[ObjectMetadataExceptionCode.NAME_CONFLICT]: msg`A name conflict occurred.`,
const getObjectMetadataExceptionUserFriendlyMessage = (
code: ObjectMetadataExceptionCode,
) => {
switch (code) {
case ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND:
return msg`Object not found.`;
case ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT:
return msg`Invalid object input.`;
case ObjectMetadataExceptionCode.OBJECT_MUTATION_NOT_ALLOWED:
return msg`This object cannot be modified.`;
case ObjectMetadataExceptionCode.OBJECT_ALREADY_EXISTS:
return msg`An object with this name already exists.`;
case ObjectMetadataExceptionCode.MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD:
return msg`Custom object is missing a label identifier field.`;
case ObjectMetadataExceptionCode.INVALID_ORM_OUTPUT:
return msg`Invalid data format.`;
case ObjectMetadataExceptionCode.INTERNAL_SERVER_ERROR:
return STANDARD_ERROR_MESSAGE;
case ObjectMetadataExceptionCode.NAME_CONFLICT:
return msg`A name conflict occurred.`;
default:
assertUnreachable(code);
}
};
export class ObjectMetadataException extends CustomException<ObjectMetadataExceptionCode> {
@@ -37,7 +50,7 @@ export class ObjectMetadataException extends CustomException<ObjectMetadataExcep
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
objectMetadataExceptionUserFriendlyMessages[code],
getObjectMetadataExceptionUserFriendlyMessage(code),
});
}
}

Some files were not shown because too many files have changed in this diff Show More