From df0736e8fbc88319df735ef9e718e93c0520c1ba Mon Sep 17 00:00:00 2001 From: Travis Jensen Date: Fri, 6 Sep 2019 10:52:43 -0700 Subject: [PATCH] Merging Forge.DataContracts into Forge.TreeWalker for simplicity. --- .../Forge.TreeWalker.UnitTests.csproj | 4 - Forge.TreeWalker/Forge.TreeWalker.csproj | 9 +- Forge.TreeWalker/Properties/AssemblyInfo.cs | 6 +- .../contracts/ForgeSchemaValidationRules.json | 193 ++++++++++++++ Forge.TreeWalker/contracts/ForgeTree.cs | 252 ++++++++++++++++++ Forge.TreeWalker/packages.config | 1 + Forge.TreeWalker/src/TreeWalkerSession.cs | 2 +- Forge.sln | 6 - 8 files changed, 452 insertions(+), 21 deletions(-) create mode 100644 Forge.TreeWalker/contracts/ForgeSchemaValidationRules.json create mode 100644 Forge.TreeWalker/contracts/ForgeTree.cs 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