Skip to content
Merged
13 changes: 12 additions & 1 deletion src/Microsoft.OpenApi/Properties/SRResource.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/Microsoft.OpenApi/Validations/OpenApiValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
194 changes: 158 additions & 36 deletions src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -15,23 +14,44 @@ namespace Microsoft.OpenApi.Validations
/// <summary>
/// The rule set of the validation.
/// </summary>
public sealed class ValidationRuleSet : IEnumerable<ValidationRule>
public sealed class ValidationRuleSet
{
private readonly IDictionary<Type, IList<ValidationRule>> _rules = new Dictionary<Type, IList<ValidationRule>>();
private readonly IDictionary<string, IList<ValidationRule>> _rulesDictionary = new Dictionary<string, IList<ValidationRule>>();

private static ValidationRuleSet _defaultRuleSet;

private readonly IList<ValidationRule> _emptyRules = new List<ValidationRule>();

/// <summary>
/// Retrieve the rules that are related to a specific type
/// Gets the keys in this rule set.
/// </summary>
/// <param name="type">The type that is to be validated</param>
/// <returns>Either the rules related to the type, or an empty list.</returns>
public IList<ValidationRule> FindRules(Type type)
public ICollection<string> Keys => _rulesDictionary.Keys;

/// <summary>
/// Gets the rules in this rule set.
/// </summary>
public IList<ValidationRule> Rules => _rulesDictionary.Values.SelectMany(v => v).ToList();

/// <summary>
/// Gets the number of elements contained in this rule set.
/// </summary>
public int Count => _rulesDictionary.Count;

/// <summary>
/// Initializes a new instance of the <see cref="ValidationRuleSet"/> class.
/// </summary>
public ValidationRuleSet()
{
}

/// <summary>
/// Retrieve the rules that are related to a specific key.
/// </summary>
/// <param name="key">The key of the rules to search for.</param>
/// <returns>Either the rules related to the given key, or an empty list.</returns>
public IList<ValidationRule> FindRules(string key)
{
IList<ValidationRule> results = null;
_rules.TryGetValue(type, out results);
_rulesDictionary.TryGetValue(key, out var results);
return results ?? _emptyRules;
}

Expand Down Expand Up @@ -67,10 +87,22 @@ public static ValidationRuleSet GetEmptyRuleSet()
}

/// <summary>
/// Initializes a new instance of the <see cref="ValidationRuleSet"/> class.
/// Add validation rules to the rule set.
/// </summary>
public ValidationRuleSet()
/// <param name="ruleSet">The rule set to add validation rules to.</param>
/// <param name="rules">The validation rules to be added to the rules set.</param>
/// <exception cref="OpenApiException">Throws a null argument exception if the arguments are null.</exception>
public static void AddValidationRules(ValidationRuleSet ruleSet, IDictionary<string, IList<ValidationRule>> rules)
{
if (ruleSet == null || rules == null)
{
throw new OpenApiException(SRResource.ArgumentNull);
}

foreach (var rule in rules)
{
ruleSet.Add(rule.Key, rule.Value);
}
}

/// <summary>
Expand All @@ -86,79 +118,169 @@ public ValidationRuleSet(ValidationRuleSet ruleSet)

foreach (ValidationRule rule in ruleSet)
{
Add(rule);
Add(rule.ElementType.Name, rule);
}
}

/// <summary>
/// Initializes a new instance of the <see cref="ValidationRuleSet"/> class.
/// </summary>
/// <param name="rules">Rules to be contained in this ruleset.</param>
public ValidationRuleSet(IEnumerable<ValidationRule> rules)
public ValidationRuleSet(IDictionary<string, IList<ValidationRule>> rules)
{
if (rules == null)
{
return;
}

foreach (ValidationRule rule in rules)
foreach (var rule in rules)
{
Add(rule);
Add(rule.Key, rule.Value);
}
}

/// <summary>
/// Gets the rules in this rule set.
/// Add the new rule into the rule set.
/// </summary>
public IEnumerable<ValidationRule> Rules
/// <param name="key">The key for the rule.</param>
/// <param name="rules">The list of rules.</param>
public void Add(string key, IList<ValidationRule> rules)
{
get
foreach (var rule in rules)
{
return _rules.Values.SelectMany(v => v);
Add(key, rule);
}
}

/// <summary>
/// Add the new rule into the rule set.
/// Add a new rule into the rule set.
/// </summary>
/// <param name="key">The key for the rule.</param>
/// <param name="rule">The rule.</param>
public void Add(ValidationRule rule)
/// <exception cref="OpenApiException">Exception thrown when rule already exists.</exception>
public void Add(string key, ValidationRule rule)
{
if (!_rules.ContainsKey(rule.ElementType))
if (!_rulesDictionary.ContainsKey(key))
{
_rules[rule.ElementType] = new List<ValidationRule>();
_rulesDictionary[key] = new List<ValidationRule>();
}

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);
}

/// <summary>
/// Get the enumerator.
/// Updates an existing rule with a new one.
/// </summary>
/// <returns>The enumerator.</returns>
public IEnumerator<ValidationRule> GetEnumerator()
/// <param name="key">The key of the existing rule.</param>
/// <param name="newRule">The new rule.</param>
/// <param name="oldRule">The old rule.</param>
/// <returns>true, if the update was successful; otherwise false.</returns>
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;
}

/// <summary>
/// Removes a collection of rules.
/// </summary>
/// <param name="key">The key of the collection of rules to be removed.</param>
/// <returns>true if the collection of rules with the provided key is removed; otherwise, false.</returns>
public bool Remove(string key)
{
return _rulesDictionary.Remove(key);
}

