| | 1 | | using Plainquire.Filter.Abstractions; |
| | 2 | | using System; |
| | 3 | | using System.Diagnostics; |
| | 4 | | using System.Diagnostics.CodeAnalysis; |
| | 5 | | using System.Linq; |
| | 6 | | using System.Text.RegularExpressions; |
| | 7 | |
|
| | 8 | | namespace Plainquire.Filter; |
| | 9 | |
|
| | 10 | | /// <summary> |
| | 11 | | /// Defines a single filter. |
| | 12 | | /// </summary> |
| | 13 | | [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Provided as library, can be used from outside")] |
| | 14 | | [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] |
| | 15 | | public class ValueFilter |
| | 16 | | { |
| | 17 | | private FilterConfiguration? _configuration; |
| | 18 | |
|
| | 19 | | /// <summary> |
| | 20 | | /// Gets the filter operator. See <see cref="Operator"/> for details. |
| | 21 | | /// </summary> |
| | 22 | | public FilterOperator Operator { get; private set; } |
| | 23 | |
|
| | 24 | | /// <summary> |
| | 25 | | /// JSON or Chronic representation of the value to filter for. Unused, if <see cref="Operator"/> is <see cref="Filte |
| | 26 | | /// </summary> |
| | 27 | | public string? Value { get; private set; } |
| | 28 | |
|
| | 29 | | /// <summary> |
| | 30 | | /// Indicates whether this filter is empty. |
| | 31 | | /// </summary> |
| 18232 | 32 | | public bool IsEmpty => Operator != FilterOperator.IsNull && Operator != FilterOperator.NotNull && Value == null; |
| | 33 | |
|
| 17653 | 34 | | private ValueFilter() { } |
| | 35 | |
|
| | 36 | | /// <summary> |
| | 37 | | /// Creates the specified filter |
| | 38 | | /// </summary> |
| | 39 | | /// <typeparam name="TValue">The type of the value.</typeparam> |
| | 40 | | /// <param name="filterOperator">The filter operator.</param> |
| | 41 | | /// <param name="value">The value to filter for. Unused, if <paramref name="filterOperator"/> is <see cref="FilterOp |
| | 42 | | /// <param name="configuration">The filter configuration to use.</param> |
| | 43 | | public static ValueFilter Create<TValue>(FilterOperator filterOperator, TValue? value, FilterConfiguration? configur |
| | 44 | | { |
| 17655 | 45 | | var isNullableFilterOperator = filterOperator is FilterOperator.IsNull or FilterOperator.NotNull; |
| 17655 | 46 | | if (isNullableFilterOperator) |
| 872 | 47 | | value = default; |
| 16783 | 48 | | else if (value == null) |
| 1 | 49 | | throw new ArgumentException($"Filter values cannot be null. If filtering for NULL is intended, use filter op |
| 16782 | 50 | | else if (!typeof(TValue).IsFilterableProperty()) |
| 1 | 51 | | throw new ArgumentException($"The type '{typeof(TValue)}' is not filterable by any known expression creator" |
| | 52 | |
|
| 17653 | 53 | | return new ValueFilter |
| 17653 | 54 | | { |
| 17653 | 55 | | Operator = filterOperator, |
| 17653 | 56 | | Value = ValueToFilterString(value), |
| 17653 | 57 | | _configuration = configuration |
| 17653 | 58 | | }; |
| | 59 | | } |
| | 60 | |
|
| | 61 | | /// <summary> |
| | 62 | | /// Creates the specified filter |
| | 63 | | /// </summary> |
| | 64 | | /// <param name="filterOperator">The filter operator.</param> |
| | 65 | | /// <param name="configuration">The filter configuration to use.</param> |
| | 66 | | public static ValueFilter Create(FilterOperator filterOperator, FilterConfiguration? configuration = null) |
| | 67 | | { |
| 12 | 68 | | var isNullableFilterOperator = filterOperator is FilterOperator.IsNull or FilterOperator.NotNull; |
| 12 | 69 | | if (!isNullableFilterOperator) |
| 0 | 70 | | throw new InvalidOperationException("A value is required for operators other than NULL/NOT NULL."); |
| | 71 | |
|
| 12 | 72 | | return Create<object>(filterOperator, null, configuration); |
| | 73 | | } |
| | 74 | |
|
| | 75 | | /// <summary> |
| | 76 | | /// Creates the specified filter using the default operator. |
| | 77 | | /// </summary> |
| | 78 | | /// <typeparam name="TValue">The type of the value.</typeparam> |
| | 79 | | /// <param name="value">The value to filter for.</param> |
| | 80 | | /// <param name="configuration">The filter configuration to use.</param> |
| | 81 | | public static ValueFilter Create<TValue>(TValue value, FilterConfiguration? configuration = null) |
| 0 | 82 | | => Create(FilterOperator.Default, value, configuration); |
| | 83 | |
|
| | 84 | | /// <summary> |
| | 85 | | /// Creates the specified filter. |
| | 86 | | /// </summary> |
| | 87 | | /// <param name="filterSyntax">The filter micro syntax to create the filter from.</param> |
| | 88 | | /// <param name="configuration">The filter configuration to use.</param> |
| | 89 | | public static ValueFilter Create(string? filterSyntax, FilterConfiguration? configuration = null) |
| | 90 | | { |
| 7127 | 91 | | var (filterOperator, value) = ExtractFilterOperator(filterSyntax, configuration); |
| 7127 | 92 | | return Create(filterOperator, value, configuration); |
| | 93 | | } |
| | 94 | |
|
| | 95 | | /// <inheritdoc /> |
| | 96 | | public override string? ToString() |
| | 97 | | { |
| 9 | 98 | | var configuration = _configuration ?? FilterConfiguration.Default ?? new FilterConfiguration(); |
| 9 | 99 | | var operatorSyntax = configuration.FilterOperatorMap.FirstOrDefault(x => x.Value == Operator).Key; |
| 9 | 100 | | if (string.IsNullOrEmpty(operatorSyntax) && Value == null) |
| 0 | 101 | | return null; |
| | 102 | |
|
| 9 | 103 | | return operatorSyntax + Value; |
| | 104 | | } |
| | 105 | |
|
| | 106 | | private static string ValueToFilterString(object? value) |
| | 107 | | { |
| 17653 | 108 | | var result = value switch |
| 17653 | 109 | | { |
| 772 | 110 | | null => string.Empty, |
| 188 | 111 | | DateTime dateTime => dateTime.ToString("o"), |
| 17653 | 112 | | #if NET6_0_OR_GREATER |
| 180 | 113 | | DateOnly date => date.ToString("o"), |
| 17653 | 114 | | //DateOnly date => date.ToString("yyyy-MM-dd"), |
| 17653 | 115 | | #endif |
| 396 | 116 | | DateTimeOffset dateTime => dateTime.ToString("o"), |
| 16117 | 117 | | _ => value.ToString() ?? string.Empty |
| 17653 | 118 | | }; |
| | 119 | |
|
| 17653 | 120 | | return result; |
| | 121 | | } |
| | 122 | |
|
| | 123 | | private static (FilterOperator, string?) ExtractFilterOperator(string? filter, FilterConfiguration? configuration) |
| | 124 | | { |
| 7127 | 125 | | configuration ??= FilterConfiguration.Default ?? new FilterConfiguration(); |
| | 126 | |
|
| 7127 | 127 | | if (filter == null) |
| 0 | 128 | | return (FilterOperator.Default, null); |
| | 129 | |
|
| 7127 | 130 | | var trimmedFilter = filter.TrimStart(); |
| | 131 | |
|
| 7127 | 132 | | var (filterSyntax, filterOperator) = configuration |
| 7127 | 133 | | .FilterOperatorMap |
| 7127 | 134 | | .OrderByDescending(x => x.Key.Length) |
| 7127 | 135 | | .FirstOrDefault(x => trimmedFilter.StartsWith(x.Key, StringComparison.Ordinal)); |
| | 136 | |
|
| 7127 | 137 | | var hasFilterOperator = !string.IsNullOrEmpty(filterSyntax); |
| 7127 | 138 | | filter = hasFilterOperator ? trimmedFilter : filter; |
| | 139 | |
|
| 7127 | 140 | | var filterValue = filter[filterSyntax.Length..]; |
| | 141 | |
|
| 7127 | 142 | | var escapeEscapeCharacter = Regex.Escape(configuration.EscapeCharacter.ToString()); |
| 7127 | 143 | | var escapedCharClass = @$"(?<!{escapeEscapeCharacter}){escapeEscapeCharacter}(.)"; |
| 7127 | 144 | | filterValue = Regex.Replace(filterValue, escapedCharClass, "$1", RegexOptions.None, RegexDefaults.Timeout); |
| | 145 | |
|
| 7127 | 146 | | return (filterOperator, filterValue); |
| | 147 | | } |
| | 148 | |
|
| | 149 | | [ExcludeFromCodeCoverage] |
| | 150 | | [DebuggerBrowsable(DebuggerBrowsableState.Never)] |
| | 151 | | private string? DebuggerDisplay => ToString(); |
| | 152 | | } |