Skip to content

Commit

Permalink
Fix YAML property serialization and summary output format #114 #115 #116
Browse files Browse the repository at this point in the history
 (#117)

- Fix summary is not correctly serialized with JSON or YAML output format #116
- Fix missing properties on serialized YAML output #115
- Fix incorrect property name case of YAML serialized results #114
  • Loading branch information
BernieWhite authored Mar 27, 2019
1 parent dc8acc8 commit 0346927
Show file tree
Hide file tree
Showing 19 changed files with 240 additions and 51 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@

## Unreleased

- Fix summary is not correctly serialized with JSON or YAML output format [#116](https://github.com/BernieWhite/PSRule/issues/116)
- Fix missing properties on serialized YAML output [#115](https://github.com/BernieWhite/PSRule/issues/115)
- Fix incorrect property name case of YAML serialized results [#114](https://github.com/BernieWhite/PSRule/issues/114)

## v0.4.0-B190320 (pre-release)

- Fix incorrect JSON de-serialization of nested arrays. [#109](https://github.com/BernieWhite/PSRule/issues/109)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ The following conceptual topics exist in the `PSRule` module:
- [Input.ObjectPath](docs/concepts/PSRule/en-US/about_PSRule_Options.md#inputobjectpath)
- [Logging.RuleFail](docs/concepts/PSRule/en-US/about_PSRule_Options.md#loggingrulefail)
- [Logging.RulePass](docs/concepts/PSRule/en-US/about_PSRule_Options.md#loggingrulepass)
- [Output.As](docs/concepts/PSRule/en-US/about_PSRule_Options.md#outputas)
- [Output.Format](docs/concepts/PSRule/en-US/about_PSRule_Options.md#outputformat)
- [Suppression](docs/concepts/PSRule/en-US/about_PSRule_Options.md#rule-suppression)
- [Variables](docs/concepts/PSRule/en-US/about_PSRule_Variables.md)
Expand Down
2 changes: 1 addition & 1 deletion docs/commands/PSRule/en-US/Invoke-PSRule.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ Accept wildcard characters: False

### -As

The format to return results. Results are returned using detailed by default.
The type of results to produce. Detailed results are generated by default.

The following result formats are available:

Expand Down
27 changes: 27 additions & 0 deletions docs/concepts/PSRule/en-US/about_PSRule_Options.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ The following options are available for use:
- [Input.ObjectPath](#inputobjectpath)
- [Logging.RuleFail](#loggingrulefail)
- [Logging.RulePass](#loggingrulepass)
- [Output.As](#outputas)
- [Output.Format](#outputformat)
- [Suppression](#rule-suppression)

Expand Down Expand Up @@ -438,6 +439,30 @@ logging:
rulePass: Information
```

### Output.As

Configures the type of results to produce.

This option only applies to `Invoke-PSRule`. `Invoke-PSRule` also includes a parameter `-As` to set this option at runtime. If specified, the `-As` parameter take precedence, over this option.

The following options are available:

- Detail - Return a record per rule per object.
- Summary - Return summary information for per rule.

This option can be specified using:

```powershell
# PowerShell: Using the Output.As hashtable key
$option = New-PSRuleOption -Option @{ 'Output.As' = 'Summary' };
```

```yaml
# YAML: Using the output/as property
output:
as: Summary
```

### Output.Format

Configures the format that results will be presented in.
Expand Down Expand Up @@ -566,6 +591,7 @@ logging:
rulePass: Information
output:
as: Summary
format: Json
# Configure rule suppression
Expand Down Expand Up @@ -617,6 +643,7 @@ logging:
rulePass: None
output:
as: Detail
format: None
# Configure rule suppression
Expand Down
9 changes: 9 additions & 0 deletions schemas/PSRule-options-0.4.0.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,15 @@
"type": "object",
"description": "Options that affect how output is generated.",
"properties": {
"as": {
"type": "string",
"description": "Determine if detailed or summary results are generated.",
"enum": [
"Detail",
"Summary"
],
"default": "Detail"
},
"format": {
"type": "string",
"description": "The output format.",
Expand Down
2 changes: 1 addition & 1 deletion src/PSRule.Benchmark/PSRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ private void PrepareInvokeSummaryPipeline()
{
var option = new PSRuleOption();
option.Baseline.RuleName = new string[] { "Benchmark" };
option.Output.As = ResultFormat.Summary;
var builder = PipelineBuilder.Invoke().Configure(option);
builder.Source(new RuleSource[] { new RuleSource(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "Benchmark.Rule.ps1"), null) });
builder.As(Configuration.ResultFormat.Summary);
_InvokeSummaryPipeline = builder.Build();
}

Expand Down
2 changes: 1 addition & 1 deletion src/PSRule.Benchmark/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace PSRule.Benchmark
{
internal sealed class Program
internal static class Program
{
static void Main(string[] args)
{
Expand Down
86 changes: 81 additions & 5 deletions src/PSRule/Common/YamlConverters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.TypeInspectors;
using YamlDotNet.Serialization.TypeResolvers;

Expand Down Expand Up @@ -187,32 +188,63 @@ public bool Resolve(NodeEvent nodeEvent, ref Type currentType)
internal sealed class FieldYamlTypeInspector : TypeInspectorSkeleton
{
private readonly ITypeResolver _TypeResolver;
private readonly INamingConvention _NamingConvention;

public FieldYamlTypeInspector()
{
_TypeResolver = new StaticTypeResolver();
_NamingConvention = new CamelCaseNamingConvention();
}

public override IEnumerable<IPropertyDescriptor> GetProperties(Type type, object container)
{
return GetPropertyDescriptor(type: type);
}

private IEnumerable<IPropertyDescriptor> GetPropertyDescriptor(Type type)
{
foreach (var f in SelectField(type: type))
{
yield return f;
}

foreach (var p in SelectProperty(type: type))
{
yield return p;
}
}

private IEnumerable<Field> SelectField(Type type)
{
return type
.GetRuntimeFields()
.Where(f => !f.IsStatic && f.IsPublic)
.Select(p => new Field(p, _TypeResolver, _NamingConvention));
}

private IEnumerable<Property> SelectProperty(Type type)
{
return type
.GetRuntimeFields().Where(f => !f.IsStatic && f.IsPublic)
.Select(p => new FieldDescriptor(p, _TypeResolver));
.GetProperties(bindingAttr: BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance)
.Where(p => p.CanRead && p.Name != "TargetObject")
.Select(p => new Property(p, _TypeResolver, _NamingConvention));
}

private sealed class FieldDescriptor : IPropertyDescriptor
private sealed class Field : IPropertyDescriptor
{
private readonly FieldInfo _FieldInfo;
private readonly ITypeResolver _TypeResolver;
private readonly INamingConvention _NamingConvention;

public FieldDescriptor(FieldInfo fieldInfo, ITypeResolver typeResolver)
public Field(FieldInfo fieldInfo, ITypeResolver typeResolver, INamingConvention namingConvention)
{
_FieldInfo = fieldInfo;
_TypeResolver = typeResolver;
_NamingConvention = namingConvention;
ScalarStyle = ScalarStyle.Any;
}

public string Name => _FieldInfo.Name;
public string Name => _NamingConvention.Apply(_FieldInfo.Name);

public Type Type => _FieldInfo.FieldType;

Expand Down Expand Up @@ -241,5 +273,49 @@ public IObjectDescriptor Read(object target)
return new ObjectDescriptor(propertyValue, actualType, Type, ScalarStyle);
}
}

private sealed class Property : IPropertyDescriptor
{
private readonly PropertyInfo _PropertyInfo;
private readonly ITypeResolver _TypeResolver;
private readonly INamingConvention _NamingConvention;

public Property(PropertyInfo propertyInfo, ITypeResolver typeResolver, INamingConvention namingConvention)
{
_PropertyInfo = propertyInfo;
_TypeResolver = typeResolver;
_NamingConvention = namingConvention;
ScalarStyle = ScalarStyle.Any;
}

public string Name => _NamingConvention.Apply(_PropertyInfo.Name);

public Type Type => _PropertyInfo.PropertyType;

public Type TypeOverride { get; set; }

public int Order { get; set; }

public bool CanWrite => false;

public ScalarStyle ScalarStyle { get; set; }

public T GetCustomAttribute<T>() where T : Attribute
{
return _PropertyInfo.GetCustomAttributes(typeof(T), true).OfType<T>().FirstOrDefault();
}

public void Write(object target, object value)
{
throw new NotImplementedException();
}

public IObjectDescriptor Read(object target)
{
var propertyValue = _PropertyInfo.GetValue(target);
var actualType = TypeOverride ?? _TypeResolver.Resolve(Type, propertyValue);
return new ObjectDescriptor(propertyValue, actualType, Type, ScalarStyle);
}
}
}
}
10 changes: 10 additions & 0 deletions src/PSRule/Configuration/OutputOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,33 @@ namespace PSRule.Configuration
{
public sealed class OutputOption
{
private const ResultFormat DEFAULT_AS = PSRule.Configuration.ResultFormat.Detail;
private const OutputFormat DEFAULT_FORMAT = PSRule.Configuration.OutputFormat.None;

public static readonly OutputOption Default = new OutputOption
{
As = DEFAULT_AS,
Format = DEFAULT_FORMAT
};

public OutputOption()
{
As = null;
Format = null;
}

public OutputOption(OutputOption option)
{
As = option.As;
Format = option.Format;
}

/// <summary>
/// The type of result to produce.
/// </summary>
[DefaultValue(null)]
public ResultFormat? As { get; set; }

/// <summary>
/// The output format.
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions src/PSRule/Configuration/PSRuleOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,11 @@ public static implicit operator PSRuleOption(Hashtable hashtable)
option.Logging.RulePass = (OutcomeLogStream)Enum.Parse(typeof(OutcomeLogStream), (string)value);
}

if (index.TryGetValue("output.as", out value))
{
option.Output.As = (ResultFormat)Enum.Parse(typeof(ResultFormat), (string)value);
}

if (index.TryGetValue("output.format", out value))
{
option.Output.Format = (OutputFormat)Enum.Parse(typeof(OutputFormat), (string)value);
Expand Down
12 changes: 4 additions & 8 deletions src/PSRule/PSRule.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ function Invoke-PSRule {
$Option.Input.ObjectPath = $ObjectPath;
}

if ($PSBoundParameters.ContainsKey('As')) {
$Option.Output.As = $As;
}

if ($PSBoundParameters.ContainsKey('OutputFormat')) {
$Option.Output.Format = $OutputFormat;
}
Expand All @@ -150,10 +154,6 @@ function Invoke-PSRule {
$builder.Source($sourceFiles);
$builder.Limit($Outcome);

if ($PSBoundParameters.ContainsKey('As')) {
$builder.As($As);
}

if ($PSBoundParameters.ContainsKey('InputPath')) {
$inputPaths = GetFilePath -Path $InputPath -Verbose:$VerbosePreference;
$builder.InputPath($inputPaths);
Expand Down Expand Up @@ -181,10 +181,6 @@ function Invoke-PSRule {
end {
if ($Null -ne $pipeline) {
try {
if ($As -eq [PSRule.Configuration.ResultFormat]::Summary) {
$pipeline.GetSummary();
}

$pipeline.End();
}
finally {
Expand Down
6 changes: 4 additions & 2 deletions src/PSRule/Pipeline/IPipelineStream.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Management.Automation;
using PSRule.Rules;
using System.Collections.Generic;
using System.Management.Automation;

namespace PSRule.Pipeline
{
Expand All @@ -13,7 +15,7 @@ public interface IPipelineStream

void Process(PSObject targetObject);

void End();
void End(IEnumerable<RuleSummaryRecord> summary);

void Output(InvokeResult result);
}
Expand Down
17 changes: 3 additions & 14 deletions src/PSRule/Pipeline/InvokeRulePipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public sealed class InvokeRulePipeline : RulePipeline
// Track whether Dispose has been called.
private bool _Disposed = false;

internal InvokeRulePipeline(StreamManager streamManager, PSRuleOption option, RuleSource[] source, RuleFilter filter, RuleOutcome outcome, ResultFormat resultFormat, PipelineContext context)
internal InvokeRulePipeline(StreamManager streamManager, PSRuleOption option, RuleSource[] source, RuleFilter filter, RuleOutcome outcome, PipelineContext context)
: base(context, option, source, filter)
{
_StreamManager = streamManager;
Expand All @@ -36,7 +36,7 @@ internal InvokeRulePipeline(StreamManager streamManager, PSRuleOption option, Ru

_Outcome = outcome;
_Summary = new Dictionary<string, RuleSummaryRecord>();
_ResultFormat = resultFormat;
_ResultFormat = option.Output.As.Value;
_SuppressionFilter = new RuleSuppressionFilter(option.Suppression);
}

Expand Down Expand Up @@ -76,18 +76,7 @@ public void Process(PSObject targetObject)

public void End()
{
_StreamManager.End();
}

public IEnumerable<RuleSummaryRecord> GetSummary()
{
foreach (var s in _Summary.Values.ToArray())
{
if (_Outcome == RuleOutcome.All || (s.Outcome & _Outcome) > 0)
{
yield return s;
}
}
_StreamManager.End(_Summary.Values.Where(r => _Outcome == RuleOutcome.All || (r.Outcome & _Outcome) > 0));
}

private InvokeResult ProcessTargetObject(PSObject targetObject)
Expand Down
Loading

0 comments on commit 0346927

Please sign in to comment.