Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Custom Exceptions - specjalne sytuacje rzymskie

Strażniku porządku! Konsul Caesar.js zauważył, że standardowe sygnały alarmowe nie zawsze oddają specyfikę sytuacji w rzymskim kastrum. Czas stworzyć własny system ostrzeżeń - Custom Exceptions, które precyzyjnie opiszą każdą nietypową sytuację w naszym legionie!

Na rubieżach Imperium legioniści spotykają się z sytuacjami, których nie przewidzieli twórcy zwykłych kodów błędów. Niespodziewany najazd, bunt w szeregach, przeklęty tribut - każda z tych sytuacji wymaga specjalnego protokołu reakcji. Custom Exceptions w NestJS pozwalają nam stworzyć własne, dokładne opisy problemów.

Podstawowe Custom Exceptions

Hierarchia Rzymskich Wyjątków

1// exceptions/base/legionariusze.exception.ts
2export abstract class LegionaryException extends Error {
3 public readonly code: string;
4 public readonly context: Record<string, any>;
5 public readonly severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
6 public readonly recoverable: boolean;
7 public readonly timestamp: Date;
8
9 constructor(
10 message: string,
11 code: string,
12 severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL' = 'MEDIUM',
13 recoverable: boolean = true,
14 context: Record<string, any> = {}
15 ) {
16 super(message);
17 this.name = this.constructor.name;
18 this.code = code;
19 this.severity = severity;
20 this.recoverable = recoverable;
21 this.context = context;
22 this.timestamp = new Date();
23 
24 // Zachowanie stack trace
25 Error.captureStackTrace(this, this.constructor);
26 }
27
28 toJSON() {
29 return {
30 name: this.name,
31 message: this.message,
32 code: this.code,
33 severity: this.severity,
34 recoverable: this.recoverable,
35 context: this.context,
36 timestamp: this.timestamp.toISOString(),
37 stack: this.stack
38 };
39 }
40
41 // Metody pomocnicze
42 isCritical(): boolean {
43 return this.severity === 'CRITICAL';
44 }
45
46 isRecoverable(): boolean {
47 return this.recoverable;
48 }
49
50 getRecoveryInstructions(): string[] {
51 return ['Skontaktuj się z centurionem', 'Sprawdź dokumentację'];
52 }
53}

Tribute-Related Exceptions

