• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pace / bricks / 12827718001

17 Jan 2025 10:57AM UTC coverage: 56.909% (-0.3%) from 57.237%
12827718001

Pull #384

github

monstermunchkin
Satisfy linters
Pull Request #384: Extend linting

478 of 946 new or added lines in 109 files covered. (50.53%)

133 existing lines in 53 files now uncovered.

5667 of 9958 relevant lines covered (56.91%)

21.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

1.27
/http/jsonapi/runtime/standard_params.go
1
// Copyright © 2020 by PACE Telematics GmbH. All rights reserved.
2

3
package runtime
4

5
import (
6
        "fmt"
7
        "net/http"
8
        "strconv"
9
        "strings"
10

11
        "github.com/caarlos0/env/v10"
12
        "github.com/go-pg/pg"
13
        "github.com/go-pg/pg/orm"
14

15
        "github.com/pace/bricks/maintenance/log"
16
)
17

18
type config struct {
19
        MaxPageSize     int `env:"MAX_PAGE_SIZE" envDefault:"100"`
20
        MinPageSize     int `env:"MIN_PAGE_SIZE" envDefault:"1"`
21
        DefaultPageSize int `env:"DEFAULT_PAGE_SIZE" envDefault:"50"`
22
}
23

24
var cfg config
25

26
func init() {
1✔
27
        if err := env.Parse(&cfg); err != nil {
1✔
UNCOV
28
                log.Fatalf("Failed to parse jsonapi params from environment: %v", err)
×
29
        }
×
30
}
31

32
// ValueSanitizer should sanitize query parameter values,
33
// the implementation should validate the value and transform it to the right type.
34
type ValueSanitizer interface {
35
        // SanitizeValue should sanitize a value, that should be in the column fieldName
36
        SanitizeValue(fieldName string, value string) (interface{}, error)
37
}
38

39
// ColumnMapper maps the name of a filter or sorting parameter to a database column name.
40
type ColumnMapper interface {
41
        // Map maps the value, this function decides if the value is allowed and translates it to a database column name,
42
        // the function returns the database column name and a bool that indicates that the value is allowed and mapped
43
        Map(value string) (string, bool)
44
}
45

46
// MapMapper is a very easy ColumnMapper implementation based on a map which contains all allowed values
47
// and maps them with a map.
48
type MapMapper struct {
49
        mapping map[string]string
50
}
51

52
// NewMapMapper returns a MapMapper for a specific map.
53
func NewMapMapper(mapping map[string]string) *MapMapper {
×
54
        return &MapMapper{mapping: mapping}
×
55
}
×
56

57
// Map returns the mapped value and if it is valid based on a map.
58
func (m *MapMapper) Map(value string) (string, bool) {
×
59
        val, isValid := m.mapping[value]
×
60
        return val, isValid
×
61
}
×
62

63
// URLQueryParameters contains all information that is needed for pagination, sorting and filtering.
64
// It is not depending on orm.Query.
65
type URLQueryParameters struct {
66
        HasPagination bool
67
        PageNr        int
68
        PageSize      int
69
        Order         []string
70
        Filter        map[string][]interface{}
71
}
72

73
// ReadURLQueryParameters reads sorting, filter and pagination from requests and return a UrlQueryParameters object,
74
// even if any errors occur. The returned error combines all errors of pagination, filter and sorting.
NEW
75
func ReadURLQueryParameters(r *http.Request, mapper ColumnMapper, sanitizer ValueSanitizer) (*URLQueryParameters, error) {
×
NEW
76
        result := &URLQueryParameters{}
×
NEW
77

×
78
        var errs []error
×
NEW
79

×
80
        if err := result.readPagination(r); err != nil {
×
81
                errs = append(errs, err)
×
82
        }
×
83

84
        if err := result.readSorting(r, mapper); err != nil {
×
85
                errs = append(errs, err)
×
86
        }
×
87

88
        if err := result.readFilter(r, mapper, sanitizer); err != nil {
×
89
                errs = append(errs, err)
×
90
        }
×
91

92
        if len(errs) == 0 {
×
93
                return result, nil
×
94
        }
×
95

96
        if len(errs) == 1 {
×
97
                return result, errs[0]
×
98
        }
×
99

NEW
100
        errAggregate := make([]string, len(errs))
×
NEW
101

×
NEW
102
        for i, err := range errs {
×
NEW
103
                errAggregate[i] = err.Error()
×
104
        }
×
105

UNCOV
106
        return result, fmt.Errorf("reading URL Query Parameters cased multiple errors: %v", strings.Join(errAggregate, ","))
×
107
}
108

109
// AddToQuery adds filter, sorting and pagination to a orm.Query.
NEW
110
func (u *URLQueryParameters) AddToQuery(query *orm.Query) *orm.Query {
×
111
        if u.HasPagination {
×
112
                query.Offset(u.PageSize * u.PageNr).Limit(u.PageSize)
×
113
        }
×
114

115
        for name, filterValues := range u.Filter {
×
116
                if len(filterValues) == 0 {
×
117
                        continue
×
118
                }
119

120
                if len(filterValues) == 1 {
×
121
                        query.Where(name+" = ?", filterValues[0])
×
122
                        continue
×
123
                }
124

UNCOV
125
                query.Where(name+" IN (?)", pg.In(filterValues))
×
126
        }
127

128
        for _, val := range u.Order {
×
129
                query.Order(val)
×
130
        }
×
131

UNCOV
132
        return query
×
133
}
134

