-
-
Notifications
You must be signed in to change notification settings - Fork 180
/
sort.ex
156 lines (124 loc) · 4.25 KB
/
sort.ex
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
defmodule Ash.Sort do
@moduledoc """
Utilities and types for sorting.
"""
alias Ash.Error.Query.{InvalidSortOrder, NoSuchAttribute}
@type sort_order ::
:asc | :desc | :asc_nils_first | :asc_nils_last | :desc_nils_first | :desc_nils_last
@type t :: list(atom | {atom, sort_order})
@doc """
A utility for parsing sorts provided from external input. Only allows sorting
on public attributes and aggregates.
The supported formats are:
### Sort Strings
A comma separated list of fields to sort on, each with an optional prefix.
The prefixes are:
* "+" - Same as no prefix. Sorts `:asc`.
* "++" - Sorts `:asc_nils_first`
* "-" - Sorts `:desc`
* "--" - Sorts `:desc_nils_last`
For example
"foo,-bar,++baz,--buz"
### A list of sort strings
Same prefix rules as above, but provided as a list.
For example:
["foo", "-bar", "++baz", "--buz"]
### A standard Ash sort
"""
@spec parse_input(
Ash.Resource.t(),
String.t()
| list(atom | String.t() | {atom, sort_order()} | list(String.t()))
| nil
) ::
Ash.Sort.t() | nil
def parse_input(resource, sort) when is_binary(sort) do
sort = String.split(sort, ",")
parse_input(resource, sort)
end
def parse_input(resource, sort) when is_list(sort) do
sort
|> Enum.reduce_while({:ok, []}, fn field, {:ok, sort} ->
case parse_sort(resource, field) do
{:ok, value} -> {:cont, {:ok, [value | sort]}}
{:error, error} -> {:halt, {:error, error}}
end
end)
|> case do
{:ok, values} -> {:ok, Enum.reverse(values)}
{:error, error} -> {:error, error}
end
end
def parse_input(_resource, nil), do: nil
def parse_sort(resource, {field, direction})
when direction in [
:asc,
:desc,
:asc_nils_first,
:asc_nils_last,
:desc_nils_first,
:desc_nils_last
] do
case get_field(resource, field) do
nil -> {:error, NoSuchAttribute.exception(resource: resource, name: field)}
field -> {:ok, {field, direction}}
end
end
def parse_sort(_resource, {_field, order}) do
{:error, InvalidSortOrder.exception(order: order)}
end
def parse_sort(resource, "++" <> field) do
case get_field(resource, field) do
nil -> {:error, NoSuchAttribute.exception(resource: resource, name: field)}
field -> {:ok, {field, :asc_nils_first}}
end
end
def parse_sort(resource, "--" <> field) do
case get_field(resource, field) do
nil -> {:error, NoSuchAttribute.exception(resource: resource, name: field)}
field -> {:ok, {field, :desc_nils_last}}
end
end
def parse_sort(resource, "+" <> field) do
case get_field(resource, field) do
nil -> {:error, NoSuchAttribute.exception(resource: resource, name: field)}
field -> {:ok, {field, :asc}}
end
end
def parse_sort(resource, "-" <> field) do
case get_field(resource, field) do
nil -> {:error, NoSuchAttribute.exception(resource: resource, name: field)}
field -> {:ok, {field, :desc}}
end
end
def parse_sort(resource, field) do
case get_field(resource, field) do
nil -> {:error, NoSuchAttribute.exception(resource: resource, name: field)}
field -> {:ok, {field, :asc}}
end
end
defp get_field(resource, field) do
case Ash.Resource.Info.public_attribute(resource, field) do
%{name: name} ->
name
nil ->
case Ash.Resource.Info.public_attribute(resource, field) do
%{name: name} ->
name
nil ->
nil
end
end
end
@doc """
A utility for sorting a list of records at runtime.
For example:
Ash.Sort.runtime_sort([record1, record2, record3], name: :asc, type: :desc_nils_last)
Keep in mind that it is unrealistic to expect this runtime sort to always
be exactly the same as a sort that may have been applied by your data layer.
This is especially true for strings. For example, `Postgres` strings have a
collation that affects their sorting, making it unpredictable from the perspective
of a tool using the database: https://www.postgresql.org/docs/current/collation.html
"""
defdelegate runtime_sort(results, sort), to: Ash.Actions.Sort
end