Skip to content

Commit

Permalink
Merge pull request #3134 from AdmiringWorm/PROJ-595/update-rule-engine
Browse files Browse the repository at this point in the history
(maint) Add ability to get rules from the registered rules.
  • Loading branch information
gep13 authored Apr 25, 2023
2 parents 93fd22e + 2d95b02 commit 7bf2a0b
Show file tree
Hide file tree
Showing 19 changed files with 233 additions and 34 deletions.
1 change: 1 addition & 0 deletions src/chocolatey.tests/chocolatey.tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@
<Compile Include="infrastructure.app\services\FilesServiceSpecs.cs" />
<Compile Include="infrastructure.app\services\NugetServiceSpecs.cs" />
<Compile Include="infrastructure.app\services\RegistryServiceSpecs.cs" />
<Compile Include="infrastructure.app\services\RulesServiceSpecs.cs" />
<Compile Include="infrastructure.app\services\TemplateServiceSpecs.cs" />
<Compile Include="infrastructure.app\utility\ArgumentsUtilitySpecs.cs" />
<Compile Include="infrastructure\commands\ExternalCommandArgsBuilderSpecs.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright © 2017 - 2021 Chocolatey Software, Inc
// Copyright © 2011 - 2017 RealDimensions Software, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
//
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace chocolatey.tests.infrastructure.app.services
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using chocolatey.infrastructure.app.rules;
using chocolatey.infrastructure.app.services;
using chocolatey.infrastructure.rules;
using chocolatey.infrastructure.services;
using Should;

public class RulesServiceSpecs : TinySpec
{
private RuleService _service;
private IReadOnlyList<ImmutableRule> _detectedRules;
// We can't reference RuleIdentifiers directly as it's Internal. We should either get these from there, or do something different...
private const string EmptyRequiredElement = "CHCR0001";
private const string InvalidTypeElement = "CHCU0001";
private const string MissingElementOnRequiringLicenseAcceptance = "CHCR0002";
private const string UnsupportedElementUsed = "CHCU0002";

public override void Context()
{
Type[] availableRules = typeof(IRuleService).Assembly
.GetTypes()
.Where(t => !t.IsInterface && !t.IsAbstract && typeof(IMetadataRule).IsAssignableFrom(t))
.ToArray();
var rules = new List<IMetadataRule>();

foreach (Type availableRule in availableRules)
{
// We do first here as we want it to fail if the constructor can't be found.
var rule = availableRule.GetConstructors().First().Invoke(new object[] { });
rules.Add((MetadataRuleBase)rule);
}

_service = new RuleService(rules.ToArray());
}

public override void Because()
{
_detectedRules = _service.GetAllAvailableRules();
}

[Fact]
public void GetsRulesFromService()
{
_detectedRules.Count().ShouldEqual(4);
IEnumerable<string> ruleIds = _detectedRules.Select(t => t.Id);
ruleIds.ShouldContain(UnsupportedElementUsed);
ruleIds.ShouldContain(EmptyRequiredElement);
ruleIds.ShouldContain(InvalidTypeElement);
ruleIds.ShouldContain(MissingElementOnRequiringLicenseAcceptance);
}
}
}
1 change: 1 addition & 0 deletions src/chocolatey/chocolatey.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@
<Compile Include="infrastructure\registration\AssemblyResolution.cs" />
<Compile Include="infrastructure\registration\HttpsSecurity.cs" />
<Compile Include="infrastructure\rules\IMetadataRule.cs" />
<Compile Include="infrastructure\rules\ImmutableRule.cs" />
<Compile Include="infrastructure\rules\RuleResult.cs" />
<Compile Include="infrastructure\rules\RuleType.cs" />
<Compile Include="infrastructure\services\IRuleService.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,20 @@ public override IEnumerable<RuleResult> Validate(NuspecReader reader)

if (string.IsNullOrWhiteSpace(value))
{
yield return new RuleResult(RuleType.Error, RuleIdentifiers.EmptyRequiredElement, "The {0} element in the package nuspec file cannot be empty.".FormatWith(item));
yield return GetRule(RuleIdentifiers.EmptyRequiredElement, "The {0} element in the package nuspec file cannot be empty.".FormatWith(item));
}
else if (!Uri.TryCreate(value, UriKind.Absolute, out _))
{
yield return new RuleResult(RuleType.Error, RuleIdentifiers.InvalidTypeElement, "'{0}' is not a valid URL for the {1} element in the package nuspec file.".FormatWith(value, item));
yield return GetRule(RuleIdentifiers.InvalidTypeElement, "'{0}' is not a valid URL for the {1} element in the package nuspec file.".FormatWith(value, item));
}
}
}
}

