Files
azuredatastudio/extensions/resource-deployment/src/ui/validation/validations.ts
Candice Ye dc0651aef7 Added a dynamic Cost Summary section to SQL MIAA Deployment Wizard (#17420)
* Added valueprovider for pricing. Pushing this for troubleshooting help.

* Committing changes for troubleshooting help. Moved InputValueType to typings file.

* Add readonly inputs to list

* Fixed ordering of package.json merge items

* Estimated cost moved to input page, ValueProvider only takes in a triggerfields[] and not a single string, fixed pricing logic.

* Removed pricingModel.ts

* Reverted some comments and code changes that were used in debugging.

* Changed some values from localizedConstants to single-quote constants'

* Changed some values from localizedConstants to single-quote constants'

* Added copyright header to pricingUtils.ts

* Removed try catch in extension.ts valueproviders, made some values in PricingUtils.ts top-level instead of exporting.

* Minor changes, added some comments and localized USD.

* Changes pricingutils classes to be constants, and added disposable to Hookupvalueprovider

Co-authored-by: Candice Ye <canye@microsoft.com>
Co-authored-by: chgagnon <chgagnon@microsoft.com>
2021-10-21 16:51:31 -07:00

214 lines
7.9 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { InputValueType } from 'resource-deployment';
import * as vscode from 'vscode';
import { isUndefinedOrEmpty, throwUnless } from '../../common/utils';
export interface ValidationResult {
valid: boolean;
message?: string;
}
export type Validator = () => Promise<ValidationResult>;
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',
Regex = 'regex_match',
LessThanOrEqualsTo = '<=',
GreaterThanOrEqualsTo = '>=',
NotEqualTo = '!='
}
export type ValidationInfo = RegexValidationInfo | IntegerValidationInfo | ComparisonValidationInfo;
export interface ValidationInfoBase {
readonly type: ValidationType,
readonly description: string,
}
export type IntegerValidationInfo = ValidationInfoBase;
export interface RegexValidationInfo extends ValidationInfoBase {
readonly regex: string | RegExp
}
export interface ComparisonValidationInfo extends ValidationInfoBase {
readonly target: string
}
export abstract class Validation {
private _description: string;
protected readonly _target?: string;
get target(): string | undefined {
return this._target;
}
get description(): string {
return this._description;
}
// gets the validation result for this validation object
abstract validate(): Promise<ValidationResult>;
protected getValue(): Promise<InputValueType> {
return this._valueGetter();
}
protected getTargetValue(variable: string): Promise<InputValueType> {
return this._targetValueGetter!(variable);
}
constructor(validation: ValidationInfo, 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);
}
private async isIntegerOrEmptyOrUndefined(): Promise<boolean> {
const value = await this.getValue();
return (isUndefinedOrEmpty(value))
? true
: (typeof value === 'string')
? Number.isInteger(parseFloat(value))
: Number.isInteger(value);
}
async validate(): Promise<ValidationResult> {
const isValid = await this.isIntegerOrEmptyOrUndefined();
return {
valid: isValid,
message: isValid ? undefined : this.description
};
}
}
export class RegexValidation extends Validation {
private _regex: RegExp;
get regex(): RegExp {
return this._regex;
}
constructor(validation: RegexValidationInfo, valueGetter: ValueGetter) {
super(validation, 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())?.toString();
const isValid = isUndefinedOrEmpty(value) ? true : this.regex.test(value!);
return {
valid: isValid,
message: isValid ? undefined : this.description
};
}
}
export abstract class Comparison extends Validation {
protected override _target: string; // comparison object requires a target so override the base optional setting.
protected _ensureOnTargetValidityChangeListenerAdded = false;
override get target(): string {
return this._target;
}
protected onTargetValidityChanged(onTargetValidityChangedAction: (e: boolean) => Promise<void>): void {
const onValidityChanged = this._onTargetValidityChangedGetter!(this.target);
this._onNewDisposableCreated!(onValidityChanged(isValid => onTargetValidityChangedAction(isValid)));
}
constructor(validation: ComparisonValidationInfo, valueGetter: ValueGetter, targetValueGetter: TargetValueGetter, onTargetValidityChangedGetter: OnTargetValidityChangedGetter, onNewDisposableCreated: (disposable: vscode.Disposable) => void) {
super(validation, valueGetter, targetValueGetter, onTargetValidityChangedGetter, onNewDisposableCreated);
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();
return {
valid: isValid,
message: isValid ? undefined : this.description
};
}
}
export class LessThanOrEqualsValidation extends Comparison {
async isComparisonSuccessful() {
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() {
const value = (await this.getValue());
const targetValue = (await this.getTargetValue(this.target));
return (isUndefinedOrEmpty(value) || isUndefinedOrEmpty(targetValue)) ? true : value! >= targetValue!;
}
}
export class NotEqualValidation extends Comparison {
async isComparisonSuccessful() {
const value = (await this.getValue());
const targetValue = this.target;
return (isUndefinedOrEmpty(value) || isUndefinedOrEmpty(targetValue)) ? true : value!.toString() !== targetValue!;
}
}
export function createValidation(validation: ValidationInfo, 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, targetValueGetter!, onTargetValidityChangedGetter!, onDisposableCreated!);
case ValidationType.GreaterThanOrEqualsTo: return new GreaterThanOrEqualsValidation(<ComparisonValidationInfo>validation, valueGetter, targetValueGetter!, onTargetValidityChangedGetter!, onDisposableCreated!);
case ValidationType.NotEqualTo: return new NotEqualValidation(<ComparisonValidationInfo>validation, valueGetter, targetValueGetter!, onTargetValidityChangedGetter!, onDisposableCreated!);
default: throw new Error(`unknown validation type:${validation.type}`); //dev error
}
}
export async function validateInputBoxComponent(component: azdata.InputBoxComponent, validations: Validation[] = []): Promise<boolean> {
let valid = true;
let message = '';
for (const validation of validations) {
const result = await validation.validate();
if (!result.valid) {
valid = false;
message = validation.description;
break; //bail out on first failure, remaining validations are processed after this one has been fixed by the user.
}
}
if ((component.validationErrorMessage ?? '') !== message) { // Update the message if needed
component.validationErrorMessage = message;
}
return valid;
}