UNPKG

@microsoft/botbuilder-m365

Version:

M365 extensions for Microsoft BotBuilder, Alpha release.

133 lines (114 loc) 4.51 kB
/** * @module botbuilder-m365 */ /** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ // TODO: /* eslint-disable security/detect-object-injection */ import { TurnContext } from 'botbuilder'; import { stringify } from 'yaml'; import { TurnState } from '../TurnState'; import { Block, BlockTypes } from './Block'; /** * @private */ export class VarBlock extends Block { private static readonly Prefix: string = '$'; public readonly name: string = this.varName(); constructor(content: string) { super(); this.content = content; } public get type(): BlockTypes { return BlockTypes.Variable; } public isValid(): { valid: boolean; error?: string } { let valid = true; let error: string | undefined; if (this.content[0] !== VarBlock.Prefix) { error = `A variable must start with the symbol ${VarBlock.Prefix}`; valid = false; } else if (this.content.length < 2) { error = 'The variable name is empty'; valid = false; } else { const varName = this.varName(); if (!VarBlock.isValidVarName(varName)) { error = `The variable name '${varName}' contains invalid characters. Only alphanumeric chars, underscore, and dot are allowed.`; valid = false; } else if (varName.split('.').length > 2) { error = `The variable name '${varName}' isn't currently supported. Variables must be of the form '$<property>' or '$<scope>.<property>' and sub properties aren't currently supported.`; valid = false; } } return { valid, error }; } public render(context: TurnContext, state: TurnState): string { const name = this.varName(); if (!name) { throw new Error('Variable rendering failed, the variable name is empty.'); } // Split variable name into parts and validate // TODO: Add support for longer dotted path variable names const parts = name.trim().split('.'); if (parts.length == 1) { // Assume the var is coming from the 'temp' scope parts.unshift('temp'); } else if (parts.length > 2) { throw new Error(`PromptParser: invalid variable name of '${name}' specified.`); } // Check for special cased variables first let value: any; switch (parts[0]) { case 'activity': // Return activity field value = this.caseInsensitiveFindValue(context.activity, parts[1]) ?? ''; break; default: { // Find referenced state entry const entry = this.caseInsensitiveFindValue(state, parts[0]); if (!entry) { throw new Error( `PromptParser: invalid variable name of '${name}' specified. Couldn't find a state named '${parts[0]}'.` ); } // Return state field value = this.caseInsensitiveFindValue(entry.value, parts[1]) ?? ''; break; } } // Return value return VarBlock.formatValue(value); } public static formatValue(value: any): string { return typeof value == 'object' || Array.isArray(value) ? stringify(value) : value != undefined ? value.toString() : ''; } public static hasVarPrefix(text: string): boolean { return !!text && text.length > 0 && text[0] === VarBlock.Prefix; } public static isValidVarName(text: string): boolean { return /^[a-zA-Z0-9_.]*$/.test(text); } private varName(): string { return this.content.length < 2 ? '' : this.content.slice(1); } private caseInsensitiveFindValue(obj: Record<string, any>, property: string): any { if (Object.prototype.hasOwnProperty.call(obj, property)) { return obj[property]; } else { const propKey = property.toLowerCase(); for (const key in obj) { if (key.toLowerCase() == propKey) { return obj[key]; } } return undefined; } } }