1// exceptions/tribute/tribute.exceptions.ts
2export class TributeNotFoundException extends LegionaryException {
3 constructor(tributeId: string | number, searchLocation?: string) {
4 super(
5 `tributy o ID ${tributeId} nie został znaleziony${searchLocation ? ` w lokacji: ${searchLocation}` : ''}`,
6 'TRIBUTE_NOT_FOUND',
7 'MEDIUM',
8 true,
9 { tributeId, searchLocation }
10 );
11 }
12
13 getRecoveryInstructions(): string[] {
14 return [
15 'Sprawdź czy ID tributy jest poprawne',
16 'Sprawdź czy tributy nie został przeniesiony',
17 'Użyj funkcji wyszukiwania tributów',
18 'Sprawdź historię transakcji'
19 ];
20 }
21}
22
23export class TributeAlreadyClaimedException extends LegionaryException {
24 constructor(tributeId: string | number, claimedBy: string, claimedAt: Date) {
25 super(
26 `tributy ${tributeId} został już przejęty przez ${claimedBy} dnia ${claimedAt.toLocaleDateString()}`,
27 'TRIBUTE_ALREADY_CLAIMED',
28 'HIGH',
29 false,
30 { tributeId, claimedBy, claimedAt }
31 );
32 }
33
34 getRecoveryInstructions(): string[] {
35 return [
36 'Sprawdź dostępne tributy w innych lokacjach',
37 'Skontaktuj się z aktualnym właścicielem w sprawie wymiany',
38 'Czekaj na zwolnienie tributy',
39 'Poszukaj podobnych tributów'
40 ];
41 }
42}
43
44export class CursedTributeException extends LegionaryException {
45 constructor(
46 tributeId: string | number,
47 curseName: string,
48 curseLevel: 'MINOR' | 'MAJOR' | 'LEGENDARY'
49 ) {
50 const severity = curseLevel === 'LEGENDARY' ? 'CRITICAL' : 
51 curseLevel === 'MAJOR' ? 'HIGH' : 'MEDIUM';
52 
53 super(
54 `tributy ${tributeId} jest objęty klątwą: ${curseName}. Poziom: ${curseLevel}`,
55 'CURSED_TRIBUTE',
56 severity,
57 curseLevel !== 'LEGENDARY',
58 { tributeId, curseName, curseLevel }
59 );
60 }
61
62 getRecoveryInstructions(): string[] {
63 const { curseLevel } = this.context;
64 
65 if (curseLevel === 'LEGENDARY') {
66 return [
67 'NATYCHMIAST opuść obszar tributy!',
68 'Skontaktuj się z centurionem',
69 'Wezwij specjalistę od klątw',
70 'Nie dotykaj tributy bez ochrony'
71 ];
72 }
73 
74 if (curseLevel === 'MAJOR') {
75 return [
76 'Użyj ochronnych amuletów',
77 'Pracuj w grupie co najmniej 3 legionariuszów',
78 'Przeprowadź rytuał oczyszczenia',
79 'Skonsultuj się z szamanem legionu'
80 ];
81 }
82 
83 return [
84 'Zachowaj ostrożność podczas przenoszenia',
85 'Monitoruj swoje samopoczucie',
86 'Użyj rękawic ochronnych'
87 ];
88 }
89}
90
91export class TributeCapacityExceededException extends LegionaryException {
92 constructor(
93 chestId: string | number,
94 currentCapacity: number,
95 maxCapacity: number,
96 attemptedWeight: number
97 ) {
98 super(
99 `Skrzynia ${chestId} przekroczyłaby pojemność! Aktualne: ${currentCapacity}/${maxCapacity}kg, próba dodania: ${attemptedWeight}kg`,
100 'TRIBUTE_CAPACITY_EXCEEDED',
101 'MEDIUM',
102 true,
103 { chestId, currentCapacity, maxCapacity, attemptedWeight, availableSpace: maxCapacity - currentCapacity }
104 );
105 }
106
107 getRecoveryInstructions(): string[] {
108 const { availableSpace } = this.context;
109 
110 return [
111 `Dostępne miejsce: ${availableSpace}kg`,
112 'Przenieś część tributów do innej skrzyni',
113 'Znajdź skrzynię o większej pojemności',
114 'Sprawdź czy wszystkie tributy są niezbędne',
115 'Rozważ sprzedaż mniej wartościowych przedmiotów'
116 ];
117 }
118}

Legion-Related Exceptions

