refactor: Standardize error handling for RESTful APIs

This commit is contained in:
Peng-YM 2022-07-05 17:04:58 +08:00
parent 0e089ef8ce
commit 00c28c6cb8
10 changed files with 123 additions and 68 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{
"name": "sub-store",
"version": "2.6.0",
"version": "2.6.1",
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
"main": "src/main.js",
"scripts": {

View File

@ -1,7 +1,8 @@
import { deleteByName, findByName, updateByName } from '@/utils/database';
import { COLLECTIONS_KEY } from '@/constants';
import { success } from '@/restful/response';
import { failed, success } from '@/restful/response';
import $ from '@/core/app';
import { RequestInvalidError, ResourceNotFoundError } from '@/restful/errors';
export default function register($app) {
if (!$.read(COLLECTIONS_KEY)) $.write({}, COLLECTIONS_KEY);
@ -22,10 +23,13 @@ function createCollection(req, res) {
$.info(`正在创建组合订阅:${collection.name}`);
const allCols = $.read(COLLECTIONS_KEY);
if (findByName(allCols, collection.name)) {
res.status(500).json({
status: 'failed',
message: `订阅集${collection.name}已存在!`,
});
failed(
res,
new RequestInvalidError(
'DUPLICATE_KEY',
`Collection ${collection.name} already exists.`,
),
);
}
allCols.push(collection);
$.write(allCols, COLLECTIONS_KEY);
@ -40,10 +44,14 @@ function getCollection(req, res) {
if (collection) {
success(res, collection);
} else {
res.status(404).json({
status: 'failed',
message: `未找到订阅集:${name}!`,
});
failed(
res,
new ResourceNotFoundError(
`SUBSCRIPTION_NOT_FOUND`,
`Collection ${name} does not exist`,
404,
),
);
}
}
@ -63,10 +71,14 @@ function updateCollection(req, res) {
$.write(allCols, COLLECTIONS_KEY);
success(res, newCol);
} else {
res.status(500).json({
status: 'failed',
message: `订阅集${name}不存在,无法更新!`,
});
failed(
res,
new ResourceNotFoundError(
'RESOURCE_NOT_FOUND',
`Collection ${name} does not exist!`,
),
404,
);
}
}

View File

@ -4,6 +4,8 @@ import { findByName } from '@/utils/database';
import { getFlowHeaders } from '@/utils/flow';
import { produceArtifact } from './artifacts';
import $ from '@/core/app';
import { failed } from '@/restful/response';
import { InternalServerError, ResourceNotFoundError } from '@/restful/errors';
export default function register($app) {
$app.get('/download/collection/:name', downloadCollection);
@ -51,16 +53,25 @@ async function downloadSubscription(req, res) {
`🤔 原因:${JSON.stringify(err)}`,
);
$.error(JSON.stringify(err));
res.status(500).json({
status: 'failed',
message: err,
});
failed(
res,
new InternalServerError(
'INTERNAL_SERVER_ERROR',
`Failed to download subscription: ${name}`,
`Reason: ${JSON.stringify(err)}`,
),
);
}
} else {
$.notify(`🌍 『 𝑺𝒖𝒃-𝑺𝒕𝒐𝒓𝒆 』 下载订阅失败`, `❌ 未找到订阅:${name}`);
res.status(404).json({
status: 'failed',
});
failed(
res,
new ResourceNotFoundError(
'RESOURCE_NOT_FOUND',
`Subscription ${name} does not exist!`,
),
404,
);
}
}
@ -110,18 +121,27 @@ async function downloadCollection(req, res) {
`❌ 下载组合订阅错误:${name}`,
`🤔 原因:${err}`,
);
res.status(500).json({
status: 'failed',
message: err,
});
failed(
res,
new InternalServerError(
'INTERNAL_SERVER_ERROR',
`Failed to download collection: ${name}`,
`Reason: ${JSON.stringify(err)}`,
),
);
}
} else {
$.notify(
`🌍 『 𝑺𝒖𝒃-𝑺𝒕𝒐𝒓𝒆 』 下载组合订阅失败`,
`❌ 未找到组合订阅:${name}`,
);
res.status(404).json({
status: 'failed',
});
failed(
res,
new ResourceNotFoundError(
'RESOURCE_NOT_FOUND',
`Collection ${name} does not exist!`,
),
404,
);
}
}

View File

