refactor(quick-260921-m34): Aufgabe 3a - 18 Fehlerfaenger auf unknown, mit echter Eingrenzung

catch (e: any) in groups, module-grants, ldap, user, admin-seed, calendar
und vier tenders-Diensten auf catch (e: unknown) umgestellt. Die
Eingrenzung passiert an der Verwendungsstelle, nicht per Zusicherung.

Neu: apps/api/src/prisma/prisma-error.ts mit prismaErrorCode() und
prismaErrorTarget(). Bewusst Form-Pruefungen statt instanceof
Prisma.PrismaClientKnownRequestError - gemessen: samtliche Testdoppel in
apps/api werfen new Error(...) mit angehaengtem .code (groups, user, ldap,
tenders, module-grants, admin-seed) und dashboard.service.spec.ts:451 ein
reines { code: 'P2002' }. Ein instanceof-Test haette all diese Werte in den
anderen Zweig geschickt - Verhaltensaenderung, verboten nach D-03/T-M34-06.
Die Helfer bilden err?.code und err?.meta?.target eins zu eins ab.

ldap.service.ts liest zusaetzlich meta.target; prismaErrorTarget() gibt
unknown zurueck, weil der Bestand dort Array UND Zeichenkette getrennt
behandelt - ein engerer Typ waere eine Behauptung.

calendar.service.ts:341 nutzt instanceof Error statt e?.message: gemessen
wirft validateUrlNotPrivate() ausschliesslich ForbiddenException (der
eigene catch dort setzt jeden Fremdfehler in eine um), also trifft
instanceof dieselben Faelle. Ersatzzweig 'URL not allowed' unveraendert.

noExplicitAny in apps/api/src: 56 -> 38. type-check 4/4, lint 5/5 (0
error), apps/api 72/1143, apps/web 73/531, rls-access-inventory 30/30.
noNonNullAssertion 56, as unknown as 33, Unterdrueckungsmarker 1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
This commit is contained in:
2026-09-21 17:18:12 +02:00
parent 52668c2c88
commit 32591b6690
11 changed files with 103 additions and 38 deletions
+48
View File
@@ -0,0 +1,48 @@
/**
* Eingrenzung gefangener Fehlerwerte auf die Form, die der Bestandscode an
* Prisma-Fehlern tatsaechlich liest — `code` und `meta.target`, sonst nichts.
*
* Warum kein `instanceof Prisma.PrismaClientKnownRequestError`:
* Der Bestand prueft ueberall `err?.code === 'P2002'` und trifft damit JEDES
* Objekt, das ein `code`-Feld traegt. Genau darauf bauen die Testdoppel in
* `apps/api` auf — sie werfen `const err = new Error(...); err.code = 'P2002'`
* (groups, user, ldap, tenders, module-grants, admin-seed) und an einer Stelle
* ein reines `{ code: 'P2002' }` (dashboard.service.spec.ts:451). Ein
* `instanceof`-Test wuerde alle diese Werte in den jeweils ANDEREN Zweig
* schicken. Das waere eine Verhaltensaenderung und ist deshalb verboten
* (Plan 260921-m34, D-03 und T-M34-06).
*
* Diese Funktionen sind daher bewusst Form-Pruefungen ("duck typing") und
* keine Klassenpruefungen: sie bilden `err?.code` bzw. `err?.meta?.target`
* eins zu eins ab, nur eben ohne `any`.
*/
/**
* Liefert den Prisma-Fehlercode (z.B. `P2002`, `P2025`), wenn der gefangene
* Wert ein Objekt mit einem Zeichenketten-Feld `code` ist — sonst `undefined`.
*
* Ein Feld `code`, das keine Zeichenkette ist, liefert `undefined`. Auch das
* ist verhaltensgleich: ein Vergleich `err.code === 'P2002'` waere dort
* ohnehin nie wahr geworden.
*/
export function prismaErrorCode(err: unknown): string | undefined {
if (typeof err !== 'object' || err === null) return undefined;
const code = (err as { code?: unknown }).code;
return typeof code === 'string' ? code : undefined;
}
/**
* Liefert `meta.target` eines Prisma-Fehlers als `unknown`.
*
* Bewusst `unknown` und nicht `string[]`: Prisma liefert hier je nach
* Fehlerart ein Feld-Array ODER den Indexnamen als Zeichenkette, und der
* Bestand in `ldap.service.ts` behandelt genau diese beiden Faelle bereits
* getrennt (`Array.isArray(target) ? ... : String(target ?? '')`). Ein
* engerer Rueckgabetyp waere hier eine Behauptung statt einer Messung.
*/
export function prismaErrorTarget(err: unknown): unknown {
if (typeof err !== 'object' || err === null) return undefined;
const meta = (err as { meta?: unknown }).meta;
if (typeof meta !== 'object' || meta === null) return undefined;
return (meta as { target?: unknown }).target;
}