fix(server): convert 4xx status HttpError to UserFriendlyError (#10956)

This commit is contained in:
fengmk2
2025-03-18 09:28:50 +00:00
parent 5cb2abab76
commit 86c4e0705f
12 changed files with 78 additions and 6 deletions

View File

@@ -80,6 +80,7 @@
"graphql-upload": "^17.0.0", "graphql-upload": "^17.0.0",
"html-validate": "^9.0.0", "html-validate": "^9.0.0",
"htmlrewriter": "^0.0.12", "htmlrewriter": "^0.0.12",
"http-errors": "^2.0.0",
"ioredis": "^5.4.1", "ioredis": "^5.4.1",
"is-mobile": "^5.0.0", "is-mobile": "^5.0.0",
"keyv": "^5.2.2", "keyv": "^5.2.2",
@@ -120,6 +121,7 @@
"@types/cookie-parser": "^1.4.8", "@types/cookie-parser": "^1.4.8",
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/graphql-upload": "^17.0.0", "@types/graphql-upload": "^17.0.0",
"@types/http-errors": "^2.0.4",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/mixpanel": "^2.14.9", "@types/mixpanel": "^2.14.9",
"@types/mustache": "^4.2.5", "@types/mustache": "^4.2.5",

View File

@@ -1,6 +1,7 @@
import { type Blob } from '@prisma/client'; import { type Blob } from '@prisma/client';
import { TestingApp } from './testing-app'; import { TestingApp } from './testing-app';
import { TEST_LOG_LEVEL } from './utils';
export async function listBlobs( export async function listBlobs(
app: TestingApp, app: TestingApp,
@@ -73,5 +74,13 @@ export async function setBlob(
`blob-${Math.random().toString(16).substring(2, 10)}.data` `blob-${Math.random().toString(16).substring(2, 10)}.data`
) )
.expect(200); .expect(200);
if (res.body.errors?.length) {
if (TEST_LOG_LEVEL !== 'fatal') {
// print the error stack when log level is not fatal, for better debugging
console.error('%o', res.body);
}
throw new Error(res.body.errors[0].message);
}
return res.body.data.setBlob; return res.body.data.setBlob;
} }

View File

@@ -169,3 +169,15 @@ test('should accept blob even storage out of quota if workspace has unlimited fe
await t.notThrowsAsync(setBlob(app, workspace.id, buffer)); await t.notThrowsAsync(setBlob(app, workspace.id, buffer));
await t.notThrowsAsync(setBlob(app, workspace.id, buffer)); await t.notThrowsAsync(setBlob(app, workspace.id, buffer));
}); });
test('should throw error when blob size large than max file size', async t => {
await app.signup();
const workspace = await createWorkspace(app);
const buffer = Buffer.from(new Uint8Array(1024 * 1024 * 11));
await t.throwsAsync(setBlob(app, workspace.id, buffer), {
message:
'HTTP request error, message: File truncated as it exceeds the 10485760 byte size limit.',
});
});

View File