@ -12,6 +12,13 @@ export class InternalServerError extends BaseError {
}
}
export class RequestInvalidError extends BaseError {
constructor(code, message, details) {
super(code, message, details);
this.type = 'RequestInvalidError';
}
}
export class ResourceNotFoundError extends BaseError {
constructor(code, message, details) {
super(code, message, details);

View File

@ -16,7 +16,8 @@ import registerDownloadRoutes from './download';
import registerSettingRoutes from './settings';
import registerPreviewRoutes from './preview';
import registerSortingRoutes from './sort';
import { success } from '@/restful/response';
import { failed, success } from '@/restful/response';
import { InternalServerError, RequestInvalidError } from '@/restful/errors';
export default function serve() {
const $app = express({ substore: $ });
@ -76,12 +77,9 @@ function getEnv(req, res) {
if (isStash) backend = 'Stash';
if (isShadowRocket) backend = 'ShadowRocket';
res.json({
status: 200,
data: {
backend,
version: substoreVersion,
},
success(res, {
backend,
version: substoreVersion,
});
}
@ -90,10 +88,13 @@ async function gistBackup(req, res) {
// read token
const { gistToken } = $.read(SETTINGS_KEY);
if (!gistToken) {
res.status(500).json({
status: 'failed',
message: '未找到Gist备份Token!',
});
failed(
res,
new RequestInvalidError(
'GIST_TOKEN_NOT_FOUND',
`GitHub Token is required for backup!`,
),
);
} else {
const gist = new Gist({
token: gistToken,
@ -127,14 +128,14 @@ async function gistBackup(req, res) {
}
success(res);
} catch (err) {
const msg = `${
action === 'upload' ? '上传' : '下载'
}备份失败${err}`;
$.error(msg);
res.status(500).json({
status: 'failed',
message: msg,
});
failed(
res,
new InternalServerError(
'BACKUP_FAILED',
`Failed to ${action} data to gist!`,
`Reason: ${JSON.stringify(err)}`,
),
);
}
}
}

View File

@ -2,6 +2,7 @@ import {
NetworkError,
InternalServerError,
ResourceNotFoundError,
RequestInvalidError,
} from './errors';
import { deleteByName, findByName, updateByName } from '@/utils/database';
import { SUBS_KEY, COLLECTIONS_KEY } from '@/constants';
@ -40,7 +41,7 @@ async function getFlowInfo(req, res) {
return;
}
if (sub.source === 'local') {
failed(res, new InternalServerError('NO_FLOW_INFO', 'N/A'));
failed(res, new RequestInvalidError('NO_FLOW_INFO', 'N/A'));
return;
}
try {
@ -76,10 +77,13 @@ function createSubscription(req, res) {
$.info(`正在创建订阅: ${sub.name}`);
const allSubs = $.read(SUBS_KEY);
if (findByName(allSubs, sub.name)) {
res.status(500).json({
status: 'failed',
message: `订阅${sub.name}已存在!`,
});
failed(
res,
new RequestInvalidError(
'DUPLICATE_KEY',
`Subscription ${sub.name} already exists.`,
),
);
}
allSubs.push(sub);
$.write(allSubs, SUBS_KEY);
@ -98,6 +102,14 @@ function getSubscription(req, res) {
status: 'failed',
message: `未找到订阅:${name}!`,
});
failed(
res,
new ResourceNotFoundError(
`SUBSCRIPTION_NOT_FOUND`,
`Subscription ${name} does not exist`,
404,
),
);
}
}
@ -128,10 +140,14 @@ function updateSubscription(req, res) {
$.write(allSubs, SUBS_KEY);
success(res, newSub);
} else {
res.status(500).json({
status: 'failed',
message: `订阅${name}不存在,无法更新!`,
});
failed(
res,
new ResourceNotFoundError(
'RESOURCE_NOT_FOUND',
`Subscription ${name} does not exist!`,
),
404,
);
}
}

View File

@ -75,14 +75,13 @@ function doMigrationV2() {
tfo: 'DEFAULT',
scert: 'DEFAULT',
'vmess aead': 'DEFAULT',
'useless': 'DEFAULT',
useless: 'DEFAULT',
},
};
processes.forEach((p) => {
if (p.type === 'Useless Filter') {
quickSettingOperator.args.useless = 'ENABLED';
}
else if (p.type === 'Set Property Operator') {
} else if (p.type === 'Set Property Operator') {
const { key, value } = p.args;
switch (key) {
case 'udp':

File diff suppressed because one or more lines are too long