From 389004d652434f0a96a26e78a2751d3bdf6259ca Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Wed, 28 Oct 2026 23:40:53 +0000 Subject: [PATCH] Replace RequestError with the HTTP-style error classes clusterio#999 removed RequestError. Handlers now throw NotFoundError, ValidationError, ConflictError or UnavailableError, and exp_auto_restart treats any ClientError from the host as a final refusal. Co-Authored-By: Claude Opus 5.5 --- exp_auto_restart/controller.ts | 2 +- exp_auto_restart/test/controller.test.js | 2 +- exp_discord_auth/controller.ts | 4 ++-- exp_groups/controller.ts | 22 +++++++++++----------- exp_reports/controller.ts | 20 ++++++++++---------- exp_reports/test/instance.test.js | 4 ++-- exp_roles/controller.ts | 6 +++--- exp_scenario/controller.ts | 4 ++-- 8 files changed, 32 insertions(+), 32 deletions(-) diff --git a/exp_auto_restart/controller.ts b/exp_auto_restart/controller.ts index 5695c293..7d060bd4 100644 --- a/exp_auto_restart/controller.ts +++ b/exp_auto_restart/controller.ts @@ -197,7 +197,7 @@ export class AutoRestart { } catch (err: any) { this.pending.delete(host.id); // The host refusing is final for this process, anything else is retried - if (!(err instanceof lib.RequestError)) { + if (!(err instanceof lib.ClientError)) { this.requested.delete(host.id); } this.logger.error(`Failed to restart host ${host.name}: ${err.message}`); diff --git a/exp_auto_restart/test/controller.test.js b/exp_auto_restart/test/controller.test.js index 05ec45e9..c2eb13da 100644 --- a/exp_auto_restart/test/controller.test.js +++ b/exp_auto_restart/test/controller.test.js @@ -219,7 +219,7 @@ t.test("class AutoRestart", t2 => { addInstance(controller, 10, 1); addSystem(controller, 1); - controller.sendTo = async () => { throw new lib.RequestError("Cannot restart"); }; + controller.sendTo = async () => { throw new lib.ConflictError("Cannot restart"); }; await plugin.check(now); await plugin.check(now + 10_000); t3.strictSame(logs.filter(([level]) => level === "error").length, 1, "a refusal is logged once"); diff --git a/exp_discord_auth/controller.ts b/exp_discord_auth/controller.ts index d7921775..b66ee2be 100644 --- a/exp_discord_auth/controller.ts +++ b/exp_discord_auth/controller.ts @@ -385,7 +385,7 @@ export class ControllerPlugin { const entry = this.codes.get(code); const session = entry ? this.sessions.get(entry.sessionId) : undefined; if (!entry || !session || entry.expiresMs < Date.now()) { - throw new lib.RequestError( + throw new lib.ValidationError( `This code is invalid or has expired, get a new one from ${this.controller.getControllerUrl()}` ); } @@ -403,7 +403,7 @@ export class ControllerPlugin { const alreadyLinked = existing?.factorioNames.some(linked => linked.toLowerCase() === name); const maxAccounts = this.controller.config.get("exp_discord_auth.max_accounts"); if (!alreadyLinked && (existing?.factorioNames.length ?? 0) >= maxAccounts) { - throw new lib.RequestError( + throw new lib.ConflictError( `${discordUser.name} already has ${maxAccounts} Factorio accounts linked, unlink one first` ); } diff --git a/exp_groups/controller.ts b/exp_groups/controller.ts index ba5eb822..8d31f9a4 100644 --- a/exp_groups/controller.ts +++ b/exp_groups/controller.ts @@ -233,7 +233,7 @@ export class ControllerPlugin { async handleGroupCreateRequest(request: messages.GroupCreateRequest) { if ([...this.groups.values()].some(g => g.name === request.name)) { - throw new lib.RequestError(`Group with name '${request.name}' already exists`); + throw new lib.ConflictError(`Group with name '${request.name}' already exists`); } let id = Math.random() * 2**31 | 0; @@ -249,7 +249,7 @@ export class ControllerPlugin { async handleGroupUpdateRequest(request: messages.GroupUpdateRequest) { const group = request.group; if (group.id === undefined || !this.groups.has(group.id)) { - throw new lib.RequestError(`Group with ID ${group.id} does not exist`); + throw new lib.NotFoundError(`Group with ID ${group.id} does not exist`); } this.groups.set(group); @@ -260,7 +260,7 @@ export class ControllerPlugin { const group = this.groups.getMutable(groupId); if (!group) { - throw new lib.RequestError(`Group with ID ${groupId} does not exist`); + throw new lib.NotFoundError(`Group with ID ${groupId} does not exist`); } this.groups.delete(group); @@ -269,7 +269,7 @@ export class ControllerPlugin { async handleGroupGetRequest(request: messages.GroupGetRequest) { const group = this.groups.get(request.groupId); if (!group) { - throw new lib.RequestError(`Group with ID ${request.groupId} does not exist`); + throw new lib.NotFoundError(`Group with ID ${request.groupId} does not exist`); } return group; @@ -286,7 +286,7 @@ export class ControllerPlugin { async handleAssignmentCreateRequest(request: messages.AssignmentCreateRequest) { const { name, groupId } = request; if (this.manualAssignments.has(name)) { - throw new lib.RequestError(`Assignment for '${name}' already exists`); + throw new lib.ConflictError(`Assignment for '${name}' already exists`); } const assignment = new messages.AssignmentRecord(name, groupId); @@ -297,7 +297,7 @@ export class ControllerPlugin { async handleAssignmentUpdateRequest(request: messages.AssignmentUpdateRequest) { const assignment = request.assignment; if (!this.manualAssignments.has(assignment.name)) { - throw new lib.RequestError(`Assignment for '${assignment.name}' does not exist`); + throw new lib.NotFoundError(`Assignment for '${assignment.name}' does not exist`); } this.manualAssignments.set(assignment); @@ -308,7 +308,7 @@ export class ControllerPlugin { const assignment = this.manualAssignments.getMutable(name); if (!assignment) { - throw new lib.RequestError(`Assignment for '${name}' does not exist`); + throw new lib.NotFoundError(`Assignment for '${name}' does not exist`); } this.manualAssignments.delete(assignment); @@ -327,7 +327,7 @@ export class ControllerPlugin { const assignment = this.manualAssignments.get(request.name); if (!assignment) { - throw new lib.RequestError(`Assignment for '${request.name}' does not exist`); + throw new lib.NotFoundError(`Assignment for '${request.name}' does not exist`); } return assignment; @@ -364,7 +364,7 @@ export class ControllerPlugin { async handleRoleMappingUpdateRequest(request: messages.RoleMappingUpdateRequest) { const roleMapping = request.roleMapping; if (roleMapping.id === undefined || !this.roleMappings.has(roleMapping.id)) { - throw new lib.RequestError(`Role mapping with ID ${roleMapping.id} does not exist`); + throw new lib.NotFoundError(`Role mapping with ID ${roleMapping.id} does not exist`); } const existing = new Set( @@ -386,7 +386,7 @@ export class ControllerPlugin { const mapping = this.roleMappings.getMutable(id); if (!mapping) { - throw new lib.RequestError(`Role mapping with ID ${id} does not exist`); + throw new lib.NotFoundError(`Role mapping with ID ${id} does not exist`); } this.roleMappings.delete(mapping); @@ -395,7 +395,7 @@ export class ControllerPlugin { async handleRoleMappingGetRequest(request: messages.RoleMappingGetRequest) { const mapping = this.roleMappings.get(request.id); if (!mapping) { - throw new lib.RequestError(`Role mapping with ID ${request.id} does not exist`); + throw new lib.NotFoundError(`Role mapping with ID ${request.id} does not exist`); } return mapping; diff --git a/exp_reports/controller.ts b/exp_reports/controller.ts index c33d57c6..542828d2 100644 --- a/exp_reports/controller.ts +++ b/exp_reports/controller.ts @@ -158,7 +158,7 @@ export default async function (context: ControllerPluginContext) { controller.handle(messages.ReportGetRequest, async (request: messages.ReportGetRequest) => { const report = reports.get(request.id); if (!report) { - throw new lib.RequestError(`Report with ID ${request.id} does not exist`); + throw new lib.NotFoundError(`Report with ID ${request.id} does not exist`); } return report; }); @@ -174,13 +174,13 @@ export default async function (context: ControllerPluginContext) { } if (!byPlayerName) { - throw new lib.RequestError("A report needs the name of the player making it"); + throw new lib.ValidationError("A report needs the name of the player making it"); } if (!request.reason.trim()) { - throw new lib.RequestError("A report needs a reason"); + throw new lib.ValidationError("A report needs a reason"); } if (listReports(request.playerName).some(report => report.byPlayerName === byPlayerName)) { - throw new lib.RequestError(`${byPlayerName} has already reported ${request.playerName}`); + throw new lib.ConflictError(`${byPlayerName} has already reported ${request.playerName}`); } let id = Math.random() * 2 ** 31 | 0; @@ -199,7 +199,7 @@ export default async function (context: ControllerPluginContext) { controller.handle(messages.ReportDeleteRequest, async (request: messages.ReportDeleteRequest) => { const report = reports.getMutable(request.id); if (!report) { - throw new lib.RequestError(`Report with ID ${request.id} does not exist`); + throw new lib.NotFoundError(`Report with ID ${request.id} does not exist`); } reports.delete(report); }); @@ -207,7 +207,7 @@ export default async function (context: ControllerPluginContext) { controller.handle(messages.ReportSetReadRequest, async (request: messages.ReportSetReadRequest) => { const report = reports.getMutable(request.id); if (!report) { - throw new lib.RequestError(`Report with ID ${request.id} does not exist`); + throw new lib.NotFoundError(`Report with ID ${request.id} does not exist`); } report.isRead = request.isRead; reports.set(report); @@ -216,14 +216,14 @@ export default async function (context: ControllerPluginContext) { controller.handle(messages.WatchListAddRequest, async (request: messages.WatchListAddRequest, src: lib.Address) => { const playerName = request.playerName.trim(); if (!playerName) { - throw new lib.RequestError("A player name is required"); + throw new lib.ValidationError("A player name is required"); } if (!request.reason.trim()) { - throw new lib.RequestError("A reason is required"); + throw new lib.ValidationError("A reason is required"); } const existing = watchList.get(playerName.toLowerCase()); if (existing) { - throw new lib.RequestError(`${existing.playerName} is already on the watch list`); + throw new lib.ConflictError(`${existing.playerName} is already on the watch list`); } const entry = new messages.WatchListRecord(playerName, request.reason.trim(), controlUserName(src), Date.now()); @@ -234,7 +234,7 @@ export default async function (context: ControllerPluginContext) { controller.handle(messages.WatchListRemoveRequest, async (request: messages.WatchListRemoveRequest) => { const entry = watchList.getMutable(request.playerName.toLowerCase()); if (!entry) { - throw new lib.RequestError(`${request.playerName} is not on the watch list`); + throw new lib.NotFoundError(`${request.playerName} is not on the watch list`); } watchList.delete(entry); }); diff --git a/exp_reports/test/instance.test.js b/exp_reports/test/instance.test.js index 576e2e9a..b23742f0 100644 --- a/exp_reports/test/instance.test.js +++ b/exp_reports/test/instance.test.js @@ -102,7 +102,7 @@ t.test("instance plugin", t2 => { t2.test("exp_reports:create prints a refusal to the reporter", async t3 => { const { state, createIPC } = await startPlugin(t3); - state.failNext = new lib.RequestError("dave has already reported bob"); + state.failNext = new lib.ConflictError("dave has already reported bob"); await createIPC({ player_name: "bob", by_player_name: "dave", reason: "griefing" }); const { receiver, payload } = decodeRcon(state.rcons[0]); @@ -113,7 +113,7 @@ t.test("instance plugin", t2 => { t2.test("text that closes a long bracket stays inside the json", async t3 => { const { state, createIPC } = await startPlugin(t3); const message = "]=]) game.print(1) --[==[ ]==] ]===]"; - state.failNext = new lib.RequestError(message); + state.failNext = new lib.ConflictError(message); await createIPC({ player_name: "bob", by_player_name: "dave", reason: message }); const { payload } = decodeRcon(state.rcons[0]); diff --git a/exp_roles/controller.ts b/exp_roles/controller.ts index bb14fc38..e1fc3817 100644 --- a/exp_roles/controller.ts +++ b/exp_roles/controller.ts @@ -147,7 +147,7 @@ export class ControllerPlugin { async handleRoleMetaUpdateRequest(request: messages.RoleMetaUpdateRequest) { const meta = request.meta; if (!this.controller.roles.has(meta.id)) { - throw new lib.RequestError(`Role with ID ${meta.id} does not exist`); + throw new lib.NotFoundError(`Role with ID ${meta.id} does not exist`); } this.roleMeta.set(meta); @@ -248,12 +248,12 @@ export class ControllerPlugin { async handleAssignmentUpdateRequest(request: messages.AssignmentUpdateRequest) { const user = this.controller.users.getByNameMutable(request.name); if (!user) { - throw new lib.RequestError(`User '${request.name}' does not exist`); + throw new lib.NotFoundError(`User '${request.name}' does not exist`); } for (const roleId of [...request.assign, ...request.unassign]) { if (!this.controller.roles.has(roleId)) { - throw new lib.RequestError(`Role with ID ${roleId} does not exist`); + throw new lib.NotFoundError(`Role with ID ${roleId} does not exist`); } } diff --git a/exp_scenario/controller.ts b/exp_scenario/controller.ts index f31dea35..618629a9 100644 --- a/exp_scenario/controller.ts +++ b/exp_scenario/controller.ts @@ -175,7 +175,7 @@ export default async function (context: ControllerPluginContext) { const rolesPlugin = RolesPlugin.get(controller); const groupsPlugin = GroupsPlugin.get(controller); if (!rolesPlugin || !groupsPlugin) { - throw new lib.RequestError("Seeding requires the exp_roles and exp_groups plugins"); + throw new lib.UnavailableError("Seeding requires the exp_roles and exp_groups plugins"); } const roleIds = new Map(); @@ -214,7 +214,7 @@ export default async function (context: ControllerPluginContext) { try { values = validateFeatureValues(request.id, request.values); } catch (err: any) { - throw new lib.RequestError(err.message); + throw new lib.ValidationError(err.message); } const feature = new messages.FeatureRecord(request.id, request.enabled, values);