1// exceptions/legion/legion.exceptions.ts
2export class InsufficientLegionException extends LegionaryException {
3 constructor(
4 operation: string,
5 requiredLegion: number,
6 availableLegion: number,
7 requiredRanks?: string[]
8 ) {
9 super(
10 `Operacja "${operation}" wymaga ${requiredLegion} legionariuszów, dostępnych: ${availableLegion}${requiredRanks ? `. Wymagane rangi: ${requiredRanks.join(', ')}` : ''}`,
11 'INSUFFICIENT_LEGION',
12 'HIGH',
13 true,
14 { operation, requiredLegion, availableLegion, shortage: requiredLegion - availableLegion, requiredRanks }
15 );
16 }
17
18 getRecoveryInstructions(): string[] {
19 const { shortage, requiredRanks } = this.context;
20 
21 const instructions = [
22 `Zrekrutuj co najmniej ${shortage} dodatkowych legionariuszów`,
23 'Sprawdź dostępność legionu w portach',
24 'Rozważ czasowe wynajęcie legionariuszów'
25 ];
26 
27 if (requiredRanks?.length > 0) {
28 instructions.push(`Upewnij się że masz legionariuszów o rangach: ${requiredRanks.join(', ')}`);
29 }
30 
31 return instructions;
32 }
33}
34
35export class LegionMutinyException extends LegionaryException {
36 constructor(
37 leader: string,
38 supporters: number,
39 grievances: string[]
40 ) {
41 super(
42 `Bunt legionu! Przywódca: ${leader}, liczba buntowników: ${supporters}. Powody: ${grievances.join(', ')}`,
43 'LEGION_MUTINY',
44 'CRITICAL',
45 true,
46 { leader, supporters, grievances, timestamp: new Date() }
47 );
48 }
49
50 getRecoveryInstructions(): string[] {
51 return [
52 'NATYCHMIAST zwołaj zebranie legionu',
53 'Wysłuchaj skarg buntowników',
54 'Przeprowadź negocjacje z przywódcą buntu',
55 'Rozważ ustępstwa w sprawie warunków pracy',
56 'W ostateczności użyj prawa dowódcy legionu',
57 'Skontaktuj się z legatem legionu'
58 ];
59 }
60}
61
62export class LegionaryDeserterException extends LegionaryException {
63 constructor(
64 userName: string,
65 rank: string,
66 reason: string,
67 takenItems?: string[]
68 ) {
69 super(
70 `Legionariusz ${userName} (${rank}) opuścił fort. Powód: ${reason}${takenItems?.length ? `. Zabrał: ${takenItems.join(', ')}` : ''}`,
71 'LEGIONARY_DESERTER',
72 'HIGH',
73 true,
74 { userName, rank, reason, takenItems, desertion_time: new Date() }
75 );
76 }
77
78 getRecoveryInstructions(): string[] {
79 const { rank, takenItems } = this.context;
80 
81 const instructions = [
82 `Znajdź zastępstwo dla rangi: ${rank}`,
83 'Zaktualizuj listę legionu',
84 'Sprawdź czy nie zabrał ważnych przedmiotów'
85 ];
86 
87 if (takenItems?.length > 0) {
88 instructions.push('Rozważ wydanie listu gończego za skradzione przedmioty');
89 }
90 
91 return instructions;
92 }
93}

Legion-Related Exceptions

1// exceptions/cohort/cohort.exceptions.ts
2export class LegionDamageException extends LegionaryException {
3 constructor(
4 cohortName: string,
5 damageType: 'MINOR' | 'MAJOR' | 'CRITICAL',
6 affectedSystems: string[],
7 repairCost: number
8 ) {
9 const severity = damageType === 'CRITICAL' ? 'CRITICAL' : 
10 damageType === 'MAJOR' ? 'HIGH' : 'MEDIUM';
11 
12 super(
13 `Kohorta ${cohortName} doznała uszkodzeń typu ${damageType}. Dotknięte systemy: ${affectedSystems.join(', ')}. Koszt naprawy: ${repairCost} denarów`,
14 'COHORT_DAMAGE',
15 severity,
16 damageType !== 'CRITICAL',
17 { cohortName, damageType, affectedSystems, repairCost, combatReady: damageType !== 'CRITICAL' }
18 );
19 }
20
21 getRecoveryInstructions(): string[] {
22 const { damageType, repairCost, combatReady } = this.context;
23 
24 if (!combatReady) {
25 return [
26 'NATYCHMIAST przerwij operacje!',
27 'Ewakuuj żołnierzy z zagrożonych obszarów',
28 'Wezwij pomoc legionu',
29 'Przygotuj łodzie ratunkowe',
30 'Nie próbuj żeglować w tym stanie!'
31 ];
32 }
33 
34 const instructions = [
35 `Koszt naprawy: ${repairCost} dublonów`,
36 'Znajdź najbliższy port z stocznią',
37 'Zamów potrzebne materiały do naprawy'
38 ];
39 
40 if (damageType === 'MAJOR') {
41 instructions.push('Ograniczaj prędkość do 50% maksymalnej');
42 }
43 
44 return instructions;
45 }
46}
47
48export class LegionOverloadException extends LegionaryException {
49 constructor(
50 cohortName: string,
51 currentWeight: number,
52 maxWeight: number,
53 excessWeight: number
54 ) {
55 super(
56 `Kohorta ${cohortName} jest przeciążona! Waga: ${currentWeight}/${maxWeight}t (nadwyżka: ${excessWeight}t)`,
57 'COHORT_OVERLOAD',
58 'HIGH',
59 true,
60 { cohortName, currentWeight, maxWeight, excessWeight, overloadPercentage: (excessWeight / maxWeight) * 100 }
61 );
62 }
63
64 getRecoveryInstructions(): string[] {
65 const { excessWeight, overloadPercentage } = this.context;
66 
67 const instructions = [
68 `Usuń co najmniej ${excessWeight}t ładunku`,
69 'Sprawdź rozmieszczenie ciężaru w taborze',
70 'Rozważ przeniesienie części ładunku na inny fort'
71 ];
72 
73 if (overloadPercentage > 20) {
74 instructions.unshift('PILNE: Kohorta może zostać rozbita!');
75 }
76 
77 return instructions;
78 }
79}

