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

mendersoftware / mender-server / 1978029483

11 Aug 2025 02:15PM UTC coverage: 65.755% (+0.3%) from 65.495%
1978029483

Pull #860

gitlab-ci

kjaskiewiczz
docs(useradm): move API specification to single file

Changelog: Title
Ticket: QA-1094
Signed-off-by: Krzysztof Jaskiewicz <krzysztof.jaskiewicz@northern.tech>
Pull Request #860: docs(useradm): move API specification to single file

29261 of 44500 relevant lines covered (65.76%)

1.44 hits per line

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

88.62
/backend/pkg/rest.utils/paging.go
1
// Copyright 2023 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14

15
package rest
16

17
import (
18
        "fmt"
19
        "net/http"
20
        "net/url"
21
        "strconv"
22

23
        "github.com/pkg/errors"
24
)
25

26
const (
27
        PerPageDefault = 20
28
        PerPageMax     = 500
29

30
        PageQueryParam    = "page"
31
        PerPageQueryParam = "per_page"
32
)
33

34
var (
35
        ErrPerPageLimit = errors.Errorf(
36
                `parameter "per_page" above limit (max: %d)`, PerPageMax,
37
        )
38
)
39

40
func ErrQueryParmInvalid(param, name string) error {
1✔
41
        return fmt.Errorf("invalid %s query: \"%s\"", param, name)
1✔
42
}
1✔
43

44
func ErrQueryParmLimit(name string) error {
×
45
        return fmt.Errorf("invalid %s query: value must be a non-zero positive integer", name)
×
46
}
×
47

48
// ParsePagingParameters parses the paging parameters from the URL query
49
// string and returns the parsed page, per_page or a parsing error respectively.
50
func ParsePagingParameters(r *http.Request) (int64, int64, error) {
5✔
51
        q := r.URL.Query()
5✔
52
        var (
5✔
53
                err     error
5✔
54
                page    int64
5✔
55
                perPage int64
5✔
56
        )
5✔
57
        qPage := q.Get(PageQueryParam)
5✔
58
        if qPage == "" {
10✔
59
                page = 1
5✔
60
        } else {
9✔
61
                page, err = strconv.ParseInt(qPage, 10, 64)
4✔
62
                if err != nil {
5✔
63
                        return -1, -1, ErrQueryParmInvalid("page", qPage)
1✔
64
                } else if page < 1 {
5✔
65
                        return -1, -1, ErrQueryParmLimit("page")
×
66
                }
×
67
        }
68

69
        qPerPage := q.Get(PerPageQueryParam)
5✔
70
        if qPerPage == "" {
10✔
71
                perPage = PerPageDefault
5✔
72
        } else {
8✔
73
                perPage, err = strconv.ParseInt(qPerPage, 10, 64)
3✔
74
                if err != nil {
4✔
75
                        return -1, -1, ErrQueryParmInvalid("per_page", qPerPage)
1✔
76
                } else if perPage < 1 {
4✔
77
                        return -1, -1, ErrQueryParmLimit("per_page")
×
78
                } else if perPage > PerPageMax {
3✔
79
                        return page, perPage, ErrPerPageLimit
×
80
                }
×
81
        }
82
        return page, perPage, nil
5✔
83
}
84

85
type PagingHints struct {
86
        // TotalCount provides the total count of elements available,
87
        // if provided adds another link to the last page available.
88
        TotalCount *int64
89

90
        // HasNext instructs adding the "next" link header. This option
91
        // has no effect if TotalCount is given.
92
        HasNext *bool
93

94
        // Pagination parameters
95
        Page, PerPage *int64
96
}
97

98
func NewPagingHints() *PagingHints {
4✔
99
        return new(PagingHints)
4✔
100
}
4✔
101

102
func (h *PagingHints) SetTotalCount(totalCount int64) *PagingHints {
3✔
103
        h.TotalCount = &totalCount
3✔
104
        return h
3✔
105
}
3✔
106

107
func (h *PagingHints) SetHasNext(hasNext bool) *PagingHints {
4✔
108
        h.HasNext = &hasNext
4✔
109
        return h
4✔
110
}
4✔
111

