Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Templating] Expose warnings/errors to callers #7437

Merged
merged 12 commits into from
May 11, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Antlr4.Runtime.Tree;
using Newtonsoft.Json;
using System;
using System.Collections;

namespace AdaptiveCards.Templating
{
Expand All @@ -21,6 +22,7 @@ public sealed class AdaptiveCardTemplate
{
private IParseTree parseTree;
private string jsonTemplateString;
private ArrayList templateExpansionWarnings;

/// <summary>
/// <para>Creates an instance of AdaptiveCardTemplate</para>
Expand Down Expand Up @@ -109,7 +111,11 @@ public string Expand(EvaluationContext context, Func<string, object> nullSubstit
}

AdaptiveCardsTemplateVisitor eval = new AdaptiveCardsTemplateVisitor(nullSubstitutionOption, jsonData);
return eval.Visit(parseTree).ToString();
AdaptiveCardsTemplateResult result = eval.Visit(parseTree);

templateExpansionWarnings = eval.getTemplateVisitorWarnings();

return result.ToString();
}

/// <summary>
Expand All @@ -135,8 +141,20 @@ public string Expand(EvaluationContext context, Func<string, object> nullSubstit
public string Expand(object rootData, Func<string, object> nullSubstitutionOption = null)
{
var context = new EvaluationContext(rootData);

return Expand(context, nullSubstitutionOption);
}

/// <summary>
/// Getter method for the array of warning strings from the last template expansion
/// </summary>
/// <returns>ArrayList</returns>
public ArrayList GetLastTemplateExpansionWarnings()
{
if (templateExpansionWarnings != null)
{
return templateExpansionWarnings;
}
return new ArrayList();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
Expand All @@ -23,6 +24,7 @@ public sealed class AdaptiveCardsTemplateVisitor : AdaptiveCardsTemplateParserBa
private Stack<DataContext> dataContext = new Stack<DataContext>();
private readonly JToken root;
private readonly Options options;
private ArrayList templateVisitorWarnings;

/// <summary>
/// maintains data context
Expand Down Expand Up @@ -126,6 +128,8 @@ public AdaptiveCardsTemplateVisitor(Func<string, object> nullSubstitutionOption,
{
NullSubstitution = nullSubstitutionOption != null? nullSubstitutionOption : (path) => $"${{{path}}}"
};

templateVisitorWarnings = new ArrayList();
}

/// <summary>
Expand Down Expand Up @@ -198,6 +202,15 @@ private bool HasDataContext()
return dataContext.Count != 0;
}

/// <summary>
/// Getter for templateVisitorWarnings
/// </summary>
/// <returns>ArrayList</returns>
public ArrayList getTemplateVisitorWarnings()
{
return templateVisitorWarnings;
}

/// <summary>
/// antlr runtime wil call this method when parse tree's context is <see cref="AdaptiveCardsTemplateParser.TemplateDataContext"/>
/// <para>It is used in parsing a pair that has $data as key</para>
Expand Down Expand Up @@ -336,8 +349,20 @@ public override AdaptiveCardsTemplateResult VisitValueTemplateExpression([NotNul
return result;
}

bool isTrue = false;

try
{
isTrue = IsTrue(result.Predicate, dataContext.token);
}
catch (System.FormatException)
{
templateVisitorWarnings.Add($"WARN: Could not evaluate {result.Predicate} because it could not be found in the provided data. " +
"The condition has been set to false by default.");
}

// evaluate $when
result.WhenEvaluationResult = IsTrue(result.Predicate, dataContext.token) ?
result.WhenEvaluationResult = isTrue ?
AdaptiveCardsTemplateResult.EvaluationResult.EvaluatedToTrue :
AdaptiveCardsTemplateResult.EvaluationResult.EvaluatedToFalse;

Expand Down Expand Up @@ -499,8 +524,8 @@ public override AdaptiveCardsTemplateResult VisitObj([NotNull] AdaptiveCardsTemp
{
// The when expression could not be evaluated, so we are defaulting the value to false
whenEvaluationResult = AdaptiveCardsTemplateResult.EvaluationResult.EvaluatedToFalse;
// TODO: Expose this warning to caller - documented in issue #7433
Console.WriteLine($"WARN: Could not evaluate {returnedResult} because it is not an expression or the " +

templateVisitorWarnings.Add($"WARN: Could not evaluate {returnedResult} because it is not an expression or the " +
$"expression is invalid. The $when condition has been set to false by default.");

}
Expand Down Expand Up @@ -749,18 +774,7 @@ public static bool IsTrue(string predicate, JToken data)
var (value, error) = new ValueExpression(Regex.Unescape(predicate)).TryGetValue(data);
if (error == null)
{
try
{
return bool.Parse(value as string);
}
catch (System.FormatException)
{
// If the expression didn't evaluate to a boolean, we need to return false
// TODO: Expose this warning to caller - documented in issue #7433
Console.WriteLine($"WARN: Could not evaluate boolean expression because it could not be found in the provided data. " +
"The condition has been set to false by default.");
return false;
}
return bool.Parse(value as string);
}
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using System.Diagnostics;
using System;
using AdaptiveExpressions.Memory;
using System.Collections.Generic;
using System.Collections;

namespace AdaptiveCards.Templating.Test
{
Expand Down Expand Up @@ -13269,6 +13269,79 @@ public void TestWhenExpressionNotInData()

Assert.AreEqual(expectedJson, st);
}

[TestMethod]
public void TestWhenNotExpressionWithLog()
{
string cardJson = "{\"type\": \"AdaptiveCard\", \"body\": [{\"type\": \"TextBlock\"," +
"\"size\": \"Medium\", \"weight\": \"Bolder\", \"text\": \"${title}\", \"$when\": \"notAnExpression\"}]," +
"\"$schema\": \"http://adaptivecards.io/schemas/adaptive-card.json\",\"version\": \"1.5\"}";

string expectedJson = "{\"type\":\"AdaptiveCard\",\"body\":[]," +
"\"$schema\":\"http://adaptivecards.io/schemas/adaptive-card.json\",\"version\":\"1.5\"}";

var context = new EvaluationContext();
var template = new AdaptiveCardTemplate(cardJson);
string st = template.Expand(context);

Assert.AreEqual(expectedJson, st);

ArrayList log = template.GetLastTemplateExpansionWarnings();
string expectedWarning = "WARN: Could not evaluate \"notAnExpression\" because it is not " +
"an expression or the expression is invalid. The $when condition has been set to false by default.";

Assert.AreEqual(expectedWarning, log[0]);
}

[TestMethod]
public void TestWhenInvalidExpressionNoDataWithLog()
{
string cardJson = "{\"type\": \"AdaptiveCard\", \"body\": [{\"type\": \"TextBlock\"," +
"\"size\": \"Medium\", \"weight\": \"Bolder\", \"text\": \"${title}\", \"$when\": \"${invalidExpression}\"}]," +
"\"$schema\": \"http://adaptivecards.io/schemas/adaptive-card.json\",\"version\": \"1.5\"}";

string expectedJson = "{\"type\":\"AdaptiveCard\",\"body\":[]," +
"\"$schema\":\"http://adaptivecards.io/schemas/adaptive-card.json\",\"version\":\"1.5\"}";

var context = new EvaluationContext();
var template = new AdaptiveCardTemplate(cardJson);
string st = template.Expand(context);

Assert.AreEqual(expectedJson, st);

ArrayList log = template.GetLastTemplateExpansionWarnings();
string expectedWarning = "WARN: Could not evaluate \"${invalidExpression}\" because it is not " +
"an expression or the expression is invalid. The $when condition has been set to false by default.";

Assert.AreEqual(expectedWarning, log[0]);
}

[TestMethod]
public void TestWhenExpressionNotInDataWithLog()
{
string cardJson = "{\"type\": \"AdaptiveCard\", \"body\": [{\"type\": \"TextBlock\"," +
"\"size\": \"Medium\", \"weight\": \"Bolder\", \"text\": \"${title}\", \"$when\": \"${notInData}\"}]," +
"\"$schema\": \"http://adaptivecards.io/schemas/adaptive-card.json\",\"version\": \"1.5\"}";

string expectedJson = "{\"type\":\"AdaptiveCard\",\"body\":[]," +
"\"$schema\":\"http://adaptivecards.io/schemas/adaptive-card.json\",\"version\":\"1.5\"}";

Data dt = new Data()
{
title = ""
};

var template = new AdaptiveCardTemplate(cardJson);
string st = template.Expand(dt);

Assert.AreEqual(expectedJson, st);

ArrayList log = template.GetLastTemplateExpansionWarnings();
string expectedWarning = "WARN: Could not evaluate ${notInData} " +
"because it could not be found in the provided data. The condition has been set to false by default.";

Assert.AreEqual(expectedWarning, log[0]);
}
}
[TestClass]
public sealed class TestRootKeyword
Expand Down
21 changes: 15 additions & 6 deletions source/nodejs/adaptivecards-templating/src/template-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ export interface IEvaluationContext {
* Represents a template that can be bound to data.
*/
export class Template {

private templateExpansionWarnings;

private static prepare(node: any): any {
if (typeof node === "string") {
return Template.parseInterpolatedString(node);
Expand Down Expand Up @@ -416,15 +419,13 @@ export class Template {

if (!evaluationResult.value) {
// Value was not found, and we should warn the client that the Expression was invalid
// TODO: Expose this warning to caller - documented in issue #7433
console.warn(`WARN: Unable to parse the Adaptive Expression ${when}. The $when condition has been set to false by default.`);
this.templateExpansionWarnings.push(`WARN: Unable to parse the Adaptive Expression ${when}. The $when condition has been set to false by default.`);
}

dropObject = !whenValue;
} else if (when) {
// If $when was provided, but it is not an AEL.Expression, drop the object
// TODO: Expose this warning to caller - documented in issue #7433
console.warn(`WARN: ${when} is not an Adaptive Expression. The $when condition has been set to false by default.`);
this.templateExpansionWarnings.push(`WARN: ${when} is not an Adaptive Expression. The $when condition has been set to false by default.`);
dropObject = true;
}

Expand Down Expand Up @@ -532,8 +533,16 @@ export class Template {
* is dependent on the type of the original template payload passed to the constructor.
*/
expand(context: IEvaluationContext): any {
this.templateExpansionWarnings = [];
this._context = new EvaluationContext(context);

return this.internalExpand(this._preparedPayload);
}
}

/**
* Getter method for the array of warning strings
* @returns An array storing any warnings that occurred while expanding the template
*/
public getLastTemplateExpansionWarnings(): string[] {
return this.templateExpansionWarnings;
}
}
78 changes: 78 additions & 0 deletions source/nodejs/tests/unit-tests/src/unit-tests.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,84 @@ describe("Test Templating Library", () => {

runTest(templatePayload, expectedOutput, undefined, undefined);
});

it("TemplateWhenIsNotExpressionWithLog", () => {
const templatePayload = {
"type": "AdaptiveCard",
"body": [
{
"type": "TextBlock",
"size": "Medium",
"weight": "Bolder",
"text": "${title}",
"$when": "isNotExpression"
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.5"
};

const expectedOutput = {
"type": "AdaptiveCard",
"body": [],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.5"
};

let template = new ACData.Template(templatePayload);

let context = {
$root: undefined
};

let card = template.expand(context);

expect(card).toStrictEqual(expectedOutput);

let errorLog = template.getLastTemplateExpansionWarnings();
let expectedWarning = "WARN: isNotExpression is not an Adaptive Expression. The $when condition has been set to false by default.";

expect(errorLog[0]).toStrictEqual(expectedWarning);
});

it("TemplateWhenIsInvalidExpressionWithLog", () => {
const templatePayload = {
"type": "AdaptiveCard",
"body": [
{
"type": "TextBlock",
"size": "Medium",
"weight": "Bolder",
"text": "${title}",
"$when": "${invalidExpression}"
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.5"
};

const expectedOutput = {
"type": "AdaptiveCard",
"body": [],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.5"
};

let template = new ACData.Template(templatePayload);

let context = {
$root: undefined
};

let card = template.expand(context);

expect(card).toStrictEqual(expectedOutput);

let errorLog = template.getLastTemplateExpansionWarnings();
let expectedWarning = "WARN: Unable to parse the Adaptive Expression invalidExpression. The $when condition has been set to false by default.";

expect(errorLog[0]).toStrictEqual(expectedWarning);
});
});

function runTest(templatePayload: any, expectedOutput: any, data?: any, host?: any) {
Expand Down