Weather-Related Exceptions

1// exceptions/weather/weather.exceptions.ts
2export class DangerousWeatherException extends LegionaryException {
3 constructor(
4 weatherType: 'STORM' | 'HURRICANE' | 'FOG' | 'CALM' | 'TSUNAMI',
5 intensity: number, // 1-10
6 duration: number, // w godzinach
7 affectedArea: string
8 ) {
9 const severity = intensity >= 8 ? 'CRITICAL' : 
10 intensity >= 6 ? 'HIGH' : 'MEDIUM';
11 
12 super(
13 `Niebezpieczna pogoda: ${weatherType} o intensywności ${intensity}/10 w obszarze ${affectedArea}. Czas trwania: ${duration}h`,
14 'DANGEROUS_WEATHER',
15 severity,
16 intensity < 8,
17 { weatherType, intensity, duration, affectedArea, safeToSail: intensity < 6 }
18 );
19 }
20
21 getRecoveryInstructions(): string[] {
22 const { weatherType, intensity, safeToSail, duration } = this.context;
23 
24 if (!safeToSail) {
25 return [
26 'NATYCHMIAST szukaj bezpiecznej przystani!',
27 'Nie wypływaj w takich warunkach!',
28 'Przygotuj fort na możliwe uszkodzenia',
29 'Sprawdź sprzęt ratunkowy',
30 `Oczekiwany czas trwania: ${duration} godzin`
31 ];
32 }
33 
34 const instructions = [
35 'Zwiększ czujność w systemie',
36 'Sprawdź zabezpieczenie ładunku',
37 'Monitoruj prognozy pogody'
38 ];
39 
40 switch (weatherType) {
41 case 'FOG':
42 instructions.push('Używaj sygnałów dźwiękowych', 'Zmniejsz prędkość');
43 break;
44 case 'STORM':
45 instructions.push('Zabezpiecz wszystkie luźne przedmioty', 'Przygotuj pumpy wodne');
46 break;
47 case 'CALM':
48 instructions.push('Oszczędzaj zapasy wody i żywności', 'Przygotuj wiosła awaryjne');
49 break;
50 }
51 
52 return instructions;
53 }
54}

Business Logic Exceptions