protected override IEnumerable<ImmutableRule> GetRules()
{
yield return new ImmutableRule(RuleType.Error, RuleIdentifiers.EmptyRequiredElement, "A required element does not contain any content.");
yield return new ImmutableRule(RuleType.Error, RuleIdentifiers.InvalidTypeElement, "The specified content of the element is not of the expected type and can not be accepted.");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,19 @@ namespace chocolatey.infrastructure.app.rules
using chocolatey.infrastructure.rules;
using NuGet.Packaging;

internal sealed class FrameWorkReferencesMetadataRule : MetadataRuleBase
internal class FrameWorkReferencesMetadataRule : MetadataRuleBase
{
public override IEnumerable<RuleResult> Validate(NuspecReader reader)
{
if (HasElement(reader, "frameworkReferences"))
{
yield return new RuleResult(RuleType.Error, RuleIdentifiers.UnsupportedElementUsed, "<frameworkReferences> elements are not supported in Chocolatey CLI.");
yield return GetRule(RuleIdentifiers.UnsupportedElementUsed, "<frameworkReferences> elements are not supported in Chocolatey CLI.");
}
}

protected override IEnumerable<ImmutableRule> GetRules()
{
yield return new ImmutableRule(RuleType.Error, RuleIdentifiers.UnsupportedElementUsed, "Unsupported element is used.");
}
}
}
}
6 changes: 3 additions & 3 deletions src/chocolatey/infrastructure.app/rules/IconMetadataRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ namespace chocolatey.infrastructure.app.rules
using chocolatey.infrastructure.rules;
using NuGet.Packaging;

