CodamAIDocs
Themafertig

Validierung

Welche Regeln an Feldern hängen, wann sie geprüft werden, dass alle Verstöße gesammelt in einer 422 kommen und wie der Pfad eines verschachtelten Fehlers aussieht.

Ausprägungen
Pflichtfeld bei Create/Updatenicht leerLängeMusterMin/MaxZukunft/VergangenheitScope ALWAYS/CREATE/UPDATEPatch prüft nur GesendetesKindobjekteunique (Datenbank) → 409falscher Typ im JSON → 400 invalid-value

Worum es geht

Im Hub hängst du Regeln an ein Feld: Pflichtfeld, nicht leer, Muster, Mindestwert und so weiter. CDMS prüft sie bei jedem Schreiben, also bei Create, PUT und PATCH. Ein Verstoß heißt Validierungsfehler, die Antwort ist 422.

Geprüft wird im System-Layer, gegen die Regeln aus den Metadaten des Modells. Die Annotationen wie @NotNull an den generierten Payload-Klassen beschreiben die Felder nur für die OpenAPI-Dokumentation.

Die Regeln und ihre Fehlerschlüssel

Regel im HubRegel-IDprüftrule im Verstoß
Pflichtfeld@nullableWert ist nicht nullcannot-be-null
Pflicht nur beim Anlegen@notNullOnCreatewie oben, nur bei Createcannot-be-null
Pflicht nur beim Ändern@notNullOnUpdatewie oben, nur bei PUT und PATCHcannot-be-null
nicht leer@notEmptyText nicht leer und nicht nur Leerzeichen, Liste mit mindestens einem Eintragcannot-be-empty
Länge (Eigenschaft des Feldes)–Text höchstens so viele Zeichen, Standard 255too-long
Muster@patternder ganze Text passt auf den regulären Ausdruckpattern-mismatch
Mindestwert@minZahl ≥ Grenzemin-number
Höchstwert@maxZahl ≤ Grenzemax-number
in der Zukunft@future, @futureOrPresentDatum oder Zeitpunkt nach jetzt (bzw. ab jetzt)not-in-future
in der Vergangenheit@past, @pastOrPresentDatum oder Zeitpunkt vor jetzt (bzw. bis jetzt)not-in-past
eindeutig@uniqueprüft die Datenbank beim Speichern– (eigene Antwort, siehe unten)

Ein paar Einzelheiten, die im Alltag zählen:

  • @nullable heißt Pflichtfeld, auch wenn der Name das Gegenteil vermuten lässt.
  • Jede Regel prüft nur ihren Werttyp: Länge und Muster nur Texte, Mindest- und Höchstwert nur Zahlen. Grenzen sind inklusive und dürfen Nachkommastellen haben (10.5).
  • Bei einem reinen Datum (LocalDate) vergleicht CDMS mit heute. Heute ist weder Zukunft noch Vergangenheit, dafür gibt es @futureOrPresent und @pastOrPresent.
  • Ein Wert null verletzt nur die Pflichtregeln. „Nicht leer“, Länge, Muster und Grenzen prüfen nur vorhandene Werte.
  • Die Zeitregeln setzt du in den Metadaten des Modells (YAML), die Hub-Oberfläche bietet sie nicht an.

Wann welche Regel gilt: der Scope

Viele Regeln kannst du auf einen Geltungsbereich (Scope) beschränken:

Scope einer Regel
ALWAYSCREATEUPDATE
gilt bei Createjajanein
gilt bei PUTjaneinja
gilt bei PATCHja, für gesendete Felderneinja, für gesendete Felder

Einen Scope haben „nicht leer“, Muster, Mindest- und Höchstwert (die beiden teilen sich einen) sowie die Zeitregeln. Beim Pflichtfeld steckt die Operation in der Regel-ID selbst: @nullable gilt immer, @notNullOnCreate und @notNullOnUpdate nur bei einer Operation. Die Länge aus der Feldeigenschaft gilt immer.

