Skip to content

Commit

Permalink
refactor: update the validation for new schema (#1988)
Browse files Browse the repository at this point in the history
* update the validation

* fix some comments

* update some logic
  • Loading branch information
lei9444 authored Feb 13, 2020
1 parent 0b846ce commit d9265ad
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 40 deletions.
73 changes: 33 additions & 40 deletions Composer/packages/lib/indexers/src/dialogUtils/dialogChecker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@
// Licensed under the MIT License.

import get from 'lodash/get';
import { ExpressionEngine } from 'botframework-expressions';
import formatMessage from 'format-message';
import { SDKTypes, FieldNames } from '@bfc/shared';
import { FieldNames } from '@bfc/shared';

import { Diagnostic } from '../diagnostic';

import { ExpressionType } from './validation';
import { CheckerFunc } from './types';

const ExpressionParser = new ExpressionEngine();
import { validate } from './validation';

const createPath = (path: string, type: string): string => {
const steps = [FieldNames.Events, FieldNames.Actions, FieldNames.ElseActions];
Expand All @@ -24,27 +22,7 @@ const createPath = (path: string, type: string): string => {
return `${list[0]}${focused}#${type}#${list[1]}`;
};

export const checkExpression = (exp: string, required: boolean, path: string, type: string): Diagnostic | null => {
let message = '';
if (!exp && required) {
message = formatMessage(`is missing or empty`);
} else {
try {
ExpressionParser.parse(exp);
} catch (error) {
message = `${formatMessage('must be an expression:')} ${error})`;
}
}
if (message) {
const diagnostic = new Diagnostic(message, '');
diagnostic.path = createPath(path, type);
return diagnostic;
}

return null;
};

function findAllRequiredType(schema: any): { [key: string]: boolean } {
function findAllRequiredProperties(schema: any): { [key: string]: boolean } {
if (!schema) return {};
const types = schema.anyOf?.filter(x => x.title === 'Type');
const required = {};
Expand All @@ -62,17 +40,38 @@ function findAllRequiredType(schema: any): { [key: string]: boolean } {
return required;
}

function findAllTypes(schema: any): string[] {
if (!schema) return [];
let types: string[] = [];
if (schema.type) {
if (Array.isArray(schema.type)) {
types = [...types, ...schema.type];
} else {
types.push(schema.type);
}
} else {
types = schema.oneOf?.filter(item => !!ExpressionType[item.type]).map(item => item.type);
}

return types;
}

export const IsExpression: CheckerFunc = (path, value, type, schema) => {
if (!schema) return [];
const diagnostics: Diagnostic[] = [];
const requiredTypes = findAllRequiredType(schema);
const requiredProperties = findAllRequiredProperties(schema);
Object.keys(value).forEach(key => {
const property = value[key];
if (Array.isArray(property)) {
const itemsSchema = get(schema, ['properties', key, 'items'], null);
if (itemsSchema?.$role === 'expression') {
property.forEach((child, index) => {
const diagnostic = checkExpression(child, !!requiredTypes[key], `${path}.${key}[${index}]`, type);
const diagnostic = validate(
child,
!!requiredProperties[key],
createPath(`${path}.${key}[${index}]`, type),
findAllTypes(itemsSchema)
);
if (diagnostic) diagnostics.push(diagnostic);
});
} else if (itemsSchema?.type === 'object') {
Expand All @@ -82,24 +81,18 @@ export const IsExpression: CheckerFunc = (path, value, type, schema) => {
});
}
} else if (get(schema.properties[key], '$role') === 'expression') {
const diagnostic = checkExpression(property, !!requiredTypes[key], `${path}.${key}`, type);
const diagnostic = validate(
property,
!!requiredProperties[key],
createPath(`${path}.${key}`, type),
findAllTypes(schema.properties[key])
);
if (diagnostic) diagnostics.push(diagnostic);
}
});
return diagnostics;
};

//the type of 'Microsoft.ChoiceInput' has anyof schema in choices
export const checkChoices: CheckerFunc = (path, value, type, schema) => {
const choices = value.choices;
if (typeof choices === 'string') {
const diagnostic = checkExpression(choices, false, `${path}.choices`, type);
if (diagnostic) return [diagnostic];
}
return null;
};

export const checkerFuncs: { [type: string]: CheckerFunc[] } = {
'.': [IsExpression], //this will check all types
[SDKTypes.ChoiceInput]: [checkChoices],
};
77 changes: 77 additions & 0 deletions Composer/packages/lib/indexers/src/dialogUtils/validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { ExpressionEngine, ReturnType } from 'botframework-expressions';
import formatMessage from 'format-message';

import { Diagnostic } from '../diagnostic';

export const ExpressionType = {
number: 'number',
integer: 'integer',
boolean: 'boolean',
string: 'string',
};

const ExpressionParser = new ExpressionEngine();

const isExpression = (value: string | boolean | number, types: string[]): boolean => {
//StringExpression always assumes string interpolation unless prefixed with =, producing a string
return (typeof value === 'string' && value[0] === '=') || types.length !== 1 || types[0] !== ExpressionType.string;
};

//The return type should match the schema type
const checkReturnType = (returnType: ReturnType, types: string[]): string => {
return returnType === ReturnType.Object ||
~types.indexOf(returnType) ||
(returnType === ReturnType.Number && ~types.indexOf(ExpressionType.integer))
? ''
: formatMessage('the expression type is not match');
};

export const checkExpression = (exp: string | boolean | number, required: boolean, types: string[]): string => {
let message = '';
if (!exp && required) {
message = formatMessage(`is missing or empty`);
} else {
try {
let returnType: ReturnType;
if (typeof exp === 'boolean') {
returnType = ReturnType.Boolean;
} else if (typeof exp === 'number') {
returnType = ReturnType.Number;
} else {
returnType = ExpressionParser.parse(exp).returnType;
}
message = checkReturnType(returnType, types);
} catch (error) {
message = `${formatMessage('must be an expression:')} ${error})`;
}
}

return message;
};

export const validate = (
value: string | boolean | number,
required: boolean,
path: string,
types: string[]
): Diagnostic | null => {
//if there is no type do nothing
//if the json type length more than 2, the type assumes string interpolation
if (!types.length || types.length > 2 || !isExpression(value, types)) {
return null;
}

//remove '='
if (typeof value === 'string' && value[0] === '=') {
value = value.substring(1);
}

const message = checkExpression(value, required, types);
if (!message) return null;

const diagnostic = new Diagnostic(message, '');
diagnostic.path = path;
return diagnostic;
};

0 comments on commit d9265ad

Please sign in to comment.