Worum es geht
Schickt ein Formular Daten, die gegen Feldregeln verstoßen, antwortet CDMS mit 422 und einer Liste violations. Jeder Eintrag nennt das Feld mit seinem Pfad und die verletzte Regel. Deine Aufgabe im Client: Jeden Eintrag dem passenden Eingabefeld zuordnen und dort eine verständliche Meldung zeigen.
Von der 422 zum markierten Formular
{
"messageKey": "validation-failed",
"code": "422",
"violations": [
{ "field": "name",
"rule": "cannot-be-empty" },
{ "field": "address.zip",
"rule": "pattern-mismatch" },
{ "field": "contacts[1].email",
"rule": "cannot-be-null" }
],
…
}Kunde anlegen
─────────────────────────────────────────
Name [ ]
⚠ darf nicht leer sein
Adresse
Straße [ Hauptstraße 1 ]
PLZ [ 123 ]
⚠ hat nicht das erlaubte Format
Ansprechpartner
1 Anna [ anna@muster.de ]
2 Ben [ ]
⚠ ist ein Pflichtfeld
─────────────────────────────────────────
Bitte die markierten Felder korrigieren.Der Ablauf
-
1Benutzer→Frontendklickt auf „Speichern“
-
2Frontendentfernt alte Markierungen und sperrt den Knopf
-
3Frontend→CDMS
POST /api/rest/crm/customer/createmit dem Formularinhalt indata -
4CDMSprüft alle Felder, auch in Referenzen und Listen, und sammelt jeden Verstoß
-
5CDMS→Frontend422
validation-failedmit allen Verstößen, nichts wurde gespeichert -
6Frontendordnet jeden Eintrag über
fieldeinem Eingabefeld zu und übersetztrulein einen Text -
7Frontend→Benutzerzeigt die Meldungen an den Feldern, dazu einen Hinweis über dem Formular
Den Pfad lesen
Der Pfad in field beschreibt, wo das Feld im Objekt steckt, das du geschickt hast. Er beginnt innerhalb von data:
field | gemeint ist | Eingabefeld im Formular |
|---|---|---|
name | Feld des Objekts selbst | „Name“ |
address.zip | Feld zip in der Einzelreferenz address | „PLZ“ im Block Adresse |
contacts[1].email | Feld email im zweiten Eintrag der Liste contacts, gezählt ab 0 | „E-Mail“ in Zeile 2 |
contacts | die Liste selbst, z. B. mit cannot-be-empty | der ganze Block Ansprechpartner |
lists[2].groups[0].name | beliebig tief verschachtelt | das Feld in der entsprechenden Unterzeile |
Am einfachsten wird es, wenn deine Formularfelder denselben Pfad als Namen tragen, also etwa name="contacts[1].email". Dann ist field direkt der Schlüssel, unter dem du die Meldung ablegst.
Der Index in […] ist die Position in der Liste, so wie du sie geschickt hast. Zeigt das Formular die Zeilen in derselben Reihenfolge, trifft die Meldung die richtige Zeile.
Die Regeln in Text übersetzen
rule | Vorschlag für den Text |
|---|---|
cannot-be-null | ist ein Pflichtfeld |
cannot-be-empty | darf nicht leer sein |
too-long | ist zu lang |
pattern-mismatch | hat nicht das erlaubte Format |
min-number | ist kleiner als erlaubt |
max-number | ist größer als erlaubt |
not-in-future | muss in der Zukunft liegen |
not-in-past | muss in der Vergangenheit liegen |
Die Grenzen selbst, etwa die erlaubte Länge oder den Mindestwert, nennt die Antwort nicht. Willst du sie im Text zeigen, nimm sie aus deinem Modell. Welche Regel was prüft, steht unter Validierung.
Ein Beispiel in TypeScript
type Violation = { field: string; rule: string };
const TEXT: Record<string, string> = {
'cannot-be-null': 'ist ein Pflichtfeld',
'cannot-be-empty': 'darf nicht leer sein',
'too-long': 'ist zu lang',
'pattern-mismatch': 'hat nicht das erlaubte Format',
'min-number': 'ist kleiner als erlaubt',
'max-number': 'ist größer als erlaubt',
'not-in-future': 'muss in der Zukunft liegen',
'not-in-past': 'muss in der Vergangenheit liegen',
};
function byField(violations: Violation[]) {
const errors: Record<string, string[]> = {};
for (const v of violations) {
const text = TEXT[v.rule] ?? `ist ungültig (${v.rule})`;
(errors[v.field] ??= []).push(text);
}
return errors;
}const res = await fetch(url, { method: 'POST', … });
if (res.status === 422) {
const body = await res.json();
const errors = byField(body.violations ?? []);
for (const [path, texts] of Object.entries(errors)) {
const input = form.querySelector(`[name="${path}"]`);
if (input) markField(input, texts);
else showFormMessage(`${path} ${texts.join(', ')}`);
}
}Zwei Dinge macht das Beispiel bewusst:
- Eine unbekannte Regel bekommt einen allgemeinen Text statt eines Absturzes. So bleibt dein Client stabil, wenn eine Regel dazukommt.
- Ein Feld, das das Formular nicht zeigt, landet in der Meldung über dem Formular. Das passiert etwa bei einem PUT, wenn ein Pflichtfeld nicht im Formular steht und deshalb als
nullankommt.
Alle Ausprägungen
Wann: field ist ein einzelner Name, z. B. name
Das Eingabefeld mit diesem Namen bekommt die Meldung.
Ergebnis: Der häufigste Fall.
Wann: field enthält einen Punkt, z. B. address.zip
Das Feld liegt in einem Unterobjekt. Zeigt das Formular die Adresse als eigenen Block, markiere dort das Feld zip.
Ergebnis: Pfad und Formularstruktur passen zusammen, wenn die Blöcke wie die Referenzen heißen.
Wann: field enthält einen Index, z. B. contacts[1].email
Nimm die Zeile an Position 1, gezählt ab 0, und markiere dort email. Voraussetzung: Das Formular zeigt die Zeilen in der Reihenfolge, in der du sie geschickt hast.
Ergebnis: Bei Tabellen mit Zeilen zum Hinzufügen und Entfernen die Reihenfolge nicht umsortieren, solange Meldungen sichtbar sind.
Wann: Zu field gibt es kein Eingabefeld
Zeige die Meldung über dem Formular, mit dem Feldnamen. Oft fehlt dann ein Pflichtfeld im Formular selbst.
Ergebnis: Kein Verstoß geht verloren.
Wann: rule steht nicht in deiner Texttabelle
Zeige einen allgemeinen Text wie „ist ungültig“ und nenne die Regel in Klammern.
Ergebnis: Der Client bleibt stabil, wenn Regeln dazukommen.
Wann: Das Frontend spricht nicht direkt mit CDMS, sondern über einen eigenen Server
Der BFF reicht Status und violations unverändert an das Frontend weiter. Das CDMS-Portal macht es so: Der Server legt die Liste in seine eigene Fehlerantwort, das Formular markiert die Felder daraus und zeigt oben „Bitte die markierten Felder korrigieren.“
Ergebnis: Übersetzt der BFF die Liste schon in einen einzigen Text, kann das Formular keine Felder mehr markieren.
Andere Fehler mit Feldbezug
Nicht nur 422 kann ein Feld betreffen. Auch ein Wert, der gar nicht zum Feldtyp passt, etwa Text in einem Zahlfeld, wird abgelehnt: mit 400 invalid-value|<pfad>. Das kommt vor der Prüfung der Regeln, also allein und ohne violations.
Der Pfad steht hier im messageKey hinter dem |. Bei Create und PUT beginnt er an der Wurzel des Körpers (data.amount), bei PATCH innerhalb von data (amount). Schneide ein führendes data. ab, dann passt er zu deinen Formularfeldern.
| Status | messageKey | Feldbezug | Anzeige |
|---|---|---|---|
| 422 | validation-failed | violations | jedes Feld markieren, Hinweis über dem Formular |
| 400 | invalid-value|<pfad> | Pfad im Schlüssel | dieses eine Feld markieren: „hat den falschen Typ“ |
| 409 | already-exists | keiner | Meldung über dem Formular, z. B. „Wert ist schon vergeben“. Kennst du das eindeutige Feld, markiere es selbst. |
| 422 | vom Hook gewählt, layer: hook | keiner | Meldung über dem Formular, Text nach dem messageKey des Hooks |
| 422 | missing-attribute-on-profile|… | keiner | kein Formularfehler: Dem Profil der Person fehlt ein Attribut. Allgemeine Meldung zeigen. |
| andere | – | – | allgemeine Meldung, siehe Landkarte der Statuscodes |
Fallen
Wie es weitergeht
- Welche Regeln es gibt und wann sie gelten: Validierung
- Warum PUT andere Felder meldet als PATCH: PUT oder PATCH? Die null-Falle
- Der Aufbau jeder Fehlerantwort: Das Fehlerformat