Skip to content

Instantly share code, notes, and snippets.

@MrZoidberg
Last active June 5, 2020 20:49
Show Gist options
  • Save MrZoidberg/1cb4c47ab5f01f2a230015036ac60ea4 to your computer and use it in GitHub Desktop.
Save MrZoidberg/1cb4c47ab5f01f2a230015036ac60ea4 to your computer and use it in GitHub Desktop.
Fluent validation
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
static class PersonValidationExtensions
{
[Pure]
public static Func<IValidationContext<Person, string>> FirstName(this Func<IValidationContext<Person, Person>> context)
{
return () =>
{
var prev = context();
return new ValidationContext<Person, string>(prev.RootOUV, prev.RootOUV.FirstName,nameof(prev.RootOUV.FirstName));
};
}
[Pure]
public static Func<IValidationContext<Person, string>> LastName(this Func<IValidationContext<Person, Person>> context)
{
return () =>
{
var prev = context();
return new ValidationContext<Person, string>(prev.RootOUV, prev.RootOUV.LastName,nameof(prev.RootOUV.LastName));
};
}
}
class ValidationExample
{
public (bool IsValid, string[] ValidationErrors) Validate(Person person)
{
ValidationContext<Person, Person> validationContext = new ValidationContext<Person, Person>(person);
return validationContext.SetupValidation()
.FirstName()
.ShouldNotBeEmptyOrNull()
.And()
.LastName()
.ShouldNotBeEmptyOrNull()
.ExecuteValidation();
}
}
interface IValidationContext<T,TR>
{
/// <summary>
/// Root object under validation
/// </summary>
T RootOUV { get; set; }
/// <summary>
/// Object under validation
/// </summary>
TR OUV { get; set; }
/// <summary>
/// OUV property name
/// </summary>
string OUVName { get; set; }
}
sealed class ValidationContext<T, TR>: IValidationContext<T, TR> where T : class
{
/// <summary>
/// Payment order under validation
/// </summary>
public T RootOUV { get; set; }
/// <summary>
/// Object under validation
/// </summary>
public TR OUV { get; set; }
public string OUVName { get; set; }
public ValidationContext(T rootUov, TR ouv, string ouvName)
{
RootOUV = rootUov;
OUV = ouv;
OUVName = ouvName;
}
public ValidationContext(TR rootUov)
{
if (!rootUov.GetType().IsEquivalentTo(typeof(T)))
throw new ArgumentException($"T should be {typeof(T).Name} to use this constructor", nameof(rootUov));
RootOUV = rootUov as T;
OUV = rootUov;
OUVName = typeof(T).Name;
}
}
static class ValidationContextExtensions
{
[Pure]
public static Func<IValidationContext<T, TR>> SetupValidation<T, TR>(this IValidationContext<T, TR> context)
{
return () => context;
}
[Pure]
public static Func<IValidationContext<T, T22>> Should<T, T11, T22>(this Func<IValidationContext<T, T11>> input,
Func<Func<IValidationContext<T, T11>>, IValidationContext<T, T22>> second)
{
return () => second(input);
}
[Pure]
public static Func<IValidationContext<T, T11>> If<T, T11, T22>(this Func<IValidationContext<T, T11>> input, Func<IValidationContext<T, T11>, bool> condition,
Func<Func<IValidationContext<T, T11>>, Func<IValidationContext<T, T22>>> positive)
{
return () =>
{
var result = input();
if (condition(result))
{
positive(input)();
}
return result;
};
}
public static Func<IValidationContext<T, T>> And<T, TIN>(this Func<IValidationContext<T, TIN>> input) where T : class
{
return () => new ValidationContext<T ,T>(input().RootOUV);
}
[Pure]
public static Func<IValidationContext<T,string>> ShouldNotBeEmptyOrNull<T>(this Func<IValidationContext<T, string>> input)
{
return () =>
{
var result = input();
if (string.IsNullOrEmpty(result.OUV))
{
throw new ValidationException($"{result.OUVName} cannot be empty or null");
}
return result;
};
}
[Pure]
public static (bool IsValid, string[] ValidationErrors) ExecuteValidation<T1, T11>(this Func<IValidationContext<T1, T11>> context)
{
try
{
context();
return (true, Array.Empty<string>());
}
catch (Exception e)
{
return (false, new[] {e.Message});
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment