Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions src/Rested.Core.Data/Search/ValidFieldNameGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using System.Reflection;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

namespace Rested.Core.Data.Search;

public partial class ValidFieldNameGenerator
{
#region Properties

public List<string> ValidFieldNames { get; protected set; } = [];
public List<string> IgnoredFieldNames { get; protected set; } = [];

#endregion Properties

#region Ctor

public ValidFieldNameGenerator(Type inputType)
{
GenerateValidFieldNames(inputType);
}

#endregion Ctor

#region Methods

private void GenerateValidFieldNames(Type inputType)
{
var validFieldNames = new List<string>();
var ignoredFieldNames = new List<string>();

GetFieldNamesFromTypeProperties(inputType, validFieldNames, ignoredFieldNames);

ValidFieldNames = validFieldNames;
IgnoredFieldNames = ignoredFieldNames;
}

protected virtual void GetFieldNamesFromTypeProperties(Type type, List<string> validFieldNames = null, List<string> ignoredFieldNames = null, string lastFieldName = null)
{
var properties = type.GetProperties();

validFieldNames ??= [];
ignoredFieldNames ??= [];

foreach (var property in properties)
{
if (property.GetCustomAttribute<SearchIgnoreAttribute>() is not null)
ignoredFieldNames.Add($"^{CalculateFieldName(property.Name, lastFieldName)}$");

else if (property.GetCustomAttribute<JsonIgnoreAttribute>() is not null)
ignoredFieldNames.Add($"^{CalculateFieldName(property.Name, lastFieldName)}$");

else
OnAddValidFieldName(property, validFieldNames, lastFieldName);

if (property.PropertyType.IsClass)
{
if (property.PropertyType.IsGenericType)
{
if (property.PropertyType.GetGenericTypeDefinition() == typeof(List<>))
OnAddValidListFieldName(property, validFieldNames, ignoredFieldNames, lastFieldName);

else if (property.PropertyType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
GetFieldNamesFromTypeProperties(property.PropertyType.GenericTypeArguments[1], validFieldNames, ignoredFieldNames, CalculateFieldName(property.Name, lastFieldName));
}

else if (!property.PropertyType.IsPrimitive && property.PropertyType != typeof(string))
GetFieldNamesFromTypeProperties(property.PropertyType, validFieldNames, ignoredFieldNames, CalculateFieldName(property.Name, lastFieldName));
}

else if (property.PropertyType.IsArray)
OnAddValidArrayFieldName(property, validFieldNames, ignoredFieldNames, lastFieldName);
}
}

protected virtual void OnAddValidFieldName(PropertyInfo property, List<string> validFieldNames, string lastFieldName = null)
{
var propertyName = property.Name;

if (propertyName is "Id")
OnAddValidIdFieldName(property, validFieldNames, lastFieldName);

else
validFieldNames.Add($"^{CalculateFieldName(property.Name, lastFieldName)}$");
}

protected virtual void OnAddValidIdFieldName(PropertyInfo property, List<string> validFieldNames, string lastFieldName = null)
{
validFieldNames.Add($"^{CalculateFieldName(property.Name, lastFieldName)}$");
}

protected string CalculateFieldName(string propertyName, string lastFieldName)
{
return string.IsNullOrWhiteSpace(lastFieldName) ?
propertyName.ToCamelCase() :
$@"{lastFieldName}\.{propertyName.ToCamelCase()}";
}

protected virtual void OnAddValidListFieldName(PropertyInfo property, List<string> validFieldNames, List<string> ignoredFieldNames, string lastFieldName = null)
{
var fieldName = $@"{CalculateFieldName(property.Name, lastFieldName)}\[\d*\]";

validFieldNames.Add($"^{fieldName}$");
GetFieldNamesFromTypeProperties(property.PropertyType.GenericTypeArguments[0], validFieldNames, ignoredFieldNames, fieldName);
}

protected virtual void OnAddValidArrayFieldName(PropertyInfo property, List<string> validFieldNames, List<string> ignoredFieldNames, string lastFieldName = null)
{
var fieldName = $@"{CalculateFieldName(property.Name, lastFieldName)}\[\d*\]";

validFieldNames.Add($"^{fieldName}$");
GetFieldNamesFromTypeProperties(property.PropertyType.GetElementType(), validFieldNames, ignoredFieldNames, fieldName);
}

public bool IsFieldNameValid(string fieldName)
{
if (IgnoredFieldNames.Any(ignoredFieldName => Regex.IsMatch(fieldName, ignoredFieldName)))
return false;

return ValidFieldNames.Any(validFieldName => Regex.IsMatch(fieldName, validFieldName));
}

#endregion Methods
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,9 @@ public void TestCleanup()

protected TFilterValidator CreateValidator()
{
DataUtility.GenerateValidSearchFieldNames<IDocument<Employee>>(out var validFieldNames, out var ignoredFieldNames);

return (TFilterValidator)Activator.CreateInstance(
type: typeof(TFilterValidator),
args: [validFieldNames, ignoredFieldNames, ServiceErrorCodes]);
args: [new ValidFieldNameGenerator(typeof(IDocument<Employee>)), ServiceErrorCodes]);
}

protected void TestFilterValidationRule(TFilterValidator filterValidator, TFilter filter, ServiceErrorCode serviceErrorCode, params object[] messageFormatArgs)
Expand Down
7 changes: 3 additions & 4 deletions src/Rested.Core.MediatR/Queries/SearchQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,14 @@ public abstract class SearchQueryValidator<TResultType, TSearchResults, TSearchQ
#region Properties

public abstract ServiceErrorCodes ServiceErrorCodes { get; }
public virtual ValidFieldNameGenerator ValidFieldNameGenerator { get; } = new(typeof(TResultType));

#endregion Properties

#region Ctor

public SearchQueryValidator()
{
DataUtility.GenerateValidSearchFieldNames<TResultType>(out var validFieldNames, out var ignoredFieldNames);

RuleFor(query => query.SearchRequest.Page)
.GreaterThan(0)
.WithServiceErrorCode(ServiceErrorCodes.CommonErrorCodes.PageMustBeGreaterThanZero);
Expand All @@ -102,11 +101,11 @@ public SearchQueryValidator()
.WithServiceErrorCode(ServiceErrorCodes.CommonErrorCodes.PageSizeMustBeLessThanOrEqualToMaxPageSize, query => [query.MaxPageSize]);

RuleForEach(query => query.SearchRequest.SortingFields)
.SetValidator(_ => new FieldSortInfoValidator(validFieldNames, ignoredFieldNames, ServiceErrorCodes))
.SetValidator(_ => new FieldSortInfoValidator(ValidFieldNameGenerator, ServiceErrorCodes))
.When(query => query.SearchRequest.SortingFields is not null && query.SearchRequest.SortingFields.Count > 0);

RuleFor(query => query.SearchRequest.Filters)
.SetValidator(_ => new FiltersValidator(validFieldNames, ignoredFieldNames, ServiceErrorCodes));
.SetValidator(_ => new FiltersValidator(ValidFieldNameGenerator, ServiceErrorCodes));
}

#endregion Ctor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ namespace Rested.Core.MediatR.Queries.Validators;

public class DateFieldFilterValidator : FieldFilterValidator<DateFieldFilter>
{
public DateFieldFilterValidator(IEnumerable<string> validFieldNames, IEnumerable<string> ignoredFieldNames, ServiceErrorCodes serviceErrorCodes) :
base(validFieldNames, ignoredFieldNames, serviceErrorCodes)
public DateFieldFilterValidator(ValidFieldNameGenerator validFieldNameGenerator, ServiceErrorCodes serviceErrorCodes) :
base(validFieldNameGenerator, serviceErrorCodes)
{
When(
predicate: dateFieldFilter => dateFieldFilter.FilterOperation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ namespace Rested.Core.MediatR.Queries.Validators;

public class DateTimeFieldFilterValidator : FieldFilterValidator<DateTimeFieldFilter>
{
public DateTimeFieldFilterValidator(IEnumerable<string> validFieldNames, IEnumerable<string> ignoredFieldNames, ServiceErrorCodes serviceErrorCodes) :
base(validFieldNames, ignoredFieldNames, serviceErrorCodes)
public DateTimeFieldFilterValidator(ValidFieldNameGenerator validFieldNameGenerator, ServiceErrorCodes serviceErrorCodes) :
base(validFieldNameGenerator, serviceErrorCodes)
{
When(
predicate: dateTimeFieldFilter => dateTimeFieldFilter.FilterOperation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Rested.Core.MediatR.Queries.Validators;
public abstract class FieldFilterValidator<TFieldFilter> : AbstractValidator<TFieldFilter>
where TFieldFilter : IFieldFilter
{
protected FieldFilterValidator(IEnumerable<string> validFieldNames, IEnumerable<string> ignoredFieldNames, ServiceErrorCodes serviceErrorCodes)
protected FieldFilterValidator(ValidFieldNameGenerator validFieldNameGenerator, ServiceErrorCodes serviceErrorCodes)
{
RuleFor(fieldFilter => fieldFilter.FieldName)
.NotEmpty()
Expand All @@ -19,7 +19,7 @@ protected FieldFilterValidator(IEnumerable<string> validFieldNames, IEnumerable<
action: () =>
{
RuleFor(fieldFilter => fieldFilter)
.Must(fieldFilter => DataUtility.IsFieldNameValid(fieldFilter.FieldName, validFieldNames, ignoredFieldNames))
.Must(fieldFilter => validFieldNameGenerator.IsFieldNameValid(fieldFilter.FieldName))
.WithServiceErrorCode(serviceErrorCodes.CommonErrorCodes.FieldFilterNameIsInvalid);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Rested.Core.MediatR.Queries.Validators;

public class FieldSortInfoValidator : AbstractValidator<FieldSortInfo>
{
public FieldSortInfoValidator(IEnumerable<string> validFieldNames, IEnumerable<string> ignoredFieldNames, ServiceErrorCodes serviceErrorCodes)
public FieldSortInfoValidator(ValidFieldNameGenerator validFieldNameGenerator, ServiceErrorCodes serviceErrorCodes)
{
RuleFor(fieldSortInfo => fieldSortInfo.FieldName)
.NotEmpty()
Expand All @@ -18,7 +18,7 @@ public FieldSortInfoValidator(IEnumerable<string> validFieldNames, IEnumerable<s
action: () =>
{
RuleFor(fieldSortInfo => fieldSortInfo)
.Must(m => DataUtility.IsFieldNameValid(m.FieldName, validFieldNames, ignoredFieldNames))
.Must(fieldSortInfo => validFieldNameGenerator.IsFieldNameValid(fieldSortInfo.FieldName))
.WithServiceErrorCode(serviceErrorCodes.CommonErrorCodes.SortingFieldNameIsInvalid);
});
}
Expand Down
12 changes: 6 additions & 6 deletions src/Rested.Core.MediatR/Queries/Validators/FiltersValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ namespace Rested.Core.MediatR.Queries.Validators;

public class FiltersValidator : AbstractValidator<List<IFilter>>
{
public FiltersValidator(IEnumerable<string> validFieldNames, IEnumerable<string> ignoredFieldNames, ServiceErrorCodes serviceErrorCodes)
public FiltersValidator(ValidFieldNameGenerator validFieldNameGenerator, ServiceErrorCodes serviceErrorCodes)
{
RuleForEach(filters => filters)
.SetInheritanceValidator(v =>
{
v.Add(_ => new TextFieldFilterValidator(validFieldNames, ignoredFieldNames, serviceErrorCodes));
v.Add(_ => new NumberFieldFilterValidator(validFieldNames, ignoredFieldNames, serviceErrorCodes));
v.Add(_ => new DateFieldFilterValidator(validFieldNames, ignoredFieldNames, serviceErrorCodes));
v.Add(_ => new DateTimeFieldFilterValidator(validFieldNames, ignoredFieldNames, serviceErrorCodes));
v.Add(_ => new OperatorFilterValidator(validFieldNames, ignoredFieldNames, serviceErrorCodes));
v.Add(_ => new TextFieldFilterValidator(validFieldNameGenerator, serviceErrorCodes));
v.Add(_ => new NumberFieldFilterValidator(validFieldNameGenerator, serviceErrorCodes));
v.Add(_ => new DateFieldFilterValidator(validFieldNameGenerator, serviceErrorCodes));
v.Add(_ => new DateTimeFieldFilterValidator(validFieldNameGenerator, serviceErrorCodes));
v.Add(_ => new OperatorFilterValidator(validFieldNameGenerator, serviceErrorCodes));
})
.When(filters => filters is not null && filters.Count > 0);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ namespace Rested.Core.MediatR.Queries.Validators;

public class NumberFieldFilterValidator : FieldFilterValidator<NumberFieldFilter>
{
public NumberFieldFilterValidator(IEnumerable<string> validFieldNames, IEnumerable<string> ignoredFieldNames, ServiceErrorCodes serviceErrorCodes) :
base(validFieldNames, ignoredFieldNames, serviceErrorCodes)
public NumberFieldFilterValidator(ValidFieldNameGenerator validFieldNameGenerator, ServiceErrorCodes serviceErrorCodes) :
base(validFieldNameGenerator, serviceErrorCodes)
{
When(
predicate: numberFieldFilter => numberFieldFilter.FilterOperation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Rested.Core.MediatR.Queries.Validators;

public class OperatorFilterValidator : AbstractValidator<OperatorFilter>
{
public OperatorFilterValidator(IEnumerable<string> validFieldNames, IEnumerable<string> ignoredFieldNames, ServiceErrorCodes serviceErrorCodes)
public OperatorFilterValidator(ValidFieldNameGenerator validFieldNameGenerator, ServiceErrorCodes serviceErrorCodes)
{
RuleFor(operatorFilter => operatorFilter.Operator)
.IsInEnum()
Expand All @@ -17,7 +17,7 @@ public OperatorFilterValidator(IEnumerable<string> validFieldNames, IEnumerable<
.WithServiceErrorCode(serviceErrorCodes.CommonErrorCodes.OperatorFilterFiltersIsRequired);

RuleFor(operatorFilter => operatorFilter.Filters)
.SetValidator(_ => new FiltersValidator(validFieldNames, ignoredFieldNames, serviceErrorCodes))
.SetValidator(_ => new FiltersValidator(validFieldNameGenerator, serviceErrorCodes))
.When(operatorFilter => operatorFilter.Filters is not null && operatorFilter.Filters.Count > 0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ namespace Rested.Core.MediatR.Queries.Validators;

public class TextFieldFilterValidator : FieldFilterValidator<TextFieldFilter>
{
public TextFieldFilterValidator(IEnumerable<string> validFieldNames, IEnumerable<string> ignoredFieldNames, ServiceErrorCodes serviceErrorCodes) :
base(validFieldNames, ignoredFieldNames, serviceErrorCodes)
public TextFieldFilterValidator(ValidFieldNameGenerator validFieldNameGenerator, ServiceErrorCodes serviceErrorCodes) :
base(validFieldNameGenerator, serviceErrorCodes)
{
When(
predicate: textFieldFilter => textFieldFilter.FilterOperation
Expand Down