Improved Validations for ARC Wizards (#12945)

This commit is contained in:
Arvind Ranasaria
2020-11-18 22:03:59 -08:00
committed by GitHub
parent e63e4f0901
commit c7cca5afea
12 changed files with 728 additions and 456 deletions

View File

@@ -4,7 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { throwUnless } from '../../common/utils';
import * as vscode from 'vscode';
import { isUndefinedOrEmpty, throwUnless } from '../../common/utils';
import { InputValueType } from '../modelViewUtils';
export interface ValidationResult {
valid: boolean;
@@ -12,10 +14,12 @@ export interface ValidationResult {
}
export type Validator = () => Promise<ValidationResult>;
export type ValidationValueType = string | number | undefined;
export type VariableValueGetter = (variable: string) => Promise<ValidationValueType>;
export type ValueGetter = () => Promise<ValidationValueType>;
export type OnValidation = (isValid: boolean) => Promise<void>;
export type ValueGetter = () => Promise<InputValueType>;
export type TargetValueGetter = (variable: string) => Promise<InputValueType>;
export type OnTargetValidityChangedGetter = (variable: string) => vscode.Event<boolean>;
export const enum ValidationType {
IsInteger = 'is_integer',
@@ -51,35 +55,43 @@ export abstract class Validation {
get description(): string {
return this._description;
}
protected get onValidation(): OnValidation {
return this._onValidation;
}
// gets the validation result for this validation object
abstract validate(): Promise<ValidationResult>;
protected getValue(): Promise<ValidationValueType> {
protected getValue(): Promise<InputValueType> {
return this._valueGetter();
}
protected getVariableValue(variable: string): Promise<ValidationValueType> {
return this._variableValueGetter!(variable);
protected getTargetValue(variable: string): Promise<InputValueType> {
return this._targetValueGetter!(variable);
}
constructor(validation: ValidationInfo, protected _valueGetter: ValueGetter, protected _variableValueGetter?: VariableValueGetter) {
constructor(validation: ValidationInfo, protected _onValidation: OnValidation, protected _valueGetter: ValueGetter, protected _targetValueGetter?: TargetValueGetter, protected _onTargetValidityChangedGetter?: OnTargetValidityChangedGetter, protected _onNewDisposableCreated?: (disposable: vscode.Disposable) => void) {
this._description = validation.description;
}
}
export class IntegerValidation extends Validation {
constructor(validation: IntegerValidationInfo, valueGetter: ValueGetter) {
super(validation, valueGetter);
constructor(validation: IntegerValidationInfo, onValidation: OnValidation, valueGetter: ValueGetter) {
super(validation, onValidation, valueGetter);
}
private async isInteger(): Promise<boolean> {
private async isIntegerOrEmptyOrUndefined(): Promise<boolean> {
const value = await this.getValue();
return (typeof value === 'string') ? Number.isInteger(parseFloat(value)) : Number.isInteger(value);
return (isUndefinedOrEmpty(value))
? true
: (typeof value === 'string')
? Number.isInteger(parseFloat(value))
: Number.isInteger(value);
}
async validate(): Promise<ValidationResult> {
const isValid = await this.isInteger();
const isValid = await this.isIntegerOrEmptyOrUndefined();
await this.onValidation(isValid);
return {
valid: isValid,
message: isValid ? undefined : this.description
@@ -94,17 +106,16 @@ export class RegexValidation extends Validation {
return this._regex;
}
constructor(validation: RegexValidationInfo, valueGetter: ValueGetter) {
super(validation, valueGetter);
constructor(validation: RegexValidationInfo, validationMessageUpdater: OnValidation, valueGetter: ValueGetter) {
super(validation, validationMessageUpdater, valueGetter);
throwUnless(validation.regex !== undefined);
this._regex = (typeof validation.regex === 'string') ? new RegExp(validation.regex) : validation.regex;
}
async validate(): Promise<ValidationResult> {
const value = await this.getValue();
const isValid = value === undefined
? false
: this.regex.test(value.toString());
const value = (await this.getValue())?.toString();
const isValid = isUndefinedOrEmpty(value) ? true : this.regex.test(value!);
await this.onValidation(isValid);
return {
valid: isValid,
message: isValid ? undefined : this.description
@@ -113,21 +124,40 @@ export class RegexValidation extends Validation {
}
export abstract class Comparison extends Validation {
protected _target: string; // comparison object require a target so override the base optional setting.
protected _target: string; // comparison object requires a target so override the base optional setting.
protected _ensureOnTargetValidityChangeListenerAdded = false;
get target(): string {
return this._target;
}
constructor(validation: ComparisonValidationInfo, valueGetter: ValueGetter, variableValueGetter: VariableValueGetter) {
super(validation, valueGetter, variableValueGetter);
protected onTargetValidityChanged(onTargetValidityChangedAction: (e: boolean) => Promise<void>): void {
const onValidityChanged = this._onTargetValidityChangedGetter(this.target);
this._onNewDisposableCreated(onValidityChanged(isValid => onTargetValidityChangedAction(isValid)));
}
constructor(validation: ComparisonValidationInfo, onValidation: OnValidation, valueGetter: ValueGetter, targetValueGetter: TargetValueGetter, protected _onTargetValidityChangedGetter: OnTargetValidityChangedGetter, protected _onNewDisposableCreated: (disposable: vscode.Disposable) => void) {
super(validation, onValidation, valueGetter, targetValueGetter);
throwUnless(validation.target !== undefined);
this._target = validation.target;
}
private validateOnTargetValidityChange() {
if (!this._ensureOnTargetValidityChangeListenerAdded) {
this._ensureOnTargetValidityChangeListenerAdded = true;
this.onTargetValidityChanged(async (isTargetValid: boolean) => {
if (isTargetValid) { // if target is valid
await this.validate();
}
});
}
}
abstract isComparisonSuccessful(): Promise<boolean>;
async validate(): Promise<ValidationResult> {
this.validateOnTargetValidityChange();
const isValid = await this.isComparisonSuccessful();
await this.onValidation(isValid);
return {
valid: isValid,
message: isValid ? undefined : this.description
@@ -137,22 +167,26 @@ export abstract class Comparison extends Validation {
export class LessThanOrEqualsValidation extends Comparison {
async isComparisonSuccessful() {
return (await this.getValue())! <= ((await this.getVariableValue(this.target))!);
const value = (await this.getValue());
const targetValue = (await this.getTargetValue(this.target));
return (isUndefinedOrEmpty(value) || isUndefinedOrEmpty(targetValue)) ? true : value! <= targetValue!;
}
}
export class GreaterThanOrEqualsValidation extends Comparison {
async isComparisonSuccessful() {
return (await this.getValue())! >= ((await this.getVariableValue(this.target))!);
const value = (await this.getValue());
const targetValue = (await this.getTargetValue(this.target));
return (isUndefinedOrEmpty(value) || isUndefinedOrEmpty(targetValue)) ? true : value! >= targetValue!;
}
}
export function createValidation(validation: ValidationInfo, valueGetter: ValueGetter, variableValueGetter?: VariableValueGetter): Validation {
export function createValidation(validation: ValidationInfo, onValidation: OnValidation, valueGetter: ValueGetter, targetValueGetter?: TargetValueGetter, onTargetValidityChangedGetter?: OnTargetValidityChangedGetter, onDisposableCreated?: (disposable: vscode.Disposable) => void): Validation {
switch (validation.type) {
case ValidationType.Regex: return new RegexValidation(<RegexValidationInfo>validation, valueGetter);
case ValidationType.IsInteger: return new IntegerValidation(<IntegerValidationInfo>validation, valueGetter);
case ValidationType.LessThanOrEqualsTo: return new LessThanOrEqualsValidation(<ComparisonValidationInfo>validation, valueGetter, variableValueGetter!);
case ValidationType.GreaterThanOrEqualsTo: return new GreaterThanOrEqualsValidation(<ComparisonValidationInfo>validation, valueGetter, variableValueGetter!);
case ValidationType.Regex: return new RegexValidation(<RegexValidationInfo>validation, onValidation, valueGetter);
case ValidationType.IsInteger: return new IntegerValidation(<IntegerValidationInfo>validation, onValidation, valueGetter);
case ValidationType.LessThanOrEqualsTo: return new LessThanOrEqualsValidation(<ComparisonValidationInfo>validation, onValidation, valueGetter, targetValueGetter!, onTargetValidityChangedGetter!, onDisposableCreated!);
case ValidationType.GreaterThanOrEqualsTo: return new GreaterThanOrEqualsValidation(<ComparisonValidationInfo>validation, onValidation, valueGetter, targetValueGetter!, onTargetValidityChangedGetter!, onDisposableCreated!);
default: throw new Error(`unknown validation type:${validation.type}`); //dev error
}
}
@@ -161,8 +195,7 @@ export async function validateInputBoxComponent(component: azdata.InputBoxCompon
for (const validation of validations) {
const result = await validation.validate();
if (!result.valid) {
component.updateProperty('validationErrorMessage', result.message);
return false;
return false; //bail out on first failure, remaining validations are processed after this one has been fixed by the user.
}
}
return true;