diff --git a/docs/CHANGELOG-v2.md b/docs/CHANGELOG-v2.md index d77e8051de..3f53a1d30a 100644 --- a/docs/CHANGELOG-v2.md +++ b/docs/CHANGELOG-v2.md @@ -13,11 +13,17 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +What's changed since pre-release v2.3.0-B0030: + +- General improvements: + - Added support for full path from JSON objects by @BernieWhite. + [#1174](https://github.com/microsoft/PSRule/issues/1174) + ## v2.3.0-B0030 (pre-release) What's changed since pre-release v2.3.0-B0015: -- General improvements +- General improvements: - Improved reporting of full object path from pre-processed results by @BernieWhite. [#1169](https://github.com/microsoft/PSRule/issues/1169) diff --git a/src/PSRule/Common/JsonConverters.cs b/src/PSRule/Common/JsonConverters.cs index 5463055803..57b292207e 100644 --- a/src/PSRule/Common/JsonConverters.cs +++ b/src/PSRule/Common/JsonConverters.cs @@ -219,6 +219,7 @@ private static bool SkipComments(JsonReader reader) private static PSObject ReadObject(JsonReader reader, bool bindTargetInfo, TargetSourceInfo sourceInfo) { SkipComments(reader); + var path = reader.Path; if (reader.TokenType != JsonToken.StartObject || !reader.Read()) throw new PipelineSerializationException(PSRuleResources.ReadJsonFailed); @@ -276,6 +277,8 @@ private static PSObject ReadObject(JsonReader reader, bool bindTargetInfo, Targe { result.UseTargetInfo(out var info); info.SetSource(sourceInfo?.File, lineNumber, linePosition); + if (string.IsNullOrEmpty(info.Path)) + info.Path = path; } return result; } diff --git a/src/PSRule/Common/PSObjectExtensions.cs b/src/PSRule/Common/PSObjectExtensions.cs index 20a6766865..595b67ede4 100644 --- a/src/PSRule/Common/PSObjectExtensions.cs +++ b/src/PSRule/Common/PSObjectExtensions.cs @@ -8,6 +8,7 @@ using System.Management.Automation; using System.Threading; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using PSRule.Data; using PSRule.Runtime; @@ -118,7 +119,14 @@ public static TargetIssueInfo[] GetIssueInfo(this PSObject o) public static string GetTargetPath(this PSObject o) { - return o.TryTargetInfo(out var targetInfo) ? targetInfo.Path : string.Empty; + if (o == null) + return string.Empty; + + if (o.TryTargetInfo(out var targetInfo)) + return targetInfo.Path; + + var baseObject = o.BaseObject; + return baseObject is JToken token ? token.Path : string.Empty; } public static void ConvertTargetInfoProperty(this PSObject o) diff --git a/src/PSRule/Runtime/PSRuleMemberInfo.cs b/src/PSRule/Runtime/PSRuleMemberInfo.cs index 65ca5dd9b5..748b18ffb7 100644 --- a/src/PSRule/Runtime/PSRuleMemberInfo.cs +++ b/src/PSRule/Runtime/PSRuleMemberInfo.cs @@ -11,13 +11,11 @@ namespace PSRule.Runtime { - [DebuggerDisplay("Instance = {_Instance}")] + [DebuggerDisplay("Path = {Path}, File = {File}")] internal sealed class PSRuleTargetInfo : PSMemberInfo { internal const string PropertyName = "_PSRule"; - private readonly string _Instance = Guid.NewGuid().ToString(); - public PSRuleTargetInfo() { SetMemberName(PropertyName); diff --git a/tests/PSRule.Tests/InputFormatDeserializerTests.cs b/tests/PSRule.Tests/InputFormatDeserializerTests.cs index abbda57291..6708c862fd 100644 --- a/tests/PSRule.Tests/InputFormatDeserializerTests.cs +++ b/tests/PSRule.Tests/InputFormatDeserializerTests.cs @@ -49,7 +49,9 @@ public void DeserializeObjectsJson() actual[0].Value.TryTargetInfo(out var info1); actual[1].Value.TryTargetInfo(out var info2); Assert.Equal("some-file.json", info1.Source[0].File); + Assert.Equal("master.items[0]", info1.Path); Assert.NotNull(info2.Source[0]); + Assert.Equal("[1]", info2.Path); // Single item actual = PipelineReceiverActions.ConvertFromJson(GetJsonContent("Single"), PipelineReceiverActions.PassThru).ToArray(); diff --git a/tests/PSRule.Tests/PipelineTests.cs b/tests/PSRule.Tests/PipelineTests.cs index fe92a66711..cc60544385 100644 --- a/tests/PSRule.Tests/PipelineTests.cs +++ b/tests/PSRule.Tests/PipelineTests.cs @@ -6,9 +6,11 @@ using System.IO; using System.Linq; using System.Management.Automation; +using Newtonsoft.Json.Linq; using PSRule.Configuration; using PSRule.Pipeline; using PSRule.Resources; +using PSRule.Rules; using Xunit; namespace PSRule @@ -45,6 +47,45 @@ public void InvokePipeline() pipeline.End(); } + [Fact] + public void InvokePipelineWithJObject() + { + var parent = new JObject + { + ["resources"] = new JArray(new object[] { + new JObject + { + ["Name"] = "TestValue" + }, + new JObject + { + ["Name"] = "TestValue2" + } + }) + }; + + var option = GetOption(); + option.Rule.Include = new string[] { "ScriptReasonTest" }; + option.Input.Format = InputFormat.File; + var builder = PipelineBuilder.Invoke(GetSource(), option, null); + var writer = new TestWriter(option); + var pipeline = builder.Build(writer); + + Assert.NotNull(pipeline); + pipeline.Begin(); + pipeline.Process(PSObject.AsPSObject(parent["resources"][0])); + pipeline.Process(PSObject.AsPSObject(parent["resources"][1])); + pipeline.End(); + + var actual = (writer.Output[0] as InvokeResult).AsRecord().FirstOrDefault(); + Assert.Equal(RuleOutcome.Pass, actual.Outcome); + + actual = (writer.Output[1] as InvokeResult).AsRecord().FirstOrDefault(); + Assert.Equal(RuleOutcome.Fail, actual.Outcome); + Assert.Equal("Name", actual.Detail.Reason.First().Path); + Assert.Equal("resources[1].Name", actual.Detail.Reason.First().FullPath); + } + [Fact] public void BuildGetPipeline() { @@ -158,9 +199,13 @@ public void PipelineWithSource() items = writer.Output.OfType().SelectMany(i => i.AsRecord()).ToArray(); Assert.Equal(4, items.Length); Assert.Equal("master.items[0].Name", items[0].Detail.Reason.First().FullPath); - Assert.Equal("Name", items[1].Detail.Reason.First().FullPath); + Assert.Equal("Name", items[0].Detail.Reason.First().Path); + Assert.Equal("[1].Name", items[1].Detail.Reason.First().FullPath); + Assert.Equal("Name", items[1].Detail.Reason.First().Path); Assert.Equal("resources[0].Name", items[2].Detail.Reason.First().FullPath); + Assert.Equal("Name", items[2].Detail.Reason.First().Path); Assert.Equal("Name", items[3].Detail.Reason.First().FullPath); + Assert.Equal("Name", items[3].Detail.Reason.First().Path); // With IgnoreObjectSource option.Rule.Include = new string[] { "FromFile1" };