Zum Inhalt springen

Fehler behandeln

Kurzantwort: Jeder Fehler kommt als JSON mit error, message, messageKey, code und layer. Bei Validierungsfehlern kommt zusätzlich violations mit allen betroffenen Feldern auf einmal — die lassen sich direkt am Formular anzeigen. Wiederholen lohnt sich nur bei 401, 409 und 503.


{
"error": "ApiValidationException",
"message": "validation-failed",
"messageKey": "CDMS_VALIDATION_FAILED",
"code": "422",
"layer": "API",
"violations": [
{ "field": "name", "rule": "cannot-be-null" },
{ "field": "items.0.quantity", "rule": "min-number" }
]
}
Feldwofür Sie es brauchen
codeder Statuscode, auch im Körper
messageKeystabiler Schlüssel — darauf sollten Ihre Übersetzungen zeigen
messagetechnische Meldung, oft mit Details nach `
errorAusnahmeklasse, gut fürs Log
layerwo es passierte: api, system, database, hook
violationsnur bei Feldvalidierung

Zeigen Sie message nicht ungefiltert an. Sie ist für Entwickler geschrieben. Bauen Sie Ihre Texte auf messageKey und violations auf.

CodeBedeutungIhre Reaktion
400Request ist inhaltlich falsch: response fehlt, ungültiger Filter, ungültige UUIDFehler im Client beheben — nie wiederholen
401Token fehlt, abgelaufen oder ungültigToken erneuern, einmal wiederholen
403Rolle fehlt, Mandantenwechsel nicht erlaubtZugriff verwehrt — nicht wiederholen
404existiert nicht oder ist für Sie unsichtbarwie „nicht vorhanden” behandeln
409Konfliktkurz warten, wiederholen
413Upload-ProblemDateien und Namen prüfen
422Feldregeln verletztviolations ans Formular
500Serverfehler, Fehlkonfiguration, unbekanntes Feldmelden, nicht wiederholen
503Dienst vorübergehend nicht verfügbarmit wachsender Wartezeit wiederholen

404 statt 403 bei fremden Daten ist Absicht: Der Server verrät nicht, dass ein Objekt existiert, das Sie nicht sehen dürfen. Ein 404 heißt für Sie also „nicht verfügbar”, nicht zwingend „gibt es nicht”.

type Violation = { field: string; rule: string };
const rules: Record<string, string> = {
'cannot-be-null': 'Pflichtfeld',
'cannot-be-empty': 'Darf nicht leer sein',
'too-long': 'Zu lang',
'pattern-mismatch': 'Ungültiges Format',
'min-number': 'Wert zu klein',
'max-number': 'Wert zu groß',
};
function toFormErrors(violations: Violation[]) {
return Object.fromEntries(
violations.map((v) => [v.field, rules[v.rule] ?? 'Ungültiger Wert']),
);
}

Der Feldpfad enthält die Verschachtelung — items.0.quantity meint das erste Element der Liste items. Damit markieren Sie auch in dynamischen Formularen die richtige Zeile.

Alle Verstöße kommen in einer Antwort. Sie müssen nicht Feld für Feld absenden, um alle Fehler zu finden.

messageKey bzw. messageUrsache
response (400)die response-Liste fehlt
missing-idPATCH ohne id im data
missing-object|<id>|<klasse>verknüpftes Objekt existiert nicht oder ist unsichtbar
missing-type-for-abstract-field|<feld>@type fehlt bei einem abstrakten Ziel
recursive-create-not-allowed|<feld>über diese Beziehung darf nicht angelegt werden
missing-permission|<rolle>Ihnen fehlt diese Rolle
missing-attribute-on-profile|<attribut>Ihr Profil trägt ein nötiges Attribut nicht
object-already-exists|use-updateSingleton existiert schon
no-data-exists|use-createSingleton existiert noch nicht
duplicate-filenames|…zwei gleichnamige Dateien im Upload
CDMS_TENANT_NOT_SERVEDder Mandant ist nicht aktiv
CDMS_TENANT_SWITCH_NOT_AUTHORIZEDMandantenwechsel nicht erlaubt

Die Angaben nach | sind Parameter — Feldname, ID, Rollenname. Für eigene Meldungen lässt sich der Schlüssel am | abschneiden.

async function call(path: string, body: unknown, retry = true): Promise<unknown> {
const res = await fetch(`${base}${path}`, {
method: 'POST',
headers: { Authorization: `Bearer ${await token()}`, 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
if (res.ok) {
const data = await res.json();
// Teilerfolg bei LENIENT: 200, aber ohne Daten
if (data?.messageKey === 'CDMS_CREATE_SUCCEEDED_READ_FAILED') {
return { partial: true, id: data.id };
}
return data;
}
const err = await res.json().catch(() => ({}));
if (res.status === 401 && retry) {
await refreshToken();
return call(path, body, false); // genau einmal
}
if (res.status === 422) throw new ValidationError(err.violations ?? []);
if (res.status === 404) return null;
throw new ApiError(err.messageKey ?? `HTTP ${res.status}`, res.status, err);
}

Drei Dinge, die dabei zählen:

  1. Beim 401 nur einmal wiederholen — sonst dreht sich der Client im Kreis, wenn das Problem nicht am Token liegt.
  2. Den Fehlerkörper immer lesen, auch wenn er leer sein könnte. Er enthält die einzige brauchbare Information.
  3. Den Teilerfolg abfangen, wenn Sie LENIENT verwenden — siehe Transaktionen.

Bei einem 500 steht in der Antwort bewusst nur eine grobe Meldung; die Einzelheiten liegen im Serverlog. Wenn Sie einen Fehler melden, helfen: messageKey, code, layer, der Zeitpunkt und der aufgerufene Pfad.

Die vollständige Liste aller Statuscodes und Ausnahmen steht unter Fehler und Statuscodes.