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

Commit

Permalink
Merge pull request #1314 from thecodejunkie/modelvalidationdescriptor…
Browse files Browse the repository at this point in the history
…-improvements

Grouped validation rules per property
  • Loading branch information
grumpydev committed Nov 7, 2013
2 parents 52e05d4 + 21f12a9 commit 65f4f0a
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 38 deletions.
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>
/// Gets the <see cref="System.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 @@ -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>
/// Gets the <see cref="System.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()) ?
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>
/// Gets the <see cref="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;
}
}
}

0 comments on commit 65f4f0a

Please sign in to comment.