Skip to content
This repository has been archived by the owner on Jan 24, 2021. It is now read-only.

Grouped validation rules per property #1314

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/Nancy.Tests/Unit/Validation/CompositeValidatorFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,25 @@ public void Should_yield_composite_description()
{
// Given
var fakeValidators = A.CollectionOfFake<IModelValidator>(2);
A.CallTo(() => fakeValidators[0].Description).Returns(new ModelValidationDescriptor(new[] { new ModelValidationRule("Test1", s => s, new[] { "Member1" }) }));
A.CallTo(() => fakeValidators[1].Description).Returns(new ModelValidationDescriptor(new[] { new ModelValidationRule("Test2", s => s, new[] { "Member2" }) }));
var subject = new CompositeValidator(fakeValidators);
A.CallTo(() => fakeValidators[0].Description).Returns(new ModelValidationDescriptor(new[] { new ModelValidationRule("Test1", s => s, new[] { "Member1" }) }, typeof(object)));
A.CallTo(() => fakeValidators[1].Description).Returns(new ModelValidationDescriptor(new[] { new ModelValidationRule("Test2", s => s, new[] { "Member2" }) }, typeof(object)));
var subject = new CompositeValidator(fakeValidators, typeof(object));

// When
var result = subject.Description;

// Then
result.Rules.ShouldHaveCount(2);
result.Rules.ShouldHave(r => r.RuleType == "Test1" && r.MemberNames.Contains("Member1"));
result.Rules.ShouldHave(r => r.RuleType == "Test2" && r.MemberNames.Contains("Member2"));
result.Rules.First().Value.ShouldHave(r => r.RuleType == "Test1" && r.MemberNames.Contains("Member1"));
result.Rules.Last().Value.ShouldHave(r => r.RuleType == "Test2" && r.MemberNames.Contains("Member2"));
}

[Fact]
public void Should_invoke_each_validator()
{
// Given
var fakeValidators = A.CollectionOfFake<IModelValidator>(2);
var subject = new CompositeValidator(fakeValidators);
var subject = new CompositeValidator(fakeValidators, typeof(object));

// When
subject.Validate("blah", new NancyContext());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,9 @@ public void Should_contain_validation_result_from_validatable_object_adapter()
public void Should_return_descriptor_with_rules_from_all_validators()
{
// Given
var rule1 = new ModelValidationRule(string.Empty, s => string.Empty);
var rule2 = new ModelValidationRule(string.Empty, s => string.Empty);
var rule3 = new ModelValidationRule(string.Empty, s => string.Empty);
var rule1 = new ModelValidationRule(string.Empty, s => string.Empty, new[] { "One" });
var rule2 = new ModelValidationRule(string.Empty, s => string.Empty, new[] { "Two" });
var rule3 = new ModelValidationRule(string.Empty, s => string.Empty, new[] { "Three" });

A.CallTo(() => this.propertyValidator1.GetRules()).Returns(new[] { rule1 });
A.CallTo(() => this.propertyValidator2.GetRules()).Returns(new[] { rule2, rule3 });
Expand All @@ -131,9 +131,6 @@ public void Should_return_descriptor_with_rules_from_all_validators()

// Then
descriptor.Rules.Count().ShouldEqual(3);
descriptor.Rules.Contains(rule1).ShouldBeTrue();
descriptor.Rules.Contains(rule2).ShouldBeTrue();
descriptor.Rules.Contains(rule3).ShouldBeTrue();
}

public class ModelUnderTest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class DataAnnotationsValidator : IModelValidator
/// <param name="validatableObjectAdapter">The <see cref="validatableObjectAdapter"/> instance that should be used by the validator.</param>
public DataAnnotationsValidator(Type typeForValidation, IPropertyValidatorFactory factory, IValidatableObjectAdapter validatableObjectAdapter)
{
this.ModelType = typeForValidation;
this.validatableObjectAdapter = validatableObjectAdapter;
this.validators = factory.GetValidators(typeForValidation);
}
Expand All @@ -34,6 +35,11 @@ public ModelValidationDescriptor Description
get { return this.descriptor ?? (this.descriptor = GetModelValidationDescriptor()); }
}

