qwen_2.5_model / src /common /utils /strict-boolean.ts
Muhammad Noman
Deploy OpenWA to Hugging Face Spaces
46252cd
Raw
History Blame Contribute Delete
3.19 kB
import { Transform, TransformFnParams } from 'class-transformer';
/**
* Accept a boolean only when the caller spelled one unambiguously, and leave anything else
* untouched so `@IsBoolean()` rejects it.
*
* The global `ValidationPipe` runs with `transformOptions.enableImplicitConversion: true`
* (`src/config/app-validation.ts`). For a `boolean`-typed property that makes class-transformer
* cast *any* non-empty string to `true` β€” `'false'`, `'0'` and `'no'` all become `true` β€” and it
* happens before `@IsBoolean()` ever runs, so the validator can never reject it. Requests reach a
* DTO as strings whenever the body arrives through the global `express.urlencoded` parser
* (`src/main.ts`), whose scalars are always strings.
*
* The callback deliberately reads `obj[key]` (the untouched plain source) instead of `value`:
* implicit conversion has already run by the time a `@Transform` callback is invoked, so `value`
* is the coerced `true` and the caller's original spelling is only still recoverable from `obj`.
*
* Only exact `'true'` / `'false'` are mapped. Anything else keeps its original value and fails
* validation β€” for a permission flag, an ambiguous spelling is safer refused than guessed.
*
* Because it reads `obj` and never `value`, it does NOT compose: class-transformer threads each
* `@Transform` result into the next, and this one discards whatever a previously-registered
* transform produced. Do not stack another `@Transform` on a property that uses it.
*/
export function coerceStrictBoolean({ obj, key }: Pick<TransformFnParams, 'obj' | 'key'>): unknown {
const raw = (obj as Record<string, unknown> | undefined)?.[key];
if (typeof raw === 'boolean') return raw;
if (raw === 'true') return true;
if (raw === 'false') return false;
return raw;
}
/** Property decorator form of {@link coerceStrictBoolean}. Pair it with `@IsBoolean()`. */
export const ToStrictBoolean = (): PropertyDecorator => Transform(coerceStrictBoolean);
/**
* The numeric counterpart, for the same reason and with the same `obj[key]` trick.
*
* Implicit conversion applies `Number(value)` to a `number`-typed property, and `Number('')` and
* `Number(' ')` are both `0` β€” so an empty form field arrives as a real, valid zero rather than
* being rejected as missing. On a field where `0` is itself meaningful (a disappearing-message
* timer, where `0` means "off") that silently performs an action the caller never asked for.
*
* Only a genuine number or a string that is entirely a finite number is converted. Everything else
* keeps its original value and fails `@IsInt()`/`@IsNumber()`.
*/
export function coerceStrictNumber({ obj, key }: Pick<TransformFnParams, 'obj' | 'key'>): unknown {
const raw = (obj as Record<string, unknown> | undefined)?.[key];
if (typeof raw === 'number') return raw;
if (typeof raw !== 'string' || raw.trim() === '') return raw;
const parsed = Number(raw);
return Number.isFinite(parsed) ? parsed : raw;
}
/** Property decorator form of {@link coerceStrictNumber}. Pair it with `@IsInt()` / `@IsNumber()`. */
export const ToStrictNumber = (): PropertyDecorator => Transform(coerceStrictNumber);