-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
VCST-2460: Refactor and enhance CategoryPropertyNameValidator (#762)
feat: Refactored CategoryPropertyNameValidator to validate property name on whole Virto Commerce, instead of child categories only. (#762) feat: Added validation for a legacy property name that can contain special symbols. (#762)
- Loading branch information
Showing
6 changed files
with
84 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
110 changes: 51 additions & 59 deletions
110
src/VirtoCommerce.CatalogModule.Data/Validation/CategoryPropertyNameValidator.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,101 +1,93 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using System.Text.RegularExpressions; | ||
using System.Threading.Tasks; | ||
using FluentValidation; | ||
using FluentValidation.Results; | ||
using VirtoCommerce.CatalogModule.Core.Model; | ||
using VirtoCommerce.CatalogModule.Core.Model.Search; | ||
using VirtoCommerce.CatalogModule.Core.Search; | ||
using VirtoCommerce.CatalogModule.Core.Services; | ||
using VirtoCommerce.CatalogModule.Data.Search; | ||
using VirtoCommerce.CatalogModule.Data.Services; | ||
using VirtoCommerce.Platform.Core.Common; | ||
using VirtoCommerce.Platform.Data.ExportImport; | ||
|
||
namespace VirtoCommerce.CatalogModule.Data.Validation | ||
{ | ||
public class CategoryPropertyNameValidator : AbstractValidator<CategoryPropertyValidationRequest> | ||
{ | ||
private readonly ICategoryService _categoryService; | ||
private readonly IPropertyService _propertyService; | ||
private readonly Func<int, string, string, CategoryHierarchyIterator> _categoryHierarchyIteratorFactory; | ||
private readonly IPropertySearchService _propertySearchService; | ||
|
||
public CategoryPropertyNameValidator( | ||
ICategoryService categoryService, | ||
IPropertyService propertyService, | ||
Func<int, string, string, CategoryHierarchyIterator> categoryHierarchyIteratorFactory) | ||
IPropertySearchService propertySearchService) | ||
{ | ||
_categoryService = categoryService; | ||
_propertyService = propertyService; | ||
_categoryHierarchyIteratorFactory = categoryHierarchyIteratorFactory; | ||
_propertySearchService = propertySearchService; | ||
|
||
AttachValidators(); | ||
} | ||
|
||
private void AttachValidators() | ||
{ | ||
RuleFor(r => r) | ||
.CustomAsync(async (r, context, _) => | ||
{ | ||
var (isPropertyUniqueForHierarchy, categoryName) = await CheckPropertyUniquenessAsync(r); | ||
|
||
if (!isPropertyUniqueForHierarchy) | ||
{ | ||
context.AddFailure(new ValidationFailure(r.PropertyName, "duplicate-property") | ||
{ | ||
CustomState = new | ||
{ | ||
PropertyName = r.PropertyName, | ||
CategoryName = categoryName, | ||
}, | ||
}); | ||
} | ||
}); | ||
.CustomAsync(async (r, context, _) => | ||
{ | ||
var (isPropertyUniqueForHierarchy, categoryName) = await CheckPropertyUniquenessAsync(r); | ||
|
||
if (!isPropertyUniqueForHierarchy) | ||
{ | ||
context.AddFailure(new ValidationFailure(r.PropertyName, "duplicate-property") | ||
{ | ||
CustomState = new | ||
{ | ||
PropertyName = r.PropertyName, | ||
CategoryName = categoryName, | ||
}, | ||
}); | ||
} | ||
}); | ||
} | ||
|
||
/// <summary> | ||
/// Check the property uniqueness for the whole catalog | ||
/// </summary> | ||
/// <param name="request"></param> | ||
/// <returns></returns> | ||
protected virtual async Task<(bool, string)> CheckPropertyUniquenessAsync(CategoryPropertyValidationRequest request) | ||
{ | ||
var categoryIterator = _categoryHierarchyIteratorFactory(500, request.CatalogId, request.CategoryId); | ||
Property existingProperty; | ||
// Allow to create a new property with the same name and same type | ||
var existingProperty = await FindProperty(request); | ||
|
||
do | ||
if (existingProperty != null) | ||
{ | ||
var categoryIds = await categoryIterator.GetNextPageAsync(); | ||
|
||
if (categoryIds.IsNullOrEmpty()) | ||
{ | ||
// Category hierarchy iterator returns only child-relation category ids, excluding the category id itself | ||
// so is required to load properties for requested category too | ||
categoryIds = categoryIds.Append(request.CategoryId).ToImmutableArray(); | ||
} | ||
|
||
var categories = await _categoryService.GetNoCloneAsync(categoryIds.ToList(), CategoryResponseGroup.WithProperties.ToString()); | ||
|
||
var properties = categories.SelectMany(x => x.Properties); | ||
|
||
// If properties are empty and the requested Category missed | ||
// it might meaning that catalog doesn't contains any categories and | ||
if (string.IsNullOrEmpty(request.CategoryId) && !properties.Any()) | ||
{ | ||
properties = await _propertyService.GetAllCatalogPropertiesAsync(request.CatalogId); | ||
} | ||
|
||
var requiredPropertyType = EnumUtility.SafeParse(request.PropertyType, PropertyType.Category); | ||
|
||
existingProperty = properties.FirstOrDefault(x => x.Name.EqualsInvariant(request.PropertyName) && x.Type.Equals(requiredPropertyType)); | ||
|
||
if (existingProperty != null) | ||
{ | ||
break; | ||
} | ||
var categoryName = (await _categoryService.GetNoCloneAsync(existingProperty.CategoryId, CategoryResponseGroup.Info.ToString()))?.Name; | ||
return (false, categoryName); | ||
} | ||
|
||
} while (categoryIterator.HasMoreResults); | ||
return (true, null); | ||
} | ||
|
||
var categoryName = string.Empty; | ||
private async Task<Property> FindProperty(CategoryPropertyValidationRequest request) | ||
{ | ||
var propertyName = request.PropertyName; | ||
// "Alcholic % Volume" == "Alcholic_Volume" | ||
var azureFieldPropertyName = Regex.Replace(propertyName, @"\W", "_"); | ||
|
||
if (existingProperty != null) | ||
var properties = await _propertySearchService.SearchPropertiesAsync(new PropertySearchCriteria | ||
{ | ||
categoryName = (await _categoryService.GetNoCloneAsync(existingProperty.CategoryId, CategoryResponseGroup.Info.ToString()))?.Name; | ||
} | ||
PropertyTypes = [request.PropertyType], | ||
PropertyNames = [propertyName, azureFieldPropertyName], | ||
ExcludedPropertyValueTypes = [request.PropertyValueType], | ||
Take = 1 | ||
}); | ||
|
||
|
||
return (existingProperty == null, categoryName); | ||
return properties.Results.FirstOrDefault(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.