forked from ByteAether/QueryLink
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathQueryStringExtensions.cs
More file actions
132 lines (119 loc) · 4.42 KB
/
Copy pathQueryStringExtensions.cs
File metadata and controls
132 lines (119 loc) · 4.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
using System.Collections;
using System.ComponentModel;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using static ByteAether.QueryLink.Definitions;
namespace ByteAether.QueryLink;
/// <summary>
/// Provides extension methods for converting Definitions to and from query strings.
/// </summary>
public static class QueryStringExtensions
{
private static readonly IReadOnlyDictionary<FilterOperator, string> _operatorToStringMap
= ((FilterOperator[])Enum.GetValues(typeof(FilterOperator)))
.ToDictionary(
x => x,
x => typeof(FilterOperator)
.GetField(x.ToString())?
.GetCustomAttribute<DescriptionAttribute>()?
.Description ?? x.ToString()
);
private static readonly IReadOnlyDictionary<string, FilterOperator> _operatorFromStringMap
= _operatorToStringMap.ToDictionary(x => x.Value, x => x.Key);
private static readonly Regex _filterSplitter = new(
$"({string.Join(
'|',
_operatorToStringMap
.Values
.OrderByDescending(x => x.Length)
.Select(Regex.Escape)
)})",
RegexOptions.Compiled
);
/// <summary>
/// Converts the definitions to a query string.
/// </summary>
/// <param name="definitions">The definitions to convert.</param>
/// <param name="filterKey">The key for filter definitions in the query string. Default is "filter".</param>
/// <param name="orderKey">The key for order definitions in the query string. Default is "order".</param>
/// <returns>The query string representation of the definitions.</returns>
public static string ToQueryString(
this Definitions definitions,
string filterKey = "filter",
string orderKey = "order"
) => new StringBuilder()
.Append(
string.Join('&', definitions.Filters
.Select(ToQueryString)
.Select(x => $"{filterKey}[]={x}")
)
)
.Append(definitions.Orders.Any() ? $"&{orderKey}=" : string.Empty)
.Append(
string.Join(
',',
definitions.Orders.Select(ToQueryString)
)
)
.ToString();
/// <summary>
/// Creates a Definitions object from a query string.
/// </summary>
/// <param name="queryString">The query string to parse.</param>
/// <param name="filterKey">The key for filter definitions in the query string. Default is "filter".</param>
/// <param name="orderKey">The key for order definitions in the query string. Default is "order".</param>
/// <returns>The Definitions object created from the query string.</returns>
public static Definitions FromQueryString(
string queryString,
string filterKey = "filter",
string orderKey = "order"
)
{
var parsedQuery = HttpUtility.ParseQueryString(queryString);
var queryParams = parsedQuery.AllKeys
.SelectMany(x => parsedQuery.GetValues(x)!.Select(y => (Key: x, Value: y)));
var filters = new List<FilterDefinition<object?>>();
var orders = new List<OrderDefinition>();
foreach (var queryParam in queryParams)
{
if (queryParam.Key == $"{filterKey}[]")
{
var valParts = _filterSplitter.Split(queryParam.Value, 2);
var op = _operatorFromStringMap[valParts[1]];
var filterValue = StringValueParser.Parse(valParts[2]);
var decodedName = HttpUtility.UrlDecode(valParts[0]).Replace("%2E", ".");
filters.Add(new FilterDefinition<object?>(decodedName, op, filterValue));
}
else if (queryParam.Key == orderKey)
{
orders.AddRange(queryParam.Value
.Split(',')
.Select(part =>
{
var isReversed = part[0] == '-';
var fieldName = HttpUtility.UrlDecode(part.TrimStart('-', '+')).Replace("%2E", ".");
return new OrderDefinition(fieldName, isReversed);
})
);
}
}
return new Definitions()
{
Filters = filters,
Orders = orders
};
}
private static string ToQueryString(OrderDefinition def)
=> HttpUtility.UrlEncode((def.IsReversed ? "-" : string.Empty) + def.Name).Replace(".", "%2E");
private static string ToQueryString<T>(FilterDefinition<T> def)
{
var valueSet =
new[] { FilterOperator.Has, FilterOperator.Nhas, FilterOperator.In, FilterOperator.Nin }.Contains(def.Operation)
&& def.Value!.GetType() != typeof(string)
? "[" + string.Join(',', (def.Value as IEnumerable)!.OfType<object>().Select(x => x.ToString()?.Replace(",", "\\,"))) + "]"
: def.Value?.ToString();
return HttpUtility.UrlEncode($"{def.Name}{_operatorToStringMap[def.Operation]}{valueSet}").Replace(".", "%2E");
}
}