diff --git a/Forge.TreeWalker.UnitTests/Forge.TreeWalker.UnitTests.csproj b/Forge.TreeWalker.UnitTests/Forge.TreeWalker.UnitTests.csproj
index 5dd596f..1cba3da 100644
--- a/Forge.TreeWalker.UnitTests/Forge.TreeWalker.UnitTests.csproj
+++ b/Forge.TreeWalker.UnitTests/Forge.TreeWalker.UnitTests.csproj
@@ -64,10 +64,6 @@
-
- {c49a8494-13e5-4214-8434-708ba280c5b1}
- Forge.DataContracts
-
{00fc1c22-6ae9-4f60-8a3e-05885ba34c9c}
Forge.TreeWalker
diff --git a/Forge.TreeWalker/Forge.TreeWalker.csproj b/Forge.TreeWalker/Forge.TreeWalker.csproj
index 0158631..1c111c5 100644
--- a/Forge.TreeWalker/Forge.TreeWalker.csproj
+++ b/Forge.TreeWalker/Forge.TreeWalker.csproj
@@ -46,6 +46,7 @@
+
@@ -66,6 +67,7 @@
+
@@ -83,14 +85,9 @@
+
-
-
- {c49a8494-13e5-4214-8434-708ba280c5b1}
- Forge.DataContracts
-
-
diff --git a/Forge.TreeWalker/Properties/AssemblyInfo.cs b/Forge.TreeWalker/Properties/AssemblyInfo.cs
index 4cea6eb..d591c5e 100644
--- a/Forge.TreeWalker/Properties/AssemblyInfo.cs
+++ b/Forge.TreeWalker/Properties/AssemblyInfo.cs
@@ -2,16 +2,14 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-[assembly: InternalsVisibleTo("Forge.TreeWalker.UnitTests")]
-
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
-[assembly: AssemblyTitle("Forge")]
+[assembly: AssemblyTitle("Forge.TreeWalker")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft")]
-[assembly: AssemblyProduct("Forge")]
+[assembly: AssemblyProduct("Forge.TreeWalker")]
[assembly: AssemblyCopyright("Copyright © 2019")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
diff --git a/Forge.TreeWalker/contracts/ForgeSchemaValidationRules.json b/Forge.TreeWalker/contracts/ForgeSchemaValidationRules.json
new file mode 100644
index 0000000..8c763a4
--- /dev/null
+++ b/Forge.TreeWalker/contracts/ForgeSchemaValidationRules.json
@@ -0,0 +1,193 @@
+{
+ "type": "object",
+ "definitions": {
+ "TreeDefinition": {
+ "type": "object",
+ "patternProperties": {
+ ".*?": {
+ "$ref": "#/definitions/TreeNodeDefinition"
+ }
+ }
+ },
+ "TreeNodeDefinition": {
+ "oneOf": [
+ {
+ "$ref": "#/definitions/SelectionTypeNodeDefinition"
+ },
+ {
+ "$ref": "#/definitions/ActionTypeNodeDefinition"
+ },
+ {
+ "$ref": "#/definitions/LeafTypeNodeDefinition"
+ }
+ ]
+ },
+ "SelectionTypeNodeDefinition": {
+ "type": "object",
+ "properties": {
+ "Type": {
+ "type": "string",
+ "enum": [ "Selection" ]
+ },
+ "ChildSelector": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ChildSelectorDefinition"
+ },
+ "minItems": 1
+ },
+ "Properties": {
+ "type": "object"
+ }
+ },
+ "additionalProperties": false,
+ "required": [ "Type" ]
+ },
+ "ActionTypeNodeDefinition": {
+ "type": "object",
+ "properties": {
+ "Type": {
+ "type": "string",
+ "enum": [ "Action" ]
+ },
+ "Timeout": {
+ "type": [ "number", "string" ]
+ },
+ "Actions": {
+ "patternProperties": {
+ ".*?": {
+ "$ref": "#/definitions/ActionDefinition"
+ }
+ },
+ "minProperties": 1
+ },
+ "ChildSelector": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ChildSelectorDefinition"
+ },
+ "minItems": 1
+ },
+ "Properties": {
+ "type": "object"
+ }
+ },
+ "additionalProperties": false,
+ "required": [ "Type", "Actions" ]
+ },
+ "LeafTypeNodeDefinition": {
+ "type": "object",
+ "properties": {
+ "Type": {
+ "type": "string",
+ "enum": [ "Leaf" ]
+ },
+ "Actions": {
+ "patternProperties": {
+ ".*?": {
+ "$ref": "#/definitions/LeafNodeSummaryActionDefinition"
+ }
+ },
+ "minProperties": 1,
+ "maxProperties": 1
+ },
+ "Properties": {
+ "type": "object"
+ }
+ },
+ "additionalProperties": false,
+ "required": [ "Type" ]
+ },
+ "ChildSelectorDefinition": {
+ "type": "object",
+ "properties": {
+ "Label": {
+ "type": "string"
+ },
+ "ShouldSelect": {
+ "type": "string"
+ },
+ "Child": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false,
+ "required": [ "Label", "Child" ]
+ },
+ "RetryPolicy": {
+ "type": "object",
+ "properties": {
+ "Type": {
+ "type": "string",
+ "enum": [ "None", "FixedInterval", "ExponentialBackoff" ]
+ },
+ "MinBackoffMs": {
+ "type": "number"
+ },
+ "MaxBackoffMs": {
+ "type": "number"
+ }
+ },
+ "required": [ "Type" ]
+ },
+ "ActionDefinition": {
+ "type": "object",
+ "properties": {
+ "Action": {
+ "type": "string"
+ },
+ "Input": {
+ "type": "object"
+ },
+ "Properties": {
+ "type": "object"
+ },
+ "Timeout": {
+ "type": [ "number", "string" ]
+ },
+ "ContinuationOnTimeout": {
+ "type": "boolean"
+ },
+ "RetryPolicy": {
+ "$ref": "#/definitions/RetryPolicy"
+ },
+ "ContinuationOnRetryExhaustion": {
+ "type": "boolean"
+ }
+ },
+ "additionalProperties": false,
+ "required": [ "Action" ]
+ },
+ "LeafNodeSummaryActionDefinition": {
+ "type": "object",
+ "properties": {
+ "Action": {
+ "enum": [ "LeafNodeSummaryAction" ]
+ },
+ "Input": {
+ "type": [ "object", "string" ],
+ "properties": {
+ "Status": {
+ "type": "string"
+ },
+ "StatusCode": {
+ "type": [ "number", "string" ]
+ },
+ "Output": {
+ "type": [ "object", "string" ]
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false,
+ "required": [ "Action", "Input" ]
+ }
+ },
+ "properties": {
+ "Tree": {
+ "$ref": "#/definitions/TreeDefinition"
+ }
+ },
+ "additionalProperties": false
+}
\ No newline at end of file
diff --git a/Forge.TreeWalker/contracts/ForgeTree.cs b/Forge.TreeWalker/contracts/ForgeTree.cs
new file mode 100644
index 0000000..155e897
--- /dev/null
+++ b/Forge.TreeWalker/contracts/ForgeTree.cs
@@ -0,0 +1,252 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//
+// The Forge schema data contracts.
+//
+//-----------------------------------------------------------------------
+
+namespace Forge.DataContracts
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Runtime.Serialization;
+
+ ///
+ /// The Forge tree.
+ /// This outermost data structure holds the Forge schema.
+ ///
+ [DataContract]
+ public class ForgeTree
+ {
+ ///
+ /// Dictionary mapping unique TreeNodeKeys to TreeNodes.
+ ///
+ [DataMember]
+ public Dictionary Tree { get; set; }
+ }
+
+ ///
+ /// The tree node.
+ /// Holds information to navigate the tree and perform actions.
+ ///
+ [DataContract]
+ public class TreeNode
+ {
+ ///
+ /// The tree node type.
+ ///
+ [DataMember(IsRequired = true)]
+ public TreeNodeType Type { get; private set; }
+
+ ///
+ /// Additional properties passed to wrapper class.
+ /// String properties starting with represent a code-snippet that will be evaluated.
+ ///
+ [DataMember]
+ public dynamic Properties { get; set; }
+
+ ///
+ /// The child selectors.
+ ///
+ [DataMember]
+ public ChildSelector[] ChildSelector { get; private set; }
+
+ #region Properties used only by TreeNodeType.Action nodes
+
+ ///
+ /// The actions to execute when the TreeNodeType is Action.
+ /// Dictionary mapping unique TreeActionKeys to TreeActions.
+ ///
+ [DataMember]
+ public Dictionary Actions { get; set; }
+
+ ///
+ /// Timeout in milliseconds for executing the TreeActions. Default to -1 (infinite) if not specified.
+ /// String properties starting with represent a code-snippet that will be evaluated.
+ ///
+ [DataMember]
+ public dynamic Timeout { get; set; }
+
+ #endregion
+ }
+
+ ///
+ /// The child selector for the TreeNode.
+ /// Used to navigate the tree by referencing child TreeNodes.
+ ///
+ [DataContract]
+ public class ChildSelector
+ {
+ ///
+ /// String code-snippet that can be parsed and evaluated to a boolean value.
+ /// If the expression is true, visit the attached child TreeNode.
+ /// If the expression is empty, evaluate to true by default.
+ ///
+ [DataMember]
+ public string ShouldSelect { get; set; }
+
+ ///
+ /// Reader-friendly label that describes the intention of the ShouldSelect expression.
+ /// Used in ForgeEditor for display purposes.
+ ///
+ [DataMember]
+ public string Label { get; set; }
+
+ ///
+ /// String key pointer to a child TreeNode.
+ /// Visit this child if the attached ShouldSelect expression evaluates to true.
+ ///
+ [DataMember(IsRequired = true)]
+ public string Child { get; private set; }
+ }
+
+ ///
+ /// The tree action for the TreeNode.
+ /// Holds instructions and policies for executing an action.
+ ///
+ [DataContract]
+ public class TreeAction
+ {
+ ///
+ /// String name of the action that maps to an action-task.
+ /// These actions may be predefined Forge actions or action-tasks passed by a Wrapper class.
+ ///
+ [DataMember(IsRequired = true)]
+ public string Action { get; set; }
+
+ ///
+ /// Dynamic input parameters passed to the action-task.
+ /// Wrapper class is responsible for making sure the action-task input matches the input defined in the schema.
+ /// String properties starting with represent a code-snippet that will be evaluated.
+ ///
+ [DataMember]
+ public dynamic Input { get; private set; }
+
+ ///
+ /// Additional properties passed to wrapper class.
+ /// String properties starting with represent a code-snippet that will be evaluated.
+ ///
+ [DataMember]
+ public dynamic Properties { get; set; }
+
+ ///
+ /// Timeout in milliseconds for executing the action. Default to -1 (infinite) if not specified.
+ /// String properties starting with represent a code-snippet that will be evaluated.
+ ///
+ [DataMember]
+ public dynamic Timeout { get; set; }
+
+ ///
+ /// A flag that represents how to handle the exit of the action due to timeout. If false (default), then the session will end on the
+ /// timeout. If true and a timeout is hit, the action will continue on as if it were successful after committing a "TimeoutOnAction" response.
+ ///
+ [DataMember]
+ public bool ContinuationOnTimeout { get; set; }
+
+ ///
+ /// Retry policy of the action.
+ ///
+ [DataMember]
+ public RetryPolicy RetryPolicy { get; private set; }
+
+ ///
+ /// A flag that represents how to handle the exit of the action due to retry exhaustion. If false (default), then the session will end once
+ /// retries are exhausted or no retries are specified. If true and retries are exhausted, the action will continue on as if it were successful
+ /// after committing a "RetryExhaustedOnAction" response.
+ ///
+ [DataMember]
+ public bool ContinuationOnRetryExhaustion { get; set; }
+ }
+
+ ///
+ /// The retry policy for the TreeAction.
+ ///
+ [DataContract]
+ public class RetryPolicy
+ {
+ ///
+ /// The retry policy type.
+ ///
+ [DataMember(IsRequired = true)]
+ public RetryPolicyType Type { get; private set; }
+
+ ///
+ /// Minimum backoff time in milliseconds.
+ /// When retrying an action, wait at least this long before your next attempt.
+ /// This is useful to ensure actions are not retried too quickly.
+ ///
+ [DataMember]
+ public long MinBackoffMs { get; private set; }
+
+ ///
+ /// Maximum backoff time in milliseconds.
+ /// When retrying an action, wait at most this long before your next attempt.
+ /// This is useful to ensure exponential backoff doesn't wait too long.
+ ///
+ [DataMember]
+ public long MaxBackoffMs { get; private set; }
+ }
+
+ ///
+ /// The retry policy types.
+ ///
+ [DataContract]
+ public enum RetryPolicyType
+ {
+ ///
+ /// Do not retry.
+ ///
+ [EnumMember]
+ None = 0,
+
+ ///
+ /// Retry at a fixed interval every MinBackoffMs.
+ ///
+ [EnumMember]
+ FixedInterval = 1,
+
+ ///
+ /// Retry with an exponential backoff.
+ /// Start with MinBackoffMs, then wait Math.Min(MinBackoffMs * 2^(retryCount), MaxBackoffMs).
+ ///
+ [EnumMember]
+ ExponentialBackoff = 2
+
+ // TODO: Add a FixedCount type that will give the full timeout duration for the set number of retries.
+ }
+
+ ///
+ /// The tree node types.
+ ///
+ [DataContract]
+ public enum TreeNodeType
+ {
+ ///
+ /// Undefined.
+ ///
+ [EnumMember]
+ Unknown = 0,
+
+ ///
+ /// Selection type node.
+ ///
+ [EnumMember]
+ Selection = 1,
+
+ ///
+ /// Action type node.
+ /// This node includes TreeAction(s).
+ ///
+ [EnumMember]
+ Action = 2,
+
+ ///
+ /// Leaf type node.
+ /// This represents an end state in tree.
+ ///
+ [EnumMember]
+ Leaf = 3
+ }
+}
\ No newline at end of file
diff --git a/Forge.TreeWalker/packages.config b/Forge.TreeWalker/packages.config
index 53b4e31..5179306 100644
--- a/Forge.TreeWalker/packages.config
+++ b/Forge.TreeWalker/packages.config
@@ -7,6 +7,7 @@
+
diff --git a/Forge.TreeWalker/src/TreeWalkerSession.cs b/Forge.TreeWalker/src/TreeWalkerSession.cs
index a0a01fb..47cb780 100644
--- a/Forge.TreeWalker/src/TreeWalkerSession.cs
+++ b/Forge.TreeWalker/src/TreeWalkerSession.cs
@@ -371,7 +371,7 @@ namespace Forge.TreeWalker
/// If the cancellation token was triggered.
/// If an unexpected exception was thrown.
/// The key of the next child to visit, or null if no match was found.
- internal async Task VisitNode(string treeNodeKey)
+ public async Task VisitNode(string treeNodeKey)
{
TreeNode treeNode = this.Schema.Tree[treeNodeKey];
diff --git a/Forge.sln b/Forge.sln
index 59a24ba..49973d6 100644
--- a/Forge.sln
+++ b/Forge.sln
@@ -5,8 +5,6 @@ VisualStudioVersion = 16.0.29215.179
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Forge.TreeWalker", "Forge.TreeWalker\Forge.TreeWalker.csproj", "{00FC1C22-6AE9-4F60-8A3E-05885BA34C9C}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Forge.DataContracts", "Forge.DataContracts\Forge.DataContracts.csproj", "{C49A8494-13E5-4214-8434-708BA280C5B1}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Forge.TreeWalker.UnitTests", "Forge.TreeWalker.UnitTests\Forge.TreeWalker.UnitTests.csproj", "{A33AF1FF-1291-4CB1-A733-3243E1FE967E}"
EndProject
Global
@@ -19,10 +17,6 @@ Global
{00FC1C22-6AE9-4F60-8A3E-05885BA34C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{00FC1C22-6AE9-4F60-8A3E-05885BA34C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{00FC1C22-6AE9-4F60-8A3E-05885BA34C9C}.Release|Any CPU.Build.0 = Release|Any CPU
- {C49A8494-13E5-4214-8434-708BA280C5B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C49A8494-13E5-4214-8434-708BA280C5B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C49A8494-13E5-4214-8434-708BA280C5B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C49A8494-13E5-4214-8434-708BA280C5B1}.Release|Any CPU.Build.0 = Release|Any CPU
{A33AF1FF-1291-4CB1-A733-3243E1FE967E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A33AF1FF-1291-4CB1-A733-3243E1FE967E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A33AF1FF-1291-4CB1-A733-3243E1FE967E}.Release|Any CPU.ActiveCfg = Release|Any CPU