NEW
135
func (u *URLQueryParameters) readPagination(r *http.Request) error {
×
136
        pageStr := r.URL.Query().Get("page[number]")
×
137
        sizeStr := r.URL.Query().Get("page[size]")
×
NEW
138

×
139
        if pageStr == "" {
×
140
                u.HasPagination = false
×
141
                return nil
×
142
        }
×
143

144
        u.HasPagination = true
×
NEW
145

×
146
        pageNr, err := strconv.Atoi(pageStr)
×
147
        if err != nil {
×
148
                return err
×
149
        }
×
150

151
        var pageSize int
×
152
        if sizeStr != "" {
×
153
                pageSize, err = strconv.Atoi(sizeStr)
×
154
                if err != nil {
×
155
                        return err
×
156
                }
×
157
        } else {
×
158
                pageSize = cfg.DefaultPageSize
×
159
        }
×
160

161
        if (pageSize < cfg.MinPageSize) || (pageSize > cfg.MaxPageSize) {
×
162
                return fmt.Errorf("invalid pagesize not between min. and max. value, min: %d, max: %d", cfg.MinPageSize, cfg.MaxPageSize)
×
163
        }
×
164

165
        u.PageNr = pageNr
×
166
        u.PageSize = pageSize
×
NEW
167

×
UNCOV
168
        return nil
×
169
}
170

NEW
171
func (u *URLQueryParameters) readSorting(r *http.Request, mapper ColumnMapper) error {
×
172
        sort := r.URL.Query().Get("sort")
×
173
        if sort == "" {
×
174
                return nil
×
175
        }
×
176

177
        sorting := strings.Split(sort, ",")
×
178

×
179
        var order string
×
NEW
180

×
NEW
181
        resultedOrders := make([]string, 0)
×
NEW
182
        errSortingWithReason := make([]string, 0)
×
NEW
183

×
184
        for _, val := range sorting {
×
185
                if val == "" {
×
186
                        continue
×
187
                }
188

189
                order = " ASC"
×
190
                if strings.HasPrefix(val, "-") {
×
191
                        order = " DESC"
×
192
                }
×
193

194
                val = strings.TrimPrefix(val, "-")
×
195

×
196
                key, isValid := mapper.Map(val)
×
197
                if !isValid {
×
198
                        errSortingWithReason = append(errSortingWithReason, val)
×
199
                        continue
×
200
                }
201

UNCOV
202
                resultedOrders = append(resultedOrders, key+order)
×
203
        }
204

205
        u.Order = resultedOrders
×
NEW
206

×
207
        if len(errSortingWithReason) > 0 {
×
208
                return fmt.Errorf("at least one sorting parameter is not valid: %q", strings.Join(errSortingWithReason, ","))
×
209
        }
×
210

UNCOV
211
        return nil
×
212
}
213

NEW
214
func (u *URLQueryParameters) readFilter(r *http.Request, mapper ColumnMapper, sanitizer ValueSanitizer) error {
×
215
        filter := make(map[string][]interface{})
×
NEW
216

×
217
        var invalidFilter []string
×
NEW
218

×
219
        for queryName, queryValues := range r.URL.Query() {
×
220
                if !(strings.HasPrefix(queryName, "filter[") && strings.HasSuffix(queryName, "]")) {
×
221
                        continue
×
222
                }
223

224
                key, isValid := getFilterKey(queryName, mapper)
×
225
                if !isValid {
×
226
                        invalidFilter = append(invalidFilter, key)
×
227
                        continue
×
228
                }
229

230
                filterValues, isValid := getFilterValues(key, queryValues, sanitizer)
×
231
                if !isValid {
×
232
                        invalidFilter = append(invalidFilter, key)
×
233
                        continue
×
234
                }
235

UNCOV
236
                filter[key] = filterValues
×
237
        }
238

239
        u.Filter = filter
×
NEW
240

×
241
        if len(invalidFilter) != 0 {
×
242
                return fmt.Errorf("at least one filter parameter is not valid: %q", strings.Join(invalidFilter, ","))
×
243
        }
×
244

UNCOV
245
        return nil
×
246
}
247

248
func getFilterKey(queryName string, modelMapping ColumnMapper) (string, bool) {
×
249
        field := strings.TrimPrefix(queryName, "filter[")
×
250
        field = strings.TrimSuffix(field, "]")
×
251
        mapped, isValid := modelMapping.Map(field)
×
NEW
252

×
253
        if !isValid {
×
254
                return field, false
×
255
        }
×
256

UNCOV
257
        return mapped, true
×
258
}
259

260
func getFilterValues(fieldName string, queryValues []string, sanitizer ValueSanitizer) ([]interface{}, bool) {
×
261
        var filterValues []interface{}
×
NEW
262

×
263
        for _, value := range queryValues {
×
264
                separatedValues := strings.Split(value, ",")
×
265
                for _, separatedValue := range separatedValues {
×
266
                        sanitized, err := sanitizer.SanitizeValue(fieldName, separatedValue)
×
267
                        if err != nil {
×
268
                                return nil, false
×
269
                        }
×
270

UNCOV
271
                        filterValues = append(filterValues, sanitized)
×
272
                }
273
        }
274

UNCOV
275
        return filterValues, true
×
276
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc