CodamAIDocs
Themafertig

Validierungsfehler ins Formular bringen

Wie ein Client die gesammelten Verstöße einer 422 den Formularfeldern zuordnet, auch bei verschachtelten Pfaden.

Ausprägungen
einfaches FeldFeld in einer EinzelreferenzFeld in einer ListeFeld, das das Formular nicht zeigtunbekannte Regel400 invalid-value mit PfadFehler ohne Feldbezugüber einen BFF

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

Kunde mit Adresse und Ansprechpartnern anlegen
Antwort 422
{
  "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" }
  ],
  …
}
Formular danach
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

Vom Absenden bis zur Markierung
  1. 1
    Benutzer→Frontend
    klickt auf „Speichern“
  2. 2
    Frontend
    entfernt alte Markierungen und sperrt den Knopf
  3. 3
    Frontend→CDMS
    POST /api/rest/crm/customer/create mit dem Formularinhalt in data
  4. 4
    CDMS
    prüft alle Felder, auch in Referenzen und Listen, und sammelt jeden Verstoß
  5. 5
    CDMS→Frontend
    422 validation-failed mit allen Verstößen, nichts wurde gespeichert
  6. 6
    Frontend
    ordnet jeden Eintrag über field einem Eingabefeld zu und übersetzt rule in einen Text
  7. 7
    Frontend→Benutzer
    zeigt 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:

fieldgemeint istEingabefeld im Formular
nameFeld des Objekts selbst„Name“
address.zipFeld zip in der Einzelreferenz address„PLZ“ im Block Adresse
contacts[1].emailFeld email im zweiten Eintrag der Liste contacts, gezählt ab 0„E-Mail“ in Zeile 2
contactsdie Liste selbst, z. B. mit cannot-be-emptyder ganze Block Ansprechpartner
lists[2].groups[0].namebeliebig tief verschachteltdas 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

ruleVorschlag für den Text
cannot-be-nullist ein Pflichtfeld
cannot-be-emptydarf nicht leer sein
too-longist zu lang
pattern-mismatchhat nicht das erlaubte Format
min-numberist kleiner als erlaubt
max-numberist größer als erlaubt
not-in-futuremuss in der Zukunft liegen
not-in-pastmuss 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

Verstöße nach Feldern ordnen
Zuordnen
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;
}
Verwenden
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 null ankommt.

Alle Ausprägungen

Was mit welchem Eintrag passiert

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.

Wohin mit welchem Fehler?
StatusmessageKeyFeldbezugAnzeige
422validation-failedviolationsjedes Feld markieren, Hinweis über dem Formular
400invalid-value|<pfad>Pfad im Schlüsseldieses eine Feld markieren: „hat den falschen Typ“
409already-existskeinerMeldung über dem Formular, z. B. „Wert ist schon vergeben“. Kennst du das eindeutige Feld, markiere es selbst.
422vom Hook gewählt, layer: hookkeinerMeldung über dem Formular, Text nach dem messageKey des Hooks
422missing-attribute-on-profile|…keinerkein Formularfehler: Dem Profil der Person fehlt ein Attribut. Allgemeine Meldung zeigen.
andere––allgemeine Meldung, siehe Landkarte der Statuscodes

Fallen

Wie es weitergeht

Quellen im Code und in der Wissensdatenbank
  • CDMS/cdms-rest-api – CdmsExceptionMapper (violations mit field und rule), ClientErrorTranslator (invalid-value|<pfad>)
  • CDMS/cdms-system-layer – AbstractLayer.violatedRules, assertValid; ValidationRequestContext.pathOf
  • commons – FieldViolation, TemporalRule (not-in-future, not-in-past)
  • CDMS/frontend – shared/utils/cdmsViolations.ts (parseViolations, violationsByField), app/utils/cdms/apiError.ts (toApiErrorInfo), server/utils/useCmsApi.ts
  • documentation/05-api-guide/10-fehler.md, 60-erweiterung/02-validierung.md
Suchen