import { applyDecorators } from '@nestjs/common'; import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { ArrayMaxSize, ArrayMinSize, ArrayUnique, IsArray, IsNotEmpty, IsString, ValidateNested, } from 'class-validator'; import { BULK_MAX_COLONIES } from './bulk-limits'; const DUPLICATE_COLONY_MESSAGE = '$property enthält ein Volk doppelt'; // The size rule every bulk list shares: at least one colony, at most // BULK_MAX_COLONIES. function bulkListSize(): PropertyDecorator[] { return [ IsArray({ message: '$property muss eine Liste sein' }), ArrayMinSize(1, { message: '$property braucht mindestens ein Volk' }), ArrayMaxSize(BULK_MAX_COLONIES, { message: `$property darf höchstens ${BULK_MAX_COLONIES} Völker enthalten`, }), ]; } // The rule for one colony id - in colonyIds (each) and in every entry - so an // empty id is invalid input on both paths, never an "unknown colony". function colonyIdChecks(each: boolean): PropertyDecorator[] { return [ IsString({ each, message: '$property enthält eine ungültige Volk-ID' }), IsNotEmpty({ each, message: '$property enthält eine leere Volk-ID' }), ]; } /** `colonyIds` of a bulk entry with shared values: distinct, non-empty ids. */ export function IsBulkColonyIds(): PropertyDecorator { return applyDecorators( ApiProperty({ type: [String], minItems: 1, maxItems: BULK_MAX_COLONIES, uniqueItems: true, description: `Die Völker, für die derselbe Eintrag gilt (1 bis ${BULK_MAX_COLONIES}, jedes nur einmal)`, }), ...bulkListSize(), ArrayUnique({ message: DUPLICATE_COLONY_MESSAGE }), ...colonyIdChecks(true), ); } /** `colonyId` of one entry in `entries`: a non-empty id. */ export function IsBulkEntryColonyId(): PropertyDecorator { return applyDecorators( ApiProperty({ description: 'Das Volk dieses Eintrags' }), ...colonyIdChecks(false), ); } /** * `entries` of a bulk entry with one value set per colony: each entry is * validated as its class, and each colony appears once. */ export function IsBulkEntries( entryType: new () => { colonyId: string }, ): PropertyDecorator { return applyDecorators( ApiProperty({ type: [entryType], minItems: 1, maxItems: BULK_MAX_COLONIES, description: `Ein Eintrag je Volk (1 bis ${BULK_MAX_COLONIES}, jedes Volk nur einmal)`, }), ...bulkListSize(), // class-validator skips null entries before calling the selector. ArrayUnique((entry: { colonyId?: unknown }) => entry.colonyId, { message: DUPLICATE_COLONY_MESSAGE, }), ValidateNested({ each: true }), Type(() => entryType), ); }