@@ -264,6 +264,11 @@ export const USER_FRIENDLY_ERRORS = {
message: ({ code, message }) => message: ({ code, message }) =>
`GraphQL bad request, code: ${code}, ${message}`, `GraphQL bad request, code: ${code}, ${message}`,
}, },
http_request_error: {
type: 'bad_request',
args: { message: 'string' },
message: ({ message }) => `HTTP request error, message: ${message}`,
},
// Input errors // Input errors
query_too_long: { query_too_long: {

View File

@@ -45,6 +45,16 @@ export class GraphqlBadRequest extends UserFriendlyError {
} }
} }
@ObjectType() @ObjectType()
class HttpRequestErrorDataType {
@Field() message!: string
}
export class HttpRequestError extends UserFriendlyError {
constructor(args: HttpRequestErrorDataType, message?: string | ((args: HttpRequestErrorDataType) => string)) {
super('bad_request', 'http_request_error', message, args);
}
}
@ObjectType()
class QueryTooLongDataType { class QueryTooLongDataType {
@Field() max!: number @Field() max!: number
} }
@@ -896,6 +906,7 @@ export enum ErrorNames {
NOT_FOUND, NOT_FOUND,
BAD_REQUEST, BAD_REQUEST,
GRAPHQL_BAD_REQUEST, GRAPHQL_BAD_REQUEST,
HTTP_REQUEST_ERROR,
QUERY_TOO_LONG, QUERY_TOO_LONG,
VALIDATION_ERROR, VALIDATION_ERROR,
USER_NOT_FOUND, USER_NOT_FOUND,
@@ -1011,5 +1022,5 @@ registerEnumType(ErrorNames, {
export const ErrorDataUnionType = createUnionType({ export const ErrorDataUnionType = createUnionType({
name: 'ErrorDataUnion', name: 'ErrorDataUnion',
types: () => types: () =>
[GraphqlBadRequestDataType, QueryTooLongDataType, ValidationErrorDataType, WrongSignInCredentialsDataType, UnknownOauthProviderDataType, InvalidOauthCallbackCodeDataType, MissingOauthQueryParameterDataType, InvalidEmailDataType, InvalidPasswordLengthDataType, WorkspacePermissionNotFoundDataType, SpaceNotFoundDataType, MemberNotFoundInSpaceDataType, NotInSpaceDataType, AlreadyInSpaceDataType, SpaceAccessDeniedDataType, SpaceOwnerNotFoundDataType, SpaceShouldHaveOnlyOneOwnerDataType, DocNotFoundDataType, DocActionDeniedDataType, DocUpdateBlockedDataType, VersionRejectedDataType, InvalidHistoryTimestampDataType, DocHistoryNotFoundDataType, BlobNotFoundDataType, ExpectToGrantDocUserRolesDataType, ExpectToRevokeDocUserRolesDataType, ExpectToUpdateDocUserRoleDataType, UnsupportedSubscriptionPlanDataType, SubscriptionAlreadyExistsDataType, SubscriptionNotExistsDataType, SameSubscriptionRecurringDataType, SubscriptionPlanNotFoundDataType, CopilotDocNotFoundDataType, CopilotMessageNotFoundDataType, CopilotPromptNotFoundDataType, CopilotProviderSideErrorDataType, CopilotInvalidContextDataType, CopilotContextFileNotSupportedDataType, CopilotFailedToModifyContextDataType, CopilotFailedToMatchContextDataType, RuntimeConfigNotFoundDataType, InvalidRuntimeConfigTypeDataType, InvalidLicenseUpdateParamsDataType, WorkspaceMembersExceedLimitToDowngradeDataType, UnsupportedClientVersionDataType, MentionUserDocAccessDeniedDataType] as const, [GraphqlBadRequestDataType, HttpRequestErrorDataType, QueryTooLongDataType, ValidationErrorDataType, WrongSignInCredentialsDataType, UnknownOauthProviderDataType, InvalidOauthCallbackCodeDataType, MissingOauthQueryParameterDataType, InvalidEmailDataType, InvalidPasswordLengthDataType, WorkspacePermissionNotFoundDataType, SpaceNotFoundDataType, MemberNotFoundInSpaceDataType, NotInSpaceDataType, AlreadyInSpaceDataType, SpaceAccessDeniedDataType, SpaceOwnerNotFoundDataType, SpaceShouldHaveOnlyOneOwnerDataType, DocNotFoundDataType, DocActionDeniedDataType, DocUpdateBlockedDataType, VersionRejectedDataType, InvalidHistoryTimestampDataType, DocHistoryNotFoundDataType, BlobNotFoundDataType, ExpectToGrantDocUserRolesDataType, ExpectToRevokeDocUserRolesDataType, ExpectToUpdateDocUserRoleDataType, UnsupportedSubscriptionPlanDataType, SubscriptionAlreadyExistsDataType, SubscriptionNotExistsDataType, SameSubscriptionRecurringDataType, SubscriptionPlanNotFoundDataType, CopilotDocNotFoundDataType, CopilotMessageNotFoundDataType, CopilotPromptNotFoundDataType, CopilotProviderSideErrorDataType, CopilotInvalidContextDataType, CopilotContextFileNotSupportedDataType, CopilotFailedToModifyContextDataType, CopilotFailedToMatchContextDataType, RuntimeConfigNotFoundDataType, InvalidRuntimeConfigTypeDataType, InvalidLicenseUpdateParamsDataType, WorkspaceMembersExceedLimitToDowngradeDataType, UnsupportedClientVersionDataType, MentionUserDocAccessDeniedDataType] as const,
}); });

View File

@@ -11,12 +11,14 @@ import { ThrottlerException } from '@nestjs/throttler';
import { BaseWsExceptionFilter } from '@nestjs/websockets'; import { BaseWsExceptionFilter } from '@nestjs/websockets';
import { Response } from 'express'; import { Response } from 'express';
import { GraphQLError } from 'graphql'; import { GraphQLError } from 'graphql';
import { HttpError } from 'http-errors';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { Socket } from 'socket.io'; import { Socket } from 'socket.io';
import { ZodError } from 'zod'; import { ZodError } from 'zod';
import { import {
GraphqlBadRequest, GraphqlBadRequest,
HttpRequestError,
InternalServerError, InternalServerError,
NotFound, NotFound,
TooManyRequest, TooManyRequest,
@@ -57,6 +59,16 @@ export function mapAnyError(error: any): UserFriendlyError {
return new ValidationError({ return new ValidationError({
errors: error.message, errors: error.message,
}); });
} else if (
error instanceof HttpError &&
error.status >= 400 &&
error.status < 500
) {
const e = new HttpRequestError({
message: error.message,
});
e.status = error.status;
return e;
} else { } else {
const e = new InternalServerError(); const e = new InternalServerError();
e.cause = error; e.cause = error;

View File

@@ -376,7 +376,7 @@ type EditorType {
name: String! name: String!
} }
union ErrorDataUnion = AlreadyInSpaceDataType | BlobNotFoundDataType | CopilotContextFileNotSupportedDataType | CopilotDocNotFoundDataType | CopilotFailedToMatchContextDataType | CopilotFailedToModifyContextDataType | CopilotInvalidContextDataType | CopilotMessageNotFoundDataType | CopilotPromptNotFoundDataType | CopilotProviderSideErrorDataType | DocActionDeniedDataType | DocHistoryNotFoundDataType | DocNotFoundDataType | DocUpdateBlockedDataType | ExpectToGrantDocUserRolesDataType | ExpectToRevokeDocUserRolesDataType | ExpectToUpdateDocUserRoleDataType | GraphqlBadRequestDataType | InvalidEmailDataType | InvalidHistoryTimestampDataType | InvalidLicenseUpdateParamsDataType | InvalidOauthCallbackCodeDataType | InvalidPasswordLengthDataType | InvalidRuntimeConfigTypeDataType | MemberNotFoundInSpaceDataType | MentionUserDocAccessDeniedDataType | MissingOauthQueryParameterDataType | NotInSpaceDataType | QueryTooLongDataType | RuntimeConfigNotFoundDataType | SameSubscriptionRecurringDataType | SpaceAccessDeniedDataType | SpaceNotFoundDataType | SpaceOwnerNotFoundDataType | SpaceShouldHaveOnlyOneOwnerDataType | SubscriptionAlreadyExistsDataType | SubscriptionNotExistsDataType | SubscriptionPlanNotFoundDataType | UnknownOauthProviderDataType | UnsupportedClientVersionDataType | UnsupportedSubscriptionPlanDataType | ValidationErrorDataType | VersionRejectedDataType | WorkspaceMembersExceedLimitToDowngradeDataType | WorkspacePermissionNotFoundDataType | WrongSignInCredentialsDataType union ErrorDataUnion = AlreadyInSpaceDataType | BlobNotFoundDataType | CopilotContextFileNotSupportedDataType | CopilotDocNotFoundDataType | CopilotFailedToMatchContextDataType | CopilotFailedToModifyContextDataType | CopilotInvalidContextDataType | CopilotMessageNotFoundDataType | CopilotPromptNotFoundDataType | CopilotProviderSideErrorDataType | DocActionDeniedDataType | DocHistoryNotFoundDataType | DocNotFoundDataType | DocUpdateBlockedDataType | ExpectToGrantDocUserRolesDataType | ExpectToRevokeDocUserRolesDataType | ExpectToUpdateDocUserRoleDataType | GraphqlBadRequestDataType | HttpRequestErrorDataType | InvalidEmailDataType | InvalidHistoryTimestampDataType | InvalidLicenseUpdateParamsDataType | InvalidOauthCallbackCodeDataType | InvalidPasswordLengthDataType | InvalidRuntimeConfigTypeDataType | MemberNotFoundInSpaceDataType | MentionUserDocAccessDeniedDataType | MissingOauthQueryParameterDataType | NotInSpaceDataType | QueryTooLongDataType | RuntimeConfigNotFoundDataType | SameSubscriptionRecurringDataType | SpaceAccessDeniedDataType | SpaceNotFoundDataType | SpaceOwnerNotFoundDataType | SpaceShouldHaveOnlyOneOwnerDataType | SubscriptionAlreadyExistsDataType | SubscriptionNotExistsDataType | SubscriptionPlanNotFoundDataType | UnknownOauthProviderDataType | UnsupportedClientVersionDataType | UnsupportedSubscriptionPlanDataType | ValidationErrorDataType | VersionRejectedDataType | WorkspaceMembersExceedLimitToDowngradeDataType | WorkspacePermissionNotFoundDataType | WrongSignInCredentialsDataType
enum ErrorNames { enum ErrorNames {
ACCESS_DENIED ACCESS_DENIED
@@ -429,6 +429,7 @@ enum ErrorNames {
FAILED_TO_SAVE_UPDATES FAILED_TO_SAVE_UPDATES
FAILED_TO_UPSERT_SNAPSHOT FAILED_TO_UPSERT_SNAPSHOT
GRAPHQL_BAD_REQUEST GRAPHQL_BAD_REQUEST
HTTP_REQUEST_ERROR
INTERNAL_SERVER_ERROR INTERNAL_SERVER_ERROR
INVALID_AUTH_STATE INVALID_AUTH_STATE
INVALID_CHECKOUT_PARAMETERS INVALID_CHECKOUT_PARAMETERS
@@ -554,6 +555,10 @@ type GraphqlBadRequestDataType {
message: String! message: String!
} }
type HttpRequestErrorDataType {
message: String!
}
input ImportUsersInput { input ImportUsersInput {
users: [CreateUserInput!]! users: [CreateUserInput!]!
} }

View File

@@ -489,6 +489,7 @@ export type ErrorDataUnion =
| ExpectToRevokeDocUserRolesDataType | ExpectToRevokeDocUserRolesDataType
| ExpectToUpdateDocUserRoleDataType | ExpectToUpdateDocUserRoleDataType
| GraphqlBadRequestDataType | GraphqlBadRequestDataType
| HttpRequestErrorDataType
| InvalidEmailDataType | InvalidEmailDataType
| InvalidHistoryTimestampDataType | InvalidHistoryTimestampDataType
| InvalidLicenseUpdateParamsDataType | InvalidLicenseUpdateParamsDataType
@@ -569,6 +570,7 @@ export enum ErrorNames {
FAILED_TO_SAVE_UPDATES = 'FAILED_TO_SAVE_UPDATES', FAILED_TO_SAVE_UPDATES = 'FAILED_TO_SAVE_UPDATES',
FAILED_TO_UPSERT_SNAPSHOT = 'FAILED_TO_UPSERT_SNAPSHOT', FAILED_TO_UPSERT_SNAPSHOT = 'FAILED_TO_UPSERT_SNAPSHOT',
GRAPHQL_BAD_REQUEST = 'GRAPHQL_BAD_REQUEST', GRAPHQL_BAD_REQUEST = 'GRAPHQL_BAD_REQUEST',
HTTP_REQUEST_ERROR = 'HTTP_REQUEST_ERROR',
INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR', INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR',
INVALID_AUTH_STATE = 'INVALID_AUTH_STATE', INVALID_AUTH_STATE = 'INVALID_AUTH_STATE',
INVALID_CHECKOUT_PARAMETERS = 'INVALID_CHECKOUT_PARAMETERS', INVALID_CHECKOUT_PARAMETERS = 'INVALID_CHECKOUT_PARAMETERS',
@@ -697,6 +699,11 @@ export interface GraphqlBadRequestDataType {
message: Scalars['String']['output']; message: Scalars['String']['output'];
} }
export interface HttpRequestErrorDataType {
__typename?: 'HttpRequestErrorDataType';
message: Scalars['String']['output'];
}
export interface ImportUsersInput { export interface ImportUsersInput {
users: Array<CreateUserInput>; users: Array<CreateUserInput>;
} }

View File

@@ -2,16 +2,16 @@
"ar": 96, "ar": 96,
"ca": 4, "ca": 4,
"da": 5, "da": 5,
"de": 97, "de": 96,
"el-GR": 96, "el-GR": 96,
"en": 100, "en": 100,
"es-AR": 97, "es-AR": 96,
"es-CL": 98, "es-CL": 98,
"es": 96, "es": 96,
"fa": 96, "fa": 96,
"fr": 96, "fr": 96,
"hi": 2, "hi": 2,
"it-IT": 97, "it-IT": 96,
"it": 1, "it": 1,
"ja": 96, "ja": 96,
"ko": 61, "ko": 61,

View File

@@ -7282,6 +7282,12 @@ export function useAFFiNEI18N(): {
code: string; code: string;
message: string; message: string;
}>): string; }>): string;
/**
* `HTTP request error, message: {{message}}`
*/
["error.HTTP_REQUEST_ERROR"](options: {
readonly message: string;
}): string;
/** /**
* `Query is too long, max length is {{max}}.` * `Query is too long, max length is {{max}}.`
*/ */

View File

@@ -1813,6 +1813,7 @@
"error.NOT_FOUND": "Resource not found.", "error.NOT_FOUND": "Resource not found.",
"error.BAD_REQUEST": "Bad request.", "error.BAD_REQUEST": "Bad request.",
"error.GRAPHQL_BAD_REQUEST": "GraphQL bad request, code: {{code}}, {{message}}", "error.GRAPHQL_BAD_REQUEST": "GraphQL bad request, code: {{code}}, {{message}}",
"error.HTTP_REQUEST_ERROR": "HTTP request error, message: {{message}}",
"error.QUERY_TOO_LONG": "Query is too long, max length is {{max}}.", "error.QUERY_TOO_LONG": "Query is too long, max length is {{max}}.",
"error.VALIDATION_ERROR": "Validation error, errors: {{errors}}", "error.VALIDATION_ERROR": "Validation error, errors: {{errors}}",
"error.USER_NOT_FOUND": "User not found.", "error.USER_NOT_FOUND": "User not found.",

View File

@@ -892,6 +892,7 @@ __metadata:
"@types/cookie-parser": "npm:^1.4.8" "@types/cookie-parser": "npm:^1.4.8"
"@types/express": "npm:^4.17.21" "@types/express": "npm:^4.17.21"
"@types/graphql-upload": "npm:^17.0.0" "@types/graphql-upload": "npm:^17.0.0"
"@types/http-errors": "npm:^2.0.4"
"@types/lodash-es": "npm:^4.17.12" "@types/lodash-es": "npm:^4.17.12"
"@types/mixpanel": "npm:^2.14.9" "@types/mixpanel": "npm:^2.14.9"
"@types/mustache": "npm:^4.2.5" "@types/mustache": "npm:^4.2.5"
@@ -921,6 +922,7 @@ __metadata:
graphql-upload: "npm:^17.0.0" graphql-upload: "npm:^17.0.0"
html-validate: "npm:^9.0.0" html-validate: "npm:^9.0.0"
htmlrewriter: "npm:^0.0.12" htmlrewriter: "npm:^0.0.12"
http-errors: "npm:^2.0.0"
ioredis: "npm:^5.4.1" ioredis: "npm:^5.4.1"
is-mobile: "npm:^5.0.0" is-mobile: "npm:^5.0.0"
keyv: "npm:^5.2.2" keyv: "npm:^5.2.2"
@@ -14347,7 +14349,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/http-errors@npm:*": "@types/http-errors@npm:*, @types/http-errors@npm:^2.0.4":
version: 2.0.4 version: 2.0.4
resolution: "@types/http-errors@npm:2.0.4" resolution: "@types/http-errors@npm:2.0.4"
checksum: 10/1f3d7c3b32c7524811a45690881736b3ef741bf9849ae03d32ad1ab7062608454b150a4e7f1351f83d26a418b2d65af9bdc06198f1c079d75578282884c4e8e3 checksum: 10/1f3d7c3b32c7524811a45690881736b3ef741bf9849ae03d32ad1ab7062608454b150a4e7f1351f83d26a418b2d65af9bdc06198f1c079d75578282884c4e8e3