diff --git a/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs b/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs
index 96c0ce501..18f1a59d6 100644
--- a/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs
+++ b/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs
@@ -77,7 +77,18 @@ internal static string ArgumentNullOrWhiteSpace {
return ResourceManager.GetString("ArgumentNullOrWhiteSpace", resourceCulture);
}
}
-
+
+ ///
+ /// Looks up a localized string similar to The argument '{0}' is null..
+ ///
+ internal static string ArgumentNull
+ {
+ get
+ {
+ return ResourceManager.GetString("ArgumentNull", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to The filed name '{0}' of extension doesn't begin with x-..
///
diff --git a/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs b/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs
index a0aee12e7..64f901c53 100644
--- a/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs
+++ b/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs
@@ -317,7 +317,7 @@ private void Validate(object item, Type type)
type = typeof(IOpenApiReferenceable);
}
- var rules = _ruleSet.FindRules(type);
+ var rules = _ruleSet.FindRules(type.Name);
foreach (var rule in rules)
{
rule.Evaluate(this as IValidationContext, item);
diff --git a/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs
index 11bc39f04..c34d4a451 100644
--- a/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs
+++ b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs
@@ -4,7 +4,6 @@
using System;
using System.Linq;
using System.Reflection;
-using System.Collections;
using System.Collections.Generic;
using Microsoft.OpenApi.Exceptions;
using Microsoft.OpenApi.Properties;
@@ -15,23 +14,44 @@ namespace Microsoft.OpenApi.Validations
///
/// The rule set of the validation.
///
- public sealed class ValidationRuleSet : IEnumerable
+ public sealed class ValidationRuleSet
{
- private readonly IDictionary> _rules = new Dictionary>();
+ private readonly IDictionary> _rulesDictionary = new Dictionary>();
private static ValidationRuleSet _defaultRuleSet;
private readonly IList _emptyRules = new List();
///
- /// Retrieve the rules that are related to a specific type
+ /// Gets the keys in this rule set.
///
- /// The type that is to be validated
- /// Either the rules related to the type, or an empty list.
- public IList FindRules(Type type)
+ public ICollection Keys => _rulesDictionary.Keys;
+
+ ///
+ /// Gets the rules in this rule set.
+ ///
+ public IList Rules => _rulesDictionary.Values.SelectMany(v => v).ToList();
+
+ ///
+ /// Gets the number of elements contained in this rule set.
+ ///
+ public int Count => _rulesDictionary.Count;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ValidationRuleSet()
+ {
+ }
+
+ ///
+ /// Retrieve the rules that are related to a specific key.
+ ///
+ /// The key of the rules to search for.
+ /// Either the rules related to the given key, or an empty list.
+ public IList FindRules(string key)
{
- IList results = null;
- _rules.TryGetValue(type, out results);
+ _rulesDictionary.TryGetValue(key, out var results);
return results ?? _emptyRules;
}
@@ -67,10 +87,22 @@ public static ValidationRuleSet GetEmptyRuleSet()
}
///
- /// Initializes a new instance of the class.
+ /// Add validation rules to the rule set.
///
- public ValidationRuleSet()
+ /// The rule set to add validation rules to.
+ /// The validation rules to be added to the rules set.
+ /// Throws a null argument exception if the arguments are null.
+ public static void AddValidationRules(ValidationRuleSet ruleSet, IDictionary> rules)
{
+ if (ruleSet == null || rules == null)
+ {
+ throw new OpenApiException(SRResource.ArgumentNull);
+ }
+
+ foreach (var rule in rules)
+ {
+ ruleSet.Add(rule.Key, rule.Value);
+ }
}
///
@@ -86,7 +118,7 @@ public ValidationRuleSet(ValidationRuleSet ruleSet)
foreach (ValidationRule rule in ruleSet)
{
- Add(rule);
+ Add(rule.ElementType.Name, rule);
}
}
@@ -94,71 +126,161 @@ public ValidationRuleSet(ValidationRuleSet ruleSet)
/// Initializes a new instance of the class.
///
/// Rules to be contained in this ruleset.
- public ValidationRuleSet(IEnumerable rules)
+ public ValidationRuleSet(IDictionary> rules)
{
if (rules == null)
{
return;
}
- foreach (ValidationRule rule in rules)
+ foreach (var rule in rules)
{
- Add(rule);
+ Add(rule.Key, rule.Value);
}
}
///
- /// Gets the rules in this rule set.
+ /// Add the new rule into the rule set.
///
- public IEnumerable Rules
+ /// The key for the rule.
+ /// The list of rules.
+ public void Add(string key, IList rules)
{
- get
+ foreach (var rule in rules)
{
- return _rules.Values.SelectMany(v => v);
+ Add(key, rule);
}
}
///
- /// Add the new rule into the rule set.
+ /// Add a new rule into the rule set.
///
+ /// The key for the rule.
/// The rule.
- public void Add(ValidationRule rule)
+ /// Exception thrown when rule already exists.
+ public void Add(string key, ValidationRule rule)
{
- if (!_rules.ContainsKey(rule.ElementType))
+ if (!_rulesDictionary.ContainsKey(key))
{
- _rules[rule.ElementType] = new List();
+ _rulesDictionary[key] = new List();
}
- if (_rules[rule.ElementType].Contains(rule))
+ if (_rulesDictionary[key].Contains(rule))
{
throw new OpenApiException(SRResource.Validation_RuleAddTwice);
}
- _rules[rule.ElementType].Add(rule);
+ _rulesDictionary[key].Add(rule);
}
///
- /// Get the enumerator.
+ /// Updates an existing rule with a new one.
///
- /// The enumerator.
- public IEnumerator GetEnumerator()
+ /// The key of the existing rule.
+ /// The new rule.
+ /// The old rule.
+ /// true, if the update was successful; otherwise false.
+ public bool Update(string key, ValidationRule newRule, ValidationRule oldRule)
{
- foreach (var ruleList in _rules.Values)
+ if (_rulesDictionary.TryGetValue(key, out var currentRules))
{
- foreach (var rule in ruleList)
- {
- yield return rule;
- }
+ currentRules.Add(newRule);
+ return currentRules.Remove(oldRule);
}
+ return false;
+ }
+
+ ///
+ /// Removes a collection of rules.
+ ///
+ /// The key of the collection of rules to be removed.
+ /// true if the collection of rules with the provided key is removed; otherwise, false.
+ public bool Remove(string key)
+ {
+ return _rulesDictionary.Remove(key);
+ }
+
+ ///
+ /// Removes a rule by key.
+ ///
+ /// The key of the rule to be removed.
+ /// The rule to be removed.
+ /// true if the rule is successfully removed; otherwise, false.
+ public bool Remove(string key, ValidationRule rule)
+ {
+ if (_rulesDictionary.TryGetValue(key, out IList validationRules))
+ {
+ return validationRules.Remove(rule);
+ }
+
+ return false;
+ }
+
+ ///
+ /// Removes the first rule that matches the provided rule from the list of rules.
+ ///
+ /// The rule to be removed.
+ /// true if the rule is successfully removed; otherwise, false.
+ public bool Remove(ValidationRule rule)
+ {
+ return _rulesDictionary.Values.FirstOrDefault(x => x.Remove(rule)) is not null;
+ }
+
+ ///
+ /// Clears all rules in this rule set.
+ ///
+ public void Clear()
+ {
+ _rulesDictionary.Clear();
+ }
+
+ ///
+ /// Determines whether the rule set contains an element with the specified key.
+ ///
+ /// The key to locate in the rule set.
+ /// true if the rule set contains an element with the key; otherwise, false.
+ public bool ContainsKey(string key)
+ {
+ return _rulesDictionary.ContainsKey(key);
+ }
+
+ ///
+ /// Determines whether the provided rule is contained in the specified key in the rule set.
+ ///
+ /// The key to locate.
+ /// The rule to locate.
+ ///
+ public bool Contains(string key, ValidationRule rule)
+ {
+ return _rulesDictionary.TryGetValue(key, out IList validationRules) && validationRules.Contains(rule);
+ }
+
+ ///
+ /// Gets the rules associated with the specified key.
+ ///
+ /// The key whose rules to get.
+ /// When this method returns, the rules associated with the specified key, if the
+ /// key is found; otherwise, an empty object.
+ /// This parameter is passed uninitialized.
+ /// true if the specified key has rules.
+ public bool TryGetValue(string key, out IList rules)
+ {
+ return _rulesDictionary.TryGetValue(key, out rules);
}
///
/// Get the enumerator.
///
/// The enumerator.
- IEnumerator IEnumerable.GetEnumerator()
+ public IEnumerator GetEnumerator()
{
- return this.GetEnumerator();
+ foreach (var ruleList in _rulesDictionary.Values)
+ {
+ foreach (var rule in ruleList)
+ {
+ yield return rule;
+ }
+ }
}
private static ValidationRuleSet BuildDefaultRuleSet()
@@ -179,7 +301,7 @@ private static ValidationRuleSet BuildDefaultRuleSet()
ValidationRule rule = propertyValue as ValidationRule;
if (rule != null)
{
- ruleSet.Add(rule);
+ ruleSet.Add(rule.ElementType.Name, rule);
}
}
diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
index 2948950f7..c12a59de5 100755
--- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
+++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
@@ -1222,15 +1222,27 @@ namespace Microsoft.OpenApi.Validations
{
protected ValidationRule() { }
}
- public sealed class ValidationRuleSet : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable
+ public sealed class ValidationRuleSet
{
public ValidationRuleSet() { }
public ValidationRuleSet(Microsoft.OpenApi.Validations.ValidationRuleSet ruleSet) { }
- public ValidationRuleSet(System.Collections.Generic.IEnumerable rules) { }
- public System.Collections.Generic.IEnumerable Rules { get; }
- public void Add(Microsoft.OpenApi.Validations.ValidationRule rule) { }
- public System.Collections.Generic.IList FindRules(System.Type type) { }
+ public ValidationRuleSet(System.Collections.Generic.IDictionary> rules) { }
+ public int Count { get; }
+ public System.Collections.Generic.ICollection Keys { get; }
+ public System.Collections.Generic.IList Rules { get; }
+ public void Add(string key, Microsoft.OpenApi.Validations.ValidationRule rule) { }
+ public void Add(string key, System.Collections.Generic.IList rules) { }
+ public void Clear() { }
+ public bool Contains(string key, Microsoft.OpenApi.Validations.ValidationRule rule) { }
+ public bool ContainsKey(string key) { }
+ public System.Collections.Generic.IList FindRules(string key) { }
public System.Collections.Generic.IEnumerator GetEnumerator() { }
+ public bool Remove(Microsoft.OpenApi.Validations.ValidationRule rule) { }
+ public bool Remove(string key) { }
+ public bool Remove(string key, Microsoft.OpenApi.Validations.ValidationRule rule) { }
+ public bool TryGetValue(string key, out System.Collections.Generic.IList rules) { }
+ public bool Update(string key, Microsoft.OpenApi.Validations.ValidationRule newRule, Microsoft.OpenApi.Validations.ValidationRule oldRule) { }
+ public static void AddValidationRules(Microsoft.OpenApi.Validations.ValidationRuleSet ruleSet, System.Collections.Generic.IDictionary> rules) { }
public static Microsoft.OpenApi.Validations.ValidationRuleSet GetDefaultRuleSet() { }
public static Microsoft.OpenApi.Validations.ValidationRuleSet GetEmptyRuleSet() { }
}
diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs
index ef036a56b..85420890c 100644
--- a/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs
+++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs
@@ -108,7 +108,7 @@ public void ValidateCustomExtension()
{
var ruleset = ValidationRuleSet.GetDefaultRuleSet();
- ruleset.Add(
+ ruleset.Add(typeof(OpenApiAny).Name,
new ValidationRule(
(context, item) =>
{
diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiReferenceValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiReferenceValidationTests.cs
index 3ed365c8d..43576475d 100644
--- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiReferenceValidationTests.cs
+++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiReferenceValidationTests.cs
@@ -1,11 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
-using System;
using System.Collections.Generic;
using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using Microsoft.OpenApi.Extensions;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models;
@@ -67,7 +64,14 @@ public void ReferencedSchemaShouldOnlyBeValidatedOnce()
};
// Act
- var errors = document.Validate(new ValidationRuleSet() { new AlwaysFailRule() });
+ var rules = new Dictionary>()
+ {
+ { typeof(OpenApiSchema).Name,
+ new List() { new AlwaysFailRule() }
+ }
+ };
+
+ var errors = document.Validate(new ValidationRuleSet(rules));
// Assert
@@ -97,8 +101,15 @@ public void UnresolvedReferenceSchemaShouldNotBeValidated()
}
};
- // Act
- var errors = document.Validate(new ValidationRuleSet() { new AlwaysFailRule() });
+ // Act
+ var rules = new Dictionary>()
+ {
+ { typeof(AlwaysFailRule).Name,
+ new List() { new AlwaysFailRule() }
+ }
+ };
+
+ var errors = document.Validate(new ValidationRuleSet(rules));
// Assert
Assert.True(errors.Count() == 0);
@@ -147,7 +158,14 @@ public void UnresolvedSchemaReferencedShouldNotBeValidated()
};
// Act
- var errors = document.Validate(new ValidationRuleSet() { new AlwaysFailRule() });
+ var rules = new Dictionary>()
+ {
+ { typeof(AlwaysFailRule).Name,
+ new List() { new AlwaysFailRule() }
+ }
+ };
+
+ var errors = document.Validate(new ValidationRuleSet(rules));
// Assert
Assert.True(errors.Count() == 0);
diff --git a/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs b/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs
index 5124375ac..7685f80ca 100644
--- a/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs
+++ b/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs
@@ -1,50 +1,191 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
+using System.Collections.Generic;
using System.Linq;
+using Microsoft.OpenApi.Models;
using Xunit;
-using Xunit.Abstractions;
namespace Microsoft.OpenApi.Validations.Tests
{
public class ValidationRuleSetTests
{
- private readonly ITestOutputHelper _output;
+ private readonly ValidationRule _contactValidationRule = new ValidationRule(
+ (context, item) => { });
- public ValidationRuleSetTests(ITestOutputHelper output)
+ private readonly ValidationRule _headerValidationRule = new ValidationRule(
+ (context, item) => { });
+
+ private readonly ValidationRule _parameterValidationRule = new ValidationRule(
+ (context, item) => { });
+
+ private readonly IDictionary> _rulesDictionary;
+
+ public ValidationRuleSetTests()
+ {
+ _rulesDictionary = new Dictionary>()
+ {
+ {"contact", new List { _contactValidationRule } },
+ {"header", new List { _headerValidationRule } },
+ {"parameter", new List { _parameterValidationRule } }
+ };
+ }
+
+ [Fact]
+ public void RuleSetConstructorsReturnsTheCorrectRules()
{
- _output = output;
+ // Arrange & Act
+ var ruleSet_1 = ValidationRuleSet.GetDefaultRuleSet();
+ var ruleSet_2 = new ValidationRuleSet(ValidationRuleSet.GetDefaultRuleSet());
+ var ruleSet_3 = new ValidationRuleSet(_rulesDictionary);
+ var ruleSet_4 = new ValidationRuleSet();
+
+ // Assert
+ Assert.NotNull(ruleSet_1?.Rules);
+ Assert.NotNull(ruleSet_2?.Rules);
+ Assert.NotNull(ruleSet_3?.Rules);
+ Assert.NotNull(ruleSet_4);
+
+ Assert.NotEmpty(ruleSet_1.Rules);
+ Assert.NotEmpty(ruleSet_2.Rules);
+ Assert.NotEmpty(ruleSet_3.Rules);
+ Assert.Empty(ruleSet_4.Rules);
+
+ // Update the number if you add new default rule(s).
+ Assert.Equal(22, ruleSet_1.Rules.Count);
+ Assert.Equal(22, ruleSet_2.Rules.Count);
+ Assert.Equal(3, ruleSet_3.Rules.Count);
}
[Fact]
- public void DefaultRuleSetReturnsTheCorrectRules()
+ public void RemoveValidatioRuleGivenTheValidationRuleWorks()
{
// Arrange
- var ruleSet = new ValidationRuleSet();
+ var ruleSet = new ValidationRuleSet(_rulesDictionary);
+ var responseValidationRule = new ValidationRule((context, item) => { });
+
+ // Act and Assert
+ Assert.True(ruleSet.Remove(_contactValidationRule));
+ Assert.False(ruleSet.Rules.Contains(_contactValidationRule));
+ Assert.False(ruleSet.Remove(_contactValidationRule)); // rule already removed
+ }
+
+ [Fact]
+ public void RemoveValidationRuleGivenTheKeyAndValidationRuleWorks()
+ {
+ // Arrange
+ var ruleSet = new ValidationRuleSet(_rulesDictionary);
// Act
+ ruleSet.Remove("contact", _contactValidationRule);
+ ruleSet.Remove("parameter", _headerValidationRule); // validation rule not in parameter key; shouldn't remove
+ ruleSet.Remove("foo", _parameterValidationRule); // key does not exist; shouldn't remove
+
var rules = ruleSet.Rules;
// Assert
- Assert.NotNull(rules);
- Assert.Empty(rules);
+ Assert.False(rules.Contains(_contactValidationRule));
+ Assert.True(rules.Contains(_headerValidationRule));
+ Assert.True(rules.Contains(_parameterValidationRule));
}
[Fact]
- public void DefaultRuleSetPropertyReturnsTheCorrectRules()
+ public void RemoveRulesGivenAKeyWorks()
{
- // Arrange & Act
- var ruleSet = ValidationRuleSet.GetDefaultRuleSet();
- Assert.NotNull(ruleSet); // guard
+ // Arrange
+ var ruleSet = new ValidationRuleSet(_rulesDictionary);
+ var responseValidationRule = new ValidationRule((context, item) => { });
+ ruleSet.Add("response", new List { responseValidationRule });
+ Assert.True(ruleSet.ContainsKey("response"));
+ Assert.True(ruleSet.Rules.Contains(responseValidationRule)); // guard
- var rules = ruleSet.Rules;
+ // Act
+ ruleSet.Remove("response");
// Assert
- Assert.NotNull(rules);
- Assert.NotEmpty(rules);
+ Assert.False(ruleSet.ContainsKey("response"));
+ }
- // Update the number if you add new default rule(s).
- Assert.Equal(22, rules.Count());
+ [Fact]
+ public void AddNewValidationRuleWorks()
+ {
+ // Arrange
+ var ruleSet = new ValidationRuleSet(_rulesDictionary);
+ var responseValidationRule = new ValidationRule((context, item) => { });
+ var tagValidationRule = new ValidationRule((context, item) => { });
+ var pathsValidationRule = new ValidationRule((context, item) => { });
+
+ // Act
+ ruleSet.Add("response", new List { responseValidationRule });
+ ruleSet.Add("tag", new List { tagValidationRule });
+ var rulesDictionary = new Dictionary>()
+ {
+ {"paths", new List { pathsValidationRule } }
+ };
+
+ ValidationRuleSet.AddValidationRules(ruleSet, rulesDictionary);
+
+ // Assert
+ Assert.True(ruleSet.ContainsKey("response"));
+ Assert.True(ruleSet.ContainsKey("tag"));
+ Assert.True(ruleSet.ContainsKey("paths"));
+ Assert.True(ruleSet.Rules.Contains(responseValidationRule));
+ Assert.True(ruleSet.Rules.Contains(tagValidationRule));
+ Assert.True(ruleSet.Rules.Contains(pathsValidationRule));
+ }
+
+ [Fact]
+ public void UpdateValidationRuleWorks()
+ {
+ // Arrange
+ var ruleSet = new ValidationRuleSet(_rulesDictionary);
+ var responseValidationRule = new ValidationRule((context, item) => { });
+ ruleSet.Add("response", new List { responseValidationRule });
+
+ // Act
+ var pathsValidationRule = new ValidationRule((context, item) => { });
+ ruleSet.Update("response", pathsValidationRule, responseValidationRule);
+
+ // Assert
+ Assert.True(ruleSet.Contains("response", pathsValidationRule));
+ Assert.False(ruleSet.Contains("response", responseValidationRule));
+ }
+
+ [Fact]
+ public void TryGetValueWorks()
+ {
+ // Arrange
+ var ruleSet = new ValidationRuleSet(_rulesDictionary);
+
+ // Act
+ ruleSet.TryGetValue("contact", out var validationRules);
+
+ // Assert
+ Assert.True(validationRules.Any());
+ Assert.True(validationRules.Contains(_contactValidationRule));
+ }
+
+ [Fact]
+ public void ClearAllRulesWorks()
+ {
+ // Arrange
+ var ruleSet = new ValidationRuleSet();
+ var tagValidationRule = new ValidationRule((context, item) => { });
+ var pathsValidationRule = new ValidationRule((context, item) => { });
+ var rulesDictionary = new Dictionary>()
+ {
+ {"paths", new List { pathsValidationRule } },
+ {"tag", new List { tagValidationRule } }
+ };
+
+ ValidationRuleSet.AddValidationRules(ruleSet, rulesDictionary);
+ Assert.NotEmpty(ruleSet.Rules);
+
+ // Act
+ ruleSet.Clear();
+
+ // Assert
+ Assert.Empty(ruleSet.Rules);
}
}
}