From 93626d668a34aa484784cd2b8930a376d3897ece Mon Sep 17 00:00:00 2001 From: "Bryan Wang (PSHCT)" Date: Fri, 15 Sep 2017 23:20:20 -0700 Subject: [PATCH] Handle unwrapping exceptions to CloudError objects, update Newtonsoft.Json because of bug, add emptied HttpResponseMessage to error response --- .../CloudErrorTransform.json | 19 ++++ .../PSSwagger.LTF.ConsoleServer.csproj | 5 +- .../PSSwagger.LTF.ConsoleServer/Program.cs | 27 +++++- .../PSSwagger.LTF.ConsoleServer/config.json | 1 + .../PSSwagger.LTF.IO.Lib.csproj | 2 +- .../src/PSSwagger.LTF.Lib/LiveTestServer.cs | 5 +- .../Messages/LiveTestError.cs | 11 ++- .../Messages/LiveTestRequest.cs | 61 ++++++++++++- .../PSSwagger.LTF.Lib.csproj | 2 +- .../Transforms/DynamicObjectTransform.cs | 86 +++++++++++++++++++ .../DynamicObjectTransformDefinition.cs | 53 ++++++++++++ .../vs-csproj/PSSwagger.LTF.Lib.csproj | 3 + 12 files changed, 260 insertions(+), 15 deletions(-) create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/CloudErrorTransform.json create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Transforms/DynamicObjectTransform.cs create mode 100644 PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Transforms/DynamicObjectTransformDefinition.cs diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/CloudErrorTransform.json b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/CloudErrorTransform.json new file mode 100644 index 0000000..ca67260 --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/CloudErrorTransform.json @@ -0,0 +1,19 @@ +{ + "type": "System.Management.Automation.ActionPreferenceStopException", + "transforms": [ + { + "query": "select", + "property": "ErrorRecord", + "result": "System.Management.Automation.ErrorRecord" + }, + { + "query": "select", + "property": "Exception", + "result": "Microsoft.Rest.Azure.CloudException" + }, + { + "query": "select", + "property": "Body" + } + ] +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/PSSwagger.LTF.ConsoleServer.csproj b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/PSSwagger.LTF.ConsoleServer.csproj index 64e6192..5c58507 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/PSSwagger.LTF.ConsoleServer.csproj +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/PSSwagger.LTF.ConsoleServer.csproj @@ -10,9 +10,12 @@ - + + + PreserveNewest + \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/Program.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/Program.cs index 31a21d2..e6b1b62 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/Program.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/Program.cs @@ -11,6 +11,7 @@ namespace PSSwagger.LTF.ConsoleServer using Lib.PowerShell; using Lib.ServiceTracing; using Newtonsoft.Json; + using PSSwagger.LTF.Lib.Transforms; using System; using System.Collections.Generic; using System.Globalization; @@ -81,7 +82,8 @@ static void Main(string[] args) RunspaceManager = runspace, ModulePath = serverArgs.ModulePath, TracingManager = new ServiceTracingManager(), - SpecificationPaths = serverArgs.SpecificationPaths + SpecificationPaths = serverArgs.SpecificationPaths, + ObjectTransforms = serverArgs.GetTransforms() }); try @@ -109,6 +111,7 @@ class ServerArgs public List Errors { get; set; } public bool EnablePipeLog { get; set; } public bool EnableEventLog { get; set; } + public List TransformDefinitionFiles { get; set; } public ServerArgs() { @@ -118,6 +121,18 @@ public ServerArgs() this.LogPipeName = "psswagger-ltf-consoleserver"; this.EnablePipeLog = true; this.EnableEventLog = false; + this.TransformDefinitionFiles = new List(); + } + + public IList GetTransforms() + { + List transforms = new List(); + foreach (string file in this.TransformDefinitionFiles) + { + transforms.Add(JsonConvert.DeserializeObject(File.ReadAllText(file))); + } + + return transforms; } public ServerArgs Parse(string[] args) @@ -166,6 +181,10 @@ public ServerArgs Parse(string[] args) case "logpipename": this.LogPipeName = arg; break; + case "transform": + // Let's merge these instead of overwriting maybe? + this.TransformDefinitionFiles.Add(arg); + break; default: this.Errors.Add(String.Format(CultureInfo.CurrentCulture, "Unknown argument: {0}", lastArg)); break; @@ -216,6 +235,12 @@ public ServerArgs Parse(string jsonFilePath) { this.SpecificationPaths = fromFile.SpecificationPaths; } + + if (fromFile.TransformDefinitionFiles != null && fromFile.TransformDefinitionFiles.Count > 0) + { + // Let's merge these instead of overwriting maybe? + this.TransformDefinitionFiles.AddRange(fromFile.TransformDefinitionFiles); + } } return this; diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/config.json b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/config.json index 7a73a41..8506565 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/config.json +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.ConsoleServer/config.json @@ -1,2 +1,3 @@ { + "TransformDefinitionFiles": [ "CloudErrorTransform.json" ] } \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/PSSwagger.LTF.IO.Lib.csproj b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/PSSwagger.LTF.IO.Lib.csproj index 58dd87f..93881b2 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/PSSwagger.LTF.IO.Lib.csproj +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.IO.Lib/PSSwagger.LTF.IO.Lib.csproj @@ -3,7 +3,7 @@ net452 - + diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/LiveTestServer.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/LiveTestServer.cs index d80b65c..d53ffb3 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/LiveTestServer.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/LiveTestServer.cs @@ -16,6 +16,7 @@ namespace PSSwagger.LTF.Lib using System.IO; using System.Threading; using System.Threading.Tasks; + using Transforms; public class LiveTestServerStartParams { @@ -27,9 +28,11 @@ public class LiveTestServerStartParams public IList SpecificationPaths { get; set; } public LiveTestCredentialFactory CredentialFactory { get; set; } public ServiceTracingManager TracingManager { get; set; } + public IList ObjectTransforms { get; set; } public LiveTestServerStartParams() { this.SpecificationPaths = new List(); + this.ObjectTransforms = new List(); } } @@ -187,7 +190,7 @@ public async Task RunAsync() } else { - response = msg.MakeResponse(commandResult, serviceTracer, this.parameters.Logger); + response = msg.MakeResponse(commandResult, serviceTracer, parameters.ObjectTransforms, this.parameters.Logger); } } catch (Exception exRequest) diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestError.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestError.cs index 068e9f4..46985d9 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestError.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestError.cs @@ -3,6 +3,9 @@ // Licensed under the MIT license. namespace PSSwagger.LTF.Lib.Messages { + using Microsoft.Rest; + using System.Net.Http; + /// /// Error response from test operation. /// @@ -10,11 +13,7 @@ public class LiveTestError { public long Code { get; set; } public string Message { get; set; } - public LiveTestResult Data { get; set; } - - public LiveTestError() - { - this.Data = new LiveTestResult(); - } + public object Data { get; set; } + public HttpResponseMessage HttpResponse { get; set; } } } \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestRequest.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestRequest.cs index 950bb2a..bb0b727 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestRequest.cs +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Messages/LiveTestRequest.cs @@ -11,6 +11,7 @@ namespace PSSwagger.LTF.Lib.Messages using System.Collections.Generic; using System.Linq; using System.Net.Http; + using Transforms; /// /// An Azure Live Test Framework JSON-RPC request. @@ -28,7 +29,7 @@ public class LiveTestRequest : JsonRpcBase [JsonIgnore] public bool HttpResponse { get; set; } - public LiveTestResponse MakeResponse(CommandExecutionResult commandResult, IServiceTracer tracer, Logger logger) + public LiveTestResponse MakeResponse(CommandExecutionResult commandResult, IServiceTracer tracer, IList transforms, Logger logger) { LiveTestResponse response = MakeBaseResponse(); if (commandResult.HadErrors) @@ -41,7 +42,31 @@ public LiveTestResponse MakeResponse(CommandExecutionResult commandResult, IServ response.Error = new LiveTestError(); response.Error.Code = InvalidRequest; - response.Error.Data = GetLiveTestResult(commandResult.Errors, tracer); + List errors = new List(); + foreach (object originalError in commandResult.Errors) + { + errors.AddRange(TransformObject(originalError, transforms)); + } + + response.Error.Data = errors.Count == 0 ? null : errors.Count == 1 ? errors[0] : errors; + if (this.HttpResponse) + { + HttpResponseMessage responseMessage = tracer.HttpResponses.LastOrDefault(); + if (responseMessage != null) + { + // Kill the Content property - doesn't work with Newtonsoft.Json serialization + HttpResponseMessage clonedMessage = new HttpResponseMessage(responseMessage.StatusCode); + foreach (var header in responseMessage.Headers) + { + clonedMessage.Headers.Add(header.Key, header.Value); + } + + clonedMessage.ReasonPhrase = responseMessage.ReasonPhrase; + clonedMessage.RequestMessage = responseMessage.RequestMessage; + clonedMessage.Version = responseMessage.Version; + response.Error.HttpResponse = clonedMessage; + } + } } else { @@ -50,7 +75,13 @@ public LiveTestResponse MakeResponse(CommandExecutionResult commandResult, IServ logger.LogAsync("Command executed successfully."); } - response.Result = GetLiveTestResult(commandResult.Results, tracer); + List results = new List(); + foreach (object originalResult in commandResult.Results) + { + results.AddRange(TransformObject(originalResult, transforms)); + } + + response.Result = GetLiveTestResult(results, tracer); } return response; @@ -61,10 +92,32 @@ public LiveTestResponse MakeResponse(Exception ex, int errorCode) LiveTestResponse response = MakeBaseResponse(); response.Error = new LiveTestError(); response.Error.Code = errorCode; - response.Error.Data.Response = ex; + response.Error.Data = ex; return response; } + private IEnumerable TransformObject(object obj, IList transforms) + { + bool transformed = false; + foreach (object result in transforms.Where(t => t.CanTransform(obj)).SelectMany(t => t.Transform(obj))) + { + transformed = true; + IEnumerable transformedResults = TransformObject(result, transforms); + if (transformedResults != null) + { + foreach (object innerResult in transformedResults) + { + yield return innerResult; + } + } + } + + if (!transformed) + { + yield return obj; + } + } + private LiveTestResult GetLiveTestResult(IEnumerable resultsEnumerable, IServiceTracer tracer) { LiveTestResult result = new LiveTestResult(); diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/PSSwagger.LTF.Lib.csproj b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/PSSwagger.LTF.Lib.csproj index 9f36504..c6078b9 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/PSSwagger.LTF.Lib.csproj +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/PSSwagger.LTF.Lib.csproj @@ -3,7 +3,7 @@ net452 - + diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Transforms/DynamicObjectTransform.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Transforms/DynamicObjectTransform.cs new file mode 100644 index 0000000..c339f3d --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Transforms/DynamicObjectTransform.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +namespace PSSwagger.LTF.Lib.Transforms +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + + /// + /// Defines transform steps for a single type. + /// + public class DynamicObjectTransform + { + /// + /// Gets or sets the type to apply transform to. + /// + public string Type { get; set; } + + /// + /// Gets or sets the ordered list of transforms to apply to this type. + /// + public DynamicObjectTransformDefinition[] Transforms { get; set; } + + /// + /// Checks if exactly matches the Type property. + /// + /// Object to check. Can be null. + /// False if null or .GetType().FullName does not match this.Type; True otherwise. + public bool CanTransform(object obj) + { + if (obj == null) + { + return false; + } + + return obj.GetType().FullName.Equals(this.Type, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Transform the given object into one or more objects. For multi-step transforms, all steps should define an expected output type. Otherwise, the client should re-query all transformers. + /// + /// Object to transform. Can be null. + /// Null if is null. Otherwise, returns the result of valid transforms. + public IEnumerable Transform(object obj) + { + if (this.Transforms != null) + { + List toTransform = new List(); + List transformResult = new List(); + List swap = null; + toTransform.Add(obj); + foreach (DynamicObjectTransformDefinition definition in this.Transforms) + { + transformResult.Clear(); + foreach (object transformObject in toTransform) + { + IEnumerable newObjects = definition.Transform(transformObject); + if (!String.IsNullOrEmpty(definition.Result)) + { + newObjects = newObjects.Where(o => o.GetType().FullName.Equals(definition.Result, StringComparison.OrdinalIgnoreCase)); + } + + transformResult.AddRange(newObjects); + } + + // Nothing to transform to the next phase, just return the previous phase + if (transformResult.Count == 0) + { + return toTransform; + } + + swap = toTransform; + toTransform = transformResult; + transformResult = swap; + } + + // The result of the last phase will be in toTransform + return toTransform; + } + + return null; + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Transforms/DynamicObjectTransformDefinition.cs b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Transforms/DynamicObjectTransformDefinition.cs new file mode 100644 index 0000000..0978425 --- /dev/null +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/Transforms/DynamicObjectTransformDefinition.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +// Licensed under the MIT license. +using System.Collections.Generic; +using System.Reflection; + +namespace PSSwagger.LTF.Lib.Transforms +{ + /// + /// Defines a single transform step. + /// + public class DynamicObjectTransformDefinition + { + /// + /// Gets or sets the transform type. Supported: 'select' + /// + public string Query { get; set; } + + /// + /// Gets or sets the property to select. Valid for query types: 'select' + /// + public string Property { get; set; } + + /// + /// Gets or sets the result type. Optional. If defined, subsequent transforms will only take place if the output type matches this type. This optimizes multi-step transforms. + /// + public string Result { get; set; } + + public IEnumerable Transform(object obj) + { + if (!string.IsNullOrEmpty(this.Query)) + { + switch (this.Query.ToLowerInvariant()) + { + case "select": + yield return Select(obj); + break; + } + } + } + + private object Select(object obj) + { + PropertyInfo pi = obj.GetType().GetProperty(this.Property); + if (pi != null) + { + return pi.GetValue(obj); + } + + return null; + } + } +} \ No newline at end of file diff --git a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/PSSwagger.LTF.Lib.csproj b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/PSSwagger.LTF.Lib.csproj index ab9d0bb..89f9014 100644 --- a/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/PSSwagger.LTF.Lib.csproj +++ b/PSSwagger.LiveTestFramework/src/PSSwagger.LTF.Lib/vs-csproj/PSSwagger.LTF.Lib.csproj @@ -94,6 +94,9 @@ PostProcessors\%(FileName).cs + + Transforms\%(FileName).cs +