From b5d73c50c1a1d7ac43961391ef31aeeb3842ad9f Mon Sep 17 00:00:00 2001
From: Claude Code agent
<311327716+modernitconsultants@users.noreply.github.com>
Date: Wed, 9 Sep 2026 22:26:52 +1000
Subject: [PATCH 1/2] fix(api): enum validation messages named no allowed
values
Post a folio charge with type "beverage" and the API answers
type must be one of the following values:
with nothing after the colon. The caller is told they are wrong and never
told what right looks like.
class-validator's IsEnum is for TypeScript enum objects. It validates with
Object.keys(entity).map(k => entity[k]), which happens to read an array's
values, so the check works; but it builds its message from
Object.entries(entity).filter(([k]) => isNaN(parseInt(k))), which drops every
key of an array because array keys are all numeric. So the allowed-value list
comes out empty. IsIn is the decorator for a list of allowed values, and
interpolates it.
Every enum decorator in the API was affected: 88 across 64 files, covering
accounting, folio, payment, reservation, rate plan, housekeeping, media,
groups, waitlist and the booking-requests package.
Validation behaviour does not change. Both decorators compare the same value
against the same list.
apps/api/src/validation-messages.spec.ts reads class-validator's metadata
registry rather than the source, so it covers the copies PartialType and
OmitType generate, which no grep sees. It asserts a rendered message too, and
that is what caught the first version of the metadata assertion: it filtered
on `type`, which is 'customValidation' for every ValidateBy decorator, so it
matched nothing and passed with an @IsEnum deliberately planted.
---
README.md | 8 +-
.../dto/create-accounting-code.dto.ts | 4 +-
.../dto/list-accounting-codes.dto.ts | 4 +-
.../accounting/dto/list-ar-ledgers.dto.ts | 4 +-
.../accounting/dto/list-deposits.dto.ts | 4 +-
.../src/modules/admin/dto/create-user.dto.ts | 4 +-
.../src/modules/admin/dto/update-user.dto.ts | 4 +-
.../ancillary/dto/create-service.dto.ts | 6 +-
.../cashier/dto/record-movement.dto.ts | 4 +-
.../dto/create-channel-connection.dto.ts | 4 +-
.../channel/dto/inbound-reservation.dto.ts | 4 +-
.../channel/dto/set-rate-override.dto.ts | 4 +-
.../dto/update-channel-connection.dto.ts | 6 +-
.../src/modules/connect/dto/agent-book.dto.ts | 4 +-
.../door-lock/dto/list-credentials.dto.ts | 4 +-
.../dto/post-folio-inbound-charge.dto.ts | 4 +-
.../modules/folio/dto/create-charge.dto.ts | 4 +-
.../src/modules/folio/dto/create-folio.dto.ts | 4 +-
.../folio/dto/create-routing-rule.dto.ts | 4 +-
.../src/modules/folio/dto/list-charges.dto.ts | 4 +-
.../src/modules/folio/dto/list-folios.dto.ts | 6 +-
.../folio/dto/move-transactions.dto.ts | 4 +-
.../modules/groups/dto/create-block.dto.ts | 4 +-
.../groups/dto/create-group-profile.dto.ts | 4 +-
.../src/modules/groups/dto/list-blocks.dto.ts | 4 +-
.../groups/dto/list-group-profiles.dto.ts | 4 +-
.../modules/groups/dto/update-block.dto.ts | 4 +-
.../groups/dto/update-group-profile.dto.ts | 4 +-
.../src/modules/guest/dto/create-guest.dto.ts | 4 +-
.../modules/guest/dto/search-guests.dto.ts | 4 +-
.../dto/add-house-account-charge.dto.ts | 4 +-
.../dto/add-house-account-payment.dto.ts | 4 +-
.../dto/list-house-accounts.dto.ts | 6 +-
.../dto/open-house-account.dto.ts | 4 +-
.../house-account/dto/sell-product.dto.ts | 4 +-
.../housekeeping/dto/create-task.dto.ts | 4 +-
.../housekeeping/dto/list-tasks.dto.ts | 6 +-
apps/api/src/modules/ical/dto/ical.dto.ts | 6 +-
.../lost-and-found/dto/lost-and-found.dto.ts | 12 +-
.../src/modules/media/dto/create-media.dto.ts | 6 +-
.../src/modules/media/dto/query-media.dto.ts | 4 +-
.../modules/media/dto/reorder-media.dto.ts | 4 +-
.../src/modules/media/dto/update-media.dto.ts | 4 +-
.../src/modules/media/dto/upload-media.dto.ts | 6 +-
.../payment/dto/correct-payment.dto.ts | 4 +-
.../modules/payment/dto/create-payment.dto.ts | 4 +-
.../modules/payment/dto/list-payments.dto.ts | 6 +-
.../dto/create-cancellation-policy.dto.ts | 6 +-
.../modules/pos/dto/post-pos-charge.dto.ts | 4 +-
.../rate-plan/dto/create-rate-plan.dto.ts | 8 +-
.../rate-plan/dto/rate-adjustment.dto.ts | 4 +-
.../reservation/dto/bulk-action.dto.ts | 4 +-
.../reservation/dto/create-reservation.dto.ts | 4 +-
.../dto/import-reservations.dto.ts | 4 +-
.../reservation/dto/list-reservations.dto.ts | 4 +-
.../modules/room/dto/hk-observation.dto.ts | 4 +-
.../dto/service-request.dto.ts | 12 +-
.../modules/tax/dto/create-tax-rule.dto.ts | 4 +-
.../modules/tax/dto/update-tax-rule.dto.ts | 4 +-
.../modules/turnaways/dto/turnaways.dto.ts | 6 +-
.../src/modules/waitlist/dto/waitlist.dto.ts | 4 +-
apps/api/src/validation-messages.spec.ts | 103 ++++++++++++++++++
docs/test-stats.json | 6 +-
.../http/dto/accept-booking-request.dto.ts | 4 +-
.../dto/amend-booking-request-stay.dto.ts | 4 +-
.../http/dto/booking-request-payment.dto.ts | 6 +-
.../src/http/dto/list-booking-requests.dto.ts | 8 +-
67 files changed, 262 insertions(+), 159 deletions(-)
create mode 100644 apps/api/src/validation-messages.spec.ts
diff --git a/README.md b/README.md
index ecf58c4a..2ee249db 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
-
+
@@ -510,7 +510,7 @@ Operator notes for activating existing adapters, metasearch landings on the dire
| OTA Channels | Booking.com + Expedia (EQC) + SiteMinder + DerbySoft | Direct + aggregated OTA connectivity (ARI + content) |
| XML Processing | fast-xml-parser | Booking.com OTA XML protocol |
| Package Manager | pnpm workspaces | Monorepo management |
-| Testing | Vitest (2245 passing tests across 268 files with passing tests) | Unit and integration tests |
+| Testing | Vitest (2249 passing tests across 269 files with passing tests) | Unit and integration tests |
| Build | tsup (packages) + Vite (dashboard) + nest build (API) | Fast builds |
| Containers | Docker + docker-compose | Local dev and production deployment |
| CI/CD | GitHub Actions | Automated testing, builds, and releases |
@@ -648,7 +648,7 @@ Before going live, verify the items in [`docs/deployment.md`](./docs/deployment.
### Run tests
```bash
-# Passing-test count: 2245 test cases across 268 files (skipped excluded)
+# Passing-test count: 2249 test cases across 269 files (skipped excluded)
# API tests only
pnpm --filter @telivityhaip/api test
@@ -1197,7 +1197,7 @@ HAIP is built in public and contributions are welcome.
pnpm install # Install dependencies
pnpm build # Build all workspace packages
pnpm dev # Start API in dev mode (hot reload)
-pnpm test # Run all tests (2245 passing, 268 files with passes; skipped excluded)
+pnpm test # Run all tests (2249 passing, 269 files with passes; skipped excluded)
pnpm lint # ESLint
```
diff --git a/apps/api/src/modules/accounting/dto/create-accounting-code.dto.ts b/apps/api/src/modules/accounting/dto/create-accounting-code.dto.ts
index d3a5e978..03eb5e29 100644
--- a/apps/api/src/modules/accounting/dto/create-accounting-code.dto.ts
+++ b/apps/api/src/modules/accounting/dto/create-accounting-code.dto.ts
@@ -3,7 +3,7 @@ import {
IsNotEmpty,
IsOptional,
IsUUID,
- IsEnum,
+ IsIn,
MaxLength,
} from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
@@ -15,7 +15,7 @@ export class CreateAccountingCodeDto {
propertyId!: string;
@ApiProperty({ enum: ['transaction', 'gl'], description: 'Code kind (KB 5)' })
- @IsEnum(['transaction', 'gl'])
+ @IsIn(['transaction', 'gl'])
kind!: string;
@ApiProperty({ example: 'ROOM-REV', description: 'Code' })
diff --git a/apps/api/src/modules/accounting/dto/list-accounting-codes.dto.ts b/apps/api/src/modules/accounting/dto/list-accounting-codes.dto.ts
index bdf1361b..f3713bdf 100644
--- a/apps/api/src/modules/accounting/dto/list-accounting-codes.dto.ts
+++ b/apps/api/src/modules/accounting/dto/list-accounting-codes.dto.ts
@@ -1,4 +1,4 @@
-import { IsOptional, IsUUID, IsEnum, IsBoolean, IsInt, Min, Max } from 'class-validator';
+import { IsOptional, IsUUID, IsIn, IsBoolean, IsInt, Min, Max } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
@@ -9,7 +9,7 @@ export class ListAccountingCodesDto {
@ApiPropertyOptional({ enum: ['transaction', 'gl'] })
@IsOptional()
- @IsEnum(['transaction', 'gl'])
+ @IsIn(['transaction', 'gl'])
kind?: string;
@ApiPropertyOptional({ description: 'Include archived codes', default: false })
diff --git a/apps/api/src/modules/accounting/dto/list-ar-ledgers.dto.ts b/apps/api/src/modules/accounting/dto/list-ar-ledgers.dto.ts
index 4a7c0b79..a7b87723 100644
--- a/apps/api/src/modules/accounting/dto/list-ar-ledgers.dto.ts
+++ b/apps/api/src/modules/accounting/dto/list-ar-ledgers.dto.ts
@@ -1,4 +1,4 @@
-import { IsOptional, IsUUID, IsEnum, IsInt, Min, Max } from 'class-validator';
+import { IsOptional, IsUUID, IsIn, IsInt, Min, Max } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
@@ -9,7 +9,7 @@ export class ListArLedgersDto {
@ApiPropertyOptional({ enum: ['open', 'closed'] })
@IsOptional()
- @IsEnum(['open', 'closed'])
+ @IsIn(['open', 'closed'])
status?: string;
@ApiPropertyOptional({ default: 1 })
diff --git a/apps/api/src/modules/accounting/dto/list-deposits.dto.ts b/apps/api/src/modules/accounting/dto/list-deposits.dto.ts
index d2169700..d8d64999 100644
--- a/apps/api/src/modules/accounting/dto/list-deposits.dto.ts
+++ b/apps/api/src/modules/accounting/dto/list-deposits.dto.ts
@@ -1,4 +1,4 @@
-import { IsOptional, IsUUID, IsEnum, IsInt, Min, Max } from 'class-validator';
+import { IsOptional, IsUUID, IsIn, IsInt, Min, Max } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
@@ -14,7 +14,7 @@ export class ListDepositsDto {
@ApiPropertyOptional({ enum: ['held', 'applied', 'refunded', 'forfeited'] })
@IsOptional()
- @IsEnum(['held', 'applied', 'refunded', 'forfeited'])
+ @IsIn(['held', 'applied', 'refunded', 'forfeited'])
status?: string;
@ApiPropertyOptional({ default: 1 })
diff --git a/apps/api/src/modules/admin/dto/create-user.dto.ts b/apps/api/src/modules/admin/dto/create-user.dto.ts
index fe850581..5d6bece8 100644
--- a/apps/api/src/modules/admin/dto/create-user.dto.ts
+++ b/apps/api/src/modules/admin/dto/create-user.dto.ts
@@ -4,7 +4,7 @@ import {
IsNotEmpty,
IsOptional,
IsEmail,
- IsEnum,
+ IsIn,
IsArray,
MaxLength,
} from 'class-validator';
@@ -30,7 +30,7 @@ export class CreateUserDto {
@ApiPropertyOptional({ enum: USER_STATUSES, default: 'active' })
@IsOptional()
- @IsEnum(USER_STATUSES)
+ @IsIn(USER_STATUSES)
status?: (typeof USER_STATUSES)[number];
@ApiPropertyOptional({ format: 'uuid', description: 'Keycloak subject (link real login — LATER)' })
diff --git a/apps/api/src/modules/admin/dto/update-user.dto.ts b/apps/api/src/modules/admin/dto/update-user.dto.ts
index 700af695..49486b6e 100644
--- a/apps/api/src/modules/admin/dto/update-user.dto.ts
+++ b/apps/api/src/modules/admin/dto/update-user.dto.ts
@@ -1,4 +1,4 @@
-import { IsString, IsOptional, IsEnum, MaxLength } from 'class-validator';
+import { IsString, IsOptional, IsIn, MaxLength } from 'class-validator';
import { ApiPropertyOptional } from '@nestjs/swagger';
import { USER_STATUSES } from './create-user.dto';
@@ -11,6 +11,6 @@ export class UpdateUserDto {
@ApiPropertyOptional({ enum: USER_STATUSES })
@IsOptional()
- @IsEnum(USER_STATUSES)
+ @IsIn(USER_STATUSES)
status?: (typeof USER_STATUSES)[number];
}
diff --git a/apps/api/src/modules/ancillary/dto/create-service.dto.ts b/apps/api/src/modules/ancillary/dto/create-service.dto.ts
index f9651069..4afe6e03 100644
--- a/apps/api/src/modules/ancillary/dto/create-service.dto.ts
+++ b/apps/api/src/modules/ancillary/dto/create-service.dto.ts
@@ -4,7 +4,7 @@ import {
IsOptional,
IsUUID,
IsBoolean,
- IsEnum,
+ IsIn,
IsArray,
IsInt,
Min,
@@ -58,7 +58,7 @@ export class CreateServiceDto {
description?: string;
@ApiProperty({ enum: SERVICE_CHARGE_TYPES })
- @IsEnum(SERVICE_CHARGE_TYPES)
+ @IsIn(SERVICE_CHARGE_TYPES)
chargeType!: (typeof SERVICE_CHARGE_TYPES)[number];
@ApiProperty({ example: '25.00' })
@@ -78,7 +78,7 @@ export class CreateServiceDto {
taxCode?: string;
@ApiProperty({ enum: SERVICE_POSTING_RULES })
- @IsEnum(SERVICE_POSTING_RULES)
+ @IsIn(SERVICE_POSTING_RULES)
postingRule!: (typeof SERVICE_POSTING_RULES)[number];
@ApiPropertyOptional({
diff --git a/apps/api/src/modules/cashier/dto/record-movement.dto.ts b/apps/api/src/modules/cashier/dto/record-movement.dto.ts
index 5d4bb1e5..8b964d30 100644
--- a/apps/api/src/modules/cashier/dto/record-movement.dto.ts
+++ b/apps/api/src/modules/cashier/dto/record-movement.dto.ts
@@ -3,7 +3,7 @@ import {
IsNotEmpty,
IsOptional,
IsUUID,
- IsEnum,
+ IsIn,
} from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
@@ -17,7 +17,7 @@ export class RecordMovementDto {
propertyId!: string;
@ApiProperty({ enum: ['payment', 'refund', 'paid_out', 'drop'], description: 'Movement type (KB 12.3)' })
- @IsEnum(['payment', 'refund', 'paid_out', 'drop'])
+ @IsIn(['payment', 'refund', 'paid_out', 'drop'])
type!: string;
@ApiProperty({ example: '50.00', description: 'Movement amount' })
diff --git a/apps/api/src/modules/channel/dto/create-channel-connection.dto.ts b/apps/api/src/modules/channel/dto/create-channel-connection.dto.ts
index 2cb20da2..1979c3cd 100644
--- a/apps/api/src/modules/channel/dto/create-channel-connection.dto.ts
+++ b/apps/api/src/modules/channel/dto/create-channel-connection.dto.ts
@@ -1,4 +1,4 @@
-import { IsUUID, IsString, IsOptional, IsArray, ValidateNested, IsEnum } from 'class-validator';
+import { IsUUID, IsString, IsOptional, IsArray, ValidateNested, IsIn } from 'class-validator';
import { Type } from 'class-transformer';
export class RatePlanMappingDto {
@@ -31,7 +31,7 @@ export class CreateChannelConnectionDto {
adapterType!: string;
@IsOptional()
- @IsEnum(['push', 'pull', 'bidirectional'])
+ @IsIn(['push', 'pull', 'bidirectional'])
syncDirection?: string;
@IsOptional()
diff --git a/apps/api/src/modules/channel/dto/inbound-reservation.dto.ts b/apps/api/src/modules/channel/dto/inbound-reservation.dto.ts
index 4923228b..04137fe5 100644
--- a/apps/api/src/modules/channel/dto/inbound-reservation.dto.ts
+++ b/apps/api/src/modules/channel/dto/inbound-reservation.dto.ts
@@ -1,4 +1,4 @@
-import { IsUUID, IsString, IsOptional, IsNumber, IsDateString, IsEnum, ValidateNested } from 'class-validator';
+import { IsUUID, IsString, IsOptional, IsNumber, IsDateString, IsIn, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
export class ChannelReservationDto {
@@ -51,7 +51,7 @@ export class ChannelReservationDto {
@IsString()
specialRequests?: string;
- @IsEnum(['new', 'modified', 'cancelled'])
+ @IsIn(['new', 'modified', 'cancelled'])
status!: 'new' | 'modified' | 'cancelled';
}
diff --git a/apps/api/src/modules/channel/dto/set-rate-override.dto.ts b/apps/api/src/modules/channel/dto/set-rate-override.dto.ts
index dda88b2a..713b6d95 100644
--- a/apps/api/src/modules/channel/dto/set-rate-override.dto.ts
+++ b/apps/api/src/modules/channel/dto/set-rate-override.dto.ts
@@ -1,6 +1,6 @@
import {
IsUUID,
- IsEnum,
+ IsIn,
IsNumber,
IsOptional,
IsDateString,
@@ -24,7 +24,7 @@ export class SetRateOverrideDto {
ratePlanId!: string;
@ApiProperty({ enum: ['percentage', 'fixed'] })
- @IsEnum(['percentage', 'fixed'])
+ @IsIn(['percentage', 'fixed'])
adjustmentType!: 'percentage' | 'fixed';
@ApiProperty({
diff --git a/apps/api/src/modules/channel/dto/update-channel-connection.dto.ts b/apps/api/src/modules/channel/dto/update-channel-connection.dto.ts
index cedf6e34..d4024b30 100644
--- a/apps/api/src/modules/channel/dto/update-channel-connection.dto.ts
+++ b/apps/api/src/modules/channel/dto/update-channel-connection.dto.ts
@@ -1,4 +1,4 @@
-import { IsString, IsOptional, IsArray, ValidateNested, IsEnum } from 'class-validator';
+import { IsString, IsOptional, IsArray, ValidateNested, IsIn } from 'class-validator';
import { Type } from 'class-transformer';
import { RatePlanMappingDto, RoomTypeMappingDto } from './create-channel-connection.dto';
@@ -8,11 +8,11 @@ export class UpdateChannelConnectionDto {
channelName?: string;
@IsOptional()
- @IsEnum(['active', 'inactive', 'pending_setup'])
+ @IsIn(['active', 'inactive', 'pending_setup'])
status?: string;
@IsOptional()
- @IsEnum(['push', 'pull', 'bidirectional'])
+ @IsIn(['push', 'pull', 'bidirectional'])
syncDirection?: string;
@IsOptional()
diff --git a/apps/api/src/modules/connect/dto/agent-book.dto.ts b/apps/api/src/modules/connect/dto/agent-book.dto.ts
index 772278c2..5c015eda 100644
--- a/apps/api/src/modules/connect/dto/agent-book.dto.ts
+++ b/apps/api/src/modules/connect/dto/agent-book.dto.ts
@@ -5,7 +5,7 @@ import {
IsNumber,
IsOptional,
IsEmail,
- IsEnum,
+ IsIn,
Min,
} from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
@@ -75,7 +75,7 @@ export class AgentBookDto {
// Payment
@ApiPropertyOptional({ enum: ['pay_at_property', 'prepaid', 'virtual_card'] })
@IsOptional()
- @IsEnum(['pay_at_property', 'prepaid', 'virtual_card'])
+ @IsIn(['pay_at_property', 'prepaid', 'virtual_card'])
paymentMethod?: 'pay_at_property' | 'prepaid' | 'virtual_card';
@ApiPropertyOptional()
diff --git a/apps/api/src/modules/door-lock/dto/list-credentials.dto.ts b/apps/api/src/modules/door-lock/dto/list-credentials.dto.ts
index 459648b4..5e94547f 100644
--- a/apps/api/src/modules/door-lock/dto/list-credentials.dto.ts
+++ b/apps/api/src/modules/door-lock/dto/list-credentials.dto.ts
@@ -1,4 +1,4 @@
-import { IsEnum, IsOptional, IsUUID, IsInt, Min, Max } from 'class-validator';
+import { IsIn, IsOptional, IsUUID, IsInt, Min, Max } from 'class-validator';
import { Type } from 'class-transformer';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
@@ -9,7 +9,7 @@ export class ListDoorLockCredentialsDto {
@ApiPropertyOptional({ enum: ['active', 'revoked'] })
@IsOptional()
- @IsEnum(['active', 'revoked'])
+ @IsIn(['active', 'revoked'])
status?: 'active' | 'revoked';
@ApiPropertyOptional({ default: 1 })
diff --git a/apps/api/src/modules/folio-inbound/dto/post-folio-inbound-charge.dto.ts b/apps/api/src/modules/folio-inbound/dto/post-folio-inbound-charge.dto.ts
index 48caa65c..3e9c2700 100644
--- a/apps/api/src/modules/folio-inbound/dto/post-folio-inbound-charge.dto.ts
+++ b/apps/api/src/modules/folio-inbound/dto/post-folio-inbound-charge.dto.ts
@@ -1,6 +1,6 @@
import {
IsDateString,
- IsEnum,
+ IsIn,
IsNotEmpty,
IsOptional,
IsString,
@@ -30,7 +30,7 @@ export class PostFolioInboundChargeDto {
roomNumber!: string;
@ApiProperty({ enum: INBOUND_CHARGE_TYPES, example: 'minibar' })
- @IsEnum(INBOUND_CHARGE_TYPES)
+ @IsIn(INBOUND_CHARGE_TYPES)
type!: (typeof INBOUND_CHARGE_TYPES)[number];
@ApiProperty({ example: '18.50' })
diff --git a/apps/api/src/modules/folio/dto/create-charge.dto.ts b/apps/api/src/modules/folio/dto/create-charge.dto.ts
index 6c17bb67..79ba6d1f 100644
--- a/apps/api/src/modules/folio/dto/create-charge.dto.ts
+++ b/apps/api/src/modules/folio/dto/create-charge.dto.ts
@@ -3,7 +3,7 @@ import {
IsNotEmpty,
IsOptional,
IsUUID,
- IsEnum,
+ IsIn,
IsBoolean,
IsDateString,
MaxLength,
@@ -20,7 +20,7 @@ export class CreateChargeDto {
@ApiProperty({
enum: ['room', 'tax', 'food_beverage', 'minibar', 'phone', 'laundry', 'parking', 'spa', 'incidental', 'fee', 'adjustment', 'package'],
})
- @IsEnum(['room', 'tax', 'food_beverage', 'minibar', 'phone', 'laundry', 'parking', 'spa', 'incidental', 'fee', 'adjustment', 'package'])
+ @IsIn(['room', 'tax', 'food_beverage', 'minibar', 'phone', 'laundry', 'parking', 'spa', 'incidental', 'fee', 'adjustment', 'package'])
type!: string;
@ApiProperty({ example: 'Room charge - Standard King' })
diff --git a/apps/api/src/modules/folio/dto/create-folio.dto.ts b/apps/api/src/modules/folio/dto/create-folio.dto.ts
index 56c2e930..3a273b6b 100644
--- a/apps/api/src/modules/folio/dto/create-folio.dto.ts
+++ b/apps/api/src/modules/folio/dto/create-folio.dto.ts
@@ -3,7 +3,7 @@ import {
IsNotEmpty,
IsOptional,
IsUUID,
- IsEnum,
+ IsIn,
MaxLength,
} from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
@@ -30,7 +30,7 @@ export class CreateFolioDto {
guestId!: string;
@ApiProperty({ enum: ['guest', 'master', 'city_ledger'], default: 'guest' })
- @IsEnum(['guest', 'master', 'city_ledger'])
+ @IsIn(['guest', 'master', 'city_ledger'])
type!: string;
@ApiProperty({ example: 'USD', description: 'ISO 4217 currency code' })
diff --git a/apps/api/src/modules/folio/dto/create-routing-rule.dto.ts b/apps/api/src/modules/folio/dto/create-routing-rule.dto.ts
index 0b1b09ea..ce2bba33 100644
--- a/apps/api/src/modules/folio/dto/create-routing-rule.dto.ts
+++ b/apps/api/src/modules/folio/dto/create-routing-rule.dto.ts
@@ -2,7 +2,7 @@ import {
IsNotEmpty,
IsOptional,
IsUUID,
- IsEnum,
+ IsIn,
IsInt,
} from 'class-validator';
import { Type } from 'class-transformer';
@@ -38,7 +38,7 @@ export class CreateRoutingRuleDto {
reservationId!: string;
@ApiProperty({ enum: CHARGE_TYPES })
- @IsEnum(CHARGE_TYPES)
+ @IsIn(CHARGE_TYPES)
chargeType!: string;
@ApiProperty({ description: 'Folio that charges of this type post to' })
diff --git a/apps/api/src/modules/folio/dto/list-charges.dto.ts b/apps/api/src/modules/folio/dto/list-charges.dto.ts
index 7d33ec00..639a5771 100644
--- a/apps/api/src/modules/folio/dto/list-charges.dto.ts
+++ b/apps/api/src/modules/folio/dto/list-charges.dto.ts
@@ -1,4 +1,4 @@
-import { IsOptional, IsUUID, IsEnum, IsDateString, IsInt, Min, Max } from 'class-validator';
+import { IsOptional, IsUUID, IsIn, IsDateString, IsInt, Min, Max } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
@@ -11,7 +11,7 @@ export class ListChargesDto {
enum: ['room', 'tax', 'food_beverage', 'minibar', 'phone', 'laundry', 'parking', 'spa', 'incidental', 'fee', 'adjustment', 'package'],
})
@IsOptional()
- @IsEnum(['room', 'tax', 'food_beverage', 'minibar', 'phone', 'laundry', 'parking', 'spa', 'incidental', 'fee', 'adjustment', 'package'])
+ @IsIn(['room', 'tax', 'food_beverage', 'minibar', 'phone', 'laundry', 'parking', 'spa', 'incidental', 'fee', 'adjustment', 'package'])
type?: string;
@ApiPropertyOptional()
diff --git a/apps/api/src/modules/folio/dto/list-folios.dto.ts b/apps/api/src/modules/folio/dto/list-folios.dto.ts
index 1acf50e5..69a6e4dd 100644
--- a/apps/api/src/modules/folio/dto/list-folios.dto.ts
+++ b/apps/api/src/modules/folio/dto/list-folios.dto.ts
@@ -1,4 +1,4 @@
-import { IsOptional, IsUUID, IsEnum, IsInt, Min, Max } from 'class-validator';
+import { IsOptional, IsUUID, IsIn, IsInt, Min, Max } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
@@ -19,12 +19,12 @@ export class ListFoliosDto {
@ApiPropertyOptional({ enum: ['guest', 'master', 'city_ledger'] })
@IsOptional()
- @IsEnum(['guest', 'master', 'city_ledger'])
+ @IsIn(['guest', 'master', 'city_ledger'])
type?: string;
@ApiPropertyOptional({ enum: ['open', 'settled', 'closed'] })
@IsOptional()
- @IsEnum(['open', 'settled', 'closed'])
+ @IsIn(['open', 'settled', 'closed'])
status?: string;
@ApiPropertyOptional({ default: 1 })
diff --git a/apps/api/src/modules/folio/dto/move-transactions.dto.ts b/apps/api/src/modules/folio/dto/move-transactions.dto.ts
index 18d4dd8a..9e205106 100644
--- a/apps/api/src/modules/folio/dto/move-transactions.dto.ts
+++ b/apps/api/src/modules/folio/dto/move-transactions.dto.ts
@@ -2,7 +2,7 @@ import {
IsNotEmpty,
IsOptional,
IsUUID,
- IsEnum,
+ IsIn,
} from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
@@ -42,6 +42,6 @@ export class MoveTransactionsDto {
@ApiPropertyOptional({ enum: CHARGE_TYPES, description: 'Move all charges of this type' })
@IsOptional()
- @IsEnum(CHARGE_TYPES)
+ @IsIn(CHARGE_TYPES)
chargeType?: string;
}
diff --git a/apps/api/src/modules/groups/dto/create-block.dto.ts b/apps/api/src/modules/groups/dto/create-block.dto.ts
index 46500e5a..24678a98 100644
--- a/apps/api/src/modules/groups/dto/create-block.dto.ts
+++ b/apps/api/src/modules/groups/dto/create-block.dto.ts
@@ -2,7 +2,7 @@ import {
IsUUID,
IsString,
IsOptional,
- IsEnum,
+ IsIn,
IsBoolean,
IsInt,
IsDateString,
@@ -80,6 +80,6 @@ export class CreateBlockDto {
@ApiPropertyOptional({ enum: BLOCK_STATUSES, default: 'tentative' })
@IsOptional()
- @IsEnum(BLOCK_STATUSES)
+ @IsIn(BLOCK_STATUSES)
status?: string;
}
diff --git a/apps/api/src/modules/groups/dto/create-group-profile.dto.ts b/apps/api/src/modules/groups/dto/create-group-profile.dto.ts
index fac6bcc7..e6a7ab35 100644
--- a/apps/api/src/modules/groups/dto/create-group-profile.dto.ts
+++ b/apps/api/src/modules/groups/dto/create-group-profile.dto.ts
@@ -2,7 +2,7 @@ import {
IsUUID,
IsString,
IsOptional,
- IsEnum,
+ IsIn,
IsBoolean,
IsEmail,
MaxLength,
@@ -23,7 +23,7 @@ export class CreateGroupProfileDto {
@ApiProperty({ enum: GROUP_TYPES, default: 'corporate' })
@IsOptional()
- @IsEnum(GROUP_TYPES)
+ @IsIn(GROUP_TYPES)
type?: string;
@ApiPropertyOptional()
diff --git a/apps/api/src/modules/groups/dto/list-blocks.dto.ts b/apps/api/src/modules/groups/dto/list-blocks.dto.ts
index 3b0aa067..8f9044c7 100644
--- a/apps/api/src/modules/groups/dto/list-blocks.dto.ts
+++ b/apps/api/src/modules/groups/dto/list-blocks.dto.ts
@@ -1,4 +1,4 @@
-import { IsUUID, IsOptional, IsEnum, IsInt, Min, Max } from 'class-validator';
+import { IsUUID, IsOptional, IsIn, IsInt, Min, Max } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
@@ -16,7 +16,7 @@ export class ListBlocksDto {
@ApiPropertyOptional({ enum: BLOCK_STATUSES })
@IsOptional()
- @IsEnum(BLOCK_STATUSES)
+ @IsIn(BLOCK_STATUSES)
status?: string;
@ApiPropertyOptional({ default: 1 })
diff --git a/apps/api/src/modules/groups/dto/list-group-profiles.dto.ts b/apps/api/src/modules/groups/dto/list-group-profiles.dto.ts
index 8d2cafc6..d4d7a94c 100644
--- a/apps/api/src/modules/groups/dto/list-group-profiles.dto.ts
+++ b/apps/api/src/modules/groups/dto/list-group-profiles.dto.ts
@@ -1,4 +1,4 @@
-import { IsUUID, IsOptional, IsEnum, IsInt, Min, Max } from 'class-validator';
+import { IsUUID, IsOptional, IsIn, IsInt, Min, Max } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
@@ -11,7 +11,7 @@ export class ListGroupProfilesDto {
@ApiPropertyOptional({ enum: GROUP_TYPES })
@IsOptional()
- @IsEnum(GROUP_TYPES)
+ @IsIn(GROUP_TYPES)
type?: string;
@ApiPropertyOptional({ default: 1 })
diff --git a/apps/api/src/modules/groups/dto/update-block.dto.ts b/apps/api/src/modules/groups/dto/update-block.dto.ts
index b0b4a2dc..5651ccbc 100644
--- a/apps/api/src/modules/groups/dto/update-block.dto.ts
+++ b/apps/api/src/modules/groups/dto/update-block.dto.ts
@@ -1,7 +1,7 @@
import {
IsString,
IsOptional,
- IsEnum,
+ IsIn,
IsBoolean,
IsInt,
IsDateString,
@@ -75,6 +75,6 @@ export class UpdateBlockDto {
@ApiPropertyOptional({ enum: BLOCK_STATUSES })
@IsOptional()
- @IsEnum(BLOCK_STATUSES)
+ @IsIn(BLOCK_STATUSES)
status?: string;
}
diff --git a/apps/api/src/modules/groups/dto/update-group-profile.dto.ts b/apps/api/src/modules/groups/dto/update-group-profile.dto.ts
index c83da602..a0364625 100644
--- a/apps/api/src/modules/groups/dto/update-group-profile.dto.ts
+++ b/apps/api/src/modules/groups/dto/update-group-profile.dto.ts
@@ -1,7 +1,7 @@
import {
IsString,
IsOptional,
- IsEnum,
+ IsIn,
IsEmail,
MaxLength,
} from 'class-validator';
@@ -18,7 +18,7 @@ export class UpdateGroupProfileDto {
@ApiPropertyOptional({ enum: GROUP_TYPES })
@IsOptional()
- @IsEnum(GROUP_TYPES)
+ @IsIn(GROUP_TYPES)
type?: string;
@ApiPropertyOptional()
diff --git a/apps/api/src/modules/guest/dto/create-guest.dto.ts b/apps/api/src/modules/guest/dto/create-guest.dto.ts
index 7b0b3bc9..ea033fee 100644
--- a/apps/api/src/modules/guest/dto/create-guest.dto.ts
+++ b/apps/api/src/modules/guest/dto/create-guest.dto.ts
@@ -4,7 +4,7 @@ import {
IsOptional,
IsEmail,
IsBoolean,
- IsEnum,
+ IsIn,
IsDateString,
IsObject,
MaxLength,
@@ -131,7 +131,7 @@ export class CreateGuestDto {
@ApiPropertyOptional({ enum: ['none', 'silver', 'gold', 'platinum', 'diamond'], default: 'none' })
@IsOptional()
- @IsEnum(['none', 'silver', 'gold', 'platinum', 'diamond'])
+ @IsIn(['none', 'silver', 'gold', 'platinum', 'diamond'])
vipLevel?: string;
@ApiPropertyOptional()
diff --git a/apps/api/src/modules/guest/dto/search-guests.dto.ts b/apps/api/src/modules/guest/dto/search-guests.dto.ts
index 94bc0dc2..4ccd45b9 100644
--- a/apps/api/src/modules/guest/dto/search-guests.dto.ts
+++ b/apps/api/src/modules/guest/dto/search-guests.dto.ts
@@ -1,4 +1,4 @@
-import { IsOptional, IsString, IsEnum, IsBoolean, IsInt, Min, Max } from 'class-validator';
+import { IsOptional, IsString, IsIn, IsBoolean, IsInt, Min, Max } from 'class-validator';
import { ApiPropertyOptional } from '@nestjs/swagger';
import { Transform, Type } from 'class-transformer';
@@ -22,7 +22,7 @@ export class SearchGuestsDto {
@ApiPropertyOptional({ enum: ['none', 'silver', 'gold', 'platinum', 'diamond'] })
@IsOptional()
- @IsEnum(['none', 'silver', 'gold', 'platinum', 'diamond'])
+ @IsIn(['none', 'silver', 'gold', 'platinum', 'diamond'])
vipLevel?: string;
@ApiPropertyOptional()
diff --git a/apps/api/src/modules/house-account/dto/add-house-account-charge.dto.ts b/apps/api/src/modules/house-account/dto/add-house-account-charge.dto.ts
index c0f79c94..9ee051d2 100644
--- a/apps/api/src/modules/house-account/dto/add-house-account-charge.dto.ts
+++ b/apps/api/src/modules/house-account/dto/add-house-account-charge.dto.ts
@@ -3,7 +3,7 @@ import {
IsNotEmpty,
IsOptional,
IsUUID,
- IsEnum,
+ IsIn,
MaxLength,
} from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
@@ -35,7 +35,7 @@ export class AddHouseAccountChargeDto {
description: 'House-account charges post to non-room revenue categories (KB 13.4)',
})
@IsOptional()
- @IsEnum(CHARGE_TYPES)
+ @IsIn(CHARGE_TYPES)
type?: string;
@ApiProperty({ example: 'Bottle of water' })
diff --git a/apps/api/src/modules/house-account/dto/add-house-account-payment.dto.ts b/apps/api/src/modules/house-account/dto/add-house-account-payment.dto.ts
index f3c68119..c587208d 100644
--- a/apps/api/src/modules/house-account/dto/add-house-account-payment.dto.ts
+++ b/apps/api/src/modules/house-account/dto/add-house-account-payment.dto.ts
@@ -3,7 +3,7 @@ import {
IsNotEmpty,
IsOptional,
IsUUID,
- IsEnum,
+ IsIn,
MaxLength,
} from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
@@ -26,7 +26,7 @@ export class AddHouseAccountPaymentDto {
propertyId!: string;
@ApiProperty({ enum: PAYMENT_METHODS, example: 'cash' })
- @IsEnum(PAYMENT_METHODS)
+ @IsIn(PAYMENT_METHODS)
method!: string;
@ApiProperty({ example: '12.50' })
diff --git a/apps/api/src/modules/house-account/dto/list-house-accounts.dto.ts b/apps/api/src/modules/house-account/dto/list-house-accounts.dto.ts
index 0e247e6e..bbc7579d 100644
--- a/apps/api/src/modules/house-account/dto/list-house-accounts.dto.ts
+++ b/apps/api/src/modules/house-account/dto/list-house-accounts.dto.ts
@@ -1,4 +1,4 @@
-import { IsOptional, IsUUID, IsEnum, IsInt, Min, Max } from 'class-validator';
+import { IsOptional, IsUUID, IsIn, IsInt, Min, Max } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
@@ -9,12 +9,12 @@ export class ListHouseAccountsDto {
@ApiPropertyOptional({ enum: ['retail', 'vendor', 'internal', 'other'] })
@IsOptional()
- @IsEnum(['retail', 'vendor', 'internal', 'other'])
+ @IsIn(['retail', 'vendor', 'internal', 'other'])
kind?: string;
@ApiPropertyOptional({ enum: ['open', 'closed'] })
@IsOptional()
- @IsEnum(['open', 'closed'])
+ @IsIn(['open', 'closed'])
status?: string;
@ApiPropertyOptional({ default: 1 })
diff --git a/apps/api/src/modules/house-account/dto/open-house-account.dto.ts b/apps/api/src/modules/house-account/dto/open-house-account.dto.ts
index 6fdec1ee..da42e56e 100644
--- a/apps/api/src/modules/house-account/dto/open-house-account.dto.ts
+++ b/apps/api/src/modules/house-account/dto/open-house-account.dto.ts
@@ -3,7 +3,7 @@ import {
IsNotEmpty,
IsOptional,
IsUUID,
- IsEnum,
+ IsIn,
MaxLength,
} from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
@@ -22,7 +22,7 @@ export class OpenHouseAccountDto {
@ApiPropertyOptional({ enum: ['retail', 'vendor', 'internal', 'other'], default: 'retail' })
@IsOptional()
- @IsEnum(['retail', 'vendor', 'internal', 'other'])
+ @IsIn(['retail', 'vendor', 'internal', 'other'])
kind?: string;
@ApiProperty({ example: 'USD' })
diff --git a/apps/api/src/modules/house-account/dto/sell-product.dto.ts b/apps/api/src/modules/house-account/dto/sell-product.dto.ts
index 766970fc..047c9dc4 100644
--- a/apps/api/src/modules/house-account/dto/sell-product.dto.ts
+++ b/apps/api/src/modules/house-account/dto/sell-product.dto.ts
@@ -3,7 +3,7 @@ import {
IsNotEmpty,
IsOptional,
IsUUID,
- IsEnum,
+ IsIn,
IsInt,
Min,
} from 'class-validator';
@@ -43,6 +43,6 @@ export class SellProductDto {
description: 'If provided, take payment for the sale immediately (KB 13.3)',
})
@IsOptional()
- @IsEnum(PAYMENT_METHODS)
+ @IsIn(PAYMENT_METHODS)
paymentMethod?: string;
}
diff --git a/apps/api/src/modules/housekeeping/dto/create-task.dto.ts b/apps/api/src/modules/housekeeping/dto/create-task.dto.ts
index 47526757..de24f791 100644
--- a/apps/api/src/modules/housekeeping/dto/create-task.dto.ts
+++ b/apps/api/src/modules/housekeeping/dto/create-task.dto.ts
@@ -1,6 +1,6 @@
import {
IsUUID,
- IsEnum,
+ IsIn,
IsOptional,
IsInt,
IsDateString,
@@ -39,7 +39,7 @@ export class CreateTaskDto {
roomId!: string;
@ApiProperty({ enum: TASK_TYPES })
- @IsEnum(TASK_TYPES)
+ @IsIn(TASK_TYPES)
type!: string;
@ApiPropertyOptional({ default: 0 })
diff --git a/apps/api/src/modules/housekeeping/dto/list-tasks.dto.ts b/apps/api/src/modules/housekeeping/dto/list-tasks.dto.ts
index 507d6e09..cf50fd6b 100644
--- a/apps/api/src/modules/housekeeping/dto/list-tasks.dto.ts
+++ b/apps/api/src/modules/housekeeping/dto/list-tasks.dto.ts
@@ -1,4 +1,4 @@
-import { IsUUID, IsOptional, IsEnum, IsDateString, IsInt, Min, Max } from 'class-validator';
+import { IsUUID, IsOptional, IsIn, IsDateString, IsInt, Min, Max } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
@@ -12,12 +12,12 @@ export class ListTasksDto {
@ApiPropertyOptional({ enum: TASK_STATUSES })
@IsOptional()
- @IsEnum(TASK_STATUSES)
+ @IsIn(TASK_STATUSES)
status?: string;
@ApiPropertyOptional({ enum: TASK_TYPES })
@IsOptional()
- @IsEnum(TASK_TYPES)
+ @IsIn(TASK_TYPES)
type?: string;
@ApiPropertyOptional()
diff --git a/apps/api/src/modules/ical/dto/ical.dto.ts b/apps/api/src/modules/ical/dto/ical.dto.ts
index 0ad651cb..e3d281cb 100644
--- a/apps/api/src/modules/ical/dto/ical.dto.ts
+++ b/apps/api/src/modules/ical/dto/ical.dto.ts
@@ -1,7 +1,7 @@
import {
IsBoolean,
IsDateString,
- IsEnum,
+ IsIn,
IsOptional,
IsString,
IsUUID,
@@ -22,7 +22,7 @@ export class CreateIcalFeedDto {
roomTypeId!: string;
@ApiProperty({ enum: ICAL_FEED_DIRECTIONS })
- @IsEnum(ICAL_FEED_DIRECTIONS)
+ @IsIn(ICAL_FEED_DIRECTIONS)
direction!: (typeof ICAL_FEED_DIRECTIONS)[number];
@ApiProperty({ example: 'Airbnb Standard King calendar' })
@@ -66,7 +66,7 @@ export class ListIcalFeedsDto {
@ApiPropertyOptional({ enum: ICAL_FEED_DIRECTIONS })
@IsOptional()
- @IsEnum(ICAL_FEED_DIRECTIONS)
+ @IsIn(ICAL_FEED_DIRECTIONS)
direction?: (typeof ICAL_FEED_DIRECTIONS)[number];
}
diff --git a/apps/api/src/modules/lost-and-found/dto/lost-and-found.dto.ts b/apps/api/src/modules/lost-and-found/dto/lost-and-found.dto.ts
index 2cdf71fd..b5de9c69 100644
--- a/apps/api/src/modules/lost-and-found/dto/lost-and-found.dto.ts
+++ b/apps/api/src/modules/lost-and-found/dto/lost-and-found.dto.ts
@@ -2,7 +2,7 @@ import {
IsUUID,
IsOptional,
IsString,
- IsEnum,
+ IsIn,
IsDateString,
} from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
@@ -32,7 +32,7 @@ export class CreateLostAndFoundItemDto {
@ApiPropertyOptional({ enum: CATEGORIES, default: 'general' })
@IsOptional()
- @IsEnum(CATEGORIES)
+ @IsIn(CATEGORIES)
category?: (typeof CATEGORIES)[number];
@ApiProperty()
@@ -73,12 +73,12 @@ export class UpdateLostAndFoundItemDto {
@ApiPropertyOptional({ enum: STATUSES })
@IsOptional()
- @IsEnum(STATUSES)
+ @IsIn(STATUSES)
status?: string;
@ApiPropertyOptional({ enum: CATEGORIES })
@IsOptional()
- @IsEnum(CATEGORIES)
+ @IsIn(CATEGORIES)
category?: (typeof CATEGORIES)[number];
@ApiPropertyOptional()
@@ -94,11 +94,11 @@ export class ListLostAndFoundItemsDto {
@ApiPropertyOptional({ enum: STATUSES })
@IsOptional()
- @IsEnum(STATUSES)
+ @IsIn(STATUSES)
status?: string;
@ApiPropertyOptional({ enum: CATEGORIES })
@IsOptional()
- @IsEnum(CATEGORIES)
+ @IsIn(CATEGORIES)
category?: (typeof CATEGORIES)[number];
}
diff --git a/apps/api/src/modules/media/dto/create-media.dto.ts b/apps/api/src/modules/media/dto/create-media.dto.ts
index c86dedaa..717ee9c2 100644
--- a/apps/api/src/modules/media/dto/create-media.dto.ts
+++ b/apps/api/src/modules/media/dto/create-media.dto.ts
@@ -3,7 +3,7 @@ import {
IsString,
IsNotEmpty,
IsOptional,
- IsEnum,
+ IsIn,
IsInt,
IsBoolean,
MaxLength,
@@ -28,7 +28,7 @@ export class CreateMediaDto {
propertyId!: string;
@ApiProperty({ enum: MEDIA_OWNER_TYPES })
- @IsEnum(MEDIA_OWNER_TYPES)
+ @IsIn(MEDIA_OWNER_TYPES)
ownerType!: (typeof MEDIA_OWNER_TYPES)[number];
@ApiProperty({ format: 'uuid', description: 'property / room_type / room id' })
@@ -42,7 +42,7 @@ export class CreateMediaDto {
@ApiPropertyOptional({ enum: MEDIA_CATEGORIES, default: 'other' })
@IsOptional()
- @IsEnum(MEDIA_CATEGORIES)
+ @IsIn(MEDIA_CATEGORIES)
category?: (typeof MEDIA_CATEGORIES)[number];
@ApiPropertyOptional()
diff --git a/apps/api/src/modules/media/dto/query-media.dto.ts b/apps/api/src/modules/media/dto/query-media.dto.ts
index 088087b3..5648e386 100644
--- a/apps/api/src/modules/media/dto/query-media.dto.ts
+++ b/apps/api/src/modules/media/dto/query-media.dto.ts
@@ -1,4 +1,4 @@
-import { IsUUID, IsEnum } from 'class-validator';
+import { IsUUID, IsIn } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { MEDIA_OWNER_TYPES } from './create-media.dto';
@@ -12,7 +12,7 @@ export class QueryMediaDto {
propertyId!: string;
@ApiProperty({ enum: MEDIA_OWNER_TYPES })
- @IsEnum(MEDIA_OWNER_TYPES)
+ @IsIn(MEDIA_OWNER_TYPES)
ownerType!: (typeof MEDIA_OWNER_TYPES)[number];
@ApiProperty({ format: 'uuid' })
diff --git a/apps/api/src/modules/media/dto/reorder-media.dto.ts b/apps/api/src/modules/media/dto/reorder-media.dto.ts
index 4999005b..755a658c 100644
--- a/apps/api/src/modules/media/dto/reorder-media.dto.ts
+++ b/apps/api/src/modules/media/dto/reorder-media.dto.ts
@@ -1,4 +1,4 @@
-import { IsUUID, IsEnum, IsArray, ArrayNotEmpty } from 'class-validator';
+import { IsUUID, IsIn, IsArray, ArrayNotEmpty } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { MEDIA_OWNER_TYPES } from './create-media.dto';
@@ -8,7 +8,7 @@ export class ReorderMediaDto {
propertyId!: string;
@ApiProperty({ enum: MEDIA_OWNER_TYPES })
- @IsEnum(MEDIA_OWNER_TYPES)
+ @IsIn(MEDIA_OWNER_TYPES)
ownerType!: (typeof MEDIA_OWNER_TYPES)[number];
@ApiProperty({ format: 'uuid' })
diff --git a/apps/api/src/modules/media/dto/update-media.dto.ts b/apps/api/src/modules/media/dto/update-media.dto.ts
index dd6d944a..c2c612d3 100644
--- a/apps/api/src/modules/media/dto/update-media.dto.ts
+++ b/apps/api/src/modules/media/dto/update-media.dto.ts
@@ -1,7 +1,7 @@
import {
IsString,
IsOptional,
- IsEnum,
+ IsIn,
IsInt,
IsBoolean,
IsUrl,
@@ -20,7 +20,7 @@ export class UpdateMediaDto {
@ApiPropertyOptional({ enum: MEDIA_CATEGORIES })
@IsOptional()
- @IsEnum(MEDIA_CATEGORIES)
+ @IsIn(MEDIA_CATEGORIES)
category?: (typeof MEDIA_CATEGORIES)[number];
@ApiPropertyOptional()
diff --git a/apps/api/src/modules/media/dto/upload-media.dto.ts b/apps/api/src/modules/media/dto/upload-media.dto.ts
index c83efb44..cbee7d2e 100644
--- a/apps/api/src/modules/media/dto/upload-media.dto.ts
+++ b/apps/api/src/modules/media/dto/upload-media.dto.ts
@@ -2,7 +2,7 @@ import {
IsUUID,
IsString,
IsOptional,
- IsEnum,
+ IsIn,
MaxLength,
} from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
@@ -18,7 +18,7 @@ export class UploadMediaDto {
propertyId!: string;
@ApiProperty({ enum: MEDIA_OWNER_TYPES })
- @IsEnum(MEDIA_OWNER_TYPES)
+ @IsIn(MEDIA_OWNER_TYPES)
ownerType!: (typeof MEDIA_OWNER_TYPES)[number];
@ApiProperty({ format: 'uuid' })
@@ -27,7 +27,7 @@ export class UploadMediaDto {
@ApiPropertyOptional({ enum: MEDIA_CATEGORIES, default: 'other' })
@IsOptional()
- @IsEnum(MEDIA_CATEGORIES)
+ @IsIn(MEDIA_CATEGORIES)
category?: (typeof MEDIA_CATEGORIES)[number];
@ApiPropertyOptional()
diff --git a/apps/api/src/modules/payment/dto/correct-payment.dto.ts b/apps/api/src/modules/payment/dto/correct-payment.dto.ts
index 7f147ba3..89a63a62 100644
--- a/apps/api/src/modules/payment/dto/correct-payment.dto.ts
+++ b/apps/api/src/modules/payment/dto/correct-payment.dto.ts
@@ -1,4 +1,4 @@
-import { IsNotEmpty, IsOptional, IsUUID, IsEnum } from 'class-validator';
+import { IsNotEmpty, IsOptional, IsUUID, IsIn } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
/**
@@ -16,6 +16,6 @@ export class CorrectPaymentDto {
description: 'Optional op override; must be the legal op for the payment state',
})
@IsOptional()
- @IsEnum(['void', 'refund', 'adjust'])
+ @IsIn(['void', 'refund', 'adjust'])
op?: 'void' | 'refund' | 'adjust';
}
diff --git a/apps/api/src/modules/payment/dto/create-payment.dto.ts b/apps/api/src/modules/payment/dto/create-payment.dto.ts
index 5911e4d8..30c0f71d 100644
--- a/apps/api/src/modules/payment/dto/create-payment.dto.ts
+++ b/apps/api/src/modules/payment/dto/create-payment.dto.ts
@@ -3,7 +3,7 @@ import {
IsNotEmpty,
IsOptional,
IsUUID,
- IsEnum,
+ IsIn,
IsDateString,
MaxLength,
} from 'class-validator';
@@ -35,7 +35,7 @@ export class CreatePaymentDto {
description:
'Manual settle tender. Use credit_card/debit_card here for offline card machines (no gateway). Gateway cards must use POST /payments/authorize. pix = Brazil PIX paid direct to the property.',
})
- @IsEnum([
+ @IsIn([
'credit_card',
'debit_card',
'cash',
diff --git a/apps/api/src/modules/payment/dto/list-payments.dto.ts b/apps/api/src/modules/payment/dto/list-payments.dto.ts
index 0b6f52fc..0a209f00 100644
--- a/apps/api/src/modules/payment/dto/list-payments.dto.ts
+++ b/apps/api/src/modules/payment/dto/list-payments.dto.ts
@@ -1,4 +1,4 @@
-import { IsOptional, IsUUID, IsEnum, IsInt, Min, Max } from 'class-validator';
+import { IsOptional, IsUUID, IsIn, IsInt, Min, Max } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
@@ -14,7 +14,7 @@ export class ListPaymentsDto {
@ApiPropertyOptional({ enum: ['pending', 'authorized', 'captured', 'settled', 'refunded', 'partially_refunded', 'failed', 'voided'] })
@IsOptional()
- @IsEnum(['pending', 'authorized', 'captured', 'settled', 'refunded', 'partially_refunded', 'failed', 'voided'])
+ @IsIn(['pending', 'authorized', 'captured', 'settled', 'refunded', 'partially_refunded', 'failed', 'voided'])
status?: string;
@ApiPropertyOptional({
@@ -30,7 +30,7 @@ export class ListPaymentsDto {
],
})
@IsOptional()
- @IsEnum([
+ @IsIn([
'credit_card',
'debit_card',
'cash',
diff --git a/apps/api/src/modules/policy/dto/create-cancellation-policy.dto.ts b/apps/api/src/modules/policy/dto/create-cancellation-policy.dto.ts
index a90bd8b5..5e50b51f 100644
--- a/apps/api/src/modules/policy/dto/create-cancellation-policy.dto.ts
+++ b/apps/api/src/modules/policy/dto/create-cancellation-policy.dto.ts
@@ -4,7 +4,7 @@ import {
IsOptional,
IsUUID,
IsBoolean,
- IsEnum,
+ IsIn,
IsInt,
IsNumberString,
MaxLength,
@@ -58,7 +58,7 @@ export class CreateCancellationPolicyDto {
@ApiPropertyOptional({ enum: CANCELLATION_PENALTY_TYPES, default: 'first_night' })
@IsOptional()
- @IsEnum(CANCELLATION_PENALTY_TYPES)
+ @IsIn(CANCELLATION_PENALTY_TYPES)
penaltyType?: (typeof CANCELLATION_PENALTY_TYPES)[number];
@ApiPropertyOptional({ example: '50.00', description: 'Required when penaltyType=percentage' })
@@ -71,7 +71,7 @@ export class CreateCancellationPolicyDto {
default: 'refund_if_refundable',
})
@IsOptional()
- @IsEnum(CANCELLATION_DEPOSIT_HANDLINGS)
+ @IsIn(CANCELLATION_DEPOSIT_HANDLINGS)
depositHandling?: (typeof CANCELLATION_DEPOSIT_HANDLINGS)[number];
@ApiPropertyOptional({ default: true })
diff --git a/apps/api/src/modules/pos/dto/post-pos-charge.dto.ts b/apps/api/src/modules/pos/dto/post-pos-charge.dto.ts
index 699cbbec..bd6ba31e 100644
--- a/apps/api/src/modules/pos/dto/post-pos-charge.dto.ts
+++ b/apps/api/src/modules/pos/dto/post-pos-charge.dto.ts
@@ -3,7 +3,7 @@ import {
IsNotEmpty,
IsOptional,
IsUUID,
- IsEnum,
+ IsIn,
IsDateString,
MaxLength,
} from 'class-validator';
@@ -43,7 +43,7 @@ export class PostPosChargeDto {
folioId!: string;
@ApiProperty({ enum: POS_CHARGE_TYPES, example: 'food_beverage' })
- @IsEnum(POS_CHARGE_TYPES)
+ @IsIn(POS_CHARGE_TYPES)
type!: (typeof POS_CHARGE_TYPES)[number];
@ApiProperty({ example: 'Dinner — Oceanfront Grill (check #4821)' })
diff --git a/apps/api/src/modules/rate-plan/dto/create-rate-plan.dto.ts b/apps/api/src/modules/rate-plan/dto/create-rate-plan.dto.ts
index b4311883..50603432 100644
--- a/apps/api/src/modules/rate-plan/dto/create-rate-plan.dto.ts
+++ b/apps/api/src/modules/rate-plan/dto/create-rate-plan.dto.ts
@@ -3,7 +3,7 @@ import {
IsNotEmpty,
IsOptional,
IsUUID,
- IsEnum,
+ IsIn,
IsBoolean,
IsInt,
IsNumber,
@@ -49,7 +49,7 @@ export class CreateRatePlanDto {
description?: string;
@ApiProperty({ enum: ['bar', 'derived', 'negotiated', 'package', 'promotional'] })
- @IsEnum(['bar', 'derived', 'negotiated', 'package', 'promotional'])
+ @IsIn(['bar', 'derived', 'negotiated', 'package', 'promotional'])
type!: string;
@ApiProperty({ example: '199.99' })
@@ -71,7 +71,7 @@ export class CreateRatePlanDto {
@ApiPropertyOptional({ enum: ['percentage', 'fixed'] })
@IsOptional()
- @IsEnum(['percentage', 'fixed'])
+ @IsIn(['percentage', 'fixed'])
@ValidateIf((o) => o.type === 'derived')
derivedAdjustmentType?: string;
@@ -88,7 +88,7 @@ export class CreateRatePlanDto {
@ApiPropertyOptional({ enum: ['room_only', 'breakfast', 'half_board', 'full_board', 'all_inclusive'] })
@IsOptional()
- @IsEnum(['room_only', 'breakfast', 'half_board', 'full_board', 'all_inclusive'])
+ @IsIn(['room_only', 'breakfast', 'half_board', 'full_board', 'all_inclusive'])
mealPlan?: string;
@ApiPropertyOptional({ example: '2024-01-01' })
diff --git a/apps/api/src/modules/rate-plan/dto/rate-adjustment.dto.ts b/apps/api/src/modules/rate-plan/dto/rate-adjustment.dto.ts
index 72662431..2b3b7106 100644
--- a/apps/api/src/modules/rate-plan/dto/rate-adjustment.dto.ts
+++ b/apps/api/src/modules/rate-plan/dto/rate-adjustment.dto.ts
@@ -1,9 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
-import { IsEnum, IsNumber, IsInt, Min, Max } from 'class-validator';
+import { IsIn, IsNumber, IsInt, Min, Max } from 'class-validator';
export class RateAdjustmentRuleDto {
@ApiProperty({ enum: ['percentage', 'fixed'] })
- @IsEnum(['percentage', 'fixed'])
+ @IsIn(['percentage', 'fixed'])
adjustmentType!: 'percentage' | 'fixed';
@ApiProperty({ description: 'Negative = discount, positive = surcharge' })
diff --git a/apps/api/src/modules/reservation/dto/bulk-action.dto.ts b/apps/api/src/modules/reservation/dto/bulk-action.dto.ts
index 71fb372f..fcce0b4b 100644
--- a/apps/api/src/modules/reservation/dto/bulk-action.dto.ts
+++ b/apps/api/src/modules/reservation/dto/bulk-action.dto.ts
@@ -1,7 +1,7 @@
import {
IsArray,
IsUUID,
- IsEnum,
+ IsIn,
IsOptional,
IsString,
ArrayMinSize,
@@ -19,7 +19,7 @@ export class BulkActionDto {
description: 'Action to apply to each reservation',
enum: ['check_in', 'check_out', 'cancel'],
})
- @IsEnum(['check_in', 'check_out', 'cancel'])
+ @IsIn(['check_in', 'check_out', 'cancel'])
action!: 'check_in' | 'check_out' | 'cancel';
@ApiPropertyOptional({ description: 'Reason — used as cancellation reason for the cancel action' })
diff --git a/apps/api/src/modules/reservation/dto/create-reservation.dto.ts b/apps/api/src/modules/reservation/dto/create-reservation.dto.ts
index 227941fa..814326f7 100644
--- a/apps/api/src/modules/reservation/dto/create-reservation.dto.ts
+++ b/apps/api/src/modules/reservation/dto/create-reservation.dto.ts
@@ -4,7 +4,7 @@ import {
IsInt,
IsOptional,
IsString,
- IsEnum,
+ IsIn,
Min,
MaxLength,
} from 'class-validator';
@@ -63,7 +63,7 @@ export class CreateReservationDto {
specialRequests?: string;
@ApiProperty({ enum: ['direct', 'ota', 'gds', 'phone', 'walk_in', 'agent', 'group', 'corporate'] })
- @IsEnum(['direct', 'ota', 'gds', 'phone', 'walk_in', 'agent', 'group', 'corporate'])
+ @IsIn(['direct', 'ota', 'gds', 'phone', 'walk_in', 'agent', 'group', 'corporate'])
source!: string;
@ApiPropertyOptional({ example: 'booking_com' })
diff --git a/apps/api/src/modules/reservation/dto/import-reservations.dto.ts b/apps/api/src/modules/reservation/dto/import-reservations.dto.ts
index 4197cda0..f1aa8464 100644
--- a/apps/api/src/modules/reservation/dto/import-reservations.dto.ts
+++ b/apps/api/src/modules/reservation/dto/import-reservations.dto.ts
@@ -8,7 +8,7 @@ import {
IsBoolean,
Min,
MaxLength,
- IsEnum,
+ IsIn,
ValidateNested,
ArrayMinSize,
} from 'class-validator';
@@ -74,7 +74,7 @@ export class CreateReservationRow {
currencyCode!: string;
@ApiProperty({ enum: ['direct', 'ota', 'gds', 'phone', 'walk_in', 'agent', 'group', 'corporate'] })
- @IsEnum(['direct', 'ota', 'gds', 'phone', 'walk_in', 'agent', 'group', 'corporate'])
+ @IsIn(['direct', 'ota', 'gds', 'phone', 'walk_in', 'agent', 'group', 'corporate'])
source!: string;
@ApiPropertyOptional({ default: 1 })
diff --git a/apps/api/src/modules/reservation/dto/list-reservations.dto.ts b/apps/api/src/modules/reservation/dto/list-reservations.dto.ts
index b40e646b..38da78f7 100644
--- a/apps/api/src/modules/reservation/dto/list-reservations.dto.ts
+++ b/apps/api/src/modules/reservation/dto/list-reservations.dto.ts
@@ -1,4 +1,4 @@
-import { IsUUID, IsOptional, IsEnum, IsDateString, IsInt, Min, Max, IsString } from 'class-validator';
+import { IsUUID, IsOptional, IsIn, IsDateString, IsInt, Min, Max, IsString } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
@@ -21,7 +21,7 @@ export class ListReservationsDto {
@ApiPropertyOptional({ enum: RESERVATION_STATUSES })
@IsOptional()
- @IsEnum(RESERVATION_STATUSES)
+ @IsIn(RESERVATION_STATUSES)
status?: string;
@ApiPropertyOptional({
diff --git a/apps/api/src/modules/room/dto/hk-observation.dto.ts b/apps/api/src/modules/room/dto/hk-observation.dto.ts
index 7939a87d..b95a9c21 100644
--- a/apps/api/src/modules/room/dto/hk-observation.dto.ts
+++ b/apps/api/src/modules/room/dto/hk-observation.dto.ts
@@ -1,4 +1,4 @@
-import { IsEnum, IsInt, IsOptional, IsUUID, Min } from 'class-validator';
+import { IsIn, IsInt, IsOptional, IsUUID, Min } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
@@ -6,7 +6,7 @@ const OCCUPANCY = ['unknown', 'vacant', 'occupied'] as const;
export class HkObservationDto {
@ApiProperty({ enum: OCCUPANCY })
- @IsEnum(OCCUPANCY)
+ @IsIn(OCCUPANCY)
occupancy!: (typeof OCCUPANCY)[number];
@ApiPropertyOptional()
diff --git a/apps/api/src/modules/service-requests/dto/service-request.dto.ts b/apps/api/src/modules/service-requests/dto/service-request.dto.ts
index 242b2923..bfc41ee0 100644
--- a/apps/api/src/modules/service-requests/dto/service-request.dto.ts
+++ b/apps/api/src/modules/service-requests/dto/service-request.dto.ts
@@ -2,7 +2,7 @@ import {
IsUUID,
IsOptional,
IsString,
- IsEnum,
+ IsIn,
IsInt,
} from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
@@ -36,7 +36,7 @@ export class CreateServiceRequestDto {
reservationId?: string;
@ApiProperty({ enum: TYPES })
- @IsEnum(TYPES)
+ @IsIn(TYPES)
type!: string;
@ApiPropertyOptional({ default: 0 })
@@ -73,7 +73,7 @@ export class UpdateServiceRequestDto {
@ApiPropertyOptional({ enum: TYPES })
@IsOptional()
- @IsEnum(TYPES)
+ @IsIn(TYPES)
type?: string;
@ApiPropertyOptional()
@@ -84,7 +84,7 @@ export class UpdateServiceRequestDto {
@ApiPropertyOptional({ enum: STATUSES })
@IsOptional()
- @IsEnum(STATUSES)
+ @IsIn(STATUSES)
status?: string;
@ApiPropertyOptional()
@@ -105,12 +105,12 @@ export class ListServiceRequestsDto {
@ApiPropertyOptional({ enum: STATUSES })
@IsOptional()
- @IsEnum(STATUSES)
+ @IsIn(STATUSES)
status?: string;
@ApiPropertyOptional({ enum: TYPES })
@IsOptional()
- @IsEnum(TYPES)
+ @IsIn(TYPES)
type?: string;
}
diff --git a/apps/api/src/modules/tax/dto/create-tax-rule.dto.ts b/apps/api/src/modules/tax/dto/create-tax-rule.dto.ts
index 9ba84a28..9909569d 100644
--- a/apps/api/src/modules/tax/dto/create-tax-rule.dto.ts
+++ b/apps/api/src/modules/tax/dto/create-tax-rule.dto.ts
@@ -6,7 +6,7 @@ import {
IsArray,
IsDateString,
IsObject,
- IsEnum,
+ IsIn,
IsInt,
ValidateIf,
Min,
@@ -20,7 +20,7 @@ export class CreateTaxRuleDto {
@IsString()
code!: string;
- @IsEnum(['percentage', 'flat_per_night', 'flat_per_stay', 'split_component'])
+ @IsIn(['percentage', 'flat_per_night', 'flat_per_stay', 'split_component'])
type!: 'percentage' | 'flat_per_night' | 'flat_per_stay' | 'split_component';
@IsString()
diff --git a/apps/api/src/modules/tax/dto/update-tax-rule.dto.ts b/apps/api/src/modules/tax/dto/update-tax-rule.dto.ts
index 3821f77d..d40487b4 100644
--- a/apps/api/src/modules/tax/dto/update-tax-rule.dto.ts
+++ b/apps/api/src/modules/tax/dto/update-tax-rule.dto.ts
@@ -6,7 +6,7 @@ import {
IsArray,
IsDateString,
IsObject,
- IsEnum,
+ IsIn,
IsInt,
ValidateIf,
Min,
@@ -22,7 +22,7 @@ export class UpdateTaxRuleDto {
@IsOptional()
code?: string;
- @IsEnum(['percentage', 'flat_per_night', 'flat_per_stay', 'split_component'])
+ @IsIn(['percentage', 'flat_per_night', 'flat_per_stay', 'split_component'])
@IsOptional()
type?: 'percentage' | 'flat_per_night' | 'flat_per_stay' | 'split_component';
diff --git a/apps/api/src/modules/turnaways/dto/turnaways.dto.ts b/apps/api/src/modules/turnaways/dto/turnaways.dto.ts
index 3b4e9175..4b6452a9 100644
--- a/apps/api/src/modules/turnaways/dto/turnaways.dto.ts
+++ b/apps/api/src/modules/turnaways/dto/turnaways.dto.ts
@@ -1,7 +1,7 @@
import {
IsBoolean,
IsDateString,
- IsEnum,
+ IsIn,
IsInt,
IsNotEmpty,
IsOptional,
@@ -29,7 +29,7 @@ export class CreateTurnawayReasonCodeDto {
description!: string;
@ApiProperty({ enum: TURNAWAY_TYPES })
- @IsEnum(TURNAWAY_TYPES)
+ @IsIn(TURNAWAY_TYPES)
type!: (typeof TURNAWAY_TYPES)[number];
@ApiPropertyOptional({ default: true })
@@ -89,7 +89,7 @@ export class CreateTurnawayDto {
reasonCodeId?: string;
@ApiProperty({ enum: TURNAWAY_TYPES })
- @IsEnum(TURNAWAY_TYPES)
+ @IsIn(TURNAWAY_TYPES)
type!: (typeof TURNAWAY_TYPES)[number];
@ApiPropertyOptional({ example: 'phone' })
diff --git a/apps/api/src/modules/waitlist/dto/waitlist.dto.ts b/apps/api/src/modules/waitlist/dto/waitlist.dto.ts
index 4af2b07f..d1f7fcb6 100644
--- a/apps/api/src/modules/waitlist/dto/waitlist.dto.ts
+++ b/apps/api/src/modules/waitlist/dto/waitlist.dto.ts
@@ -1,7 +1,7 @@
import {
IsDateString,
IsEmail,
- IsEnum,
+ IsIn,
IsInt,
IsOptional,
IsString,
@@ -158,7 +158,7 @@ export class ListWaitlistEntriesDto {
@ApiPropertyOptional({ enum: WAITLIST_STATUSES })
@IsOptional()
- @IsEnum(WAITLIST_STATUSES)
+ @IsIn(WAITLIST_STATUSES)
status?: (typeof WAITLIST_STATUSES)[number];
}
diff --git a/apps/api/src/validation-messages.spec.ts b/apps/api/src/validation-messages.spec.ts
new file mode 100644
index 00000000..3930d626
--- /dev/null
+++ b/apps/api/src/validation-messages.spec.ts
@@ -0,0 +1,103 @@
+/**
+ * Every 400 this API returns for a bad enum value must name the values it
+ * would have accepted.
+ *
+ * WHY THIS EXISTS. `@IsEnum(['a','b'])` reads correctly and validates
+ * correctly, but produces the message
+ *
+ * type must be one of the following values:
+ *
+ * with nothing after the colon. class-validator's `IsEnum` is for TypeScript
+ * enum OBJECTS: it validates with `Object.keys(entity).map(k => entity[k])`,
+ * which happens to read an array's values, but it builds its message from
+ * `Object.entries(entity).filter(([k]) => isNaN(parseInt(k)))`, which drops
+ * every key of an array because array keys are all numeric. So the check
+ * passes an array through and the message comes out empty. `@IsIn([...])` is
+ * the decorator for a list of allowed values, and interpolates the list.
+ *
+ * Every enum decorator in the API was affected -- 88 across 64 files -- so a
+ * client sending `type: "beverage"` was told only that it was wrong, never
+ * that `food_beverage` exists.
+ *
+ * THE TEST READS METADATA, NOT SOURCE. Grepping for the decorator would only
+ * find the spelling it was told to look for; class-validator's own registry
+ * holds what will actually be evaluated at runtime, including the copies
+ * PartialType and OmitType generate, which no grep sees.
+ */
+import 'reflect-metadata';
+import { describe, expect, it } from 'vitest';
+import { getMetadataStorage } from 'class-validator';
+
+// Load every DTO so its decorators register. A file that is never imported
+// contributes no metadata, which would make this test pass by not looking.
+// `!**/*.spec.ts` matters: one DTO directory holds its own spec, and pulling
+// it in here would run that file's `describe` blocks inside this one.
+const dtoModules = import.meta.glob(['./modules/**/dto/**/*.ts', '!**/*.spec.ts'], { eager: true });
+
+interface Meta {
+ // `type` is 'customValidation' for EVERY decorator built on ValidateBy,
+ // which is all of them here. `name` is the discriminator. Filtering on
+ // `type === 'isEnum'` matches nothing and makes this file pass while the
+ // bug it exists to catch is present.
+ type?: string;
+ name?: string;
+ target?: { name?: string };
+ propertyName?: string;
+ constraints?: unknown[];
+}
+
+function allMetadata(): Meta[] {
+ const storage = getMetadataStorage() as unknown as {
+ validationMetadatas: Map