/// <summary>
/// The type of the model that is being validated by the validator.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pff.. call yourself the human stylecop with xml comments like that? ;)

/// </summary>
public Type ModelType { get; private set; }

/// <summary>
/// Validates the specified instance.
/// </summary>
Expand Down Expand Up @@ -63,7 +69,7 @@ private ModelValidationDescriptor GetModelValidationDescriptor()
var rules =
this.validators.SelectMany(x => x.GetRules());

return new ModelValidationDescriptor(rules);
return new ModelValidationDescriptor(rules, this.ModelType);
}
}
}
16 changes: 12 additions & 4 deletions src/Nancy.Validation.FluentValidation/FluentValidationValidator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace Nancy.Validation.FluentValidation
{
using System;
using System.Collections.Generic;
using System.Linq;
using global::FluentValidation;
Expand All @@ -20,9 +21,11 @@ public class FluentValidationValidator : IModelValidator
/// specified <see cref="IValidator"/>.
/// </summary>
/// <param name="validator">The Fluent Validation validator that should be used.</param>
/// <param name="factory"> </param>
public FluentValidationValidator(IValidator validator, IFluentAdapterFactory factory)
{
/// <param name="factory">Factory for creating adapters for the type that is being validated.</param>
/// <param name="modelType">The type of the model that is being validated.</param>
public FluentValidationValidator(IValidator validator, IFluentAdapterFactory factory, Type modelType)
{
this.ModelType = modelType;
this.validator = validator;
this.factory = factory;
}
Expand All @@ -36,6 +39,11 @@ public ModelValidationDescriptor Description
get { return CreateDescriptor(); }
}

/// <summary>
/// The type of the model that is being validated by the validator.
/// </summary>
public Type ModelType { get; private set; }

/// <summary>
/// Validates the specified instance.
/// </summary>
Expand Down Expand Up @@ -78,7 +86,7 @@ private ModelValidationDescriptor CreateDescriptor()
}
}

return new ModelValidationDescriptor(rules);
return new ModelValidationDescriptor(rules, this.ModelType);
}

private static IEnumerable<ModelValidationError> GetErrors(ValidationResult results)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public IModelValidator Create(Type type)
GetValidatorInstance(type);

return (instance != null) ?
new FluentValidationValidator(instance, this.adapterFactory) :
new FluentValidationValidator(instance, this.adapterFactory, type) :
null;
}

Expand Down
33 changes: 21 additions & 12 deletions src/Nancy/Validation/CompositeValidator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace Nancy.Validation
{
using System;
using System.Collections.Generic;
using System.Linq;

Expand All @@ -10,24 +11,31 @@ public class CompositeValidator : IModelValidator
{
private readonly IEnumerable<IModelValidator> validators;

/// <summary>
/// Gets the description of the validator.
/// </summary>
public ModelValidationDescriptor Description { get; private set; }

/// <summary>
/// Initializes a new instance of the <see cref="CompositeValidator"/> class.
/// </summary>
/// <param name="validators">The validators.</param>
public CompositeValidator(IEnumerable<IModelValidator> validators)
/// <param name="modelType">The type of the model that is being validated.</param>
public CompositeValidator(IEnumerable<IModelValidator> validators, Type modelType)
{
var modelValidators =
validators.ToArray();

this.Description = CreateCompositeDescription(modelValidators);
this.ModelType = modelType;
this.Description = CreateCompositeDescription(modelValidators, modelType);
this.validators = modelValidators;
}

/// <summary>
/// Gets the description of the validator.
/// </summary>
public ModelValidationDescriptor Description { get; private set; }

/// <summary>
/// The type of the model that is being validated by the validator.
/// </summary>
public Type ModelType { get; private set; }

/// <summary>
/// Validates the specified instance.
/// </summary>
Expand All @@ -42,17 +50,18 @@ public ModelValidationResult Validate(object instance, NancyContext context)
.SelectMany(r => r.Errors)
.ToArray();

return !errors.Any() ?
return (!errors.Any()) ?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's with the superfluous brackets?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the way it's (usually) done in our code base

ModelValidationResult.Valid :
new ModelValidationResult(errors);
}

private static ModelValidationDescriptor CreateCompositeDescription(IEnumerable<IModelValidator> validators)
private static ModelValidationDescriptor CreateCompositeDescription(IEnumerable<IModelValidator> validators, Type modelType)
{
var rules =
validators.SelectMany(v => v.Description.Rules);
var rules = validators
.SelectMany(v => v.Description.Rules)
.ToDictionary(x => x.Key, x => x.Value);

return new ModelValidationDescriptor(rules);
return new ModelValidationDescriptor(rules, modelType);
}
}
}
2 changes: 1 addition & 1 deletion src/Nancy/Validation/DefaultValidatorLocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ private IModelValidator CreateValidator(Type type)

