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
@@ -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),
});
}
}
@@ -17,12 +17,17 @@ export enum PageLayoutTabExceptionMessageKey {
PAGE_LAYOUT_TAB_NOT_DELETED = 'PAGE_LAYOUT_TAB_NOT_DELETED',
}
const pageLayoutTabExceptionUserFriendlyMessages: Record<
PageLayoutTabExceptionCode,
MessageDescriptor
> = {
[PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND]: msg`Page layout tab not found.`,
[PageLayoutTabExceptionCode.INVALID_PAGE_LAYOUT_TAB_DATA]: msg`Invalid page layout tab data.`,
const getPageLayoutTabExceptionUserFriendlyMessage = (
code: PageLayoutTabExceptionCode,
) => {
switch (code) {
case PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND:
return msg`Page layout tab not found.`;
case PageLayoutTabExceptionCode.INVALID_PAGE_LAYOUT_TAB_DATA:
return msg`Invalid page layout tab data.`;
default:
assertUnreachable(code);
}
};
export class PageLayoutTabException extends CustomException<PageLayoutTabExceptionCode> {
@@ -33,7 +38,8 @@ export class PageLayoutTabException extends CustomException<PageLayoutTabExcepti
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? pageLayoutTabExceptionUserFriendlyMessages[code],
userFriendlyMessage ??
getPageLayoutTabExceptionUserFriendlyMessage(code),
});
}
}
@@ -20,12 +20,17 @@ export enum PageLayoutWidgetExceptionMessageKey {
INVALID_WIDGET_CONFIGURATION = 'INVALID_WIDGET_CONFIGURATION',
}
const pageLayoutWidgetExceptionUserFriendlyMessages: Record<
PageLayoutWidgetExceptionCode,
MessageDescriptor
> = {
[PageLayoutWidgetExceptionCode.PAGE_LAYOUT_WIDGET_NOT_FOUND]: msg`Page layout widget not found.`,
[PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA]: msg`Invalid page layout widget data.`,
const getPageLayoutWidgetExceptionUserFriendlyMessage = (
code: PageLayoutWidgetExceptionCode,
) => {
switch (code) {
case PageLayoutWidgetExceptionCode.PAGE_LAYOUT_WIDGET_NOT_FOUND:
return msg`Page layout widget not found.`;
case PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA:
return msg`Invalid page layout widget data.`;
default:
assertUnreachable(code);
}
};
export class PageLayoutWidgetException extends CustomException<PageLayoutWidgetExceptionCode> {
@@ -37,7 +42,7 @@ export class PageLayoutWidgetException extends CustomException<PageLayoutWidgetE
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
pageLayoutWidgetExceptionUserFriendlyMessages[code],
getPageLayoutWidgetExceptionUserFriendlyMessage(code),
});
}
}
@@ -16,13 +16,19 @@ export enum PageLayoutExceptionMessageKey {
TAB_NOT_FOUND_FOR_WIDGET_DUPLICATION = 'TAB_NOT_FOUND_FOR_WIDGET_DUPLICATION',
}
const pageLayoutExceptionUserFriendlyMessages: Record<
PageLayoutExceptionCode,
MessageDescriptor
> = {
[PageLayoutExceptionCode.PAGE_LAYOUT_NOT_FOUND]: msg`Page layout not found.`,
[PageLayoutExceptionCode.INVALID_PAGE_LAYOUT_DATA]: msg`Invalid page layout data.`,
[PageLayoutExceptionCode.TAB_NOT_FOUND_FOR_WIDGET_DUPLICATION]: msg`Tab not found for widget duplication.`,
const getPageLayoutExceptionUserFriendlyMessage = (
code: PageLayoutExceptionCode,
) => {
switch (code) {
case PageLayoutExceptionCode.PAGE_LAYOUT_NOT_FOUND:
return msg`Page layout not found.`;
case PageLayoutExceptionCode.INVALID_PAGE_LAYOUT_DATA:
return msg`Invalid page layout data.`;
case PageLayoutExceptionCode.TAB_NOT_FOUND_FOR_WIDGET_DUPLICATION:
return msg`Tab not found for widget duplication.`;
default:
assertUnreachable(code);
}
};
export class PageLayoutException extends CustomException<PageLayoutExceptionCode> {
@@ -33,7 +39,7 @@ export class PageLayoutException extends CustomException<PageLayoutExceptionCode
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? pageLayoutExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getPageLayoutExceptionUserFriendlyMessage(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';
@@ -48,52 +49,97 @@ export enum PermissionsExceptionCode {
ROLE_CANNOT_BE_ASSIGNED_TO_USERS = 'ROLE_CANNOT_BE_ASSIGNED_TO_USERS',
}
const permissionsExceptionUserFriendlyMessages: Record<
PermissionsExceptionCode,
MessageDescriptor
> = {
[PermissionsExceptionCode.PERMISSION_DENIED]: msg`You do not have permission to perform this action.`,
[PermissionsExceptionCode.ADMIN_ROLE_NOT_FOUND]: msg`Admin role not found.`,
[PermissionsExceptionCode.USER_WORKSPACE_NOT_FOUND]: msg`User workspace not found.`,
[PermissionsExceptionCode.WORKSPACE_ID_ROLE_USER_WORKSPACE_MISMATCH]: msg`Workspace ID and role mismatch.`,
[PermissionsExceptionCode.TOO_MANY_ADMIN_CANDIDATES]: msg`Too many admin candidates found.`,
[PermissionsExceptionCode.USER_WORKSPACE_ALREADY_HAS_ROLE]: msg`User already has a role assigned.`,
[PermissionsExceptionCode.WORKSPACE_MEMBER_NOT_FOUND]: msg`Workspace member not found.`,
[PermissionsExceptionCode.ROLE_NOT_FOUND]: msg`Role not found.`,
[PermissionsExceptionCode.CANNOT_UNASSIGN_LAST_ADMIN]: msg`Cannot remove the last admin from the workspace.`,
[PermissionsExceptionCode.CANNOT_DELETE_LAST_ADMIN_USER]: msg`Cannot delete the last admin user.`,
[PermissionsExceptionCode.UNKNOWN_OPERATION_NAME]: msg`Unknown operation.`,
[PermissionsExceptionCode.UNKNOWN_REQUIRED_PERMISSION]: msg`Unknown permission required.`,
[PermissionsExceptionCode.CANNOT_UPDATE_SELF_ROLE]: msg`You cannot update your own role.`,
[PermissionsExceptionCode.NO_ROLE_FOUND_FOR_USER_WORKSPACE]: msg`No role found for this user in the workspace.`,
[PermissionsExceptionCode.API_KEY_ROLE_NOT_FOUND]: msg`API key role not found.`,
[PermissionsExceptionCode.NO_AUTHENTICATION_CONTEXT]: msg`Authentication is required.`,
[PermissionsExceptionCode.INVALID_ARG]: msg`Invalid argument provided.`,
[PermissionsExceptionCode.ROLE_LABEL_ALREADY_EXISTS]: msg`A role with this label already exists.`,
[PermissionsExceptionCode.DEFAULT_ROLE_NOT_FOUND]: msg`Default role not found.`,
[PermissionsExceptionCode.OBJECT_METADATA_NOT_FOUND]: msg`Object metadata not found.`,
[PermissionsExceptionCode.INVALID_SETTING]: msg`Invalid permission setting.`,
[PermissionsExceptionCode.ROLE_NOT_EDITABLE]: msg`This role cannot be edited.`,
[PermissionsExceptionCode.DEFAULT_ROLE_CANNOT_BE_DELETED]: msg`The default role cannot be deleted.`,
[PermissionsExceptionCode.NO_PERMISSIONS_FOUND_IN_DATASOURCE]: msg`No permissions found in datasource.`,
[PermissionsExceptionCode.CANNOT_ADD_OBJECT_PERMISSION_ON_SYSTEM_OBJECT]: msg`Cannot add permissions on system objects.`,
[PermissionsExceptionCode.CANNOT_ADD_FIELD_PERMISSION_ON_SYSTEM_OBJECT]: msg`Cannot add field permissions on system objects.`,
[PermissionsExceptionCode.METHOD_NOT_ALLOWED]: msg`This method is not allowed.`,
[PermissionsExceptionCode.RAW_SQL_NOT_ALLOWED]: msg`Raw SQL queries are not allowed.`,
[PermissionsExceptionCode.CANNOT_GIVE_WRITING_PERMISSION_ON_NON_READABLE_OBJECT]: msg`Cannot give write permission on non-readable objects.`,
[PermissionsExceptionCode.CANNOT_GIVE_WRITING_PERMISSION_WITHOUT_READING_PERMISSION]: msg`Cannot give write permission without read permission.`,
[PermissionsExceptionCode.FIELD_METADATA_NOT_FOUND]: msg`Field metadata not found.`,
[PermissionsExceptionCode.ONLY_FIELD_RESTRICTION_ALLOWED]: msg`Only field restrictions are allowed.`,
[PermissionsExceptionCode.FIELD_RESTRICTION_ONLY_ALLOWED_ON_READABLE_OBJECT]: msg`Field restrictions only apply to readable objects.`,
[PermissionsExceptionCode.FIELD_RESTRICTION_ON_UPDATE_ONLY_ALLOWED_ON_UPDATABLE_OBJECT]: msg`Update field restrictions only apply to updatable objects.`,
[PermissionsExceptionCode.UPSERT_FIELD_PERMISSION_FAILED]: msg`Failed to update field permission.`,
[PermissionsExceptionCode.PERMISSION_NOT_FOUND]: msg`Permission not found.`,
[PermissionsExceptionCode.OBJECT_PERMISSION_NOT_FOUND]: msg`Object permission not found.`,
[PermissionsExceptionCode.EMPTY_FIELD_PERMISSION_NOT_ALLOWED]: msg`Empty field permissions are not allowed.`,
[PermissionsExceptionCode.JOIN_COLUMN_NAME_REQUIRED]: msg`Join column name is required.`,
[PermissionsExceptionCode.COMPOSITE_TYPE_NOT_FOUND]: msg`Composite type not found.`,
[PermissionsExceptionCode.ROLE_MUST_HAVE_AT_LEAST_ONE_TARGET]: msg`Role must have at least one target.`,
[PermissionsExceptionCode.ROLE_CANNOT_BE_ASSIGNED_TO_USERS]: msg`This role cannot be assigned to users.`,
const getPermissionsExceptionUserFriendlyMessage = (
code: PermissionsExceptionCode,
) => {
switch (code) {
case PermissionsExceptionCode.PERMISSION_DENIED:
return msg`You do not have permission to perform this action.`;
case PermissionsExceptionCode.ADMIN_ROLE_NOT_FOUND:
return msg`Admin role not found.`;
case PermissionsExceptionCode.USER_WORKSPACE_NOT_FOUND:
return msg`User workspace not found.`;
case PermissionsExceptionCode.WORKSPACE_ID_ROLE_USER_WORKSPACE_MISMATCH:
return msg`Workspace ID and role mismatch.`;
case PermissionsExceptionCode.TOO_MANY_ADMIN_CANDIDATES:
return msg`Too many admin candidates found.`;
case PermissionsExceptionCode.USER_WORKSPACE_ALREADY_HAS_ROLE:
return msg`User already has a role assigned.`;
case PermissionsExceptionCode.WORKSPACE_MEMBER_NOT_FOUND:
return msg`Workspace member not found.`;
case PermissionsExceptionCode.ROLE_NOT_FOUND:
return msg`Role not found.`;
case PermissionsExceptionCode.CANNOT_UNASSIGN_LAST_ADMIN:
return msg`Cannot remove the last admin from the workspace.`;
case PermissionsExceptionCode.CANNOT_DELETE_LAST_ADMIN_USER:
return msg`Cannot delete the last admin user.`;
case PermissionsExceptionCode.UNKNOWN_OPERATION_NAME:
return msg`Unknown operation.`;
case PermissionsExceptionCode.UNKNOWN_REQUIRED_PERMISSION:
return msg`Unknown permission required.`;
case PermissionsExceptionCode.CANNOT_UPDATE_SELF_ROLE:
return msg`You cannot update your own role.`;
case PermissionsExceptionCode.NO_ROLE_FOUND_FOR_USER_WORKSPACE:
return msg`No role found for this user in the workspace.`;
case PermissionsExceptionCode.API_KEY_ROLE_NOT_FOUND:
return msg`API key role not found.`;
case PermissionsExceptionCode.NO_AUTHENTICATION_CONTEXT:
return msg`Authentication is required.`;
case PermissionsExceptionCode.INVALID_ARG:
return msg`Invalid argument provided.`;
case PermissionsExceptionCode.ROLE_LABEL_ALREADY_EXISTS:
return msg`A role with this label already exists.`;
case PermissionsExceptionCode.DEFAULT_ROLE_NOT_FOUND:
return msg`Default role not found.`;
case PermissionsExceptionCode.OBJECT_METADATA_NOT_FOUND:
return msg`Object metadata not found.`;
case PermissionsExceptionCode.INVALID_SETTING:
return msg`Invalid permission setting.`;
case PermissionsExceptionCode.ROLE_NOT_EDITABLE:
return msg`This role cannot be edited.`;
case PermissionsExceptionCode.DEFAULT_ROLE_CANNOT_BE_DELETED:
return msg`The default role cannot be deleted.`;
case PermissionsExceptionCode.NO_PERMISSIONS_FOUND_IN_DATASOURCE:
return msg`No permissions found in datasource.`;
case PermissionsExceptionCode.CANNOT_ADD_OBJECT_PERMISSION_ON_SYSTEM_OBJECT:
return msg`Cannot add permissions on system objects.`;
case PermissionsExceptionCode.CANNOT_ADD_FIELD_PERMISSION_ON_SYSTEM_OBJECT:
return msg`Cannot add field permissions on system objects.`;
case PermissionsExceptionCode.METHOD_NOT_ALLOWED:
return msg`This method is not allowed.`;
case PermissionsExceptionCode.RAW_SQL_NOT_ALLOWED:
return msg`Raw SQL queries are not allowed.`;
case PermissionsExceptionCode.CANNOT_GIVE_WRITING_PERMISSION_ON_NON_READABLE_OBJECT:
return msg`Cannot give write permission on non-readable objects.`;
case PermissionsExceptionCode.CANNOT_GIVE_WRITING_PERMISSION_WITHOUT_READING_PERMISSION:
return msg`Cannot give write permission without read permission.`;
case PermissionsExceptionCode.FIELD_METADATA_NOT_FOUND:
return msg`Field metadata not found.`;
case PermissionsExceptionCode.ONLY_FIELD_RESTRICTION_ALLOWED:
return msg`Only field restrictions are allowed.`;
case PermissionsExceptionCode.FIELD_RESTRICTION_ONLY_ALLOWED_ON_READABLE_OBJECT:
return msg`Field restrictions only apply to readable objects.`;
case PermissionsExceptionCode.FIELD_RESTRICTION_ON_UPDATE_ONLY_ALLOWED_ON_UPDATABLE_OBJECT:
return msg`Update field restrictions only apply to updatable objects.`;
case PermissionsExceptionCode.UPSERT_FIELD_PERMISSION_FAILED:
return msg`Failed to update field permission.`;
case PermissionsExceptionCode.PERMISSION_NOT_FOUND:
return msg`Permission not found.`;
case PermissionsExceptionCode.OBJECT_PERMISSION_NOT_FOUND:
return msg`Object permission not found.`;
case PermissionsExceptionCode.EMPTY_FIELD_PERMISSION_NOT_ALLOWED:
return msg`Empty field permissions are not allowed.`;
case PermissionsExceptionCode.JOIN_COLUMN_NAME_REQUIRED:
return msg`Join column name is required.`;
case PermissionsExceptionCode.COMPOSITE_TYPE_NOT_FOUND:
return msg`Composite type not found.`;
case PermissionsExceptionCode.ROLE_MUST_HAVE_AT_LEAST_ONE_TARGET:
return msg`Role must have at least one target.`;
case PermissionsExceptionCode.ROLE_CANNOT_BE_ASSIGNED_TO_USERS:
return msg`This role cannot be assigned to users.`;
default:
assertUnreachable(code);
}
};
export class PermissionsException extends CustomException<PermissionsExceptionCode> {
@@ -104,7 +150,7 @@ export class PermissionsException extends CustomException<PermissionsExceptionCo
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? permissionsExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getPermissionsExceptionUserFriendlyMessage(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 RemoteServerExceptionCode {
INVALID_REMOTE_SERVER_INPUT = 'INVALID_REMOTE_SERVER_INPUT',
}
const remoteServerExceptionUserFriendlyMessages: Record<
RemoteServerExceptionCode,
MessageDescriptor
> = {
[RemoteServerExceptionCode.REMOTE_SERVER_NOT_FOUND]: msg`Remote server not found.`,
[RemoteServerExceptionCode.REMOTE_SERVER_ALREADY_EXISTS]: msg`Remote server already exists.`,
[RemoteServerExceptionCode.REMOTE_SERVER_MUTATION_NOT_ALLOWED]: msg`This remote server cannot be modified.`,
[RemoteServerExceptionCode.REMOTE_SERVER_CONNECTION_ERROR]: msg`Failed to connect to remote server.`,
[RemoteServerExceptionCode.INVALID_REMOTE_SERVER_INPUT]: msg`Invalid remote server input.`,
const getRemoteServerExceptionUserFriendlyMessage = (
code: RemoteServerExceptionCode,
) => {
switch (code) {
case RemoteServerExceptionCode.REMOTE_SERVER_NOT_FOUND:
return msg`Remote server not found.`;
case RemoteServerExceptionCode.REMOTE_SERVER_ALREADY_EXISTS:
return msg`Remote server already exists.`;
case RemoteServerExceptionCode.REMOTE_SERVER_MUTATION_NOT_ALLOWED:
return msg`This remote server cannot be modified.`;
case RemoteServerExceptionCode.REMOTE_SERVER_CONNECTION_ERROR:
return msg`Failed to connect to remote server.`;
case RemoteServerExceptionCode.INVALID_REMOTE_SERVER_INPUT:
return msg`Invalid remote server input.`;
default:
assertUnreachable(code);
}
};
export class RemoteServerException extends CustomException<RemoteServerExceptionCode> {
@@ -30,7 +39,8 @@ export class RemoteServerException extends CustomException<RemoteServerException
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? remoteServerExceptionUserFriendlyMessages[code],
userFriendlyMessage ??
getRemoteServerExceptionUserFriendlyMessage(code),
});
}
}
@@ -1,5 +1,6 @@
import { type MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { assertUnreachable } from 'twenty-shared/utils';
import {
appendCommonExceptionCode,
@@ -10,12 +11,17 @@ export const DistantTableExceptionCode = appendCommonExceptionCode({
TIMEOUT_ERROR: 'TIMEOUT_ERROR',
} as const);
const distantTableExceptionUserFriendlyMessages: Record<
keyof typeof DistantTableExceptionCode,
MessageDescriptor
> = {
TIMEOUT_ERROR: msg`Request timed out.`,
INTERNAL_SERVER_ERROR: msg`An unexpected error occurred.`,
const getDistantTableExceptionUserFriendlyMessage = (
code: keyof typeof DistantTableExceptionCode,
) => {
switch (code) {
case DistantTableExceptionCode.TIMEOUT_ERROR:
return msg`Request timed out.`;
case DistantTableExceptionCode.INTERNAL_SERVER_ERROR:
return msg`An unexpected error occurred.`;
default:
assertUnreachable(code);
}
};
export class DistantTableException extends CustomException<
@@ -28,7 +34,8 @@ export class DistantTableException extends CustomException<
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? distantTableExceptionUserFriendlyMessages[code],
userFriendlyMessage ??
getDistantTableExceptionUserFriendlyMessage(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,17 @@ export enum ForeignTableExceptionCode {
INVALID_FOREIGN_TABLE_INPUT = 'INVALID_FOREIGN_TABLE_INPUT',
}
const foreignTableExceptionUserFriendlyMessages: Record<
ForeignTableExceptionCode,
MessageDescriptor
> = {
[ForeignTableExceptionCode.FOREIGN_TABLE_MUTATION_NOT_ALLOWED]: msg`This foreign table cannot be modified.`,
[ForeignTableExceptionCode.INVALID_FOREIGN_TABLE_INPUT]: msg`Invalid foreign table input.`,
const getForeignTableExceptionUserFriendlyMessage = (
code: ForeignTableExceptionCode,
) => {
switch (code) {
case ForeignTableExceptionCode.FOREIGN_TABLE_MUTATION_NOT_ALLOWED:
return msg`This foreign table cannot be modified.`;
case ForeignTableExceptionCode.INVALID_FOREIGN_TABLE_INPUT:
return msg`Invalid foreign table input.`;
default:
assertUnreachable(code);
}
};
export class ForeignTableException extends CustomException<ForeignTableExceptionCode> {
@@ -24,7 +30,8 @@ export class ForeignTableException extends CustomException<ForeignTableException
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? foreignTableExceptionUserFriendlyMessages[code],
userFriendlyMessage ??
getForeignTableExceptionUserFriendlyMessage(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 RemoteTableExceptionCode {
NO_FIELD_METADATA_FOUND = 'NO_FIELD_METADATA_FOUND',
}
const remoteTableExceptionUserFriendlyMessages: Record<
RemoteTableExceptionCode,
MessageDescriptor
> = {
[RemoteTableExceptionCode.REMOTE_TABLE_NOT_FOUND]: msg`Remote table not found.`,
[RemoteTableExceptionCode.INVALID_REMOTE_TABLE_INPUT]: msg`Invalid remote table input.`,
[RemoteTableExceptionCode.REMOTE_TABLE_ALREADY_EXISTS]: msg`Remote table already exists.`,
[RemoteTableExceptionCode.NO_FOREIGN_TABLES_FOUND]: msg`No foreign tables found.`,
[RemoteTableExceptionCode.NO_OBJECT_METADATA_FOUND]: msg`Object metadata not found.`,
[RemoteTableExceptionCode.NO_FIELD_METADATA_FOUND]: msg`Field metadata not found.`,
const getRemoteTableExceptionUserFriendlyMessage = (
code: RemoteTableExceptionCode,
) => {
switch (code) {
case RemoteTableExceptionCode.REMOTE_TABLE_NOT_FOUND:
return msg`Remote table not found.`;
case RemoteTableExceptionCode.INVALID_REMOTE_TABLE_INPUT:
return msg`Invalid remote table input.`;
case RemoteTableExceptionCode.REMOTE_TABLE_ALREADY_EXISTS:
return msg`Remote table already exists.`;
case RemoteTableExceptionCode.NO_FOREIGN_TABLES_FOUND:
return msg`No foreign tables found.`;
case RemoteTableExceptionCode.NO_OBJECT_METADATA_FOUND:
return msg`Object metadata not found.`;
case RemoteTableExceptionCode.NO_FIELD_METADATA_FOUND:
return msg`Field metadata not found.`;
default:
assertUnreachable(code);
}
};
export class RemoteTableException extends CustomException<RemoteTableExceptionCode> {
@@ -32,7 +42,7 @@ export class RemoteTableException extends CustomException<RemoteTableExceptionCo
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? remoteTableExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getRemoteTableExceptionUserFriendlyMessage(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 RoleTargetExceptionCode {
ROLE_NOT_FOUND = 'ROLE_NOT_FOUND',
}
const roleTargetExceptionUserFriendlyMessages: Record<
RoleTargetExceptionCode,
MessageDescriptor
> = {
[RoleTargetExceptionCode.ROLE_TARGET_NOT_FOUND]: msg`Role target not found.`,
[RoleTargetExceptionCode.INVALID_ROLE_TARGET_DATA]: msg`Invalid role target data.`,
[RoleTargetExceptionCode.ROLE_TARGET_MISSING_IDENTIFIER]: msg`Role target is missing identifier.`,
[RoleTargetExceptionCode.ROLE_CANNOT_BE_ASSIGNED_TO_ENTITY]: msg`Role cannot be assigned to this entity.`,
[RoleTargetExceptionCode.ROLE_NOT_FOUND]: msg`Role not found.`,
const getRoleTargetExceptionUserFriendlyMessage = (
code: RoleTargetExceptionCode,
) => {
switch (code) {
case RoleTargetExceptionCode.ROLE_TARGET_NOT_FOUND:
return msg`Role target not found.`;
case RoleTargetExceptionCode.INVALID_ROLE_TARGET_DATA:
return msg`Invalid role target data.`;
case RoleTargetExceptionCode.ROLE_TARGET_MISSING_IDENTIFIER:
return msg`Role target is missing identifier.`;
case RoleTargetExceptionCode.ROLE_CANNOT_BE_ASSIGNED_TO_ENTITY:
return msg`Role cannot be assigned to this entity.`;
case RoleTargetExceptionCode.ROLE_NOT_FOUND:
return msg`Role not found.`;
default:
assertUnreachable(code);
}
};
export class RoleTargetException extends CustomException<RoleTargetExceptionCode> {
@@ -30,7 +39,7 @@ export class RoleTargetException extends CustomException<RoleTargetExceptionCode
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? roleTargetExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getRoleTargetExceptionUserFriendlyMessage(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 RouteTriggerExceptionCode {
SERVERLESS_FUNCTION_EXECUTION_ERROR = 'SERVERLESS_FUNCTION_EXECUTION_ERROR',
}
const routeTriggerExceptionUserFriendlyMessages: Record<
RouteTriggerExceptionCode,
MessageDescriptor
> = {
[RouteTriggerExceptionCode.WORKSPACE_NOT_FOUND]: msg`Workspace not found.`,
[RouteTriggerExceptionCode.ROUTE_NOT_FOUND]: msg`Route not found.`,
[RouteTriggerExceptionCode.TRIGGER_NOT_FOUND]: msg`Trigger not found.`,
[RouteTriggerExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND]: msg`Serverless function not found.`,
[RouteTriggerExceptionCode.ROUTE_ALREADY_EXIST]: msg`Route already exists.`,
[RouteTriggerExceptionCode.ROUTE_PATH_ALREADY_EXIST]: msg`Route path already exists.`,
[RouteTriggerExceptionCode.FORBIDDEN_EXCEPTION]: msg`You do not have permission to perform this action.`,
[RouteTriggerExceptionCode.SERVERLESS_FUNCTION_EXECUTION_ERROR]: msg`Serverless function execution failed.`,
const getRouteTriggerExceptionUserFriendlyMessage = (
code: RouteTriggerExceptionCode,
) => {
switch (code) {
case RouteTriggerExceptionCode.WORKSPACE_NOT_FOUND:
return msg`Workspace not found.`;
case RouteTriggerExceptionCode.ROUTE_NOT_FOUND:
return msg`Route not found.`;
case RouteTriggerExceptionCode.TRIGGER_NOT_FOUND:
return msg`Trigger not found.`;
case RouteTriggerExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND:
return msg`Serverless function not found.`;
case RouteTriggerExceptionCode.ROUTE_ALREADY_EXIST:
return msg`Route already exists.`;
case RouteTriggerExceptionCode.ROUTE_PATH_ALREADY_EXIST:
return msg`Route path already exists.`;
case RouteTriggerExceptionCode.FORBIDDEN_EXCEPTION:
return msg`You do not have permission to perform this action.`;
case RouteTriggerExceptionCode.SERVERLESS_FUNCTION_EXECUTION_ERROR:
return msg`Serverless function execution failed.`;
default:
assertUnreachable(code);
}
};
export class RouteTriggerException extends CustomException<RouteTriggerExceptionCode> {
@@ -36,7 +48,8 @@ export class RouteTriggerException extends CustomException<RouteTriggerException
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? routeTriggerExceptionUserFriendlyMessages[code],
userFriendlyMessage ??
getRouteTriggerExceptionUserFriendlyMessage(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 ServerlessFunctionExceptionCode {
SERVERLESS_FUNCTION_EXECUTION_TIMEOUT = 'SERVERLESS_FUNCTION_EXECUTION_TIMEOUT',
}
const serverlessFunctionExceptionUserFriendlyMessages: Record<
ServerlessFunctionExceptionCode,
MessageDescriptor
> = {
[ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND]: msg`Function not found.`,
[ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_VERSION_NOT_FOUND]: msg`Function version not found.`,
[ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_ALREADY_EXIST]: msg`A function with this name already exists.`,
[ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_READY]: msg`Function is not ready.`,
[ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_BUILDING]: msg`Function is currently building.`,
[ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_CODE_UNCHANGED]: msg`Function code is unchanged.`,
[ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_EXECUTION_LIMIT_REACHED]: msg`Function execution limit reached.`,
[ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_CREATE_FAILED]: msg`Failed to create function.`,
[ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_EXECUTION_TIMEOUT]: msg`Function execution timed out.`,
const getServerlessFunctionExceptionUserFriendlyMessage = (
code: ServerlessFunctionExceptionCode,
) => {
switch (code) {
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND:
return msg`Function not found.`;
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_VERSION_NOT_FOUND:
return msg`Function version not found.`;
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_ALREADY_EXIST:
return msg`A function with this name already exists.`;
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_READY:
return msg`Function is not ready.`;
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_BUILDING:
return msg`Function is currently building.`;
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_CODE_UNCHANGED:
return msg`Function code is unchanged.`;
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_EXECUTION_LIMIT_REACHED:
return msg`Function execution limit reached.`;
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_CREATE_FAILED:
return msg`Failed to create function.`;
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_EXECUTION_TIMEOUT:
return msg`Function execution timed out.`;
default:
assertUnreachable(code);
}
};
export class ServerlessFunctionException extends CustomException<ServerlessFunctionExceptionCode> {
@@ -39,7 +52,7 @@ export class ServerlessFunctionException extends CustomException<ServerlessFunct
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
serverlessFunctionExceptionUserFriendlyMessages[code],
getServerlessFunctionExceptionUserFriendlyMessage(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 InvalidMetadataExceptionCode {
NOT_AVAILABLE = 'Name not available',
}
const invalidMetadataExceptionUserFriendlyMessages: Record<
InvalidMetadataExceptionCode,
MessageDescriptor
> = {
[InvalidMetadataExceptionCode.LABEL_REQUIRED]: msg`Label is required.`,
[InvalidMetadataExceptionCode.INPUT_TOO_SHORT]: msg`Input is too short.`,
[InvalidMetadataExceptionCode.EXCEEDS_MAX_LENGTH]: msg`Input exceeds maximum length.`,
[InvalidMetadataExceptionCode.RESERVED_KEYWORD]: msg`This name is a reserved keyword.`,
[InvalidMetadataExceptionCode.NOT_CAMEL_CASE]: msg`Name must be in camelCase format.`,
[InvalidMetadataExceptionCode.INVALID_LABEL]: msg`Invalid label format.`,
[InvalidMetadataExceptionCode.NAME_NOT_SYNCED_WITH_LABEL]: msg`Name is not synced with label.`,
[InvalidMetadataExceptionCode.INVALID_STRING]: msg`Invalid string format.`,
[InvalidMetadataExceptionCode.NOT_AVAILABLE]: msg`This name is not available.`,
const getInvalidMetadataExceptionUserFriendlyMessage = (
code: InvalidMetadataExceptionCode,
) => {
switch (code) {
case InvalidMetadataExceptionCode.LABEL_REQUIRED:
return msg`Label is required.`;
case InvalidMetadataExceptionCode.INPUT_TOO_SHORT:
return msg`Input is too short.`;
case InvalidMetadataExceptionCode.EXCEEDS_MAX_LENGTH:
return msg`Input exceeds maximum length.`;
case InvalidMetadataExceptionCode.RESERVED_KEYWORD:
return msg`This name is a reserved keyword.`;
case InvalidMetadataExceptionCode.NOT_CAMEL_CASE:
return msg`Name must be in camelCase format.`;
case InvalidMetadataExceptionCode.INVALID_LABEL:
return msg`Invalid label format.`;
case InvalidMetadataExceptionCode.NAME_NOT_SYNCED_WITH_LABEL:
return msg`Name is not synced with label.`;
case InvalidMetadataExceptionCode.INVALID_STRING:
return msg`Invalid string format.`;
case InvalidMetadataExceptionCode.NOT_AVAILABLE:
return msg`This name is not available.`;
default:
assertUnreachable(code);
}
};
export class InvalidMetadataException extends CustomException<InvalidMetadataExceptionCode> {
@@ -39,7 +52,7 @@ export class InvalidMetadataException extends CustomException<InvalidMetadataExc
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
invalidMetadataExceptionUserFriendlyMessages[code],
getInvalidMetadataExceptionUserFriendlyMessage(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 FlatViewExceptionCode = appendCommonExceptionCode({
VIEW_ALREADY_EXISTS: 'VIEW_ALREADY_EXISTS',
} as const);
const flatViewExceptionUserFriendlyMessages: Record<
keyof typeof FlatViewExceptionCode,
MessageDescriptor
> = {
VIEW_NOT_FOUND: msg`View not found.`,
VIEW_ALREADY_EXISTS: msg`View already exists.`,
INTERNAL_SERVER_ERROR: msg`An unexpected error occurred.`,
const getFlatViewExceptionUserFriendlyMessage = (
code: keyof typeof FlatViewExceptionCode,
) => {
switch (code) {
case FlatViewExceptionCode.VIEW_NOT_FOUND:
return msg`View not found.`;
case FlatViewExceptionCode.VIEW_ALREADY_EXISTS:
return msg`View already exists.`;
case FlatViewExceptionCode.INTERNAL_SERVER_ERROR:
return STANDARD_ERROR_MESSAGE;
default:
assertUnreachable(code);
}
};
export class FlatViewException extends CustomException<
@@ -30,7 +38,7 @@ export class FlatViewException extends CustomException<
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? flatViewExceptionUserFriendlyMessages[code],
userFriendlyMessage ?? getFlatViewExceptionUserFriendlyMessage(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 WorkspaceMetadataVersionExceptionCode {
METADATA_VERSION_NOT_FOUND = 'METADATA_VERSION_NOT_FOUND',
}
const workspaceMetadataVersionExceptionUserFriendlyMessages: Record<
WorkspaceMetadataVersionExceptionCode,
MessageDescriptor
> = {
[WorkspaceMetadataVersionExceptionCode.METADATA_VERSION_NOT_FOUND]: msg`Metadata version not found.`,
const getWorkspaceMetadataVersionExceptionUserFriendlyMessage = (
code: WorkspaceMetadataVersionExceptionCode,
) => {
switch (code) {
case WorkspaceMetadataVersionExceptionCode.METADATA_VERSION_NOT_FOUND:
return msg`Metadata version not found.`;
default:
assertUnreachable(code);
}
};
export class WorkspaceMetadataVersionException extends CustomException<WorkspaceMetadataVersionExceptionCode> {
@@ -23,7 +28,7 @@ export class WorkspaceMetadataVersionException extends CustomException<Workspace
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
workspaceMetadataVersionExceptionUserFriendlyMessages[code],
getWorkspaceMetadataVersionExceptionUserFriendlyMessage(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 WorkspaceMigrationExceptionCode {
ENUM_TYPE_NAME_NOT_FOUND = 'ENUM_TYPE_NAME_NOT_FOUND',
}
const workspaceMigrationExceptionUserFriendlyMessages: Record<
WorkspaceMigrationExceptionCode,
MessageDescriptor
> = {
[WorkspaceMigrationExceptionCode.NO_FACTORY_FOUND]: msg`Migration factory not found.`,
[WorkspaceMigrationExceptionCode.INVALID_ACTION]: msg`Invalid migration action.`,
[WorkspaceMigrationExceptionCode.INVALID_FIELD_METADATA]: msg`Invalid field metadata.`,
[WorkspaceMigrationExceptionCode.INVALID_COMPOSITE_TYPE]: msg`Invalid composite type.`,
[WorkspaceMigrationExceptionCode.ENUM_TYPE_NAME_NOT_FOUND]: msg`Enum type not found.`,
const getWorkspaceMigrationExceptionUserFriendlyMessage = (
code: WorkspaceMigrationExceptionCode,
) => {
switch (code) {
case WorkspaceMigrationExceptionCode.NO_FACTORY_FOUND:
return msg`Migration factory not found.`;
case WorkspaceMigrationExceptionCode.INVALID_ACTION:
return msg`Invalid migration action.`;
case WorkspaceMigrationExceptionCode.INVALID_FIELD_METADATA:
return msg`Invalid field metadata.`;
case WorkspaceMigrationExceptionCode.INVALID_COMPOSITE_TYPE:
return msg`Invalid composite type.`;
case WorkspaceMigrationExceptionCode.ENUM_TYPE_NAME_NOT_FOUND:
return msg`Enum type not found.`;
default:
assertUnreachable(code);
}
};
export class WorkspaceMigrationException extends CustomException<WorkspaceMigrationExceptionCode> {
@@ -31,7 +40,7 @@ export class WorkspaceMigrationException extends CustomException<WorkspaceMigrat
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
workspaceMigrationExceptionUserFriendlyMessages[code],
getWorkspaceMigrationExceptionUserFriendlyMessage(code),
});
}
}