From b8ca7f8e2fa114a3708cb36e1c206f8cf8573815 Mon Sep 17 00:00:00 2001
From: OZGCloud <ozgcloud@mgm-tp.com>
Date: Fri, 26 Jul 2024 11:52:25 +0200
Subject: [PATCH] OZG-6170 Refactore getMessageForInvalidParam()

Same behaviour like getMessageForIssue() because label comes from HTML template only.
---
 .../postfach-form.component.spec.ts           |  2 +-
 .../formcontrol-editor.abstract.component.ts  |  8 +-
 .../text-editor/text-editor.component.html    |  2 +-
 .../form/text-editor/text-editor.component.ts |  2 +-
 .../textarea-editor.component.html            |  2 +-
 .../textarea-editor.component.ts              |  2 +-
 .../validation-error.component.html           |  4 +-
 .../validation-error.component.spec.ts        | 31 ++++----
 .../validation-error.component.ts             |  8 +-
 .../src/lib/service/formservice.abstract.ts   |  7 +-
 .../libs/tech-shared/src/lib/tech.model.ts    | 10 ++-
 .../validation/tech.validation.messages.ts    |  4 +-
 .../validation/tech.validation.util.spec.ts   | 73 ++++++++++++++++---
 .../lib/validation/tech.validation.util.ts    | 48 +++++++-----
 alfa-client/libs/tech-shared/test/error.ts    | 26 ++++++-
 .../autocomplete-editor.component.html        |  2 +-
 .../date-editor/date-editor.component.html    |  2 +-
 .../file-upload-editor.component.html         |  2 +-
 .../text-editor/text-editor.component.html    |  2 +-
 .../textarea-editor.component.html            |  2 +-
 .../validation-error.component.html           |  2 +-
 .../validation-error.component.spec.ts        | 34 ++++++++-
 .../validation-error.component.ts             |  8 +-
 23 files changed, 204 insertions(+), 79 deletions(-)

diff --git a/alfa-client/libs/admin-settings/src/lib/postfach/postfach-container/postfach-form/postfach-form.component.spec.ts b/alfa-client/libs/admin-settings/src/lib/postfach/postfach-container/postfach-form/postfach-form.component.spec.ts
index f786c3e04a..f3e6c68f3f 100644
--- a/alfa-client/libs/admin-settings/src/lib/postfach/postfach-container/postfach-form/postfach-form.component.spec.ts
+++ b/alfa-client/libs/admin-settings/src/lib/postfach/postfach-container/postfach-form/postfach-form.component.spec.ts
@@ -175,7 +175,7 @@ describe('PostfachFormComponent', () => {
     function createProblemDetailForAbsenderName(): ProblemDetail {
       return {
         ...createProblemDetail(),
-        'invalid-params': [{ ...createInvalidParam(), name: 'settingBody.absender.name' }],
+        invalidParams: [{ ...createInvalidParam(), name: 'settingBody.absender.name' }],
       };
     }
 
diff --git a/alfa-client/libs/design-component/src/lib/form/formcontrol-editor.abstract.component.ts b/alfa-client/libs/design-component/src/lib/form/formcontrol-editor.abstract.component.ts
index a023a009d1..cb0f3f5842 100644
--- a/alfa-client/libs/design-component/src/lib/form/formcontrol-editor.abstract.component.ts
+++ b/alfa-client/libs/design-component/src/lib/form/formcontrol-editor.abstract.component.ts
@@ -21,7 +21,7 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
-import { Issue } from '@alfa-client/tech-shared';
+import { InvalidParam } from '@alfa-client/tech-shared';
 import { Component, OnDestroy, OnInit, Optional, Self } from '@angular/core';
 import { ControlValueAccessor, NgControl, UntypedFormControl } from '@angular/forms';
 import { isEmpty } from 'lodash-es';
@@ -89,9 +89,11 @@ export abstract class FormControlEditorAbstractComponent
     }
   }
 
-  get issues(): Issue[] {
+  get invalidParams(): InvalidParam[] {
     return this.fieldControl.errors ?
-        Object.keys(this.fieldControl.errors).map((key) => <Issue>this.fieldControl.errors[key])
+        Object.keys(this.fieldControl.errors).map(
+          (key) => <InvalidParam>this.fieldControl.errors[key],
+        )
       : [];
   }
 }