1// exceptions/business/trading.exceptions.ts
2export class InsufficientFundsException extends LegionaryException {
3 constructor(
4 requiredAmount: number,
5 availableAmount: number,
6 currency: string = 'dublony'
7 ) {
8 super(
9 `Niewystarczające środki! Wymagane: ${requiredAmount} ${currency}, dostępne: ${availableAmount} ${currency}`,
10 'INSUFFICIENT_FUNDS',
11 'MEDIUM',
12 true,
13 { requiredAmount, availableAmount, shortage: requiredAmount - availableAmount, currency }
14 );
15 }
16
17 getRecoveryInstructions(): string[] {
18 const { shortage, currency } = this.context;
19 
20 return [
21 `Zdobądź dodatkowe ${shortage} ${currency}`,
22 'Sprzedaj niepotrzebne tributy',
23 'Wykonaj misje dodatkowe',
24 'Rozważ pożyczkę od innych legionów',
25 'Negocjuj niższą cenę'
26 ];
27 }
28}
29
30export class InvalidTradingPartnerException extends LegionaryException {
31 constructor(
32 partnerName: string,
33 reason: 'HOSTILE' | 'BANKRUPT' | 'UNRELIABLE' | 'BANNED'
34 ) {
35 const reasonMessages = {
36 'HOSTILE': 'jest wrogo nastawiony',
37 'BANKRUPT': 'jest bankrutem',
38 'UNRELIABLE': 'jest nierzetelny',
39 'BANNED': 'jest na czarnej liście'
40 };
41 
42 super(
43 `Nie można handlować z ${partnerName} - ${reasonMessages[reason]}`,
44 'INVALID_TRADING_PARTNER',
45 reason === 'HOSTILE' ? 'HIGH' : 'MEDIUM',
46 true,
47 { partnerName, reason }
48 );
49 }
50
51 getRecoveryInstructions(): string[] {
52 const { reason } = this.context;
53 
54 switch (reason) {
55 case 'HOSTILE':
56 return [
57 'Znajdź neutralnego pośrednika',
58 'Zaoferuj flagę rozejmu',
59 'Sprawdź czy można naprawić relacje',
60 'Poszukaj alternatywnych partnerów'
61 ];
62 case 'BANKRUPT':
63 return [
64 'Sprawdź czy partner odzyskał wypłacalność',
65 'Rozważ handel na kredyt',
66 'Znajdź innych kupców'
67 ];
68 case 'UNRELIABLE':
69 return [
70 'Żądaj płatności z góry',
71 'Użyj usług depozytu',
72 'Wynegocjuj gwarancje',
73 'Rozważ zerwanie współpracy'
74 ];
75 case 'BANNED':
76 return [
77 'Sprawdź powód umieszczenia na czarnej liście',
78 'Złóż wniosek o rehabilitację',
79 'Znajdź innych partnerów'
80 ];
81 default:
82 return ['Skontaktuj się z kwestorem legionu'];
83 }
84 }
85}

Exception Factory

Fabryka do tworzenia wyjątków z kontekstem:

1// exceptions/factory/exception.factory.ts
2@Injectable()
3export class LegionaryExceptionFactory {
4 private readonly logger = new Logger(LegionaryExceptionFactory.name);
5
6 createTributeNotFound(tributeId: string | number, context?: any): TributeNotFoundException {
7 const searchLocation = context?.cohort ? `fort ${context.cohort}` : undefined;
8 const exception = new TributeNotFoundException(tributeId, searchLocation);
9 
10 this.logException(exception, context);
11 return exception;
12 }
13
14 createInsufficientLegion(
15 operation: string,
16 required: number,
17 available: number,
18 context?: any
19 ): InsufficientLegionException {
20 const exception = new InsufficientLegionException(
21 operation,
22 required,
23 available,
24 context?.requiredRanks
25 );
26 
27 this.logException(exception, context);
28 return exception;
29 }
30
31 createLegionDamage(
32 cohortName: string,
33 damageType: 'MINOR' | 'MAJOR' | 'CRITICAL',
34 affectedSystems: string[],
35 repairCost: number,
36 context?: any
37 ): LegionDamageException {
38 const exception = new LegionDamageException(cohortName, damageType, affectedSystems, repairCost);
39 
40 this.logException(exception, context);
41 
42 // Automatyczne powiadomienia dla krytycznych uszkodzeń
43 if (damageType === 'CRITICAL') {
44 this.sendCriticalAlert(exception, context);
45 }
46 
47 return exception;
48 }
49
50 createBusinessLogicException(
51 message: string,
52 code: string,
53 context?: any
54 ): LegionaryException {
55 class DynamicLegionaryException extends LegionaryException {}
56 
57 const exception = new DynamicLegionaryException(message, code, 'MEDIUM', true, context);
58 this.logException(exception, context);
59 
60 return exception;
61 }
62
63 private logException(exception: LegionaryException, context?: any): void {
64 this.logger.error(`Exception created: ${exception.code}`, {
65 message: exception.message,
66 code: exception.code,
67 severity: exception.severity,
68 context: exception.context,
69 additionalContext: context
70 });
71 }
72
73 private sendCriticalAlert(exception: LegionDamageException, context?: any): void {
74 // Implementacja systemu alertów dla krytycznych sytuacji
75 this.logger.fatal('CRITICAL COHORT DAMAGE DETECTED', {
76 exception: exception.toJSON(),
77 context,
78 requiresImmediateAttention: true
79 });
80 }
81}