Maßgeblich ist die Operation je Objekt, nicht der Endpunkt. Schickst du in einem PATCH auf eine Firma einen neuen Mitarbeiter ohne id mit, wird dieser Mitarbeiter angelegt und mit den CREATE-Regeln geprüft.

Was geprüft wird: Create, PUT, PATCH

Welche Felder die Validierung ansieht
POST /create
Regeln mit Scope ALWAYS und CREATE
  • jedes Feld des Modells
  • ein fehlendes Feld ist null
  • Defaultwerte sind schon eingesetzt, ein Feld mit Default erfüllt also die Pflicht
PUT /update/{id}
Regeln mit Scope ALWAYS und UPDATE
  • jedes Feld des Modells
  • ein fehlendes Feld ist null, weil PUT es leert
  • eine fehlende Liste zählt als null
PATCH /update/{id}
Regeln mit Scope ALWAYS und UPDATE
  • nur die gesendeten Felder
  • ein fehlendes Feld bleibt, wie es ist, und wird nicht geprüft
  • ein gesendetes null wird geprüft

Das erklärt einen häufigen Unterschied: Ein PATCH mit einem einzigen Feld geht durch, derselbe Inhalt als PUT scheitert mit cannot-be-null an allen Pflichtfeldern, die du nicht mitgeschickt hast. Siehe PUT oder PATCH?

Der Ablauf: sammeln, Hooks, prüfen

Validierung bei POST /api/rest/roster/create
  1. 1
    Client→CDMS
    schickt das Objekt mit vier Einträgen in members
  2. 2
    CDMS
    geht jedes Feld durch, auch in den Kindobjekten, und merkt sich jeden Verstoß, statt sofort abzubrechen
  3. 3
    Hook
    Before-Hooks laufen und dürfen Felder noch ändern, etwa ein Pflichtfeld füllen
  4. 4
    CDMS
    prüft jeden gemerkten Verstoß noch einmal gegen den Wert, der jetzt am Objekt steht
  5. 5
    CDMS→Client
    bleiben Verstöße übrig → 422 mit allen auf einmal, nichts wird gespeichert
  6. 6
    CDMS→Database
    keine Verstöße → speichert, danach laufen die After-Hooks

Ein Before-Hook kann einen Verstoß also auflösen, etwa indem er ein fehlendes Pflichtfeld selbst setzt. After-Hooks laufen erst nach der Prüfung, ihre Änderungen prüft CDMS nicht mehr. Mehr zu Hooks unter Hooks: Arten und Zeitpunkte.

So sieht eine 422 aus

Zwei Verstöße in einer Liste
Anfrage
POST /api/rest/roster/create
{
  "data": {
    "name": "Frühschicht",
    "members": [
      { "name": "Erste" },
      { "name": "" },
      { "name": "Dritte" },
      { }
    ]
  },
  "response": ["id"]
}
Antwort 422
{
  "error": "ApiValidationException",
  "messageKey": "validation-failed",
  "code": "422",
  "layer": "API",
  "violations": [
    { "field": "members[1].name", "rule": "cannot-be-empty" },
    { "field": "members[3].name", "rule": "cannot-be-null" }
  ]
}

So liest du den Pfad in field:

Pfadbedeutet
nameFeld des Objekts selbst
address.streetFeld street in der Einzelreferenz address
members[1].nameFeld name im zweiten Eintrag der Liste members (gezählt ab 0)
lists[2].groups[0].namebeliebig tief verschachtelt

Der Index ist die Position in der Liste, so wie du sie geschickt hast. Hat ein Feld zwei Verstöße, stehen zwei Einträge in violations. Wie ein Client daraus markierte Formularfelder macht, steht unter Validierungsfehler ins Formular bringen.

Kindobjekte

Kindobjekte prüft CDMS mit, wenn es sie schreibt:

  • Ein neues Kind ohne id wird angelegt und mit den CREATE-Regeln seines Modells geprüft.
  • Ein Kind mit id, das über die Beziehung mitgeändert wird, wird mit den UPDATE-Regeln geprüft, bei PATCH nur die gesendeten Felder.
  • Ein Kind, das nur per id verknüpft wird, prüft CDMS nicht. Es wird ja nicht verändert.

Scheitert ein Kind, wird auch der gültige Elternteil nicht gespeichert. Die ganze Anfrage ist eine Transaktion. Welche Beziehung Kinder anlegen oder ändern darf, steht unter Die vier Fälle beim verschachtelten Schreiben.

Eindeutig: die Datenbank prüft

Die Regel „eindeutig“ (@unique) wird zu einem Unique-Index in der Datenbank. CDMS prüft sie nicht vorher, sondern die Datenbank beim Speichern. Deshalb sieht die Antwort anders aus:

Zweiter Kunde mit derselben Kundennummer
Anfrage
POST /api/rest/crm/customer/create
{ "data": { "number": "K-1001", "name": "Zweite GmbH" }, "response": ["id"] }
Antwort 409
{
  "error": "DataIntegrityException",
  "messageKey": "already-exists",
  "code": "409",
  "layer": "database"
}
Regelverstoß oder Datenbankverstoß?
Regel verletzt
422 validation-failed
  • CDMS prüft vor dem Speichern
  • alle Verstöße auf einmal
  • violations mit Feldpfad und Regel
Eindeutigkeit verletzt
409 already-exists
  • die Datenbank lehnt beim Speichern ab
  • ein Fehler, ohne Feldbezug
  • kein violations

Entscheidungstabelle

Antwort auf ein Schreiben mit ungültigem Inhalt
Werte im JSON haben den richtigen Typalle Regeln erfüllt (nach den Before-Hooks)Datenbank nimmt die Zeile anAntwort
nein––400 invalid-value|<pfad>, bevor die Regeln geprüft werden
janein–422 validation-failed mit allen Verstößen
jajanein, Wert schon vorhanden409 already-exists
jajaja200

„Richtiger Typ“ heißt: Text in einem Textfeld, Zahl in einem Zahlfeld, Zeitpunkte als yyyy-MM-dd HH:mm:ss, ein Enum-Wert, den das Modell kennt. Passt ein Wert nicht, prüft CDMS die Regeln gar nicht erst und antwortet mit 400 invalid-value und dem Pfad des Feldes:

AnfrageAntwort
POST /create mit "amount": "sieben"400 invalid-value|data.amount
PATCH /update/{id} mit "dueOn": "10.02.2026"400 invalid-value|dueOn
JSON, das sich gar nicht lesen lässt400 malformed-json
Körper ohne data400 missing-data

Beim Einlesen von Create und PUT beginnt der Pfad bei der Wurzel des Körpers (data.amount). Bei PATCH beginnt er, wie in violations, innerhalb von data (dueOn).

Fallen

Wie es weitergeht

Quellen im Code und in der Wissensdatenbank
  • CDMS/cdms-system-layer – AbstractLayer.validateField, violatedRules, assertValid; ValidationRequestContext (pathOf, recheck); AbstractSystemLayer.createObject/updateObject/patchObject
  • commons – MetaFieldRules (appliesTo), RuleScope, TemporalRule, FieldViolation
  • CDMS/cdms-generator – CdmsYamlLoader.applyRuleList, normalizeScope; EntityProcessor (@Column unique, updatable)
  • CDMS/cdms-persistence-database – AbstractDatabasePersistence, PersistenceFailureTranslator (DataIntegrityException 409 already-exists)
  • CDMS/cdms-rest-api – CdmsExceptionMapper, payloads/WritePayload (ADR-009)
  • CDMS/cdms-integrationtest – AbstractFieldRulesTest, AbstractTemporalRulesTest, AbstractDatabaseRulesTest
  • documentation/60-erweiterung/02-validierung.md
Suchen