/// <summary>
/// Removes a rule by key.
/// </summary>
/// <param name="key">The key of the rule to be removed.</param>
/// <param name="rule">The rule to be removed.</param>
/// <returns>true if the rule is successfully removed; otherwise, false.</returns>
public bool Remove(string key, ValidationRule rule)
{
if (_rulesDictionary.TryGetValue(key, out IList<ValidationRule> validationRules))
{
return validationRules.Remove(rule);
}

return false;
}

/// <summary>
/// Removes the first rule that matches the provided rule from the list of rules.
/// </summary>
/// <param name="rule">The rule to be removed.</param>
/// <returns>true if the rule is successfully removed; otherwise, false.</returns>
public bool Remove(ValidationRule rule)
{
return _rulesDictionary.Values.FirstOrDefault(x => x.Remove(rule)) is not null;
}

/// <summary>
/// Clears all rules in this rule set.
/// </summary>
public void Clear()
{
_rulesDictionary.Clear();
}

/// <summary>
/// Determines whether the rule set contains an element with the specified key.
/// </summary>
/// <param name="key">The key to locate in the rule set.</param>
/// <returns>true if the rule set contains an element with the key; otherwise, false.</returns>
public bool ContainsKey(string key)
{
return _rulesDictionary.ContainsKey(key);
}

/// <summary>
/// Determines whether the provided rule is contained in the specified key in the rule set.
/// </summary>
/// <param name="key">The key to locate.</param>
/// <param name="rule">The rule to locate.</param>
/// <returns></returns>
public bool Contains(string key, ValidationRule rule)
{
return _rulesDictionary.TryGetValue(key, out IList<ValidationRule> validationRules) && validationRules.Contains(rule);
}

/// <summary>
/// Gets the rules associated with the specified key.
/// </summary>
/// <param name="key">The key whose rules to get.</param>
/// <param name="rules">When this method returns, the rules associated with the specified key, if the
/// key is found; otherwise, an empty <see cref="IList{ValidationRule}"/> object.
/// This parameter is passed uninitialized.</param>
/// <returns>true if the specified key has rules.</returns>
public bool TryGetValue(string key, out IList<ValidationRule> rules)
{
return _rulesDictionary.TryGetValue(key, out rules);
}

/// <summary>
/// Get the enumerator.
/// </summary>
/// <returns>The enumerator.</returns>
IEnumerator IEnumerable.GetEnumerator()
public IEnumerator<ValidationRule> GetEnumerator()
{
return this.GetEnumerator();
foreach (var ruleList in _rulesDictionary.Values)
{
foreach (var rule in ruleList)
{
yield return rule;
}
}
}

private static ValidationRuleSet BuildDefaultRuleSet()
Expand All @@ -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);
}
}

Expand Down
22 changes: 17 additions & 5 deletions test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1222,15 +1222,27 @@ namespace Microsoft.OpenApi.Validations
{
protected ValidationRule() { }
}
public sealed class ValidationRuleSet : System.Collections.Generic.IEnumerable<Microsoft.OpenApi.Validations.ValidationRule>, System.Collections.IEnumerable
public sealed class ValidationRuleSet
{
public ValidationRuleSet() { }
public ValidationRuleSet(Microsoft.OpenApi.Validations.ValidationRuleSet ruleSet) { }
public ValidationRuleSet(System.Collections.Generic.IEnumerable<Microsoft.OpenApi.Validations.ValidationRule> rules) { }
public System.Collections.Generic.IEnumerable<Microsoft.OpenApi.Validations.ValidationRule> Rules { get; }
public void Add(Microsoft.OpenApi.Validations.ValidationRule rule) { }
public System.Collections.Generic.IList<Microsoft.OpenApi.Validations.ValidationRule> FindRules(System.Type type) { }
public ValidationRuleSet(System.Collections.Generic.IDictionary<string, System.Collections.Generic.IList<Microsoft.OpenApi.Validations.ValidationRule>> rules) { }
public int Count { get; }
public System.Collections.Generic.ICollection<string> Keys { get; }
public System.Collections.Generic.IList<Microsoft.OpenApi.Validations.ValidationRule> Rules { get; }
public void Add(string key, Microsoft.OpenApi.Validations.ValidationRule rule) { }
public void Add(string key, System.Collections.Generic.IList<Microsoft.OpenApi.Validations.ValidationRule> rules) { }
public void Clear() { }
public bool Contains(string key, Microsoft.OpenApi.Validations.ValidationRule rule) { }
public bool ContainsKey(string key) { }
public System.Collections.Generic.IList<Microsoft.OpenApi.Validations.ValidationRule> FindRules(string key) { }
public System.Collections.Generic.IEnumerator<Microsoft.OpenApi.Validations.ValidationRule> 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<Microsoft.OpenApi.Validations.ValidationRule> 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<string, System.Collections.Generic.IList<Microsoft.OpenApi.Validations.ValidationRule>> rules) { }
public static Microsoft.OpenApi.Validations.ValidationRuleSet GetDefaultRuleSet() { }
public static Microsoft.OpenApi.Validations.ValidationRuleSet GetEmptyRuleSet() { }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public void ValidateCustomExtension()
{
var ruleset = ValidationRuleSet.GetDefaultRuleSet();

ruleset.Add(
ruleset.Add(typeof(OpenApiAny).Name,
new ValidationRule<OpenApiAny>(
(context, item) =>
{
Expand Down
Loading