Fix Swiss generate match ups internal server error by introducing neverthrow

This commit is contained in:
Kalle 2025-07-19 13:54:29 +03:00
parent fa60d0d03f
commit b2ff16426d
6 changed files with 39 additions and 11 deletions

View File

@ -8,6 +8,7 @@ import * as TournamentRepository from "~/features/tournament/TournamentRepositor
import * as Progression from "~/features/tournament-bracket/core/Progression";
import invariant from "~/utils/invariant";
import {
errorToastIfErr,
errorToastIfFalsy,
parseParams,
parseRequestPayload,
@ -180,17 +181,15 @@ export const action: ActionFunction = async ({ params, request }) => {
const bracket = tournament.bracketByIdx(data.bracketIdx);
errorToastIfFalsy(bracket, "Bracket not found");
errorToastIfFalsy(
bracket.type === "swiss",
"Can't advance non-swiss bracket",
);
const matches = Swiss.generateMatchUps({
bracket,
groupId: data.groupId,
});
await TournamentRepository.insertSwissMatches(matches);
errorToastIfErr(matches);
await TournamentRepository.insertSwissMatches(matches.value);
break;
}

View File

@ -113,7 +113,7 @@ describe("Swiss", () => {
const matches = Swiss.generateMatchUps({
bracket,
groupId: 4443,
});
})._unsafeUnwrap();
it("finds new opponents for each team in the last round", () => {
for (const match of matches) {

View File

@ -1,6 +1,7 @@
// separate from brackets-manager as this wasn't part of the original brackets-manager library
import blossom from "edmonds-blossom-fixed";
import { err, ok } from "neverthrow";
import * as R from "remeda";
import type { TournamentRepositoryInsertableMatch } from "~/features/tournament/TournamentRepository.server";
import { TOURNAMENT } from "~/features/tournament/tournament-constants";
@ -173,18 +174,19 @@ export function generateMatchUps({
}: {
bracket: Bracket;
groupId: number;
}): Array<TournamentRepositoryInsertableMatch> {
}) {
// lets consider only this groups matches
// in the case that there are more than one group
const groupsMatches = bracket.data.match.filter(
(m) => m.group_id === groupId,
);
invariant(groupsMatches.length > 0, "No matches found for group");
if (groupsMatches.length === 0) return err("No matches found for group");
if (bracket.type !== "swiss") return err("Bracket is not Swiss type");
// new matches can't be generated till old are over
if (!everyMatchOver(groupsMatches)) {
throw new Error("Not all matches are over");
return err("Not all matches are over");
}
const groupsTeams = groupsMatches
@ -247,7 +249,7 @@ export function generateMatchUps({
}),
);
return result;
return ok(result);
}
interface SwissPairingTeam {

View File

@ -8,6 +8,7 @@ import {
import type { Params, UIMatch } from "@remix-run/react";
import type { Namespace, TFunction } from "i18next";
import { nanoid } from "nanoid";
import type { Ok, Result } from "neverthrow";
import type { z } from "zod/v4";
import type { navItems } from "~/components/layout/nav-items";
import { s3UploadHandler } from "~/features/img-upload";
@ -208,6 +209,19 @@ export function errorToastIfFalsy(
throw errorToastRedirect(message);
}
/**
* To be used in loader or action function. Asserts that the provided `Result` value is an `Ok` variant of the `neverthrow` library.
*
* If the value is an `Err`, shows an error toast to the user with the error message. The function will stop execution by throwing a redirect meaning it is safe to operate on the value after this function call.
*/
export function errorToastIfErr<T, E extends string>(
value: Result<T, E>,
): asserts value is Ok<T, never> {
if (value.isErr()) {
throw errorToastRedirect(value.error);
}
}
/** Throws a redirect triggering an error toast with given message. */
export function errorToast(message: string) {
throw errorToastRedirect(message);

14
package-lock.json generated
View File

@ -37,6 +37,7 @@
"lru-cache": "^11.1.0",
"markdown-to-jsx": "^7.7.6",
"nanoid": "^5.1.5",
"neverthrow": "^8.2.0",
"node-cron": "3.0.3",
"nprogress": "^0.2.0",
"openskill": "^4.1.0",
@ -5645,7 +5646,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -13125,6 +13125,18 @@
"node": ">= 0.6"
}
},
"node_modules/neverthrow": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/neverthrow/-/neverthrow-8.2.0.tgz",
"integrity": "sha512-kOCT/1MCPAxY5iUV3wytNFUMUolzuwd/VF/1KCx7kf6CutrOsTie+84zTGTpgQycjvfLdBBdvBvFLqFD2c0wkQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "^4.24.0"
}
},
"node_modules/node-abi": {
"version": "3.74.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz",

View File

@ -57,6 +57,7 @@
"lru-cache": "^11.1.0",
"markdown-to-jsx": "^7.7.6",
"nanoid": "^5.1.5",
"neverthrow": "^8.2.0",
"node-cron": "3.0.3",
"nprogress": "^0.2.0",
"openskill": "^4.1.0",