4
4
"context"
5
5
"fmt"
6
6
"slices"
7
+ "strings"
7
8
8
9
"github.com/nais/api/internal/graph/model"
9
10
"github.com/sirupsen/logrus"
@@ -23,27 +24,27 @@ type ConcurrentSortFunc[T any] func(ctx context.Context, a T) int
23
24
// Filter is a function that returns true if the given value should be included in the result.
24
25
type Filter [T any , FilterObj any ] func (ctx context.Context , v T , filter FilterObj ) bool
25
26
26
- // TieBreaker is a combination of a SortField and a direction that might be able to resolve equal fields during sorting.
27
+ // tieBreaker is a combination of a SortField and a direction that might be able to resolve equal fields during sorting.
27
28
// If the direction is not supplied, the direction used for the original sort will be used. The referenced field must be
28
29
// registered with RegisterSort (concurrent tie-break sorters are not supported).
29
- type TieBreaker [SortField comparable ] struct {
30
- Field SortField
31
- Direction * model.OrderDirection
30
+ type tieBreaker [SortField ~ string ] struct {
31
+ field SortField
32
+ direction model.OrderDirection
32
33
}
33
34
34
- type funcs [T any , SortField comparable ] struct {
35
+ type funcs [T any , SortField ~ string ] struct {
35
36
concurrentSort ConcurrentSortFunc [T ]
36
37
sort SortFunc [T ]
37
- tieBreakers []TieBreaker [SortField ]
38
+ tieBreakers []tieBreaker [SortField ]
38
39
}
39
40
40
- type SortFilter [T any , SortField comparable , FilterObj comparable ] struct {
41
+ type SortFilter [T any , SortField ~ string , FilterObj comparable ] struct {
41
42
sorters map [SortField ]funcs [T , SortField ]
42
43
filters []Filter [T , FilterObj ]
43
44
}
44
45
45
46
// New creates a new SortFilter
46
- func New [T any , SortField comparable , FilterObj comparable ]() * SortFilter [T , SortField , FilterObj ] {
47
+ func New [T any , SortField ~ string , FilterObj comparable ]() * SortFilter [T , SortField , FilterObj ] {
47
48
return & SortFilter [T , SortField , FilterObj ]{
48
49
sorters : make (map [SortField ]funcs [T , SortField ]),
49
50
}
@@ -55,29 +56,59 @@ func (s *SortFilter[T, SortField, FilterObj]) SupportsSort(field SortField) bool
55
56
return exists
56
57
}
57
58
59
+ // sortFieldsToTieBreakers takes a slice of SortField values and returns a list of tie-breakers. The values can be
60
+ // suffixed with :ASC or :DESC to specify the direction. If no direction is supplied, ASC is assumed.
61
+ func sortFieldsToTieBreakers [SortField ~ string ](fields []SortField ) []tieBreaker [SortField ] {
62
+ ret := make ([]tieBreaker [SortField ], len (fields ))
63
+ for i , field := range fields {
64
+ direction := model .OrderDirectionAsc
65
+ if parts := strings .Split (string (field ), ":" ); len (parts ) == 2 {
66
+ if parts [1 ] != "ASC" && parts [1 ] != "DESC" {
67
+ panic (fmt .Sprintf ("invalid direction in sort field: %q" , field ))
68
+ }
69
+
70
+ direction = model .OrderDirection (parts [1 ])
71
+ field = SortField (parts [0 ])
72
+ } else if len (parts ) > 2 {
73
+ panic (fmt .Sprintf ("invalid sort field: %q" , field ))
74
+ }
75
+
76
+ ret [i ] = tieBreaker [SortField ]{
77
+ field : field ,
78
+ direction : direction ,
79
+ }
80
+ }
81
+
82
+ return ret
83
+ }
84
+
85
+ func tieBreakerToSortField [SortField ~ string ](tb tieBreaker [SortField ]) SortField {
86
+ return tb .field + ":" + SortField (tb .direction )
87
+ }
88
+
58
89
// RegisterSort will add support for sorting on a specific field. Optional tie-breakers can be supplied to resolve equal
59
90
// values, and will be executed in the given order.
60
- func (s * SortFilter [T , SortField , FilterObj ]) RegisterSort (field SortField , sort SortFunc [T ], tieBreakers ... TieBreaker [ SortField ] ) {
91
+ func (s * SortFilter [T , SortField , FilterObj ]) RegisterSort (field SortField , sort SortFunc [T ], tieBreakers ... SortField ) {
61
92
if _ , ok := s .sorters [field ]; ok {
62
93
panic (fmt .Sprintf ("sort field is already registered: %v" , field ))
63
94
}
64
95
65
96
s .sorters [field ] = funcs [T , SortField ]{
66
97
sort : sort ,
67
- tieBreakers : tieBreakers ,
98
+ tieBreakers : sortFieldsToTieBreakers ( tieBreakers ) ,
68
99
}
69
100
}
70
101
71
102
// RegisterConcurrentSort will add support for doing concurrent sorting on a specific field. Optional tie-breakers can
72
103
// be supplied to resolve equal values, and will be executed in the given order.
73
- func (s * SortFilter [T , SortField , FilterObj ]) RegisterConcurrentSort (field SortField , sort ConcurrentSortFunc [T ], tieBreakers ... TieBreaker [ SortField ] ) {
104
+ func (s * SortFilter [T , SortField , FilterObj ]) RegisterConcurrentSort (field SortField , sort ConcurrentSortFunc [T ], tieBreakers ... SortField ) {
74
105
if _ , ok := s .sorters [field ]; ok {
75
106
panic (fmt .Sprintf ("sort field is already registered: %v" , field ))
76
107
}
77
108
78
109
s .sorters [field ] = funcs [T , SortField ]{
79
110
concurrentSort : sort ,
80
- tieBreakers : tieBreakers ,
111
+ tieBreakers : sortFieldsToTieBreakers ( tieBreakers ) ,
81
112
}
82
113
}
83
114
@@ -139,14 +170,14 @@ func (s *SortFilter[T, SortField, FilterObj]) Sort(ctx context.Context, items []
139
170
}
140
171
141
172
if sorter .concurrentSort != nil {
142
- s .sortConcurrent (ctx , items , sorter .concurrentSort , field , direction , sorter .tieBreakers ... )
173
+ s .sortConcurrent (ctx , items , sorter .concurrentSort , field , direction , sorter .tieBreakers )
143
174
return
144
175
}
145
176
146
- s .sort (ctx , items , sorter .sort , field , direction , sorter .tieBreakers ... )
177
+ s .sort (ctx , items , sorter .sort , field , direction , sorter .tieBreakers )
147
178
}
148
179
149
- func (s * SortFilter [T , SortField , FilterObj ]) sortConcurrent (ctx context.Context , items []T , sort ConcurrentSortFunc [T ], field SortField , direction model.OrderDirection , tieBreakers ... TieBreaker [SortField ]) {
180
+ func (s * SortFilter [T , SortField , FilterObj ]) sortConcurrent (ctx context.Context , items []T , sort ConcurrentSortFunc [T ], field SortField , direction model.OrderDirection , tieBreakers [] tieBreaker [SortField ]) {
150
181
type sortable struct {
151
182
item T
152
183
key int
@@ -170,7 +201,7 @@ func (s *SortFilter[T, SortField, FilterObj]) sortConcurrent(ctx context.Context
170
201
171
202
slices .SortStableFunc (res , func (a , b sortable ) int {
172
203
if b .key == a .key {
173
- return s .tieBreak (ctx , a .item , b .item , field , direction , tieBreakers ... )
204
+ return s .tieBreak (ctx , a .item , b .item , field , tieBreakers )
174
205
}
175
206
176
207
if direction == model .OrderDirectionDesc {
@@ -184,7 +215,7 @@ func (s *SortFilter[T, SortField, FilterObj]) sortConcurrent(ctx context.Context
184
215
}
185
216
}
186
217
187
- func (s * SortFilter [T , SortField , FilterObj ]) sort (ctx context.Context , items []T , sort SortFunc [T ], field SortField , direction model.OrderDirection , tieBreakers ... TieBreaker [SortField ]) {
218
+ func (s * SortFilter [T , SortField , FilterObj ]) sort (ctx context.Context , items []T , sort SortFunc [T ], field SortField , direction model.OrderDirection , tieBreakers [] tieBreaker [SortField ]) {
188
219
slices .SortStableFunc (items , func (a , b T ) int {
189
220
var ret int
190
221
if direction == model .OrderDirectionDesc {
@@ -194,38 +225,33 @@ func (s *SortFilter[T, SortField, FilterObj]) sort(ctx context.Context, items []
194
225
}
195
226
196
227
if ret == 0 {
197
- return s .tieBreak (ctx , a , b , field , direction , tieBreakers ... )
228
+ return s .tieBreak (ctx , a , b , field , tieBreakers )
198
229
}
199
230
return ret
200
231
})
201
232
}
202
233
203
234
// tieBreak will resolve equal fields after the initial sort by using the supplied tie-breakers. The function will
204
235
// return as soon as a tie-breaker returns a non-zero value.
205
- func (s * SortFilter [T , SortField , FilterObj ]) tieBreak (ctx context.Context , a , b T , field SortField , direction model. OrderDirection , tieBreakers ... TieBreaker [SortField ]) int {
236
+ func (s * SortFilter [T , SortField , FilterObj ]) tieBreak (ctx context.Context , a , b T , originalSortField SortField , tieBreakers [] tieBreaker [SortField ]) int {
206
237
for _ , tb := range tieBreakers {
207
- dir := direction
208
- if tb .Direction != nil {
209
- dir = * tb .Direction
210
- }
211
-
212
- sorter , ok := s .sorters [tb .Field ]
238
+ sorter , ok := s .sorters [tb .field ]
213
239
if ! ok {
214
240
logrus .WithFields (logrus.Fields {
215
- "field_type" : fmt .Sprintf ("%T" , field ),
216
- "tie_breaker" : tb . Field ,
241
+ "field_type" : fmt .Sprintf ("%T" , originalSortField ),
242
+ "tie_breaker" : tieBreakerToSortField ( tb ) ,
217
243
}).Errorf ("no sort registered for tie-breaker" )
218
244
continue
219
245
} else if sorter .sort == nil {
220
246
logrus .WithFields (logrus.Fields {
221
- "field_type" : fmt .Sprintf ("%T" , field ),
222
- "tie_breaker" : tb . Field ,
247
+ "field_type" : fmt .Sprintf ("%T" , originalSortField ),
248
+ "tie_breaker" : tieBreakerToSortField ( tb ) ,
223
249
}).Errorf ("tie-breaker can not be a concurrent sort" )
224
250
continue
225
251
}
226
252
227
253
var v int
228
- if dir == model .OrderDirectionDesc {
254
+ if tb . direction == model .OrderDirectionDesc {
229
255
v = sorter .sort (ctx , b , a )
230
256
} else {
231
257
v = sorter .sort (ctx , a , b )
@@ -238,9 +264,15 @@ func (s *SortFilter[T, SortField, FilterObj]) tieBreak(ctx context.Context, a, b
238
264
239
265
logrus .
240
266
WithFields (logrus.Fields {
241
- "field_type" : fmt .Sprintf ("%T" , field ),
242
- "sort_field" : field ,
243
- "tie_breakers" : tieBreakers ,
267
+ "field_type" : fmt .Sprintf ("%T" , originalSortField ),
268
+ "sort_field" : originalSortField ,
269
+ "tie_breakers" : func (tbs []tieBreaker [SortField ]) []SortField {
270
+ ret := make ([]SortField , len (tbs ))
271
+ for i , tb := range tbs {
272
+ ret [i ] = tieBreakerToSortField (tb )
273
+ }
274
+ return ret
275
+ }(tieBreakers ),
244
276
}).
245
277
Errorf ("unable to tie-break sort, gotta have more tie-breakers" )
246
278
return 0
0 commit comments