diff --git a/backend/src/main/resources/dynamic-http-metadata-provider.schema.json b/backend/src/main/resources/dynamic-http-metadata-provider.schema.json index 1969c9ab4..8e1989de0 100644 --- a/backend/src/main/resources/dynamic-http-metadata-provider.schema.json +++ b/backend/src/main/resources/dynamic-http-metadata-provider.schema.json @@ -598,8 +598,7 @@ "certificateFile": { "title": "label.certificate-file", "description": "tooltip.certificate-file", - "type": "string", - "default": "" + "type": "string" } }, "anyOf": [ diff --git a/ui/src/app/metadata/metadata.module.ts b/ui/src/app/metadata/metadata.module.ts index 946c89423..fd00a2274 100644 --- a/ui/src/app/metadata/metadata.module.ts +++ b/ui/src/app/metadata/metadata.module.ts @@ -9,7 +9,8 @@ import { MetadataRoutingModule } from './metadata.routing'; import { ProviderModule } from './provider/provider.module'; import { I18nModule } from '../i18n/i18n.module'; import { CustomWidgetRegistry } from '../schema-form/registry'; -import { WidgetRegistry } from 'ngx-schema-form'; +import { WidgetRegistry, SchemaValidatorFactory } from 'ngx-schema-form'; +import { CustomSchemaValidatorFactory } from '../schema-form/service/schema-validator'; @NgModule({ @@ -23,7 +24,11 @@ import { WidgetRegistry } from 'ngx-schema-form'; I18nModule ], providers: [ - { provide: WidgetRegistry, useClass: CustomWidgetRegistry } + { provide: WidgetRegistry, useClass: CustomWidgetRegistry }, + { + provide: SchemaValidatorFactory, + useClass: CustomSchemaValidatorFactory + } ], declarations: [ MetadataPageComponent diff --git a/ui/src/app/schema-form/model/messages.ts b/ui/src/app/schema-form/model/messages.ts new file mode 100644 index 000000000..6d6fa61fd --- /dev/null +++ b/ui/src/app/schema-form/model/messages.ts @@ -0,0 +1,3 @@ +export const HARD_CODED_REQUIRED_MSG = RegExp('Missing required property'); + +export const REQUIRED_MSG_OVERRIDE = 'message.required'; \ No newline at end of file diff --git a/ui/src/app/schema-form/schema-form.module.ts b/ui/src/app/schema-form/schema-form.module.ts index 842e903dd..13aba1369 100644 --- a/ui/src/app/schema-form/schema-form.module.ts +++ b/ui/src/app/schema-form/schema-form.module.ts @@ -1,5 +1,5 @@ import { NgModule } from '@angular/core'; -import { SchemaFormModule } from 'ngx-schema-form'; +import { SchemaFormModule, SchemaValidatorFactory } from 'ngx-schema-form'; import { CommonModule } from '@angular/common'; import { ReactiveFormsModule } from '@angular/forms'; @@ -23,6 +23,7 @@ import { CustomObjectWidget } from './widget/object/object.component'; import { CustomRadioComponent } from './widget/radio/radio.component'; import { InlineObjectListComponent } from './widget/array/inline-obj-list.component'; import { InlineObjectComponent } from './widget/object/inline-obj.component'; +import { CustomSchemaValidatorFactory } from './service/schema-validator'; export const COMPONENTS = [ BooleanRadioComponent, diff --git a/ui/src/app/schema-form/service/schema-validator.ts b/ui/src/app/schema-form/service/schema-validator.ts new file mode 100644 index 000000000..075a78ec9 --- /dev/null +++ b/ui/src/app/schema-form/service/schema-validator.ts @@ -0,0 +1,19 @@ +import * as ZSchema from 'z-schema'; +import { ZSchemaValidatorFactory } from 'ngx-schema-form'; + +export class CustomSchemaValidatorFactory extends ZSchemaValidatorFactory { + + protected zschema; + + constructor() { + super(); + this.createSchemaValidator(); + } + + private createSchemaValidator() { + this.zschema = new ZSchema({ + breakOnFirstError: false + }); + } +} + diff --git a/ui/src/app/schema-form/service/schema.service.ts b/ui/src/app/schema-form/service/schema.service.ts index 9cd7346de..617769de6 100644 --- a/ui/src/app/schema-form/service/schema.service.ts +++ b/ui/src/app/schema-form/service/schema.service.ts @@ -31,7 +31,8 @@ export class SchemaService { Object .keys(condition.properties) .some( - key => values.hasOwnProperty(key) ? condition.properties[key].enum[0] === values[key] : false + key => values.hasOwnProperty(key) && condition.properties[key].enum ? + condition.properties[key].enum[0] === values[key] : false ) ); currentConditions.forEach(el => { diff --git a/ui/src/app/schema-form/widget/select/select.component.html b/ui/src/app/schema-form/widget/select/select.component.html index e66a83350..a7d1dd2ff 100644 --- a/ui/src/app/schema-form/widget/select/select.component.html +++ b/ui/src/app/schema-form/widget/select/select.component.html @@ -42,7 +42,7 @@ , - error + error diff --git a/ui/src/app/schema-form/widget/select/select.component.ts b/ui/src/app/schema-form/widget/select/select.component.ts index d504f0384..ecd50a75b 100644 --- a/ui/src/app/schema-form/widget/select/select.component.ts +++ b/ui/src/app/schema-form/widget/select/select.component.ts @@ -4,6 +4,7 @@ import { SelectWidget } from 'ngx-schema-form'; import { SchemaService } from '../../service/schema.service'; import { map, shareReplay, startWith } from 'rxjs/operators'; import { Subscription } from 'rxjs'; +import { HARD_CODED_REQUIRED_MSG } from '../../model/messages'; @Component({ selector: 'select-component', @@ -43,7 +44,7 @@ export class CustomSelectComponent extends SelectWidget implements AfterViewInit } this.errorSub = this.control.valueChanges.pipe(startWith(this.control.value)).subscribe(v => { - if (!v && this.required && !this.errorMessages.some(msg => !!msg.toLowerCase().match('required').length)) { + if (!v && this.required && !this.errorMessages.some(msg => HARD_CODED_REQUIRED_MSG.test(msg))) { this.errorMessages.push('message.required'); } }); @@ -56,4 +57,8 @@ export class CustomSelectComponent extends SelectWidget implements AfterViewInit get required(): boolean { return this.widgetService.isRequired(this.formProperty); } + + getError(error: string): string { + return HARD_CODED_REQUIRED_MSG.test(error) ? 'message.required' : error; + } } diff --git a/ui/src/app/schema-form/widget/string/string.component.html b/ui/src/app/schema-form/widget/string/string.component.html index 26964a59f..4986846ff 100644 --- a/ui/src/app/schema-form/widget/string/string.component.html +++ b/ui/src/app/schema-form/widget/string/string.component.html @@ -13,6 +13,7 @@ validate="true" [attr.readonly]="schema.readOnly?true:null" class="text-widget.id textline-widget form-control" + [class.is-invalid]="control.touched && !control.value && errorMessages.length" [attr.type]="this.getInputType()" [attr.id]="id" [formControl]="control" @@ -25,7 +26,7 @@ , - error + error { - if (!v && this.required && !this.errorMessages.some(msg => !!msg.toLowerCase().match('required').length)) { - this.errorMessages.push('message.required'); + let listener = this.formProperty.parent ? this.formProperty.parent : this.control; + this.errorSub = listener.valueChanges.pipe(startWith(listener.value)).subscribe(v => { + if (!this.control.value + && this.required + && !this.errorMessages.some(msg => HARD_CODED_REQUIRED_MSG.test(msg)) + && this.errorMessages.indexOf(REQUIRED_MSG_OVERRIDE) < 0) { + this.errorMessages.push(REQUIRED_MSG_OVERRIDE); + } + if (!this.required) { + this.errorMessages = this.errorMessages.filter(e => e !== REQUIRED_MSG_OVERRIDE); } }); } @@ -38,4 +47,8 @@ export class CustomStringComponent extends StringWidget implements AfterViewInit return req; } + + getError(error: string): string { + return HARD_CODED_REQUIRED_MSG.test(error) ? REQUIRED_MSG_OVERRIDE : error; + } } diff --git a/ui/src/app/schema-form/widget/textarea/textarea.component.html b/ui/src/app/schema-form/widget/textarea/textarea.component.html index 3705c4009..d47d6007d 100644 --- a/ui/src/app/schema-form/widget/textarea/textarea.component.html +++ b/ui/src/app/schema-form/widget/textarea/textarea.component.html @@ -21,7 +21,7 @@ , - error + error diff --git a/ui/src/app/schema-form/widget/textarea/textarea.component.ts b/ui/src/app/schema-form/widget/textarea/textarea.component.ts index 81d940bde..fdbce4b35 100644 --- a/ui/src/app/schema-form/widget/textarea/textarea.component.ts +++ b/ui/src/app/schema-form/widget/textarea/textarea.component.ts @@ -4,6 +4,7 @@ import { TextAreaWidget } from 'ngx-schema-form'; import { SchemaService } from '../../service/schema.service'; import { Subscription } from 'rxjs'; import { startWith } from 'rxjs/operators'; +import { HARD_CODED_REQUIRED_MSG } from '../../model/messages'; @Component({ selector: 'textarea-component', @@ -22,7 +23,7 @@ export class CustomTextAreaComponent extends TextAreaWidget implements AfterView ngAfterViewInit(): void { super.ngAfterViewInit(); this.errorSub = this.control.valueChanges.pipe(startWith(this.control.value)).subscribe(v => { - if (!v && this.required && !this.errorMessages.some(msg => !!msg.toLowerCase().match('required').length)) { + if (!v && this.required && !this.errorMessages.some(msg => HARD_CODED_REQUIRED_MSG.test(msg))) { this.errorMessages.push('message.required'); } }); @@ -35,4 +36,8 @@ export class CustomTextAreaComponent extends TextAreaWidget implements AfterView get required(): boolean { return this.widgetService.isRequired(this.formProperty); } + + getError(error: string): string { + return error.match('required').length ? 'message.required' : error; + } } diff --git a/ui/src/assets/schema/provider/filebacked-http-filters.schema.json b/ui/src/assets/schema/provider/filebacked-http-filters.schema.json index 4bb2f399c..820063bfa 100644 --- a/ui/src/assets/schema/provider/filebacked-http-filters.schema.json +++ b/ui/src/assets/schema/provider/filebacked-http-filters.schema.json @@ -50,8 +50,7 @@ "title": "label.certificate-file", "description": "tooltip.certificate-file", "type": "string", - "widget": "textline", - "default": "" + "widget": "textline" } }, "anyOf": [ @@ -59,6 +58,10 @@ "properties": { "requireSignedRoot": { "enum": [ true ] + }, + "certificateFile": { + "minLength": 1, + "type": "string" } }, "required": [