< Summary - Code Coverage

Information
Class: Plainquire.Sort.EntitySort
Assembly: Plainquire.Sort
File(s): /home/runner/work/plainquire/plainquire/Plainquire.Sort/Plainquire.Sort/Sorts/EntitySort.cs
Tag: 64_13932151703
Line coverage
98%
Covered lines: 85
Uncovered lines: 1
Coverable lines: 86
Total lines: 410
Line coverage: 98.8%
Branch coverage
86%
Covered branches: 26
Total branches: 30
Branch coverage: 86.6%
Method coverage
100%
Covered methods: 18
Total methods: 18
Method coverage: 100%

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor()100%11100%
.ctor(...)100%11100%
IsEmpty()100%11100%
ToString()100%11100%
GetPropertySortSyntaxInternal(...)75%44100%
GetPropertySortDirectionInternal(...)75%44100%
GetNestedInternal(...)100%11100%
AddInternal(...)100%11100%
AddInternal(...)100%11100%
AddNestedInternal(...)100%11100%
RemoveInternal(...)100%11100%
ClearInternal()100%11100%
RemoveNestedInternal(...)100%11100%
CastInternal(...)90%101094.73%
GetNestedInternal(...)50%22100%
AddNestedInternal(...)100%66100%
GetPosition(...)100%44100%
RemoveRelatedProperties(...)100%11100%

File(s)

/home/runner/work/plainquire/plainquire/Plainquire.Sort/Plainquire.Sort/Sorts/EntitySort.cs