internal sealed class IconMetadataRule : MetadataRuleBase
internal sealed class IconMetadataRule : FrameWorkReferencesMetadataRule
{
public override IEnumerable<RuleResult> Validate(NuspecReader reader)
{
if (!(reader.GetIcon() is null))
{
yield return new RuleResult(RuleType.Error, RuleIdentifiers.UnsupportedElementUsed, "<icon> elements are not supported in Chocolatey CLI, use <iconUrl> instead.");
yield return GetRule(RuleIdentifiers.UnsupportedElementUsed, "<icon> elements are not supported in Chocolatey CLI, use <iconUrl> instead.");
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,17 @@
namespace chocolatey.infrastructure.app.rules
{
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using chocolatey.infrastructure.rules;
using NuGet.Packaging;

internal sealed class LicenseMetadataRule : MetadataRuleBase
internal sealed class LicenseMetadataRule : FrameWorkReferencesMetadataRule
{
public override IEnumerable<RuleResult> Validate(NuspecReader reader)
{
if (!(reader.GetLicenseMetadata() is null))
{
yield return new RuleResult(RuleType.Error, RuleIdentifiers.UnsupportedElementUsed, "<license> elements are not supported in Chocolatey CLI, use <licenseUrl> instead.");
yield return GetRule(RuleIdentifiers.UnsupportedElementUsed, "<license> elements are not supported in Chocolatey CLI, use <licenseUrl> instead.");
}
}
}
}
}
32 changes: 31 additions & 1 deletion src/chocolatey/infrastructure.app/rules/MetadataRuleBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,36 @@ namespace chocolatey.infrastructure.app.rules

public abstract class MetadataRuleBase : IMetadataRule
{
private IDictionary<string, ImmutableRule> _cachedRules;

public abstract IEnumerable<RuleResult> Validate(NuspecReader reader);

public IReadOnlyList<ImmutableRule> GetAvailableRules()
{
if (_cachedRules is null || _cachedRules.Count == 0)
{
_cachedRules = GetRules().ToDictionary(r => r.Id, r => r);
}

return _cachedRules.Values.ToList().AsReadOnly();
}

protected RuleResult GetRule(string id, string summary = null)
{
if (_cachedRules is null || _cachedRules.Count == 0)
{
// Just to populate the cached dictionary
GetAvailableRules();
}

if (!_cachedRules.TryGetValue(id, out ImmutableRule result))
{
throw new ArgumentOutOfRangeException(nameof(id), "No rule with the identifier {0} could be found!".FormatWith(id));
}

return RuleResult.FromImmutableRule(result, summary);
}

protected static bool HasElement(NuspecReader reader, string name)
{
var metadataNode = reader.Xml.Root.Elements().FirstOrDefault(e => StringComparer.Ordinal.Equals(e.Name.LocalName, "metadata"));
Expand All @@ -52,6 +80,8 @@ protected static string GetElementValue(NuspecReader reader, string name)
return element.Value;
}

protected abstract IEnumerable<ImmutableRule> GetRules();

#pragma warning disable IDE1006
[Obsolete("This overload is deprecated and will be removed in v3.")]
public virtual IEnumerable<RuleResult> validate(NuspecReader reader)
Expand All @@ -66,4 +96,4 @@ protected static string get_element_value(NuspecReader reader, string name)
=> GetElementValue(reader, name);
#pragma warning restore IDE1006
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ namespace chocolatey.infrastructure.app.rules
using chocolatey.infrastructure.rules;
using NuGet.Packaging;

internal sealed class PackageTypesMetadataRule : MetadataRuleBase
internal sealed class PackageTypesMetadataRule : FrameWorkReferencesMetadataRule
{
public override IEnumerable<RuleResult> Validate(NuspecReader reader)
{
if (HasElement(reader, "packageTypes"))
{
yield return new RuleResult(RuleType.Error, RuleIdentifiers.UnsupportedElementUsed, "<packageTypes> elements are not supported in Chocolatey CLI.");
yield return GetRule(RuleIdentifiers.UnsupportedElementUsed, "<packageTypes> elements are not supported in Chocolatey CLI.");
}
}
}
}
}
6 changes: 3 additions & 3 deletions src/chocolatey/infrastructure.app/rules/ReadmeMetadataRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ namespace chocolatey.infrastructure.app.rules
using chocolatey.infrastructure.rules;
using NuGet.Packaging;

internal sealed class ReadmeMetadataRule : MetadataRuleBase
internal sealed class ReadmeMetadataRule : FrameWorkReferencesMetadataRule
{
public override IEnumerable<RuleResult> Validate(NuspecReader reader)
{
if (!(reader.GetReadme() is null))
{
yield return new RuleResult(RuleType.Error, RuleIdentifiers.UnsupportedElementUsed, "<readme> elements are not supported in Chocolatey CLI.");
yield return GetRule(RuleIdentifiers.UnsupportedElementUsed, "<readme> elements are not supported in Chocolatey CLI.");
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@ namespace chocolatey.infrastructure.app.rules
using chocolatey.infrastructure.rules;
using NuGet.Packaging;

internal sealed class RepositoryMetadataRule : MetadataRuleBase
internal sealed class RepositoryMetadataRule : FrameWorkReferencesMetadataRule
{
public override IEnumerable<RuleResult> Validate(NuspecReader reader)
{
var metadataNode = reader.Xml.Root.Elements().FirstOrDefault(e => StringComparer.Ordinal.Equals(e.Name.LocalName, "metadata"));

if (HasElement(reader, "repository"))
{
yield return new RuleResult(RuleType.Error, RuleIdentifiers.UnsupportedElementUsed, "<repository> elements are not supported in Chocolatey CLI, use <packageSourceUrl> instead.");
yield return GetRule(RuleIdentifiers.UnsupportedElementUsed, "<repository> elements are not supported in Chocolatey CLI, use <packageSourceUrl> instead.");
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@ public override IEnumerable<RuleResult> Validate(NuspecReader reader)
{
if (string.IsNullOrWhiteSpace(reader.GetLicenseUrl()) && reader.GetRequireLicenseAcceptance())
{
yield return new RuleResult(RuleType.Error, RuleIdentifiers.MissingElementOnRequiringLicenseAcceptance, "Enabling license acceptance requires a license url.");
yield return GetRule(RuleIdentifiers.MissingElementOnRequiringLicenseAcceptance);
}
}

protected override IEnumerable<ImmutableRule> GetRules()
{
yield return new ImmutableRule(RuleType.Error, RuleIdentifiers.MissingElementOnRequiringLicenseAcceptance, "Enabling license acceptance requires a license url.");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,14 @@ public override IEnumerable<RuleResult> Validate(NuspecReader reader)
{
if (string.IsNullOrWhiteSpace(GetElementValue(reader, item)))
{
yield return new RuleResult(RuleType.Error, RuleIdentifiers.EmptyRequiredElement, "{0} is a required element in the package nuspec file.".FormatWith(item));
yield return GetRule(RuleIdentifiers.EmptyRequiredElement, "{0} is a required element in the package nuspec file.".FormatWith(item));
}
}
}

protected override IEnumerable<ImmutableRule> GetRules()
{
yield return new ImmutableRule(RuleType.Error, RuleIdentifiers.EmptyRequiredElement, "A required element is missing or has no content in the package nuspec file.");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ namespace chocolatey.infrastructure.app.rules
using chocolatey.infrastructure.rules;
using NuGet.Packaging;

internal sealed class ServicableMetadataRule : MetadataRuleBase
internal sealed class ServicableMetadataRule : FrameWorkReferencesMetadataRule
{
public override IEnumerable<RuleResult> Validate(NuspecReader reader)
{
if (HasElement(reader, "serviceable"))
{
yield return new RuleResult(RuleType.Error, RuleIdentifiers.UnsupportedElementUsed, "<serviceable> elements are not supported in Chocolatey CLI.");
yield return GetRule(RuleIdentifiers.UnsupportedElementUsed, "<serviceable> elements are not supported in Chocolatey CLI.");
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,13 @@ public override IEnumerable<RuleResult> Validate(NuspecReader reader)
// before the package gets created
if (!string.IsNullOrEmpty(version) && !version.IsEqualTo("$version$") && !NuGetVersion.TryParse(version, out _))
{
yield return new RuleResult(RuleType.Error, RuleIdentifiers.InvalidTypeElement, "'{0}' is not a valid version string in the package nuspec file.".FormatWith(version));
yield return GetRule(RuleIdentifiers.InvalidTypeElement, "'{0}' is not a valid version string in the package nuspec file.".FormatWith(version));
}
}

protected override IEnumerable<ImmutableRule> GetRules()
{
yield return new ImmutableRule(RuleType.Error, RuleIdentifiers.InvalidTypeElement, "The specified version is not a valid version string.");
}
}
}
}
Loading

0 comments on commit 7bf2a0b

Please sign in to comment.