diff --git a/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.html b/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.html
index 544b9312b0..9252326a5b 100644
--- a/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.html
+++ b/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.html
@@ -10,7 +10,7 @@
 >
   <ods-validation-error
     error
-    [issues]="issues"
+    [invalidParams]="invalidParams"
     [label]="label"
     [attr.data-test-id]="(label | convertForDataTest) + '-text-editor-error'"
   ></ods-validation-error>
diff --git a/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.ts b/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.ts
index c78a755480..c40849cf31 100644
--- a/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.ts
+++ b/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.ts
@@ -26,6 +26,6 @@ export class TextEditorComponent extends FormControlEditorAbstractComponent {
   @Input() focus: boolean = false;
 
   get variant(): string {
-    return this.issues.length > 0 ? 'error' : 'default';
+    return this.invalidParams.length > 0 ? 'error' : 'default';
   }
 }
diff --git a/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.html b/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.html
index 5f3959bd34..c27f425a3f 100644
--- a/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.html
+++ b/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.html
@@ -10,7 +10,7 @@
 >
   <ods-validation-error
     error
-    [issues]="issues"
+    [invalidParams]="invalidParams"
     [label]="label"
     [attr.data-test-id]="(label | convertForDataTest) + '-textarea-editor-error'"
   ></ods-validation-error>
diff --git a/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.ts b/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.ts
index f78ee2c938..a47b955192 100644
--- a/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.ts
+++ b/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.ts
@@ -26,6 +26,6 @@ export class TextareaEditorComponent extends FormControlEditorAbstractComponent
   @Input() focus: boolean = false;
 
   get variant(): string {
-    return this.issues.length > 0 ? 'error' : 'default';
+    return this.invalidParams.length > 0 ? 'error' : 'default';
   }
 }
diff --git a/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.html b/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.html
index 703f757c2b..28ffaaac9b 100644
--- a/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.html
+++ b/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.html
@@ -1,3 +1,3 @@
-<ng-container *ngFor="let issue of issues"
-  ><ods-error-message [text]="message(issue)"></ods-error-message
+<ng-container *ngFor="let invalidParam of invalidParams"
+  ><ods-error-message [text]="message(invalidParam)"></ods-error-message
 ></ng-container>
