Rules Hub
Coding Rules Library
Rule priority, scope & exceptions
Use this to align rules with the senior-level structure (P0/P1/P2, scope, exceptions/tradeoffs).
Return typed result unions from validators instead of booleans or throws
Validation functions should return a discriminated union carrying both the verdict and the error message, not a bare boolean and not a thrown exception.
Bad example
| 1 | // Bare boolean: caller has to re-derive the reason, and nothing ties the message to the failure case |
| 2 | export const validateMaxAge = (maxAgeDays: number, maxLimit: number): boolean => { |
| 3 | return maxAgeDays >= 1 && maxAgeDays <= maxLimit; |
| 4 | }; |
| 5 |
|
| 6 | // Route: error message duplicated / disconnected from the validator |
| 7 | if (!validateMaxAge(maxAgeDays, 365)) { |
| 8 | return res.status(400).json({ error: 'maxAgeDays must be between 1 and 365 days.' }); |
| 9 | } |
| 10 |
|
| 11 | // Or: throwing couples the validator to control flow and transport |
| 12 | export const validateMaxAgeOrThrow = (maxAgeDays: number, maxLimit: number): void => { |
| 13 | if (maxAgeDays < 1 || maxAgeDays > maxLimit) throw new Error('bad maxAge'); |
| 14 | }; |
Explanation (EN)
A boolean forces every caller to re-author the error message, so the wording drifts and the message lives far from the rule that produced it. Throwing pushes transport decisions (status code, body) into the validator or forces try/catch around pure logic. Neither lets TypeScript narrow to a guaranteed-present error field.
Objašnjenje (HR)
Boolean prisiljava svakog pozivatelja da ponovno sastavi poruku o pogresci, pa se tekst razilazi i poruka zivi daleko od pravila koje ju je proizvelo. Bacanje iznimke gura odluke o transportu (status kod, tijelo odgovora) u validator ili namece try/catch oko ciste logike. Nijedno ne omogucuje TypeScriptu da suzi tip na zajamceno prisutno polje pogreske.
Good example
| 1 | export const validateMaxAge = ( |
| 2 | maxAgeDays: number, |
| 3 | maxLimit: number, |
| 4 | ): { valid: true } | { valid: false; error: string } => { |
| 5 | if (maxAgeDays < 1 || maxAgeDays > maxLimit) { |
| 6 | return { valid: false, error: `maxAgeDays must be between 1 and ${maxLimit} days.` }; |
| 7 | } |
| 8 | return { valid: true }; |
| 9 | }; |
| 10 |
|
| 11 | // Caller: type-narrowed access to error, transport concerns stay in the route |
| 12 | const result = validateMaxAge(maxAgeDays, MAX_AGE_LIMIT_DAYS); |
| 13 | if (!result.valid) { |
| 14 | return res.status(400).json({ error: result.error }); |
| 15 | } |
Explanation (EN)
The union keeps the message next to the rule that produced it, so callers never reinvent it. The `valid` discriminant lets TypeScript expose `error` only on the failure branch, catching mistakes at compile time. The function stays pure and transport-agnostic, so it is trivially unit-testable and reusable across multiple routes.
Objašnjenje (HR)
Unija drzi poruku uz pravilo koje ju je proizvelo, pa je pozivatelji nikad ne izmisljaju iznova. Diskriminanta `valid` omogucuje TypeScriptu da otkrije `error` samo na grani neuspjeha, hvatajuci greske u vrijeme prevodenja. Funkcija ostaje cista i neovisna o transportu, pa je trivijalno testabilna jedinicnim testovima i ponovno upotrebljiva u vise ruta.
Notes (EN)
Mirrors the shape Zod/Valibot return (`{ success } | { error }`); adopting it by hand keeps small in-house validators consistent with schema libraries you may adopt later.
Bilješke (HR)
Odgovara obliku koji vracaju Zod/Valibot (`{ success } | { error }`); rucno usvajanje cini male interne validatore dosljednima bibliotekama shema koje biste kasnije mogli usvojiti.
Exceptions / Tradeoffs (EN)
For deep nested validation or when collecting many errors at once, a richer result type (array of errors, or a schema library like Zod that already returns a typed result) is preferable to a single-error union.
Iznimke / Tradeoffi (HR)
Za duboku ugnijezdenu validaciju ili kad treba prikupiti vise gresaka odjednom, bogatiji tip rezultata (niz gresaka ili biblioteka sheme poput Zod-a koja vec vraca tipizirani rezultat) je bolji od unije s jednom greskom.