#LineLine coverage
 1using Plainquire.Filter.Abstractions;
 2using Plainquire.Sort.Abstractions;
 3using Plainquire.Sort.JsonConverters;
 4using System;
 5using System.Collections.Generic;
 6using System.Diagnostics;
 7using System.Diagnostics.CodeAnalysis;
 8using System.Linq;
 9using System.Linq.Expressions;
 10using System.Text.Json;
 11using System.Text.Json.Serialization;
 12
 13namespace Plainquire.Sort;
 14
 15/// <summary>
 16/// Hub to create sort order for <typeparamref name="TEntity"/> with fluent API.
 17/// </summary>
 18/// <typeparam name="TEntity">The entity type to be sorted.</typeparam>
 19[JsonConverter(typeof(EntitySortConverter.Factory))]
 20public class EntitySort<TEntity> : EntitySort
 21{
 22    /// <summary>
 23    /// Initializes a new instance of the <see cref="EntitySort"/> class.
 24    /// </summary>
 25    public EntitySort() { }
 26
 27    /// <summary>
 28    /// Initializes a new instance of the <see cref="EntitySort"/> class.
 29    /// </summary>
 30    /// <param name="configuration">The configuration to use.</param>
 31    public EntitySort(SortConfiguration configuration)
 32        : base(configuration) { }
 33
 34    /// <summary>
 35    /// Gets the sort order syntax for the given <paramref name="property"/>.
 36    /// </summary>
 37    /// <typeparam name="TProperty">The type of the property sorted by.</typeparam>
 38    /// <param name="property">The property to get the sort order for.</param>
 39    public string? GetPropertySortSyntax<TProperty>(Expression<Func<TEntity, TProperty?>> property)
 40        => GetPropertySortSyntaxInternal(property);
 41
 42    /// <summary>
 43    /// Get the sort order applied to the given property.
 44    /// </summary>
 45    /// <typeparam name="TProperty">The type of the property sorted by.</typeparam>
 46    /// <param name="property">The property to get the sort order for.</param>
 47    public SortDirection? GetPropertySortDirection<TProperty>(Expression<Func<TEntity, TProperty?>> property)
 48        => GetPropertySortDirectionInternal(property);
 49
 50    /// <summary>
 51    /// Gets the <see cref="EntitySort{TProperty}"/> for the given nested class <typeparamref name="TProperty"/>.
 52    /// </summary>
 53    /// <typeparam name="TProperty">The type of the property sorted by.</typeparam>
 54    /// <param name="property">The property to get the sort order for.</param>
 55    public EntitySort<TProperty> GetNested<TProperty>(Expression<Func<TEntity, TProperty?>> property)
 56        => GetNestedInternal(property);
 57
 58    /// <summary>
 59    /// Adds the sort order for the given property.
 60    /// </summary>
 61    /// <typeparam name="TProperty">The type of the property to sort by.</typeparam>
 62    /// <param name="property">The property to order.</param>
 63    /// <param name="direction">The sort order to use.</param>
 64    /// <param name="position">The sort position of the property.</param>
 65    public EntitySort<TEntity> Add<TProperty>(Expression<Func<TEntity, TProperty?>> property, SortDirection? direction =
 66    {
 67        AddInternal(property, direction, position);
 68        return this;
 69    }
 70
 71    /// <summary>
 72    /// Adds the sort order for the given property.
 73    /// </summary>
 74    /// <param name="syntax">The sort order syntax.</param>
 75    /// <param name="position">The sort position of the property.</param>
 76    public EntitySort<TEntity> Add(string syntax, int? position = null)
 77    {
 78        AddInternal(syntax, position);
 79        return this;
 80    }
 81
 82    /// <summary>
 83    /// Replaces the nested sort order for the given property.
 84    /// </summary>
 85    /// <typeparam name="TProperty">The type of the property to sort by.</typeparam>
 86    /// <param name="property">The property to order.</param>
 87    /// <param name="nestedSort">The nested class sort order.</param>
 88    public EntitySort<TEntity> AddNested<TProperty>(Expression<Func<TEntity, TProperty?>> property, EntitySort<TProperty
 89    {
 90        AddNestedInternal(property, nestedSort);
 91        return this;
 92    }
 93
 94    /// <summary>
 95    /// Remove the sort order for the specified property.
 96    /// </summary>
 97    /// <typeparam name="TProperty">The type of the property to remove sorting from.</typeparam>
 98    /// <param name="property">The property to remove the sort order for.</param>
 99    public EntitySort<TEntity> Remove<TProperty>(Expression<Func<TEntity, TProperty?>> property)
 100    {
 101        RemoveInternal(property);
 102        return this;
 103    }
 104
 105    /// <summary>
 106    /// Removes the sort order for all properties.
 107    /// </summary>
 108    public EntitySort<TEntity> Clear()
 109    {
 110        ClearInternal();
 111        return this;
 112    }
 113
 114    /// <summary>
 115    /// Remove sort orders for given property including all navigation properties.
 116    /// </summary>
 117    /// <typeparam name="TProperty"></typeparam>
 118    /// <param name="property"></param>
 119    public EntitySort<TEntity> RemoveNested<TProperty>(Expression<Func<TEntity, TProperty?>> property)
 120    {
 121        RemoveNestedInternal(property);
 122        return this;
 123    }
 124
 125    /// <summary>
 126    /// Creates a deep clone of this sort.
 127    /// </summary>
 128    public EntitySort<TEntity> Clone()
 129        => JsonSerializer.Deserialize<EntitySort<TEntity>>(JsonSerializer.Serialize(this))!;
 130
 131    /// <summary>
 132    /// Casts this sort to a different entity type (by creating a deep clone).
 133    /// Sorted properties are matched by type (check if assignable) and name (case-sensitive).
 134    /// </summary>
 135    /// <typeparam name="TDestination">The type of the destination entity to sort.</typeparam>
 136    public EntitySort<TDestination> Cast<TDestination>()
 137        => (EntitySort<TDestination>)CastInternal(this, typeof(TEntity), typeof(TDestination));
 138}
 139
 140/// <inheritdoc cref="EntitySort{TEntity}" />
 141[JsonConverter(typeof(EntitySortConverter))]
 142[DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")]
 143[SuppressMessage("ReSharper", "MemberCanBeProtected.Global", Justification = "Provided as library, can be used from outs
 144public class EntitySort
 145{
 146    internal List<PropertySort> PropertySorts;
 147
 148    /// <summary>
 149    /// Gets or sets the default configuration. Can be used to set a system-wide configuration.
 150    /// </summary>
 151    public SortConfiguration? Configuration { get; internal set; }
 152
 153    /// <summary>
 154    /// Initializes a new instance of the <see cref="EntitySort"/> class.
 155    /// </summary>
 156    public EntitySort()
 597157        => PropertySorts = [];
 158
 159    /// <summary>
 160    /// Initializes a new instance of the <see cref="EntitySort"/> class.
 161    /// </summary>
 162    /// <param name="configuration">The configuration to use.</param>
 163    public EntitySort(SortConfiguration configuration)
 15164        : this()
 15165        => Configuration = configuration;
 166
 167    /// <summary>
 168    /// Indicates whether this entity sort is empty.
 169    /// </summary>
 6170    public bool IsEmpty() => !PropertySorts.Any();
 171
 172    /// <inheritdoc />
 173    public override string ToString()
 174    {
 2175        var sortStrings = PropertySorts
 2176            .OrderBy(x => x.Position)
 2177            .Select(sort => sort.ToString())
 2178            .ToList();
 179
 2180        return string.Join(", ", sortStrings);
 181    }
 182
 183    /// <summary>
 184    /// Gets the sort order syntax for the given <paramref name="property"/>.
 185    /// </summary>
 186    /// <typeparam name="TEntity">The entity type to be sorted.</typeparam>
 187    /// <typeparam name="TProperty">The type of the property to sort by.</typeparam>
 188    /// <param name="property">The property to get the sort order for.</param>
 189    protected string? GetPropertySortSyntaxInternal<TEntity, TProperty>(Expression<Func<TEntity, TProperty?>> property)
 190    {
 1191        var propertyPath = property.GetPropertyPath();
 1192        var propertySort = PropertySorts
 1193            .OrderBy(x => x.Position)
 1194            .LastOrDefault(sort => sort.PropertyPath.EqualsOrdinal(propertyPath));
 195
 1196        return propertySort?.ToString();
 197    }
 198
 199    /// <summary>
 200    /// Get the sort direction applied to the given property.
 201    /// </summary>
 202    /// <typeparam name="TEntity">The entity type to be sorted.</typeparam>
 203    /// <typeparam name="TProperty">The type of the property sorted by.</typeparam>
 204    /// <param name="property">The property to get the sort order for.</param>
 205    protected SortDirection? GetPropertySortDirectionInternal<TEntity, TProperty>(Expression<Func<TEntity, TProperty?>> 
 206    {
 1207        var propertyPath = property.GetPropertyPath();
 1208        return PropertySorts
 1209            .OrderBy(x => x.Position)
 1210            .LastOrDefault(sort => sort.PropertyPath.EqualsOrdinal(propertyPath))?.Direction;
 211    }
 212
 213    /// <summary>
 214    /// Gets the <see cref="EntitySort{TProperty}"/> for the given nested class <typeparamref name="TProperty"/>.
 215    /// </summary>
 216    /// <typeparam name="TEntity">The entity type to be sorted.</typeparam>
 217    /// <typeparam name="TProperty">The type of the property sorted by.</typeparam>
 218    /// <param name="property">The property to get the sort order for.</param>
 219    protected EntitySort<TProperty> GetNestedInternal<TEntity, TProperty>(Expression<Func<TEntity, TProperty?>> property
 220    {
 2221        var propertyPath = property.GetPropertyPath();
 2222        return (EntitySort<TProperty>)GetNestedInternal(typeof(TProperty), propertyPath);
 223    }
 224
 225    /// <summary>
 226    /// Replaces the sort order for the given property.
 227    /// </summary>
 228    /// <typeparam name="TEntity">The entity type to be sorted.</typeparam>
 229    /// <typeparam name="TProperty">The type of the property to sort by.</typeparam>
 230    /// <param name="property">The property to order.</param>
 231    /// <param name="direction">The sort order to use.</param>
 232    /// <param name="position">The sort position of the property.</param>
 233    protected void AddInternal<TEntity, TProperty>(Expression<Func<TEntity, TProperty?>> property, SortDirection? direct
 234    {
 127235        var propertyPath = property.GetPropertyPath();
 127236        position = GetPosition(position);
 127237        var propertySort = PropertySort.Create(propertyPath, direction ?? SortDirection.Ascending, position);
 127238        PropertySorts.Add(propertySort);
 127239    }
 240
 241    /// <summary>
 242    /// Adds the sort order for the given property.
 243    /// </summary>
 244    /// <param name="syntax">The sort order syntax.</param>
 245    /// <param name="position">The sort position of the property.</param>
 246    protected void AddInternal(string syntax, int? position)
 247    {
 255248        position = GetPosition(position);
 255249        var propertySort = PropertySort.Create(syntax, position);
 253250        PropertySorts.Add(propertySort);
 253251    }
 252
 253    /// <summary>
 254    /// Adds the nested sort order for the given property
 255    /// </summary>
 256    /// <typeparam name="TEntity">The entity type to be sorted.</typeparam>
 257    /// <typeparam name="TProperty">The type of the property to sort by.</typeparam>
 258    /// <typeparam name="TNested">The nested entity type to be sorted.</typeparam>
 259    /// <param name="property">The property to set sort for.</param>
 260    /// <param name="nestedSort">The nested class sort order.</param>
 261    protected void AddNestedInternal<TEntity, TProperty, TNested>(Expression<Func<TEntity, TProperty?>> property, Entity
 262    {
 21263        var propertyPath = property.GetPropertyPath();
 21264        AddNestedInternal(propertyPath, nestedSort);
 20265    }
 266
 267    /// <summary>
 268    /// Remove the sort order for the specified property.
 269    /// </summary>
 270    /// <typeparam name="TEntity">The entity type to be sorted.</typeparam>
 271    /// <typeparam name="TProperty">The type of the property to remove sorting from.</typeparam>
 272    /// <param name="property">The property to remove the sort order for.</param>
 273    protected void RemoveInternal<TEntity, TProperty>(Expression<Func<TEntity, TProperty?>> property)
 274    {
 2275        var propertyPath = property.GetPropertyPath();
 2276        PropertySorts.RemoveAll(x => x.PropertyPath.EqualsOrdinal(propertyPath));
 2277    }
 278
 279    /// <inheritdoc cref="EntitySort{TEntity}.Clear" />
 280    protected void ClearInternal()
 1281        => PropertySorts.Clear();
 282
 283    /// <inheritdoc cref="EntitySort{TEntity}.RemoveNested{TProperty}(Expression{Func{TEntity, TProperty}})" />/>
 284    protected void RemoveNestedInternal<TEntity, TProperty>(Expression<Func<TEntity, TProperty?>> property)
 285    {
 1286        var propertyPath = property.GetPropertyPath();
 1287        PropertySorts.RemoveAll(x => x.BelongsTo(propertyPath));
 1288    }
 289
 290    /// <summary>
 291    /// Casts the given <paramref name="sourceSort"/> to the given <paramref name="destinationType"/>.
 292    /// </summary>
 293    /// <param name="sourceSort">Source to cast.</param>
 294    /// <param name="sourceType">Type of <paramref name="sourceSort"/>.</param>
 295    /// <param name="destinationType">Type to cast to.</param>
 296    /// <exception cref="ArgumentNullException"></exception>
 297    protected static EntitySort CastInternal(EntitySort sourceSort, Type sourceType, Type destinationType)
 298    {
 3299        if (sourceSort == null)
 0300            throw new ArgumentNullException(nameof(sourceSort));
 301
 3302        var destinationSourceOrderType = typeof(EntitySort<>).MakeGenericType(destinationType);
 3303        var destinationSort = (EntitySort)JsonSerializer.Deserialize(JsonSerializer.Serialize(sourceSort), destinationSo
 304
 3305        var sourceProperties = sourceType.GetProperties();
 3306        var destinationProperties = destinationType.GetProperties().ToList();
 307
 24308        foreach (var sourceProperty in sourceProperties)
 309        {
 9310            var destinationProperty = destinationProperties.FirstOrDefault(x => x.Name.EqualsOrdinal(sourceProperty.Name
 9311            if (destinationProperty == null)
 312            {
 4313                RemoveRelatedProperties(destinationSort, sourceProperty.Name);
 4314                continue;
 315            }
 316
 5317            var destinationIsAssignableFromSource = destinationProperty.PropertyType.IsAssignableFrom(sourceProperty.Pro
 5318            if (destinationIsAssignableFromSource)
 319                continue;
 320
 2321            var nestedSourceSort = destinationSort.GetNestedInternal(sourceProperty.PropertyType, sourceProperty.Name);
 322
 2323            var removedPropertyCount = RemoveRelatedProperties(destinationSort, sourceProperty.Name);
 2324            if (removedPropertyCount == 0)
 325                continue;
 326
 2327            var nestedDestinationSort = CastInternal(nestedSourceSort, sourceProperty.PropertyType, destinationProperty.
 2328            destinationSort.AddNestedInternal(destinationProperty.Name, nestedDestinationSort);
 329        }
 330
 3331        return destinationSort;
 332    }
 333
 334    /// <summary>
 335    /// Gets the <see cref="EntitySort{TProperty}"/> for the given nested class <paramref name="propertyType"/>.
 336    /// </summary>
 337    /// <param name="propertyType">The type of the property returned by <paramref name="propertyPath"/>.</param>
 338    /// <param name="propertyPath">Path to the nested property to set sort for.</param>
 339    /// <returns></returns>
 340    private EntitySort GetNestedInternal(Type propertyType, string propertyPath)
 341    {
 4342        var relatedProperties = PropertySorts
 4343            .Where(sort => sort.BelongsTo(propertyPath))
 4344            .ToList();
 345
 4346        var nestedSorts = relatedProperties
 4347            .Select(sort =>
 4348            {
 4349                var nestedPropertyPath = propertyPath.EqualsOrdinal(sort.PropertyPath)
 4350                    ? PropertySort.PATH_TO_SELF
 4351                    : sort.PropertyPath[(propertyPath.Length + 1)..];
 4352                return PropertySort.Create(nestedPropertyPath, sort.Direction, sort.Position);
 4353            })
 4354            .ToList();
 355
 4356        var genericSortType = typeof(EntitySort<>).MakeGenericType(propertyType);
 4357        var nestedSortInstance = Activator.CreateInstance(genericSortType)
 4358            ?? throw new InvalidOperationException($"Unable to create instance of type {genericSortType.Name}");
 359
 4360        var nestedSort = (EntitySort)nestedSortInstance;
 4361        nestedSort.PropertySorts.AddRange(nestedSorts);
 362
 4363        return nestedSort;
 364    }
 365
 366    /// <summary>
 367    /// Adds the nested sort order for the given property.
 368    /// </summary>
 369    /// <param name="propertyPath">Path to the nested property to set sort for.</param>
 370    /// <param name="nestedSort">The nested <see cref="EntitySort"/>.</param>
 371    /// <exception cref="ArgumentNullException"></exception>
 372    private void AddNestedInternal(string propertyPath, EntitySort nestedSort)
 373    {
 23374        if (nestedSort == null)
 1375            throw new ArgumentNullException(nameof(nestedSort));
 376
 92377        foreach (var propertySort in nestedSort.PropertySorts)
 378        {
 24379            var path = propertyPath;
 24380            if (!propertySort.PropertyPath.EqualsOrdinal(PropertySort.PATH_TO_SELF))
 23381                path += "." + propertySort.PropertyPath;
 382
 24383            PropertySorts.Add(PropertySort.Create(path, propertySort.Direction, propertySort.Position));
 384        }
 22385    }
 386
 387    private int GetPosition(int? position)
 382388        => position ?? (PropertySorts.Any() ? PropertySorts.Max(x => x.Position) + 1 : 0);
 389
 390    private static int RemoveRelatedProperties(EntitySort sort, string propertyName)
 6391        => sort.PropertySorts.RemoveAll(propertySort => propertySort.BelongsTo(propertyName));
 392
 393    [ExcludeFromCodeCoverage]
 394    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
 395    private string DebuggerDisplay
 396    {
 397        get
 398        {
 399            if (PropertySorts.Count == 0)
 400                return "<EMPTY>";
 401
 402            var sortStrings = PropertySorts
 403                .OrderBy(x => x.Position)
 404                .Select(sort => $"{sort.Position}: {sort}")
 405                .ToList();
 406
 407            return string.Join(", ", sortStrings);
 408        }
 409    }
 410}