Integration with Guards i Services

1// guards/tribute.guard.ts
2@Injectable()
3export class TributeGuard implements CanActivate {
4 constructor(private exceptionFactory: LegionaryExceptionFactory) {}
5
6 async canActivate(context: ExecutionContext): Promise<boolean> {
7 const request = context.switchToHttp().getRequest();
8 const tributeId = request.params.id;
9 const legionariusze = request.user;
10
11 // Sprawdź czy tributy istnieje
12 const tribute = await this.tributeService.findById(tributeId);
13 if (!tribute) {
14 throw this.exceptionFactory.createTributeNotFound(tributeId, {
15 legionariusze: legionariusze.name,
16 cohort: legionariusze.cohort
17 });
18 }
19
20 // Sprawdź czy tributy jest przeklęty
21 if (tribute.isCursed && !legionariusze.hasCurseResistance) {
22 throw new CursedTributeException(
23 tributeId,
24 tribute.curseName,
25 tribute.curseLevel
26 );
27 }
28
29 return true;
30 }
31}
32
33// services/legion.service.ts
34@Injectable()
35export class LegionService {
36 constructor(private exceptionFactory: LegionaryExceptionFactory) {}
37
38 async assignToMission(missionId: string, legionariuszeIds: string[]): Promise<Mission> {
39 const mission = await this.findMission(missionId);
40 const availableLegionarys = await this.getAvailableLegionarys(legionariuszeIds);
41
42 // Sprawdź czy mamy wystarczający legion
43 if (availableLegionarys.length < mission.requiredLegion) {
44 throw this.exceptionFactory.createInsufficientLegion(
45 mission.name,
46 mission.requiredLegion,
47 availableLegionarys.length,
48 {
49 missionId,
50 requiredRanks: mission.requiredRanks,
51 availableLegionarys: availableLegionarys.map(p => p.name)
52 }
53 );
54 }
55
56 // Sprawdź czy legion ma odpowiednie umiejętności
57 const missingSkills = this.checkRequiredSkills(mission, availableLegionarys);
58 if (missingSkills.length > 0) {
59 throw this.exceptionFactory.createBusinessLogicException(
60 `legion nie posiada wymaganych umiejętności: ${missingSkills.join(', ')}`,
61 'MISSING_LEGION_SKILLS',
62 {
63 missionId,
64 missingSkills,
65 assignedLegionarys: availableLegionarys.map(p => ({ name: p.name, skills: p.skills }))
66 }
67 );
68 }
69
70 return await this.executeAssignment(mission, availableLegionarys);
71 }
72}

Custom Exceptions to Twój sposób na precyzyjne komunikowanie nietypowych sytuacji. Dobrze zaprojektowane wyjątki nie tylko informują o problemie, ale także prowadzą legion przez proces rozwiązania!

Pamiętaj: każdy dobry legionariusz ma plan na każdą sytuację, a każdy dobry Exception ma instrukcje naprawy!

Przejdź do CodeWorlds