Skip to content

Commit

Permalink
Add ValidationException constructor with support for custom message a…
Browse files Browse the repository at this point in the history
…nd errors message (#1338)

* feat: add ValidationException constructor with support for custom message and errors message

* Update changelog

* Update changelog.

Co-authored-by: Jeremy Skinner <jeremy@jeremyskinner.co.uk>
  • Loading branch information
bombek92 and JeremySkinner committed Mar 31, 2020
1 parent 71dd8b2 commit 259087f
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 150 deletions.
1 change: 1 addition & 0 deletions Changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Add additional overload of SetValidator that takes a Func that receives the curr
Work around a bug in ASP.NET Core's integration testing components that can cause ConfigureServices to run multiple times.
SourceLink integration.
{CollectionIndex} placeholder can now be accessed in child validators.
Additional ValidationException constructor that allows using both the default message and a custom one together.

8.6.2 - 29 February 2020
Fix CollectionIndex placeholder not working with async workflow.
Expand Down
301 changes: 156 additions & 145 deletions src/FluentValidation.Tests/ValidateAndThrowTester.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,170 +17,181 @@
#endregion

namespace FluentValidation.Tests {
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using Newtonsoft.Json;
using Results;
using Xunit;


public class ValidateAndThrowTester {
public ValidateAndThrowTester() {
CultureScope.SetDefaultCulture();
}

[Fact]
public void Throws_exception() {
var validator = new TestValidator {
v => v.RuleFor(x => x.Surname).NotNull()
};

typeof(ValidationException).ShouldBeThrownBy(() => validator.ValidateAndThrow(new Person()));
}

[Fact]
public void Throws_exception_with_a_ruleset() {
var validator = new TestValidator {
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Results;
using Xunit;


public class ValidateAndThrowTester {
public ValidateAndThrowTester() {
CultureScope.SetDefaultCulture();
}

[Fact]
public void Throws_exception() {
var validator = new TestValidator {
v => v.RuleFor(x => x.Surname).NotNull()
};

typeof(ValidationException).ShouldBeThrownBy(() => validator.ValidateAndThrow(new Person()));
}

[Fact]
public void Throws_exception_with_a_ruleset() {
var validator = new TestValidator {
v => v.RuleFor(x => x.Surname).NotNull()
};

const string ruleSetName = "blah";
validator.RuleSet(ruleSetName, () => {
validator.RuleFor(x => x.Forename).NotNull();
});
const string ruleSetName = "blah";
validator.RuleSet(ruleSetName, () => {
validator.RuleFor(x => x.Forename).NotNull();
});

typeof(ValidationException).ShouldBeThrownBy(() => validator.ValidateAndThrow(new Person(), ruleSetName));
}
typeof(ValidationException).ShouldBeThrownBy(() => validator.ValidateAndThrow(new Person(), ruleSetName));
}

[Fact]
public void Throws_exception_async() {
var validator = new TestValidator {
v => v.RuleFor(x => x.Surname).NotNull()
};

typeof(ValidationException).ShouldBeThrownBy(() => {
try {
validator.ValidateAndThrowAsync(new Person()).Wait();
}
catch (AggregateException agrEx) {
throw agrEx.InnerException;
}
});
}

[Fact]
public void Throws_exception_with_a_ruleset_async()
{
var validator = new TestValidator {
v => v.RuleFor(x => x.Surname).NotNull()
};
[Fact]
public void Throws_exception_async() {
var validator = new TestValidator {
v => v.RuleFor(x => x.Surname).NotNull()
};

const string ruleSetName = "blah";
validator.RuleSet(ruleSetName, () => {
validator.RuleFor(x => x.Forename).NotNull();
});

typeof(ValidationException).ShouldBeThrownBy(() => {
try
{
validator.ValidateAndThrowAsync(new Person(), ruleSetName).Wait();
}
catch (AggregateException agrEx)
{
throw agrEx.InnerException;
}
});
typeof(ValidationException).ShouldBeThrownBy(() => {
try {
validator.ValidateAndThrowAsync(new Person()).Wait();
}
catch (AggregateException agrEx) {
throw agrEx.InnerException;
}
});
}

[Fact]
public void Does_not_throw_when_valid() {
var validator = new TestValidator {
v => v.RuleFor(x => x.Surname).NotNull()
};

validator.ValidateAndThrow(new Person {Surname = "foo"});
}

[Fact]
public void Does_not_throw_when_valid_and_a_ruleset()
{
var validator = new TestValidator {
[Fact]
public void Throws_exception_with_a_ruleset_async() {
var validator = new TestValidator {
v => v.RuleFor(x => x.Surname).NotNull()
};

const string ruleSetName = "blah";
validator.RuleSet(ruleSetName, () => {
validator.RuleFor(x => x.Forename).NotNull();
});
const string ruleSetName = "blah";
validator.RuleSet(ruleSetName, () => {
validator.RuleFor(x => x.Forename).NotNull();
});

var person = new Person {
Forename = "foo",
Surname = "foo"
};
validator.ValidateAndThrow(person, ruleSetName);
typeof(ValidationException).ShouldBeThrownBy(() => {
try {
validator.ValidateAndThrowAsync(new Person(), ruleSetName).Wait();
}
catch (AggregateException agrEx) {
throw agrEx.InnerException;
}
});
}

[Fact]
public void Does_not_throw_when_valid_async() {
var validator = new TestValidator {
v => v.RuleFor(x => x.Surname).NotNull()
};
[Fact]
public void Does_not_throw_when_valid() {
var validator = new TestValidator {
v => v.RuleFor(x => x.Surname).NotNull()
};

validator.ValidateAndThrowAsync(new Person { Surname = "foo" }).Wait();
}
validator.ValidateAndThrow(new Person { Surname = "foo" });
}

[Fact]
public void Does_not_throw_when_valid_and_a_ruleset_async()
{
var validator = new TestValidator {
[Fact]
public void Does_not_throw_when_valid_and_a_ruleset() {
var validator = new TestValidator {
v => v.RuleFor(x => x.Surname).NotNull()
};

const string ruleSetName = "blah";
validator.RuleSet(ruleSetName, () => {
validator.RuleFor(x => x.Forename).NotNull();
});

var person = new Person
{
Forename = "foo",
Surname = "foo"
const string ruleSetName = "blah";
validator.RuleSet(ruleSetName, () => {
validator.RuleFor(x => x.Forename).NotNull();
});

var person = new Person {
Forename = "foo",
Surname = "foo"
};
validator.ValidateAndThrow(person, ruleSetName);
}

[Fact]
public void Does_not_throw_when_valid_async() {
var validator = new TestValidator {
v => v.RuleFor(x => x.Surname).NotNull()
};

validator.ValidateAndThrowAsync(new Person { Surname = "foo" }).Wait();
}

[Fact]
public void Does_not_throw_when_valid_and_a_ruleset_async() {
var validator = new TestValidator {
v => v.RuleFor(x => x.Surname).NotNull()
};
validator.ValidateAndThrowAsync(person, ruleSetName).Wait();
}

[Fact]
public void Populates_errors() {
var validator = new TestValidator {
v => v.RuleFor(x => x.Surname).NotNull()
};

var ex = (ValidationException)typeof(ValidationException).ShouldBeThrownBy(() => validator.ValidateAndThrow(new Person()));
ex.Errors.Count().ShouldEqual(1);
}

[Fact]
public void ToString_provides_error_details() {
var validator = new TestValidator {
v => v.RuleFor(x => x.Surname).NotNull(),
v => v.RuleFor(x => x.Forename).NotNull()
};

var ex = typeof(ValidationException).ShouldBeThrownBy(() => validator.ValidateAndThrow(new Person()));
string expected = "FluentValidation.ValidationException: Validation failed: " + Environment.NewLine + " -- Surname: 'Surname' must not be empty." + Environment.NewLine + " -- Forename: 'Forename' must not be empty.";
Assert.True(ex.ToString().StartsWith(expected));
}

[Fact]
public void Serializes_exception() {
var v = new ValidationException(new List<ValidationFailure> {new ValidationFailure("test", "test")});
var raw = JsonConvert.SerializeObject(v);
var deserialized = JsonConvert.DeserializeObject<ValidationException>(raw);

deserialized.Errors.Count().ShouldEqual(1);
}
}
const string ruleSetName = "blah";
validator.RuleSet(ruleSetName, () => {
validator.RuleFor(x => x.Forename).NotNull();
});

var person = new Person {
Forename = "foo",
Surname = "foo"
};
validator.ValidateAndThrowAsync(person, ruleSetName).Wait();
}

[Fact]
public void Populates_errors() {
var validator = new TestValidator {
v => v.RuleFor(x => x.Surname).NotNull()
};

var ex = (ValidationException)typeof(ValidationException).ShouldBeThrownBy(() => validator.ValidateAndThrow(new Person()));
ex.Errors.Count().ShouldEqual(1);
}

[Fact]
public void ToString_provides_error_details() {
var validator = new TestValidator {
v => v.RuleFor(x => x.Surname).NotNull(),
v => v.RuleFor(x => x.Forename).NotNull()
};

var ex = typeof(ValidationException).ShouldBeThrownBy(() => validator.ValidateAndThrow(new Person()));
string expected = "FluentValidation.ValidationException: Validation failed: " + Environment.NewLine + " -- Surname: 'Surname' must not be empty." + Environment.NewLine + " -- Forename: 'Forename' must not be empty.";
Assert.True(ex.ToString().StartsWith(expected));
}

[Fact]
public void Serializes_exception() {
var v = new ValidationException(new List<ValidationFailure> { new ValidationFailure("test", "test") });
var raw = JsonConvert.SerializeObject(v);
var deserialized = JsonConvert.DeserializeObject<ValidationException>(raw);

deserialized.Errors.Count().ShouldEqual(1);
}

[Fact]
public void ValidationException_provides_correct_message_when_appendDefaultMessage_true() {
var userMessage = "exception occured during testing";
var validationFailures = new List<ValidationFailure> { new ValidationFailure("test", "test") };
var exception = new ValidationException(validationFailures);
var exceptionWithUserMessage = new ValidationException(userMessage, validationFailures, true);

exceptionWithUserMessage.Message.ShouldEqual($"{userMessage} {exception.Message}");
}

[Fact]
public void ValidationException_provides_correct_message_when_appendDefaultMessage_false() {
var userMessage = "exception occured during testing";
var validationFailures = new List<ValidationFailure> { new ValidationFailure("test", "test") };
var exceptionWithUserMessage = new ValidationException(userMessage, validationFailures, false);

exceptionWithUserMessage.Message.ShouldEqual(userMessage);
}
}
}
1 change: 1 addition & 0 deletions src/FluentValidation/FluentValidation.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Changes in 9.0.0:
* Work around a bug in ASP.NET Core's integration testing components that can cause ConfigureServices to run multiple times.
* SourceLink integration.
* {CollectionIndex} placeholder can now be accessed in child validators.
* Additional ValidationException constructor that allows using both the default message and a custom one together.

Full release notes can be found at https://github.com/FluentValidation/FluentValidation/blob/master/Changelog.txt
FluentValidation 8 is a major release. Please read the upgrade notes at https://fluentvalidation.net/upgrading-to-8
Expand Down
22 changes: 17 additions & 5 deletions src/FluentValidation/ValidationException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,23 @@ public class ValidationException : Exception {
public ValidationException(string message, IEnumerable<ValidationFailure> errors) : base(message) {
Errors = errors;
}
/// <summary>
/// Creates a new ValidationException
/// </summary>
/// <param name="errors"></param>
public ValidationException(IEnumerable<ValidationFailure> errors) : base(BuildErrorMessage(errors)) {

/// <summary>
/// Creates a new ValidationException
/// </summary>
/// <param name="message"></param>
/// <param name="errors"></param>
/// <param name="appendDefaultMessage">appends default validation error message to message</param>
public ValidationException(string message, IEnumerable<ValidationFailure> errors, bool appendDefaultMessage)
: base(appendDefaultMessage ? $"{message} {BuildErrorMessage(errors)}" : message) {
Errors = errors;
}

/// <summary>
/// Creates a new ValidationException
/// </summary>
/// <param name="errors"></param>
public ValidationException(IEnumerable<ValidationFailure> errors) : base(BuildErrorMessage(errors)) {
Errors = errors;
}

Expand Down

0 comments on commit 259087f

Please sign in to comment.