Validation
COMET DXP provides custom validation decorators for use with class-validator in NestJS DTOs.
@IsUndefinable()
Use @IsUndefinable() to allow a field to be undefined while still validating it when a value is provided.
This is more specific than the @IsOptional() decorator from class-validator, which allows both undefined and null.
A typical use case is a small inline form that only updates a subset of an entity's fields (e.g., only the name). Fields not present in the form are omitted from the request body entirely (undefined) and should be left unchanged on the server.
import { IsUndefinable } from "@comet/cms-api";
import { IsString } from "class-validator";
class UpdateProductInput {
@IsUndefinable()
@IsString()
name?: string;
@IsUndefinable()
@IsString()
description?: string;
}
@IsNullable()
Use @IsNullable() to allow a field to be null while still validating it when a value is provided.
This is more specific than the @IsOptional() decorator, which also allows undefined.
A typical use case is resetting or clearing an optional value (e.g., removing a product's assigned category).
import { IsNullable } from "@comet/cms-api";
import { IsString } from "class-validator";
class UpdateProductInput {
@IsNullable()
@IsString()
category: string | null;
}
Replacing @IsOptional()
The @IsOptional() decorator from class-validator allows both undefined and null. This can lead to unintentional bugs.
Use @IsUndefinable() or @IsNullable() to be more specific about which values are allowed:
| Decorator | Allows undefined | Allows null |
|---|---|---|
@IsOptional() | ✅ | ✅ |
@IsUndefinable() | ✅ | ❌ |
@IsNullable() | ❌ | ✅ |
PartialType
COMET DXP provides its own PartialType helper (from @comet/cms-api) that uses @IsUndefinable() instead of @IsOptional().
When creating partial input types, use PartialType from @comet/cms-api instead of @nestjs/mapped-types:
import { PartialType } from "@comet/cms-api";
import { CreateProductInput } from "./create-product.input";
export class UpdateProductInput extends PartialType(CreateProductInput) {}
CometValidationException
CometValidationException is the exception COMET DXP raises when validation fails. It extends CometException, the base class for business-logic errors that should be reported back to the client as a 400 Bad Request rather than logged as a server error. In addition to a message, it carries the list of ValidationErrors produced by class-validator:
class CometValidationException extends CometException {
constructor(
message: string,
readonly errors?: ValidationError[],
);
}
Automatic validation errors
DTO validation failures are turned into a CometValidationException by ValidationExceptionFactory. Wire it up as the exceptionFactory of a NestJS ValidationPipe so that every rejected DTO produces a CometValidationException instead of the default BadRequestException:
// main.ts
import { ExceptionFilter, ValidationExceptionFactory } from "@comet/cms-api";
import { ValidationPipe } from "@nestjs/common";
app.useGlobalFilters(new ExceptionFilter(config.debug));
app.useGlobalPipes(
new ValidationPipe({
exceptionFactory: ValidationExceptionFactory,
transform: true,
forbidNonWhitelisted: true,
whitelist: true,
}),
);
For dynamically typed args (for example a scope or an input whose type is only known at runtime), use DynamicDtoValidationPipe from @comet/cms-api, which is a ValidationPipe preconfigured with ValidationExceptionFactory.
Throwing it manually
Throw a CometValidationException from a resolver or service for validation rules that can't be expressed with decorators — for example a uniqueness check that requires a database lookup:
import { CometValidationException } from "@comet/cms-api";
if (!(await this.redirectService.isRedirectSourceAvailable(input.source, scope))) {
throw new CometValidationException("Validation failed");
}
Error response
The ExceptionFilter catches every CometException and converts it into a 400 Bad Request. For a CometValidationException, the collected ValidationErrors are exposed under validationErrors:
{
"statusCode": 400,
"message": "Validation failed",
"error": "CometValidationException",
"validationErrors": [{ "property": "source" }]
}
The same shape is returned for both GraphQL and REST requests. Register the ExceptionFilter globally (see the main.ts example above) so these exceptions are translated consistently across the API.