112
func (h *PagingHints) SetPage(page int64) *PagingHints {
4✔
113
        h.Page = &page
4✔
114
        return h
4✔
115
}
4✔
116

117
func (h *PagingHints) SetPerPage(perPage int64) *PagingHints {
4✔
118
        h.PerPage = &perPage
4✔
119
        return h
4✔
120
}
4✔
121

122
func MakePagingHeaders(r *http.Request, hints ...*PagingHints) ([]string, error) {
4✔
123
        // Parse hints
4✔
124
        hint := new(PagingHints)
4✔
125
        for _, h := range hints {
8✔
126
                if h == nil {
4✔
127
                        continue
×
128
                }
129
                if h.HasNext != nil {
8✔
130
                        hint.HasNext = h.HasNext
4✔
131
                }
4✔
132
                if h.TotalCount != nil {
7✔
133
                        hint.TotalCount = h.TotalCount
3✔
134
                }
3✔
135
                if h.Page != nil {
8✔
136
                        hint.Page = h.Page
4✔
137
                }
4✔
138
                if h.PerPage != nil {
8✔
139
                        hint.PerPage = h.PerPage
4✔
140
                }
4✔
141
        }
142
        if hint.Page == nil || hint.PerPage == nil {
4✔
143
                page, perPage, err := ParsePagingParameters(r)
×
144
                if err != nil {
×
145
                        return nil, err
×
146
                }
×
147
                hint.Page, hint.PerPage = &page, &perPage
×
148
        }
149
        locationURL := url.URL{
4✔
150
                Path:     r.URL.Path,
4✔
151
                RawQuery: r.URL.RawQuery,
4✔
152
                Fragment: r.URL.Fragment,
4✔
153
        }
4✔
154
        q := locationURL.Query()
4✔
155
        // Ensure per_page is set
4✔
156
        q.Set(PerPageQueryParam, strconv.FormatInt(*hint.PerPage, 10))
4✔
157
        links := make([]string, 0, 4)
4✔
158
        q.Set(PageQueryParam, "1")
4✔
159
        locationURL.RawQuery = q.Encode()
4✔
160
        links = append(links, fmt.Sprintf(
4✔
161
                "<%s>; rel=\"first\"", locationURL.String(),
4✔
162
        ))
4✔
163
        if (*hint.Page) > 1 {
7✔
164
                q.Set(PageQueryParam, strconv.FormatInt(*hint.Page-1, 10))
3✔
165
                locationURL.RawQuery = q.Encode()
3✔
166
                links = append(links, fmt.Sprintf(
3✔
167
                        "<%s>; rel=\"prev\"", locationURL.String(),
3✔
168
                ))
3✔
169
        }
3✔
170

171
        // TotalCount takes precedence over HasNext
172
        if hint.TotalCount != nil && *hint.TotalCount > 0 {
7✔
173
                lastPage := (*hint.TotalCount-1) / *hint.PerPage + 1
3✔
174
                if *hint.Page < lastPage {
6✔
175
                        // Add "next" link
3✔
176
                        q.Set(PageQueryParam, strconv.FormatUint(uint64(*hint.Page)+1, 10))
3✔
177
                        locationURL.RawQuery = q.Encode()
3✔
178
                        links = append(links, fmt.Sprintf(
3✔
179
                                "<%s>; rel=\"next\"", locationURL.String(),
3✔
180
                        ))
3✔
181
                }
3✔
182
                // Add "last" link
183
                q.Set(PageQueryParam, strconv.FormatInt(lastPage, 10))
3✔
184
                locationURL.RawQuery = q.Encode()
3✔
185
                links = append(links, fmt.Sprintf(
3✔
186
                        "<%s>; rel=\"last\"", locationURL.String(),
3✔
187
                ))
3✔
188
        } else if hint.HasNext != nil && *hint.HasNext {
6✔
189
                q.Set(PageQueryParam, strconv.FormatUint(uint64(*hint.Page)+1, 10))
2✔
190
                locationURL.RawQuery = q.Encode()
2✔
191
                links = append(links, fmt.Sprintf(
2✔
192
                        "<%s>; rel=\"next\"", locationURL.String(),
2✔
193
                ))
2✔
194
        }
2✔
195

196
        return links, nil
4✔
197
}
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

© 2025 Coveralls, Inc