Improve error handling (#13130)
In the BE we throw custom errors with precise error codes (e.g. "LABEL_ALREADY_EXISTS") before catching them in filters and rethrowing BaseGraphQLErrors (standard errors such as NotFoundError, UserInputError etc.). In the FE we were grouping sentries based on the error codes but we were actually grouping by very broad codes such as "NOT_FOUND" or "BAD_USER_INPUT", extracted from the BaseGraphQLErrors. To fix that, we update the BaseGraphQLError constructor api to allow to pass on the CustomError directly and retrieve from it the original code and store it in existing property `subCode` that we will use in the FE to send errors to sentry. This new api also eases usage of `userFriendlyMessage` that is passed on to the api response and therefore to the FE when CustomError is passed on directly to the BaseGraphQLError constructor.
This commit is contained in:
@ -22,9 +22,9 @@ export const graphqlQueryRunnerExceptionHandler = (
|
||||
case GraphqlQueryRunnerExceptionCode.FIELD_NOT_FOUND:
|
||||
case GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT:
|
||||
case GraphqlQueryRunnerExceptionCode.NOT_IMPLEMENTED:
|
||||
throw new UserInputError(error.message);
|
||||
throw new UserInputError(error);
|
||||
case GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND:
|
||||
throw new NotFoundError(error.message);
|
||||
throw new NotFoundError(error);
|
||||
case GraphqlQueryRunnerExceptionCode.RELATION_SETTINGS_NOT_FOUND:
|
||||
case GraphqlQueryRunnerExceptionCode.RELATION_TARGET_OBJECT_METADATA_NOT_FOUND:
|
||||
case GraphqlQueryRunnerExceptionCode.INVALID_POST_HOOK_PAYLOAD:
|
||||
|
||||
@ -14,16 +14,16 @@ export const workspaceExceptionHandler = (
|
||||
) => {
|
||||
switch (error.code) {
|
||||
case WorkspaceQueryRunnerExceptionCode.DATA_NOT_FOUND:
|
||||
throw new NotFoundError(error.message);
|
||||
throw new NotFoundError(error);
|
||||
case WorkspaceQueryRunnerExceptionCode.INVALID_QUERY_INPUT:
|
||||
throw new UserInputError(error.message);
|
||||
throw new UserInputError(error);
|
||||
case WorkspaceQueryRunnerExceptionCode.QUERY_VIOLATES_UNIQUE_CONSTRAINT:
|
||||
case WorkspaceQueryRunnerExceptionCode.QUERY_VIOLATES_FOREIGN_KEY_CONSTRAINT:
|
||||
case WorkspaceQueryRunnerExceptionCode.TOO_MANY_ROWS_AFFECTED:
|
||||
case WorkspaceQueryRunnerExceptionCode.NO_ROWS_AFFECTED:
|
||||
throw new ForbiddenError(error.message);
|
||||
throw new ForbiddenError(error);
|
||||
case WorkspaceQueryRunnerExceptionCode.QUERY_TIMEOUT:
|
||||
throw new TimeoutError(error.message);
|
||||
throw new TimeoutError(error);
|
||||
case WorkspaceQueryRunnerExceptionCode.INTERNAL_SERVER_ERROR:
|
||||
throw error;
|
||||
default: {
|
||||
|
||||
@ -17,9 +17,7 @@ export class ApprovedAccessDomainExceptionFilter implements ExceptionFilter {
|
||||
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_VALIDATION_TOKEN_INVALID:
|
||||
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_ALREADY_VALIDATED:
|
||||
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_MUST_BE_A_COMPANY_DOMAIN:
|
||||
throw new ForbiddenError(exception.message, {
|
||||
userFriendlyMessage: exception.userFriendlyMessage,
|
||||
});
|
||||
throw new ForbiddenError(exception);
|
||||
default: {
|
||||
const _exhaustiveCheck: never = exception.code;
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ export class AuditExceptionFilter implements ExceptionFilter {
|
||||
switch (exception.code) {
|
||||
case AuditExceptionCode.INVALID_TYPE:
|
||||
case AuditExceptionCode.INVALID_INPUT:
|
||||
throw new UserInputError(exception.message);
|
||||
throw new UserInputError(exception);
|
||||
default: {
|
||||
const _exhaustiveCheck: never = exception.code;
|
||||
|
||||
|
||||
@ -18,13 +18,9 @@ export class AuthGraphqlApiExceptionFilter implements ExceptionFilter {
|
||||
catch(exception: AuthException) {
|
||||
switch (exception.code) {
|
||||
case AuthExceptionCode.CLIENT_NOT_FOUND:
|
||||
throw new NotFoundError(exception.message, {
|
||||
userFriendlyMessage: exception.userFriendlyMessage,
|
||||
});
|
||||
throw new NotFoundError(exception);
|
||||
case AuthExceptionCode.INVALID_INPUT:
|
||||
throw new UserInputError(exception.message, {
|
||||
userFriendlyMessage: exception.userFriendlyMessage,
|
||||
});
|
||||
throw new UserInputError(exception);
|
||||
case AuthExceptionCode.FORBIDDEN_EXCEPTION:
|
||||
case AuthExceptionCode.INSUFFICIENT_SCOPES:
|
||||
case AuthExceptionCode.OAUTH_ACCESS_DENIED:
|
||||
@ -33,13 +29,12 @@ export class AuthGraphqlApiExceptionFilter implements ExceptionFilter {
|
||||
case AuthExceptionCode.SIGNUP_DISABLED:
|
||||
case AuthExceptionCode.MISSING_ENVIRONMENT_VARIABLE:
|
||||
case AuthExceptionCode.INVALID_JWT_TOKEN_TYPE:
|
||||
throw new ForbiddenError(exception.message, {
|
||||
userFriendlyMessage: exception.userFriendlyMessage,
|
||||
});
|
||||
throw new ForbiddenError(exception);
|
||||
case AuthExceptionCode.GOOGLE_API_AUTH_DISABLED:
|
||||
case AuthExceptionCode.MICROSOFT_API_AUTH_DISABLED:
|
||||
throw new ForbiddenError(exception.message, {
|
||||
userFriendlyMessage: t`Authentication is not enabled with this provider.`,
|
||||
subCode: exception.code,
|
||||
});
|
||||
case AuthExceptionCode.EMAIL_NOT_VERIFIED:
|
||||
case AuthExceptionCode.INVALID_DATA:
|
||||
@ -50,10 +45,11 @@ export class AuthGraphqlApiExceptionFilter implements ExceptionFilter {
|
||||
case AuthExceptionCode.UNAUTHENTICATED:
|
||||
throw new AuthenticationError(exception.message, {
|
||||
userFriendlyMessage: t`You must be authenticated to perform this action.`,
|
||||
subCode: exception.code,
|
||||
});
|
||||
case AuthExceptionCode.USER_NOT_FOUND:
|
||||
case AuthExceptionCode.WORKSPACE_NOT_FOUND:
|
||||
throw new AuthenticationError(exception.message);
|
||||
throw new AuthenticationError(exception);
|
||||
case AuthExceptionCode.INTERNAL_SERVER_ERROR:
|
||||
case AuthExceptionCode.USER_WORKSPACE_NOT_FOUND:
|
||||
throw exception;
|
||||
|
||||
@ -23,13 +23,9 @@ export class EmailVerificationExceptionFilter implements ExceptionFilter {
|
||||
case EmailVerificationExceptionCode.INVALID_TOKEN:
|
||||
case EmailVerificationExceptionCode.INVALID_APP_TOKEN_TYPE:
|
||||
case EmailVerificationExceptionCode.RATE_LIMIT_EXCEEDED:
|
||||
throw new ForbiddenError(exception.message, {
|
||||
subCode: exception.code,
|
||||
});
|
||||
throw new ForbiddenError(exception);
|
||||
case EmailVerificationExceptionCode.EMAIL_MISSING:
|
||||
throw new UserInputError(exception.message, {
|
||||
subCode: exception.code,
|
||||
});
|
||||
throw new UserInputError(exception);
|
||||
case EmailVerificationExceptionCode.EMAIL_ALREADY_VERIFIED:
|
||||
throw new UserInputError(exception.message, {
|
||||
subCode: exception.code,
|
||||
@ -41,9 +37,7 @@ export class EmailVerificationExceptionFilter implements ExceptionFilter {
|
||||
userFriendlyMessage: t`Email verification not required.`,
|
||||
});
|
||||
case EmailVerificationExceptionCode.INVALID_EMAIL:
|
||||
throw new UserInputError(exception.message, {
|
||||
subCode: exception.code,
|
||||
});
|
||||
throw new UserInputError(exception);
|
||||
default: {
|
||||
const _exhaustiveCheck: never = exception.code;
|
||||
|
||||
|
||||
@ -6,6 +6,8 @@ import {
|
||||
SourceLocation,
|
||||
} from 'graphql';
|
||||
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
declare module 'graphql' {
|
||||
export interface GraphQLErrorExtensions {
|
||||
exception?: {
|
||||
@ -48,29 +50,42 @@ export class BaseGraphQLError extends GraphQLError {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[key: string]: any;
|
||||
|
||||
constructor(
|
||||
message: string,
|
||||
exceptionOrMessage: string | CustomException,
|
||||
code?: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
extensions?: Record<string, any>,
|
||||
) {
|
||||
super(message);
|
||||
if (exceptionOrMessage instanceof CustomException) {
|
||||
const exception = exceptionOrMessage;
|
||||
|
||||
super(exception.message);
|
||||
|
||||
this.extensions = {
|
||||
subCode: exception.code,
|
||||
userFriendlyMessage: exception.userFriendlyMessage,
|
||||
code,
|
||||
};
|
||||
} else {
|
||||
const message = exceptionOrMessage;
|
||||
|
||||
super(message);
|
||||
|
||||
if (extensions?.extensions) {
|
||||
throw new Error(
|
||||
'Pass extensions directly as the third argument of the ApolloError constructor: `new ' +
|
||||
'ApolloError(message, code, {myExt: value})`, not `new ApolloError(message, code, ' +
|
||||
'{extensions: {myExt: value}})`',
|
||||
);
|
||||
}
|
||||
|
||||
this.extensions = { ...extensions, code };
|
||||
}
|
||||
|
||||
// if no name provided, use the default. defineProperty ensures that it stays non-enumerable
|
||||
if (!this.name) {
|
||||
Object.defineProperty(this, 'name', { value: 'GraphQLError' });
|
||||
}
|
||||
|
||||
if (extensions?.extensions) {
|
||||
throw new Error(
|
||||
'Pass extensions directly as the third argument of the ApolloError constructor: `new ' +
|
||||
'ApolloError(message, code, {myExt: value})`, not `new ApolloError(message, code, ' +
|
||||
'{extensions: {myExt: value}})`',
|
||||
);
|
||||
}
|
||||
|
||||
this.extensions = { ...extensions, code };
|
||||
}
|
||||
|
||||
toJSON(): GraphQLFormattedError {
|
||||
@ -113,28 +128,62 @@ export class ValidationError extends BaseGraphQLError {
|
||||
}
|
||||
}
|
||||
|
||||
export class AuthenticationError extends BaseGraphQLError {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
constructor(message: string, extensions?: RestrictedGraphQLErrorExtensions) {
|
||||
super(message, ErrorCode.UNAUTHENTICATED, extensions);
|
||||
export class NotFoundError extends BaseGraphQLError {
|
||||
constructor(exception: CustomException);
|
||||
|
||||
constructor(message: string, extensions?: RestrictedGraphQLErrorExtensions);
|
||||
|
||||
constructor(
|
||||
messageOrException: string | CustomException,
|
||||
extensions?: RestrictedGraphQLErrorExtensions,
|
||||
) {
|
||||
super(messageOrException, ErrorCode.NOT_FOUND, extensions);
|
||||
Object.defineProperty(this, 'name', { value: 'NotFoundError' });
|
||||
}
|
||||
}
|
||||
|
||||
export class AuthenticationError extends BaseGraphQLError {
|
||||
constructor(exception: CustomException);
|
||||
|
||||
constructor(message: string, extensions?: RestrictedGraphQLErrorExtensions);
|
||||
|
||||
constructor(
|
||||
messageOrException: string | CustomException,
|
||||
extensions?: RestrictedGraphQLErrorExtensions,
|
||||
) {
|
||||
super(messageOrException, ErrorCode.UNAUTHENTICATED, extensions);
|
||||
Object.defineProperty(this, 'name', { value: 'AuthenticationError' });
|
||||
}
|
||||
}
|
||||
|
||||
export class ForbiddenError extends BaseGraphQLError {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
constructor(message: string, extensions?: RestrictedGraphQLErrorExtensions) {
|
||||
super(message, ErrorCode.FORBIDDEN, extensions);
|
||||
constructor(exception: CustomException);
|
||||
|
||||
constructor(message: string, extensions?: RestrictedGraphQLErrorExtensions);
|
||||
|
||||
constructor(
|
||||
messageOrException: string | CustomException,
|
||||
extensions?: RestrictedGraphQLErrorExtensions,
|
||||
) {
|
||||
super(messageOrException, ErrorCode.FORBIDDEN, extensions);
|
||||
Object.defineProperty(this, 'name', { value: 'ForbiddenError' });
|
||||
}
|
||||
}
|
||||
|
||||
export class PersistedQueryNotFoundError extends BaseGraphQLError {
|
||||
constructor() {
|
||||
super('PersistedQueryNotFound', ErrorCode.PERSISTED_QUERY_NOT_FOUND);
|
||||
constructor(customException: CustomException);
|
||||
|
||||
constructor(message?: string, extensions?: RestrictedGraphQLErrorExtensions);
|
||||
|
||||
constructor(
|
||||
messageOrException?: string | CustomException,
|
||||
extensions?: RestrictedGraphQLErrorExtensions,
|
||||
) {
|
||||
super(
|
||||
messageOrException || 'PersistedQueryNotFound',
|
||||
ErrorCode.PERSISTED_QUERY_NOT_FOUND,
|
||||
extensions,
|
||||
);
|
||||
Object.defineProperty(this, 'name', {
|
||||
value: 'PersistedQueryNotFoundError',
|
||||
});
|
||||
@ -142,12 +191,15 @@ export class PersistedQueryNotFoundError extends BaseGraphQLError {
|
||||
}
|
||||
|
||||
export class PersistedQueryNotSupportedError extends BaseGraphQLError {
|
||||
constructor() {
|
||||
constructor(
|
||||
messageOrException?: string | CustomException,
|
||||
extensions?: RestrictedGraphQLErrorExtensions,
|
||||
) {
|
||||
super(
|
||||
'PersistedQueryNotSupported',
|
||||
messageOrException || 'PersistedQueryNotSupported',
|
||||
ErrorCode.PERSISTED_QUERY_NOT_SUPPORTED,
|
||||
extensions,
|
||||
);
|
||||
|
||||
Object.defineProperty(this, 'name', {
|
||||
value: 'PersistedQueryNotSupportedError',
|
||||
});
|
||||
@ -155,52 +207,71 @@ export class PersistedQueryNotSupportedError extends BaseGraphQLError {
|
||||
}
|
||||
|
||||
export class UserInputError extends BaseGraphQLError {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
constructor(message: string, extensions?: RestrictedGraphQLErrorExtensions) {
|
||||
super(message, ErrorCode.BAD_USER_INPUT, extensions);
|
||||
constructor(exception: CustomException);
|
||||
|
||||
constructor(message: string, extensions?: RestrictedGraphQLErrorExtensions);
|
||||
|
||||
constructor(
|
||||
messageOrException: string | CustomException,
|
||||
extensions?: RestrictedGraphQLErrorExtensions,
|
||||
) {
|
||||
super(messageOrException, ErrorCode.BAD_USER_INPUT, extensions);
|
||||
Object.defineProperty(this, 'name', { value: 'UserInputError' });
|
||||
}
|
||||
}
|
||||
|
||||
export class NotFoundError extends BaseGraphQLError {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
constructor(message: string, extensions?: RestrictedGraphQLErrorExtensions) {
|
||||
super(message, ErrorCode.NOT_FOUND, extensions);
|
||||
|
||||
Object.defineProperty(this, 'name', { value: 'NotFoundError' });
|
||||
}
|
||||
}
|
||||
|
||||
export class MethodNotAllowedError extends BaseGraphQLError {
|
||||
constructor(message: string) {
|
||||
super(message, ErrorCode.METHOD_NOT_ALLOWED);
|
||||
constructor(exception: CustomException);
|
||||
|
||||
constructor(message: string, extensions?: RestrictedGraphQLErrorExtensions);
|
||||
|
||||
constructor(
|
||||
messageOrException: string | CustomException,
|
||||
extensions?: RestrictedGraphQLErrorExtensions,
|
||||
) {
|
||||
super(messageOrException, ErrorCode.METHOD_NOT_ALLOWED, extensions);
|
||||
Object.defineProperty(this, 'name', { value: 'MethodNotAllowedError' });
|
||||
}
|
||||
}
|
||||
|
||||
export class ConflictError extends BaseGraphQLError {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
constructor(message: string, extensions?: RestrictedGraphQLErrorExtensions) {
|
||||
super(message, ErrorCode.CONFLICT, extensions);
|
||||
constructor(exception: CustomException);
|
||||
|
||||
constructor(message: string, extensions?: RestrictedGraphQLErrorExtensions);
|
||||
|
||||
constructor(
|
||||
messageOrException: string | CustomException,
|
||||
extensions?: RestrictedGraphQLErrorExtensions,
|
||||
) {
|
||||
super(messageOrException, ErrorCode.CONFLICT, extensions);
|
||||
Object.defineProperty(this, 'name', { value: 'ConflictError' });
|
||||
}
|
||||
}
|
||||
|
||||
export class TimeoutError extends BaseGraphQLError {
|
||||
constructor(message: string) {
|
||||
super(message, ErrorCode.TIMEOUT);
|
||||
constructor(exception: CustomException);
|
||||
|
||||
constructor(message: string, extensions?: RestrictedGraphQLErrorExtensions);
|
||||
|
||||
constructor(
|
||||
messageOrException: string | CustomException,
|
||||
extensions?: RestrictedGraphQLErrorExtensions,
|
||||
) {
|
||||
super(messageOrException, ErrorCode.TIMEOUT, extensions);
|
||||
Object.defineProperty(this, 'name', { value: 'TimeoutError' });
|
||||
}
|
||||
}
|
||||
|
||||
export class InternalServerError extends BaseGraphQLError {
|
||||
constructor(message: string) {
|
||||
super(message, ErrorCode.INTERNAL_SERVER_ERROR);
|
||||
constructor(exception: CustomException);
|
||||
|
||||
constructor(message: string, extensions?: RestrictedGraphQLErrorExtensions);
|
||||
|
||||
constructor(
|
||||
messageOrException: string | CustomException,
|
||||
extensions?: RestrictedGraphQLErrorExtensions,
|
||||
) {
|
||||
super(messageOrException, ErrorCode.INTERNAL_SERVER_ERROR, extensions);
|
||||
Object.defineProperty(this, 'name', { value: 'InternalServerError' });
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,9 +17,7 @@ export const recordTransformerGraphqlApiExceptionHandler = (
|
||||
case RecordTransformerExceptionCode.CONFLICTING_PHONE_CALLING_CODE_AND_COUNTRY_CODE:
|
||||
case RecordTransformerExceptionCode.INVALID_PHONE_CALLING_CODE:
|
||||
case RecordTransformerExceptionCode.INVALID_URL:
|
||||
throw new UserInputError(error.message, {
|
||||
userFriendlyMessage: error.userFriendlyMessage,
|
||||
});
|
||||
throw new UserInputError(error);
|
||||
default: {
|
||||
assertUnreachable(error.code);
|
||||
}
|
||||
|
||||
@ -19,13 +19,9 @@ export const handleWorkflowTriggerException = (
|
||||
case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER:
|
||||
case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_STATUS:
|
||||
case WorkflowTriggerExceptionCode.FORBIDDEN:
|
||||
throw new UserInputError(exception.message, {
|
||||
userFriendlyMessage: exception.userFriendlyMessage,
|
||||
});
|
||||
throw new UserInputError(exception);
|
||||
case WorkflowTriggerExceptionCode.NOT_FOUND:
|
||||
throw new NotFoundError(exception.message, {
|
||||
userFriendlyMessage: exception.userFriendlyMessage,
|
||||
});
|
||||
throw new NotFoundError(exception);
|
||||
case WorkflowTriggerExceptionCode.INTERNAL_ERROR:
|
||||
throw exception;
|
||||
default: {
|
||||
|
||||
@ -13,13 +13,13 @@ export const workspaceGraphqlApiExceptionHandler = (error: Error) => {
|
||||
switch (error.code) {
|
||||
case WorkspaceExceptionCode.SUBDOMAIN_NOT_FOUND:
|
||||
case WorkspaceExceptionCode.WORKSPACE_NOT_FOUND:
|
||||
throw new NotFoundError(error.message);
|
||||
throw new NotFoundError(error);
|
||||
case WorkspaceExceptionCode.DOMAIN_ALREADY_TAKEN:
|
||||
case WorkspaceExceptionCode.SUBDOMAIN_ALREADY_TAKEN:
|
||||
throw new ConflictError(error.message);
|
||||
throw new ConflictError(error);
|
||||
case WorkspaceExceptionCode.ENVIRONMENT_VAR_NOT_ENABLED:
|
||||
case WorkspaceExceptionCode.WORKSPACE_CUSTOM_DOMAIN_DISABLED:
|
||||
throw new ForbiddenError(error.message);
|
||||
throw new ForbiddenError(error);
|
||||
default: {
|
||||
const _exhaustiveCheck: never = error.code;
|
||||
|
||||
|
||||
@ -18,21 +18,13 @@ export const fieldMetadataGraphqlApiExceptionHandler = (error: Error) => {
|
||||
if (error instanceof FieldMetadataException) {
|
||||
switch (error.code) {
|
||||
case FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND:
|
||||
throw new NotFoundError(error.message, {
|
||||
userFriendlyMessage: error.userFriendlyMessage,
|
||||
});
|
||||
throw new NotFoundError(error);
|
||||
case FieldMetadataExceptionCode.INVALID_FIELD_INPUT:
|
||||
throw new UserInputError(error.message, {
|
||||
userFriendlyMessage: error.userFriendlyMessage,
|
||||
});
|
||||
throw new UserInputError(error);
|
||||
case FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED:
|
||||
throw new ForbiddenError(error.message, {
|
||||
userFriendlyMessage: error.userFriendlyMessage,
|
||||
});
|
||||
throw new ForbiddenError(error);
|
||||
case FieldMetadataExceptionCode.FIELD_ALREADY_EXISTS:
|
||||
throw new ConflictError(error.message, {
|
||||
userFriendlyMessage: error.userFriendlyMessage,
|
||||
});
|
||||
throw new ConflictError(error);
|
||||
case FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND:
|
||||
case FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR:
|
||||
case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_NOT_ENABLED:
|
||||
|
||||
@ -12,23 +12,19 @@ import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exce
|
||||
|
||||
export const objectMetadataGraphqlApiExceptionHandler = (error: Error) => {
|
||||
if (error instanceof InvalidMetadataException) {
|
||||
throw new UserInputError(error.message);
|
||||
throw new UserInputError(error);
|
||||
}
|
||||
|
||||
if (error instanceof ObjectMetadataException) {
|
||||
switch (error.code) {
|
||||
case ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND:
|
||||
throw new NotFoundError(error.message);
|
||||
throw new NotFoundError(error);
|
||||
case ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT:
|
||||
throw new UserInputError(error.message, {
|
||||
userFriendlyMessage: error.userFriendlyMessage,
|
||||
});
|
||||
throw new UserInputError(error);
|
||||
case ObjectMetadataExceptionCode.OBJECT_MUTATION_NOT_ALLOWED:
|
||||
throw new ForbiddenError(error.message);
|
||||
throw new ForbiddenError(error);
|
||||
case ObjectMetadataExceptionCode.OBJECT_ALREADY_EXISTS:
|
||||
throw new ConflictError(error.message, {
|
||||
userFriendlyMessage: error.userFriendlyMessage,
|
||||
});
|
||||
throw new ConflictError(error);
|
||||
case ObjectMetadataExceptionCode.MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD:
|
||||
throw error;
|
||||
default: {
|
||||
|
||||
@ -2,8 +2,12 @@ import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
export class PermissionsException extends CustomException {
|
||||
declare code: PermissionsExceptionCode;
|
||||
constructor(message: string, code: PermissionsExceptionCode) {
|
||||
super(message, code);
|
||||
constructor(
|
||||
message: string,
|
||||
code: PermissionsExceptionCode,
|
||||
userFriendlyMessage?: string,
|
||||
) {
|
||||
super(message, code, userFriendlyMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
import {
|
||||
ForbiddenError,
|
||||
NotFoundError,
|
||||
@ -17,18 +15,17 @@ export const permissionGraphqlApiExceptionHandler = (
|
||||
case PermissionsExceptionCode.PERMISSION_DENIED:
|
||||
throw new ForbiddenError(error.message, {
|
||||
userFriendlyMessage: 'User does not have permission.',
|
||||
subCode: error.code,
|
||||
});
|
||||
case PermissionsExceptionCode.ROLE_LABEL_ALREADY_EXISTS:
|
||||
throw new ForbiddenError(error.message, {
|
||||
userFriendlyMessage: t`A role with this label already exists.`,
|
||||
});
|
||||
throw new ForbiddenError(error);
|
||||
case PermissionsExceptionCode.CANNOT_UNASSIGN_LAST_ADMIN:
|
||||
case PermissionsExceptionCode.CANNOT_UPDATE_SELF_ROLE:
|
||||
case PermissionsExceptionCode.CANNOT_DELETE_LAST_ADMIN_USER:
|
||||
case PermissionsExceptionCode.ROLE_NOT_EDITABLE:
|
||||
case PermissionsExceptionCode.CANNOT_ADD_OBJECT_PERMISSION_ON_SYSTEM_OBJECT:
|
||||
case PermissionsExceptionCode.CANNOT_ADD_FIELD_PERMISSION_ON_SYSTEM_OBJECT:
|
||||
throw new ForbiddenError(error.message);
|
||||
throw new ForbiddenError(error);
|
||||
case PermissionsExceptionCode.INVALID_ARG:
|
||||
case PermissionsExceptionCode.INVALID_SETTING:
|
||||
case PermissionsExceptionCode.CANNOT_GIVE_WRITING_PERMISSION_ON_NON_READABLE_OBJECT:
|
||||
@ -37,13 +34,13 @@ export const permissionGraphqlApiExceptionHandler = (
|
||||
case PermissionsExceptionCode.FIELD_RESTRICTION_ONLY_ALLOWED_ON_READABLE_OBJECT:
|
||||
case PermissionsExceptionCode.FIELD_RESTRICTION_ON_UPDATE_ONLY_ALLOWED_ON_UPDATABLE_OBJECT:
|
||||
case PermissionsExceptionCode.EMPTY_FIELD_PERMISSION_NOT_ALLOWED:
|
||||
throw new UserInputError(error.message);
|
||||
throw new UserInputError(error);
|
||||
case PermissionsExceptionCode.ROLE_NOT_FOUND:
|
||||
case PermissionsExceptionCode.USER_WORKSPACE_NOT_FOUND:
|
||||
case PermissionsExceptionCode.OBJECT_METADATA_NOT_FOUND:
|
||||
case PermissionsExceptionCode.FIELD_METADATA_NOT_FOUND:
|
||||
case PermissionsExceptionCode.PERMISSION_NOT_FOUND:
|
||||
throw new NotFoundError(error.message);
|
||||
throw new NotFoundError(error);
|
||||
case PermissionsExceptionCode.UPSERT_FIELD_PERMISSION_FAILED:
|
||||
case PermissionsExceptionCode.DEFAULT_ROLE_NOT_FOUND:
|
||||
case PermissionsExceptionCode.WORKSPACE_ID_ROLE_USER_WORKSPACE_MISMATCH:
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
@ -286,6 +287,7 @@ export class RoleService {
|
||||
throw new PermissionsException(
|
||||
PermissionsExceptionMessage.ROLE_LABEL_ALREADY_EXISTS,
|
||||
PermissionsExceptionCode.ROLE_LABEL_ALREADY_EXISTS,
|
||||
t`A role with this label already exists.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,13 +14,13 @@ export const serverlessFunctionGraphQLApiExceptionHandler = (error: any) => {
|
||||
switch (error.code) {
|
||||
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND:
|
||||
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_VERSION_NOT_FOUND:
|
||||
throw new NotFoundError(error.message);
|
||||
throw new NotFoundError(error);
|
||||
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_ALREADY_EXIST:
|
||||
throw new ConflictError(error.message);
|
||||
throw new ConflictError(error);
|
||||
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_READY:
|
||||
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_BUILDING:
|
||||
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_EXECUTION_LIMIT_REACHED:
|
||||
throw new ForbiddenError(error.message);
|
||||
throw new ForbiddenError(error);
|
||||
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_CODE_UNCHANGED:
|
||||
throw error;
|
||||
default: {
|
||||
|
||||
Reference in New Issue
Block a user