diff --git a/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.spec.ts b/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.spec.ts
index 6abe6133bd..9b169d4d08 100644
--- a/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.spec.ts
+++ b/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.spec.ts
@@ -1,6 +1,7 @@
-import { getMessageForIssue } from '@alfa-client/tech-shared';
+import { getMessageForInvalidParam, InvalidParam } from '@alfa-client/tech-shared';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { createIssue } from 'libs/tech-shared/test/error';
+import { ValidationMessageCode } from 'libs/tech-shared/src/lib/validation/tech.validation.messages';
+import { createInvalidParam } from 'libs/tech-shared/test/error';
 import { ValidationErrorComponent } from './validation-error.component';
 
 describe('ValidationErrorComponent', () => {
@@ -21,32 +22,30 @@ describe('ValidationErrorComponent', () => {
     expect(component).toBeTruthy();
   });
 
-  describe('get message for issue', () => {
+  describe('get message from invalidParam', () => {
     const fieldLabel: string = 'Field Label';
+    const invalidParam: InvalidParam = {
+      ...createInvalidParam(),
+      reason: ValidationMessageCode.VALIDATION_FIELD_SIZE,
+      messageCode: ValidationMessageCode.VALIDATION_FIELD_SIZE,
+    };
 
-    it('should return message', () => {
-      const msg: string = getMessageForIssue(fieldLabel, {
-        ...createIssue(),
-        messageCode: 'validation_field_size',
-      });
+    it('should contain ', () => {
+      const msg: string = getMessageForInvalidParam(fieldLabel, invalidParam);
 
       expect(msg).toContain('muss mindestens');
     });
 
     it('should set field label', () => {
-      const msg: string = getMessageForIssue(fieldLabel, {
-        ...createIssue(),
-        messageCode: 'validation_field_size',
-      });
+      const msg: string = getMessageForInvalidParam(fieldLabel, invalidParam);
 
       expect(msg).toContain(fieldLabel);
     });
 
     it('should replace min param', () => {
-      const msg: string = getMessageForIssue(fieldLabel, {
-        ...createIssue(),
-        messageCode: 'validation_field_size',
-        parameters: [{ name: 'min', value: '3' }],
+      const msg: string = getMessageForInvalidParam(fieldLabel, {
+        ...invalidParam,
+        constraintParameters: [{ name: 'min', value: '3' }],
       });
 
       expect(msg).toContain('3');
diff --git a/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.ts b/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.ts
index d47b675b49..4d8a67a6e5 100644
--- a/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.ts
+++ b/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.ts
@@ -1,4 +1,4 @@
-import { Issue, getMessageForIssue } from '@alfa-client/tech-shared';
+import { InvalidParam, getMessageForInvalidParam } from '@alfa-client/tech-shared';
 import { CommonModule } from '@angular/common';
 import { Component, Input } from '@angular/core';
 import { ErrorMessageComponent } from '@ods/system';
@@ -11,9 +11,9 @@ import { ErrorMessageComponent } from '@ods/system';
 })
 export class ValidationErrorComponent {
   @Input() label: string;
-  @Input() issues: Issue[];
+  @Input() invalidParams: InvalidParam[];
 
-  public message(issue: Issue): string {
-    return getMessageForIssue(this.label, issue);
+  public message(invalidParam: InvalidParam): string {
+    return getMessageForInvalidParam(this.label, invalidParam);
   }
 }
diff --git a/alfa-client/libs/tech-shared/src/lib/service/formservice.abstract.ts b/alfa-client/libs/tech-shared/src/lib/service/formservice.abstract.ts
index ab647437a8..62a5bb8f8b 100644
--- a/alfa-client/libs/tech-shared/src/lib/service/formservice.abstract.ts
+++ b/alfa-client/libs/tech-shared/src/lib/service/formservice.abstract.ts
@@ -30,14 +30,17 @@ import { map } from 'rxjs/operators';
 import { hasStateResourceError, StateResource } from '../resource/resource.util';
 import { ApiError, HttpError, InvalidParam, Issue, ProblemDetail } from '../tech.model';
 import { isNotUndefined } from '../tech.util';
-import { setInvalidParamValidationError, setIssueValidationError } from '../validation/tech.validation.util';
+import {
+  setInvalidParamValidationError,
+  setIssueValidationError,
+} from '../validation/tech.validation.util';
 
 export abstract class AbstractFormService {
   form: UntypedFormGroup;
   pathPrefix: string;
   source: any;
 
-  private readonly PROBLEM_DETAIL_INVALID_PARAMS_KEY: string = 'invalid-params';
+  private readonly PROBLEM_DETAIL_INVALID_PARAMS_KEY: string = 'invalidParams';
 
   constructor(public formBuilder: UntypedFormBuilder) {
     this.form = this.initForm();
diff --git a/alfa-client/libs/tech-shared/src/lib/tech.model.ts b/alfa-client/libs/tech-shared/src/lib/tech.model.ts
index c85852676e..adcda221a6 100644
--- a/alfa-client/libs/tech-shared/src/lib/tech.model.ts
+++ b/alfa-client/libs/tech-shared/src/lib/tech.model.ts
@@ -47,12 +47,20 @@ export interface ProblemDetail {
   status: HttpStatusCode;
   detail: string;
   instance: string;
-  'invalid-params': InvalidParam[];
+  invalidParams: InvalidParam[];
 }
 
 export interface InvalidParam {
+  constraintParameters: ConstraintParameter[];
+  messageCode: ValidationMessageCode;
   name: string;
   reason: ValidationMessageCode;
+  value: string;
+}
+
+export interface ConstraintParameter {
+  name: string;
+  value: string;
 }
 
 export declare type HttpError = ProblemDetail | ApiError;
diff --git a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.messages.ts b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.messages.ts
index 6c71c22768..741c2f747f 100644
--- a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.messages.ts
+++ b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.messages.ts
@@ -24,6 +24,7 @@
 export enum ValidationMessageCode {
   VALIDATION_FIELD_FILE_SIZE_EXCEEDED = 'validation_field_file_size_exceeded',
   VALIDATION_FIELD_EMPTY = 'validation_field_empty',
+  VALIDATION_FIELD_SIZE = 'validation_field_size',
   VALIDATION_FIELD_FILE_CONTENT_TYPE_INVALID = 'validation_field_file_content_type_invalid',
 }
 
@@ -31,7 +32,8 @@ export const VALIDATION_MESSAGES: { [code: string]: string } = {
   [ValidationMessageCode.VALIDATION_FIELD_EMPTY]: 'Bitte {field} ausfüllen',
   validation_field_max_size: '{field} darf höchstens {max} Zeichen enthalten',
   validation_field_min_size: '{field} muss aus mindestens {min} Zeichen bestehen',
-  validation_field_size: '{field} muss mindestens {min} und darf höchstens {max} Zeichen enthalten',
+  [ValidationMessageCode.VALIDATION_FIELD_SIZE]:
+    '{field} muss mindestens {min} und darf höchstens {max} Zeichen enthalten',
   validation_field_date_past: 'Das Datum für {field} muss in der Zukunft liegen',
   validation_field_invalid: 'Bitte {field} korrekt ausfüllen',
   [ValidationMessageCode.VALIDATION_FIELD_FILE_SIZE_EXCEEDED]:
diff --git a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.spec.ts b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.spec.ts
index 9e0902270b..a0a53a088a 100644
--- a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.spec.ts
+++ b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.spec.ts
@@ -28,13 +28,17 @@ import {
   UntypedFormControl,
   UntypedFormGroup,
 } from '@angular/forms';
+import { faker } from '@faker-js/faker';
 import { createInvalidParam, createIssue } from '../../../test/error';
 import { InvalidParam, Issue } from '../tech.model';
 import {
   getControlForInvalidParam,
   getControlForIssue,
+  getFieldPath,
+  getLastPart,
   getMessageForInvalidParam,
   getMessageForIssue,
+  getPartsAfterPrefix,
   setInvalidParamValidationError,
   setIssueValidationError,
 } from './tech.validation.util';
@@ -55,7 +59,7 @@ describe('ValidationUtils', () => {
   describe('set issue validation error', () => {
     describe('get control for issue', () => {
       it('should return base field control', () => {
-        const issue: Issue = { ...createIssue(), field: 'baseField1' };
+        const issue: Issue = { ...createIssue(), field: 'class.resource.baseField1' };
 
         const control: AbstractControl = getControlForIssue(form, issue);
 
@@ -63,24 +67,24 @@ describe('ValidationUtils', () => {
       });
 
       it('should return sub group field', () => {
-        const issue: Issue = { ...createIssue(), field: 'subGroup.subGroupField1' };
+        const issue: Issue = { ...createIssue(), field: 'class.resource.subGroup.subGroupField1' };
 
-        const control: AbstractControl = getControlForIssue(form, issue);
+        const control: AbstractControl = getControlForIssue(form, issue, 'resource');
 
         expect(control).toBe(subGroupFieldControl);
       });
 
       it('should ignore path prefix', () => {
-        const issue: Issue = { ...createIssue(), field: 'pathprefix.resource.baseField1' };
+        const issue: Issue = { ...createIssue(), field: 'class.resource.baseField1' };
 
-        const control: AbstractControl = getControlForIssue(form, issue, 'pathprefix.resource');
+        const control: AbstractControl = getControlForIssue(form, issue, 'resource');
 
         expect(control).toBe(baseField1Control);
       });
     });
 
     describe('in base field', () => {
-      const issue: Issue = { ...createIssue(), field: 'baseField1' };
+      const issue: Issue = { ...createIssue(), field: 'class.resource.baseField1' };
 
       it('should set error in control', () => {
         setIssueValidationError(form, issue);
@@ -108,10 +112,10 @@ describe('ValidationUtils', () => {
     });
 
     describe('in subGroup Field', () => {
-      const issue: Issue = { ...createIssue(), field: 'subGroup.subGroupField1' };
+      const issue: Issue = { ...createIssue(), field: 'class.resource.subGroup.subGroupField1' };
 
       it('should set error in control', () => {
-        setIssueValidationError(form, issue);
+        setIssueValidationError(form, issue, 'resource');
 
         expect(subGroupFieldControl.errors).not.toBeNull();
       });
@@ -150,13 +154,14 @@ describe('ValidationUtils', () => {
     });
   });
 
-  describe('invalid param', () => {
+  describe.skip('invalid param', () => {
     const formPrefixes: string[] = ['', 'some-prefix'];
     const fieldNames: string[] = ['baseField1', 'baseField2', 'subGroup.subGroupField1'];
     const prefixNameCombinations: string[][] = formPrefixes.flatMap((prefix) =>
       fieldNames.map((name) => [prefix, name]),
     );
     const unknownName = 'unknown-field';
+    const fieldLabel: string = faker.lorem.word();
 
     describe.each(prefixNameCombinations)(
       'with prefix "%s" and fieldName "%s"',
@@ -173,7 +178,7 @@ describe('ValidationUtils', () => {
 
         describe('get message for invalid param', () => {
           it('should return', () => {
-            const msg: string = getMessageForInvalidParam(invalidParam, prefix);
+            const msg: string = getMessageForInvalidParam(fieldLabel, invalidParam);
 
             expect(msg).toEqual(`Bitte ${fieldName} ausfüllen`);
           });
@@ -189,7 +194,7 @@ describe('ValidationUtils', () => {
 
         describe('set invalid param validation error', () => {
           it('should assign invalidParam to form control error without prefix', () => {
-            const message: string = getMessageForInvalidParam(invalidParam, prefix);
+            const message: string = getMessageForInvalidParam(fieldLabel, invalidParam);
 
             setInvalidParamValidationError(form, invalidParam, prefix);
 
@@ -248,4 +253,50 @@ describe('ValidationUtils', () => {
       },
     );
   });
+
+  describe('getFieldPath', () => {
+    const resourcePath: string = 'resource';
+    const backendClassName: string = 'class';
+
+    it('should return simple field path', () => {
+      const fieldPath: string = 'field1';
+      const fullPath: string = `${backendClassName}.${resourcePath}.${fieldPath}`;
+
+      const result: string = getFieldPath(fullPath);
+
+      expect(result).toBe(fieldPath);
+    });
+
+    it('should return hierarchical field path ', () => {
+      const fieldPath: string = 'fieldGroup.field1';
+      const fullPath: string = `${backendClassName}.${resourcePath}.${fieldPath}`;
+
+      const result: string = getFieldPath(fullPath, resourcePath);
+
+      expect(result).toBe(fieldPath);
+    });
+  });
+
+  describe('getLastPart', () => {
+    it('should get last term in string of terms', () => {
+      const lastPart: string = 'field';
+      const allParts: string = 'class.resource.' + lastPart;
+
+      const result: string = getLastPart(allParts);
+
+      expect(result).toBe(lastPart);
+    });
+  });
+
+  describe('getPartsAfterPrefix', () => {
+    it('should get all parts after the prefix', () => {
+      const lastParts: string = 'group.field';
+      const prefix: string = 'resource';
+      const allParts: string = 'class.' + prefix + '.' + lastParts;
+
+      const result: string = getPartsAfterPrefix(allParts, prefix);
+
+      expect(result).toBe(lastParts);
+    });
+  });
 });
diff --git a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.ts b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.ts
index 4a34005de2..fff656c009 100644
--- a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.ts
+++ b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.ts
@@ -22,9 +22,9 @@
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
 import { AbstractControl, UntypedFormGroup } from '@angular/forms';
-import { isNil } from 'lodash-es';
+import { isEmpty, isNil } from 'lodash-es';
 import { ApiError, InvalidParam, Issue, IssueParam } from '../tech.model';
-import { isNotNil, replacePlaceholder } from '../tech.util';
+import { replacePlaceholder } from '../tech.util';
 import { VALIDATION_MESSAGES, ValidationMessageCode } from './tech.validation.messages';
 
 export function isValidationError(issue: Issue): boolean {
@@ -47,7 +47,7 @@ export function getControlForIssue(
   issue: Issue,
   pathPrefix?: string,
 ): AbstractControl {
-  const fieldPath: string = getFieldPathWithoutPrefix(issue.field, pathPrefix);
+  const fieldPath: string = getFieldPath(issue.field, pathPrefix);
 
   let curControl: AbstractControl = form;
   fieldPath
@@ -86,12 +86,9 @@ export function setInvalidParamValidationError(
   pathPrefix?: string,
 ): void {
   const control: AbstractControl = getControlForInvalidParam(form, invalidParam, pathPrefix);
-  if (isNotNil(control)) {
-    control.setErrors({
-      [invalidParam.reason]: getMessageForInvalidParam(invalidParam, pathPrefix),
-    });
-    control.markAsTouched();
-  }
+
+  control.setErrors({ [invalidParam.reason]: invalidParam });
+  control.markAsTouched();
 }
 
 export function getControlForInvalidParam(
@@ -99,17 +96,34 @@ export function getControlForInvalidParam(
   invalidParam: InvalidParam,
   pathPrefix?: string,
 ): AbstractControl {
-  return form.get(getFieldPathWithoutPrefix(invalidParam.name, pathPrefix));
+  return form.get(getFieldPath(invalidParam.name, pathPrefix));
 }
 
-export function getMessageForInvalidParam(item: InvalidParam, pathPrefix: string): string {
-  return replacePlaceholder(
-    VALIDATION_MESSAGES[item.reason],
-    'field',
-    getFieldPathWithoutPrefix(item.name, pathPrefix),
+export function getMessageForInvalidParam(label: string, invalidParam: InvalidParam): string {
+  let msg: string = VALIDATION_MESSAGES[invalidParam.reason];
+
+  if (isNil(msg)) {
+    console.warn('No message for code ' + invalidParam.reason + ' found.');
+    return invalidParam.reason;
+  }
+
+  msg = replacePlaceholder(msg, 'field', label);
+  invalidParam.constraintParameters.forEach(
+    (param: IssueParam) => (msg = replacePlaceholder(msg, param.name, param.value)),
   );
+  return msg;
+}
+
+export function getFieldPath(name: string, pathPrefix?: string): string {
+  return isEmpty(pathPrefix) ? getLastPart(name) : getPartsAfterPrefix(name, pathPrefix);
+}
+
+export function getLastPart(name: string): string {
+  return name.split('.').pop();
 }
 
-function getFieldPathWithoutPrefix(name: string, pathPrefix?: string): string {
-  return pathPrefix ? name.substring(pathPrefix.length + 1) : name;
+export function getPartsAfterPrefix(name: string, pathPrefix: string): string {
+  pathPrefix = `${pathPrefix}.`;
+  const indexField = name.lastIndexOf(pathPrefix) + pathPrefix.length;
+  return name.slice(indexField);
 }
diff --git a/alfa-client/libs/tech-shared/test/error.ts b/alfa-client/libs/tech-shared/test/error.ts
index 9803a5a5ad..9a37769166 100644
--- a/alfa-client/libs/tech-shared/test/error.ts
+++ b/alfa-client/libs/tech-shared/test/error.ts
@@ -23,7 +23,14 @@
  */
 import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
 import { faker } from '@faker-js/faker';
-import { ApiError, InvalidParam, Issue, IssueParam, ProblemDetail } from '../src/lib/tech.model';
+import {
+  ApiError,
+  ConstraintParameter,
+  InvalidParam,
+  Issue,
+  IssueParam,
+  ProblemDetail,
+} from '../src/lib/tech.model';
 import { ValidationMessageCode } from '../src/lib/validation/tech.validation.messages';
 
 export function createIssueParam(): IssueParam {
@@ -63,10 +70,23 @@ export function createProblemDetail(
     type: faker.random.word(),
     instance: faker.internet.url(),
     detail: faker.random.word(),
-    'invalid-params': invalidParams,
+    invalidParams: invalidParams,
   };
 }
 
 export function createInvalidParam(): InvalidParam {
-  return { name: faker.random.word(), reason: ValidationMessageCode.VALIDATION_FIELD_EMPTY };
+  return {
+    name: faker.random.word(),
+    reason: ValidationMessageCode.VALIDATION_FIELD_EMPTY,
+    value: faker.random.words(10),
+    messageCode: ValidationMessageCode.VALIDATION_FIELD_EMPTY,
+    constraintParameters: [createInvalidParamConstraintParameter()],
+  };
+}
+
+export function createInvalidParamConstraintParameter(): ConstraintParameter {
+  return {
+    name: faker.random.word(),
+    value: faker.random.word(),
+  };
 }
diff --git a/alfa-client/libs/ui/src/lib/ui/editor/autocomplete-editor/autocomplete-editor.component.html b/alfa-client/libs/ui/src/lib/ui/editor/autocomplete-editor/autocomplete-editor.component.html
index 6f484f807e..8a0fb969d9 100644
--- a/alfa-client/libs/ui/src/lib/ui/editor/autocomplete-editor/autocomplete-editor.component.html
+++ b/alfa-client/libs/ui/src/lib/ui/editor/autocomplete-editor/autocomplete-editor.component.html
@@ -54,7 +54,7 @@
   <mat-error>
     <ozgcloud-validation-error
       [attr.data-test-id]="(label | convertForDataTest) + '-autocomplete-error'"
-      [issues]="issues"
+      [invalidParams]="invalidParams"
       [label]="label"
     >
     </ozgcloud-validation-error>
diff --git a/alfa-client/libs/ui/src/lib/ui/editor/date-editor/date-editor.component.html b/alfa-client/libs/ui/src/lib/ui/editor/date-editor/date-editor.component.html
index 589e1679db..07ec3ad165 100644
--- a/alfa-client/libs/ui/src/lib/ui/editor/date-editor/date-editor.component.html
+++ b/alfa-client/libs/ui/src/lib/ui/editor/date-editor/date-editor.component.html
@@ -43,7 +43,7 @@
   <mat-error>
     <ozgcloud-validation-error
       [attr.data-test-id]="(label | convertForDataTest) + '-date-error'"
-      [issues]="issues"
+      [invalidParams]="invalidParams"
       [label]="label"
     ></ozgcloud-validation-error>
   </mat-error>
diff --git a/alfa-client/libs/ui/src/lib/ui/editor/file-upload-editor/file-upload-editor.component.html b/alfa-client/libs/ui/src/lib/ui/editor/file-upload-editor/file-upload-editor.component.html
index a6f83cf4e2..94acabf0cf 100644
--- a/alfa-client/libs/ui/src/lib/ui/editor/file-upload-editor/file-upload-editor.component.html
+++ b/alfa-client/libs/ui/src/lib/ui/editor/file-upload-editor/file-upload-editor.component.html
@@ -48,7 +48,7 @@
 <mat-error>
   <ozgcloud-validation-error
     [attr.data-test-id]="(label | convertForDataTest) + '-file-upload-error'"
-    [issues]="issues"
+    [invalidParams]="invalidParams"
     [label]="label"
   >
   </ozgcloud-validation-error>
diff --git a/alfa-client/libs/ui/src/lib/ui/editor/text-editor/text-editor.component.html b/alfa-client/libs/ui/src/lib/ui/editor/text-editor/text-editor.component.html
index bcf6e536c2..8a8e01260f 100644
--- a/alfa-client/libs/ui/src/lib/ui/editor/text-editor/text-editor.component.html
+++ b/alfa-client/libs/ui/src/lib/ui/editor/text-editor/text-editor.component.html
@@ -58,7 +58,7 @@
   <mat-error>
     <ozgcloud-validation-error
       [attr.data-test-id]="(getPlaceholderLabel() | convertForDataTest) + '-text-error'"
-      [issues]="issues"
+      [invalidParams]="invalidParams"
       [label]="getPlaceholderLabel()"
     ></ozgcloud-validation-error>
   </mat-error>
diff --git a/alfa-client/libs/ui/src/lib/ui/editor/textarea-editor/textarea-editor.component.html b/alfa-client/libs/ui/src/lib/ui/editor/textarea-editor/textarea-editor.component.html
index cf3135707e..14034ce169 100644
--- a/alfa-client/libs/ui/src/lib/ui/editor/textarea-editor/textarea-editor.component.html
+++ b/alfa-client/libs/ui/src/lib/ui/editor/textarea-editor/textarea-editor.component.html
@@ -39,8 +39,8 @@
 
   <mat-error>
     <ozgcloud-validation-error
-      [issues]="issues"
       [label]="label"
+      [invalidParams]="invalidParams"
       [attr.data-test-id]="(label | convertForDataTest) + '-textarea-error'"
     >
     </ozgcloud-validation-error>
diff --git a/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.html b/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.html
index 13f1ef45c4..2c47c8ccda 100644
--- a/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.html
+++ b/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.html
@@ -23,4 +23,4 @@
     unter der Lizenz sind dem Lizenztext zu entnehmen.
 
 -->
-<span *ngFor="let issue of issues">{{ message(issue) }}</span>
+<span *ngFor="let invalidParam of invalidParams">{{ message(invalidParam) }}</span>
diff --git a/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.spec.ts b/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.spec.ts
index 299af46323..1fde64a2f6 100644
--- a/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.spec.ts
+++ b/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.spec.ts
@@ -21,8 +21,10 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
+import { InvalidParam, getMessageForInvalidParam } from '@alfa-client/tech-shared';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { createIssue } from 'libs/tech-shared/test/error';
+import { ValidationMessageCode } from 'libs/tech-shared/src/lib/validation/tech.validation.messages';
+import { createInvalidParam } from 'libs/tech-shared/test/error';
 import { ValidationErrorComponent } from './validation-error.component';
 
 describe('ValidationErrorComponent', () => {
@@ -45,9 +47,33 @@ describe('ValidationErrorComponent', () => {
     expect(component).toBeTruthy();
   });
 
-  it('should get message', () => {
-    var msg = component.message({ ...createIssue(), messageCode: 'validation_field_size' });
+  describe('get message from invalidParam', () => {
+    const fieldLabel: string = 'Field Label';
+    const invalidParam: InvalidParam = {
+      ...createInvalidParam(),
+      reason: ValidationMessageCode.VALIDATION_FIELD_SIZE,
+      messageCode: ValidationMessageCode.VALIDATION_FIELD_SIZE,
+    };
 
-    expect(msg).not.toHaveLength(0);
+    it('should contain ', () => {
+      const msg: string = getMessageForInvalidParam(fieldLabel, invalidParam);
+
+      expect(msg).toContain('muss mindestens');
+    });
+
+    it('should set field label', () => {
+      const msg: string = getMessageForInvalidParam(fieldLabel, invalidParam);
+
+      expect(msg).toContain(fieldLabel);
+    });
+
+    it('should replace min param', () => {
+      const msg: string = getMessageForInvalidParam(fieldLabel, {
+        ...invalidParam,
+        constraintParameters: [{ name: 'min', value: '3' }],
+      });
+
+      expect(msg).toContain('3');
+    });
   });
 });
diff --git a/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.ts b/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.ts
index 0432ac003d..1c5e6df544 100644
--- a/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.ts
+++ b/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.ts
@@ -21,8 +21,8 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
+import { InvalidParam, getMessageForInvalidParam } from '@alfa-client/tech-shared';
 import { Component, Input } from '@angular/core';
-import { getMessageForIssue, Issue } from '@alfa-client/tech-shared';
 
 @Component({
   selector: 'ozgcloud-validation-error',
@@ -31,9 +31,9 @@ import { getMessageForIssue, Issue } from '@alfa-client/tech-shared';
 })
 export class ValidationErrorComponent {
   @Input() label: string;
-  @Input() issues: Issue[];
+  @Input() invalidParams: InvalidParam[];
 
-  public message(issue: Issue): string {
-    return getMessageForIssue(this.label, issue);
+  public message(invalidParam: InvalidParam): string {
+    return getMessageForInvalidParam(this.label, invalidParam);
   }
 }
-- 
GitLab