Skip to content

Commit

Permalink
Support transforming to other types. (#1332)
Browse files Browse the repository at this point in the history
* Support transforming to other types.

* Update doc comment

* Transform docs.
  • Loading branch information
JeremySkinner committed Feb 21, 2020
1 parent e2ab2ed commit bc6cab3
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 14 deletions.
1 change: 1 addition & 0 deletions Changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ PropertyValidatorContext.Instance renamed to InstanceToValidate for consistency
Removed various methods from MessageFormatter that were deprecated in 8.x
Added Slovenian translations of default error messages.
Added WithMessageArgument to the test helpers.
Transform can now be used to transform property values to other types.

8.6.0 - 4 December 2019
Add support for ASP.NET Core 3.1
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,5 @@ Example
:caption: Advanced

async
transform
advanced
36 changes: 36 additions & 0 deletions docs/transform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Transforming Values

As of FluentValidation 9.0, you can use the `Transform` method to transform a property value prior to validation being performed againt it. For example, if you have property of type `string` that atually contains numeric input, you could use `Transform` to convert the string value to a number.


```csharp
RuleFor(x => x.SomeStringProperty)
.Transform(value => int.TryParse(value, out int val) ? (int?) val : null)
.GreaterThan(10);
```

This rule transforms the value from a `string` to an nullable `int` (returning `null` if the value couldn't be converted). A greater-than check is then performed on the resulting value.

Syntactically this is not particularly nice to read, so this can be cleaned up by using an extension method:

```csharp
public static class ValidationExtensions {
public static IRuleBuilder<T, int?> TransformToInt<T>(this IRuleBuilderInitial<T, string> ruleBuilder) {
return ruleBuilder.Transform(value => int.TryParse(value, out int val) ? (int?) val : null);
}
}
```

The rule can then be written as:

```csharp
RuleFor(x => x.SomeStringProperty)
.TransformToInt()
.GreaterThan(10);
```


```eval_rst
.. note::
FluentValidation 8.x supported a limited version of the Transform method that could only be used to perform transformations on the same type (eg if the property is a string, the result of the transformation must also be a string). FluentValidation 9.0 allows transformations to be performed that change the type.
```
11 changes: 11 additions & 0 deletions src/FluentValidation.Tests/TransformTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,16 @@ public class TransformTests {
var result = validator.Validate(new Person {Surname = "bar"});
result.IsValid.ShouldBeTrue();
}

[Fact]
public void Transforms_property_value_to_another_type() {
var validator = new InlineValidator<Person>();
validator.RuleFor(x => x.Surname).Transform(name => 1).GreaterThan(10);

var result = validator.Validate(new Person {Surname = "bar"});
result.IsValid.ShouldBeFalse();
result.Errors[0].ErrorCode.ShouldEqual("GreaterThanValidator");
}

}
}
14 changes: 0 additions & 14 deletions src/FluentValidation/DefaultValidatorOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,6 @@ public static class DefaultValidatorOptions {
});
}

/// <summary>
/// Transforms the property value before validation occurs. The transformed value must be of the same type as the input value.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TProperty"></typeparam>
/// <param name="ruleBuilder"></param>
/// <param name="transformationFunc"></param>
/// <returns></returns>
public static IRuleBuilderInitial<T, TProperty> Transform<T, TProperty>(this IRuleBuilderInitial<T, TProperty> ruleBuilder, Func<TProperty, TProperty> transformationFunc) {
return ruleBuilder.Configure(cfg => {
cfg.Transformer = transformationFunc.CoerceToNonGeneric();
});
}

/// <summary>
/// Transforms the property value before validation occurs. The transformed value must be of the same type as the input value.
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions src/FluentValidation/Internal/RuleBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ public class RuleBuilder<T, TProperty> : IRuleBuilderOptions<T, TProperty>, IRul
configurator((CollectionPropertyRule<TProperty>) Rule);
return this;
}

public IRuleBuilderInitial<T, TNew> Transform<TNew>(Func<TProperty, TNew> transformationFunc) {
if (transformationFunc == null) throw new ArgumentNullException(nameof(transformationFunc));
Rule.Transformer = transformationFunc.CoerceToNonGeneric();
return new RuleBuilder<T, TNew>(Rule, ParentValidator);
}
}

internal interface IExposesParentValidator<T> {
Expand Down
11 changes: 11 additions & 0 deletions src/FluentValidation/Syntax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ namespace FluentValidation {
/// <typeparam name="T"></typeparam>
/// <typeparam name="TProperty"></typeparam>
public interface IRuleBuilderInitial<T, out TProperty> : IRuleBuilder<T, TProperty>, IConfigurable<PropertyRule, IRuleBuilderInitial<T, TProperty>> {

/// <summary>
/// Transforms the property value before validation occurs.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TProperty"></typeparam>
/// <typeparam name="TNew"></typeparam>
/// <param name="ruleBuilder"></param>
/// <param name="transformationFunc"></param>
/// <returns></returns>
IRuleBuilderInitial<T, TNew> Transform<TNew>(Func<TProperty, TNew> transformationFunc);
}

/// <summary>
Expand Down

0 comments on commit bc6cab3

Please sign in to comment.