Skip to content

Commit

Permalink
Add PropertyPath placeholder (#2134)
Browse files Browse the repository at this point in the history
  • Loading branch information
JeremySkinner committed Aug 8, 2023
1 parent 1f9102d commit 98d08a6
Show file tree
Hide file tree
Showing 8 changed files with 41 additions and 15 deletions.
4 changes: 4 additions & 0 deletions Changelog.txt
@@ -1,3 +1,7 @@
11.7.0 -
Add additional constructor for combining multiple ValidationResult instances (#2125)
Add PropertyPath placeholder (#2134)

11.6.0 - 4 Jul 2023
Add OnFailurecCreated callback in ValidatorOptions.Global (#2120)
Fix typo in Russian localization (#2102)
Expand Down
14 changes: 14 additions & 0 deletions src/FluentValidation.Tests/CustomMessageFormatTester.cs
Expand Up @@ -18,6 +18,7 @@

namespace FluentValidation.Tests;

using System.Collections.Generic;
using System.Linq;
using Validators;
using Xunit;
Expand Down Expand Up @@ -85,4 +86,17 @@ public class CustomMessageFormatTester {
result.Errors.Single().ErrorMessage.ShouldEqual("Was ''");
}

[Fact]
public void Includes_property_path() {
validator.RuleFor(x => x.Surname).NotNull().WithMessage("{PropertyPath}");
validator.RuleForEach(x => x.Orders).NotNull().WithMessage("{PropertyPath}");

var result = validator.Validate(new Person {
Orders = new List<Order> {null}
});

result.Errors[0].ErrorMessage.ShouldEqual("Surname");
result.Errors[1].ErrorMessage.ShouldEqual("Orders[0]");
}

}
15 changes: 9 additions & 6 deletions src/FluentValidation/IValidationContext.cs
Expand Up @@ -291,7 +291,7 @@ public ValidationContext(T instanceToValidate, PropertyChain propertyChain, IVal
public void AddFailure(string propertyName, string errorMessage) {
errorMessage.Guard("An error message must be specified when calling AddFailure.", nameof(errorMessage));
errorMessage = MessageFormatter.BuildMessage(errorMessage);
AddFailure(new ValidationFailure(PropertyChain.BuildPropertyName(propertyName ?? string.Empty), errorMessage));
AddFailure(new ValidationFailure(PropertyChain.BuildPropertyPath(propertyName ?? string.Empty), errorMessage));
}

/// <summary>
Expand All @@ -302,7 +302,7 @@ public ValidationContext(T instanceToValidate, PropertyChain propertyChain, IVal
public void AddFailure(string errorMessage) {
errorMessage.Guard("An error message must be specified when calling AddFailure.", nameof(errorMessage));
errorMessage = MessageFormatter.BuildMessage(errorMessage);
AddFailure(new ValidationFailure(PropertyName, errorMessage));
AddFailure(new ValidationFailure(PropertyPath, errorMessage));
}

private Func<ValidationContext<T>, string> _displayNameFunc;
Expand All @@ -313,15 +313,18 @@ public ValidationContext(T instanceToValidate, PropertyChain propertyChain, IVal
public string DisplayName => _displayNameFunc(this);

/// <summary>
/// The full name of the current property being validated.
/// The full path of the current property being validated.
/// If accessed inside a child validator, this will include the parent's path too.
/// </summary>
public string PropertyName { get; private set; }
public string PropertyPath { get; private set; }

[Obsolete("This property has been deprecated due to its misleading name. Use the PropertyPath property instead, which returns the same value.")]
public string PropertyName => PropertyPath;

internal string RawPropertyName { get; private set; }

internal void InitializeForPropertyValidator(string propertyName, Func<ValidationContext<T>, string> displayNameFunc, string rawPropertyName) {
PropertyName = propertyName;
internal void InitializeForPropertyValidator(string propertyPath, Func<ValidationContext<T>, string> displayNameFunc, string rawPropertyName) {
PropertyPath = propertyPath;
_displayNameFunc = displayNameFunc;
RawPropertyName = rawPropertyName;
}
Expand Down
6 changes: 3 additions & 3 deletions src/FluentValidation/Internal/CollectionPropertyRule.cs
Expand Up @@ -97,7 +97,7 @@ public CollectionPropertyRule(MemberInfo member, Func<T, IEnumerable<TElement>>
}

// Construct the full name of the property, taking into account overriden property names and the chain (if we're in a nested validator)
string propertyName = context.PropertyChain.BuildPropertyName(PropertyName ?? displayName);
string propertyName = context.PropertyChain.BuildPropertyPath(PropertyName ?? displayName);

if (string.IsNullOrEmpty(propertyName)) {
propertyName = InferPropertyName(Expression);
Expand Down Expand Up @@ -164,9 +164,9 @@ public CollectionPropertyRule(MemberInfo member, Func<T, IEnumerable<TElement>>
context.PropertyChain.AddIndexer(indexer, useDefaultIndexFormat);

var valueToValidate = element;
var propertyNameToValidate = context.PropertyChain.ToString();
var propertyPath = context.PropertyChain.ToString();
var totalFailuresInner = context.Failures.Count;
context.InitializeForPropertyValidator(propertyNameToValidate, GetDisplayName, PropertyName);
context.InitializeForPropertyValidator(propertyPath, GetDisplayName, PropertyName);

foreach (var component in filteredValidators) {
context.MessageFormatter.Reset();
Expand Down
2 changes: 1 addition & 1 deletion src/FluentValidation/Internal/MessageBuilderContext.cs
Expand Up @@ -37,7 +37,7 @@ public IPropertyValidator PropertyValidator

// public IValidationRule<T> Rule => _innerContext.Rule;

public string PropertyName => _innerContext.PropertyName;
public string PropertyName => _innerContext.PropertyPath;

public string DisplayName => _innerContext.DisplayName;

Expand Down
6 changes: 5 additions & 1 deletion src/FluentValidation/Internal/PropertyChain.cs
Expand Up @@ -142,10 +142,14 @@ public class PropertyChain {
return ToString().StartsWith(parentChain.ToString());
}

[Obsolete("BuildPropertyName is deprecated due to its misleading name. Use BuildPropertyPath instead which does the same thing.")]
public string BuildPropertyName(string propertyName)
=> BuildPropertyPath(propertyName);

/// <summary>
/// Builds a property path.
/// </summary>
public string BuildPropertyName(string propertyName) {
public string BuildPropertyPath(string propertyName) {
if (_memberNames.Count == 0) {
return propertyName;
}
Expand Down
6 changes: 3 additions & 3 deletions src/FluentValidation/Internal/PropertyRule.cs
Expand Up @@ -90,11 +90,11 @@ TProperty PropertyFunc(T instance)
}

// Construct the full name of the property, taking into account overriden property names and the chain (if we're in a nested validator)
string propertyName = context.PropertyChain.BuildPropertyName(PropertyName ?? displayName);
string propertyPath = context.PropertyChain.BuildPropertyPath(PropertyName ?? displayName);

// Ensure that this rule is allowed to run.
// The validatselector has the opportunity to veto this before any of the validators execute.
if (!context.Selector.CanExecute(this, propertyName, context)) {
if (!context.Selector.CanExecute(this, propertyPath, context)) {
return;
}

Expand All @@ -118,7 +118,7 @@ TProperty PropertyFunc(T instance)
var cascade = CascadeMode;
var accessor = new Lazy<TProperty>(() => PropertyFunc(context.InstanceToValidate), LazyThreadSafetyMode.None);
var totalFailures = context.Failures.Count;
context.InitializeForPropertyValidator(propertyName, GetDisplayName, PropertyName);
context.InitializeForPropertyValidator(propertyPath, GetDisplayName, PropertyName);

// Invoke each validator and collect its results.
foreach (var component in Components) {
Expand Down
3 changes: 2 additions & 1 deletion src/FluentValidation/Internal/RuleBase.cs
Expand Up @@ -286,6 +286,7 @@ public string GetDisplayName(ValidationContext<T> context)
protected void PrepareMessageFormatterForValidationError(ValidationContext<T> context, TValue value) {
context.MessageFormatter.AppendPropertyName(context.DisplayName);
context.MessageFormatter.AppendPropertyValue(value);
context.MessageFormatter.AppendArgument("PropertyPath", context.PropertyPath);

// If there's a collection index cached in the root context data then add it
// to the message formatter. This happens when a child validator is executed
Expand All @@ -312,7 +313,7 @@ public string GetDisplayName(ValidationContext<T> context)
? MessageBuilder(new MessageBuilderContext<T, TValue>(context, value, component))
: component.GetErrorMessage(context, value);

var failure = new ValidationFailure(context.PropertyName, error, value);
var failure = new ValidationFailure(context.PropertyPath, error, value);

failure.FormattedMessagePlaceholderValues = new Dictionary<string, object>(context.MessageFormatter.PlaceholderValues);
failure.ErrorCode = component.ErrorCode ?? ValidatorOptions.Global.ErrorCodeResolver(component.Validator);
Expand Down

0 comments on commit 98d08a6

Please sign in to comment.