return (validators.Length == 1) ?
validators[0] :
new CompositeValidator(validators);
new CompositeValidator(validators, type);
}
}
}
7 changes: 7 additions & 0 deletions src/Nancy/Validation/IModelValidator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
namespace Nancy.Validation
{
using System;

/// <summary>
/// Provides a way to validate a type as well as a description to use for client-side validation.
/// </summary>
Expand All @@ -10,6 +12,11 @@ public interface IModelValidator
/// </summary>
ModelValidationDescriptor Description { get; }

/// <summary>
/// The type of the model that is being validated by the validator.
/// </summary>
Type ModelType { get; }

/// <summary>
/// Validates the specified instance.
/// </summary>
Expand Down
55 changes: 48 additions & 7 deletions src/Nancy/Validation/ModelValidationDescriptor.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace Nancy.Validation
{
using System;
using System.Collections.Generic;
using System.Linq;

/// <summary>
/// A description of the rules a validator provides.
Expand All @@ -11,18 +11,59 @@ public class ModelValidationDescriptor
/// <summary>
/// Initializes a new instance of the <see cref="ModelValidationDescriptor"/> class.
/// </summary>
/// <param name="rules">The rules.</param>
public ModelValidationDescriptor(IEnumerable<ModelValidationRule> rules)
/// <param name="rules">The rules that describes the model.</param>
/// <param name="modelType">The type of the model that the rules are defined for.</param>
public ModelValidationDescriptor(IEnumerable<ModelValidationRule> rules, Type modelType)
: this(GetModelValidationRuleDictionary(rules), modelType)
{
this.Rules = rules == null
? new List<ModelValidationRule>().AsReadOnly()
: rules.ToList().AsReadOnly();
}

/// <summary>
/// Initializes a new instance of the <see cref="ModelValidationDescriptor"/> class.
/// </summary>
/// <param name="rules">The rules that describes the model, grouped by member name.</param>
/// <param name="modelType">The type of the model that the rules are defined for.</param>
public ModelValidationDescriptor(IDictionary<string, IList<ModelValidationRule>> rules, Type modelType)
{
this.Rules = rules;
this.ModelType = modelType;
}

/// <summary>
/// The type of the model that is being described.
/// </summary>
public Type ModelType { get; private set; }

/// <summary>
/// Gets the rules.
/// </summary>
/// <value>An <see cref="IEnumerable{T}"/> of <see cref="ModelValidationRule"/> instances.</value>
public IEnumerable<ModelValidationRule> Rules { get; private set; }
public IDictionary<string, IList<ModelValidationRule>> Rules { get; private set; }

private static IDictionary<string, IList<ModelValidationRule>> GetModelValidationRuleDictionary(IEnumerable<ModelValidationRule> rules)
{
var results =
new Dictionary<string, IList<ModelValidationRule>>(StringComparer.OrdinalIgnoreCase);

if (rules == null)
{
return results;
}

foreach (var rule in rules)
{
foreach (var name in rule.MemberNames)
{
if (!results.ContainsKey(name))
{
results.Add(name, new List<ModelValidationRule>());
}

results[name].Add(rule);
}
}

return results;
}
}
}