Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions src/core/Schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,13 @@ export const ID = z.string().regex(GAME_ID_REGEX);

export const AllPlayersStatsSchema = z.record(ID, PlayerStatsSchema);

const SafeNonNegativeNumberSchema = z
.number()
.nonnegative()
.max(Number.MAX_SAFE_INTEGER);
Comment on lines +344 to +347

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we call this "NaturalNumSchema"

const TileRefSchema = z.number().int().nonnegative().safe();
const UnitIDSchema = z.number().int().nonnegative().safe();
Comment on lines +348 to +349

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can get rid of this and just use NaturalNumSchema


export const QuickChatKeySchema = z.enum(
Object.entries(quickChatData).flatMap(([category, entries]) =>
entries.map((entry) => `${category}.${entry.key}`),
Expand All @@ -359,18 +366,18 @@ export const AllianceExtensionIntentSchema = z.object({
export const AttackIntentSchema = z.object({
type: z.literal("attack"),
targetID: ID.nullable(),
troops: z.number().nonnegative().nullable(),
troops: SafeNonNegativeNumberSchema.nullable(),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

troops we should update to only send natural numbs, then we can resue the SafeNaturalNum

});

export const SpawnIntentSchema = z.object({
type: z.literal("spawn"),
tile: z.number(),
tile: TileRefSchema,
});

export const BoatAttackIntentSchema = z.object({
type: z.literal("boat"),
troops: z.number().nonnegative(),
dst: z.number(),
troops: SafeNonNegativeNumberSchema,
dst: TileRefSchema,
});

export const AllianceRequestIntentSchema = z.object({
Expand Down Expand Up @@ -413,26 +420,26 @@ export const EmbargoAllIntentSchema = z.object({
export const DonateGoldIntentSchema = z.object({
type: z.literal("donate_gold"),
recipient: ID,
gold: z.number().nonnegative().nullable(),
gold: SafeNonNegativeNumberSchema.nullable(),
});

export const DonateTroopIntentSchema = z.object({
type: z.literal("donate_troops"),
recipient: ID,
troops: z.number().nonnegative().nullable(),
troops: SafeNonNegativeNumberSchema.nullable(),
});

export const BuildUnitIntentSchema = z.object({
type: z.literal("build_unit"),
unit: z.enum(UnitType),
tile: z.number(),
tile: TileRefSchema,
rocketDirectionUp: z.boolean().optional(),
});

export const UpgradeStructureIntentSchema = z.object({
type: z.literal("upgrade_structure"),
unit: z.enum(UnitType),
unitId: z.number(),
unitId: UnitIDSchema,
});

export const CancelAttackIntentSchema = z.object({
Expand All @@ -442,18 +449,18 @@ export const CancelAttackIntentSchema = z.object({

export const CancelBoatIntentSchema = z.object({
type: z.literal("cancel_boat"),
unitID: z.number(),
unitID: UnitIDSchema,
});

export const MoveWarshipIntentSchema = z.object({
type: z.literal("move_warship"),
unitIds: z.array(z.number().int()).nonempty(),
tile: z.number(),
unitIds: z.array(UnitIDSchema).nonempty(),
tile: TileRefSchema,
});

export const DeleteUnitIntentSchema = z.object({
type: z.literal("delete_unit"),
unitId: z.number(),
unitId: UnitIDSchema,
});

export const QuickChatIntentSchema = z.object({
Expand Down
49 changes: 49 additions & 0 deletions tests/ClientIntentSchema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { UnitType } from "../src/core/game/Game";
import { ClientMessageSchema } from "../src/core/Schemas";

const VALID_ID = "ABCDEFGH";

function parseIntent(intent: unknown) {
return ClientMessageSchema.safeParse({
type: "intent",
intent,
});
}

describe("Client intent schema", () => {
test.each([
[{ type: "spawn", tile: 0.5 }],
[{ type: "build_unit", unit: UnitType.City, tile: 0.5 }],
[{ type: "boat", troops: 100, dst: 0.5 }],
[{ type: "move_warship", unitIds: [1], tile: 0.5 }],
[{ type: "move_warship", unitIds: [1.5], tile: 1 }],
[{ type: "upgrade_structure", unit: UnitType.City, unitId: 1.5 }],
[{ type: "cancel_boat", unitID: 1.5 }],
[{ type: "delete_unit", unitId: 1.5 }],
])("rejects non-integer tile and unit references %#", (intent) => {
expect(parseIntent(intent).success).toBe(false);
});

test.each([
[{ type: "attack", targetID: VALID_ID, troops: 1e308 }],
[{ type: "boat", troops: 1e308, dst: 1 }],
[{ type: "donate_gold", recipient: VALID_ID, gold: 1e308 }],
[{ type: "donate_troops", recipient: VALID_ID, troops: 1e308 }],
])("rejects unsafe numeric resource amounts %#", (intent) => {
expect(parseIntent(intent).success).toBe(false);
});

test("accepts valid finite resource amounts and integer refs", () => {
expect(
parseIntent({ type: "attack", targetID: VALID_ID, troops: 10.5 }).success,
).toBe(true);
expect(parseIntent({ type: "spawn", tile: 0 }).success).toBe(true);
expect(
parseIntent({
type: "build_unit",
unit: UnitType.City,
tile: 12,
}).success,
).toBe(true);
});
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Loading