-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathodata.go
230 lines (204 loc) · 5.15 KB
/
odata.go
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package odata
import (
"fmt"
"strings"
"github.com/go-resty/resty/v2"
)
type RequestError struct {
Status int
StatusText string
Body string
}
func (r *RequestError) Error() string {
return fmt.Sprintf("request failed with status %d - %s: %q", r.Status, r.StatusText, r.Body)
}
// RequestProviders generate Resty requests that already contain a base URL and the necessary authentication information for the OData API
type RequestProvider interface {
NewRequest() (*resty.Request, error)
}
type Respose[V any] struct {
Context string `json:"@odata.context"`
Count uint64 `json:"@odata.count"`
Next string `json:"@odata.nextLink"`
Value []V `json:"value"`
}
// Result returns the data contained in the OData response
func (o *Respose[V]) Result() []V {
return o.Value
}
// Collect iterates through pages of OData results and collects them into the original result
func (o *Respose[V]) Collect(c RequestProvider) error {
for last := o; len(last.Next) > 0; {
req, err := c.NewRequest()
if err != nil {
return err
}
result := Respose[V]{}
res, err := req.SetResult(&result).Get(last.Next)
if err != nil {
return err
}
if res.IsError() {
return &RequestError{res.StatusCode(), res.Status(), res.String()}
}
o.Value = append(o.Value, result.Result()...)
last = &result
}
return nil
}
type Direction uint8
const (
Ascending Direction = iota
Descending
)
type Order map[string]Direction
func (o *Order) String() string {
sb := strings.Builder{}
for k, v := range *o {
if sb.Len() > 0 {
sb.WriteByte(',')
}
sb.WriteString(k)
switch v {
case Ascending:
sb.WriteString(" asc")
case Descending:
sb.WriteString(" desc")
}
}
return sb.String()
}
// Query is a builder type for an OData query
type Query[V any] struct {
client RequestProvider
url string
count bool
expand []string
filter string
orderBy Order
search string
selectKeys []string
skip uint64
top uint64
pathParams map[string]string
}
// NewQuery creates a new OData query for a specific URL that will be resolved with the provided RequestProvider
func NewQuery[V any](client RequestProvider, url string) *Query[V] {
return &Query[V]{
client: client,
url: url,
pathParams: make(map[string]string),
orderBy: make(Order),
}
}
// Count requests a count to be added to the OData response
func (o *Query[V]) Count() *Query[V] {
o.count = true
return o
}
// Expand selects which fields should be expanded
func (o *Query[V]) Expand(keys ...string) *Query[V] {
o.expand = keys
return o
}
// Filter sets a filter expression used to filter results
func (o *Query[V]) Filter(filter string) *Query[V] {
o.filter = filter
return o
}
// OrderBy defines the key and direction to order the results by
func (o *Query[V]) OrderBy(key string, direction ...Direction) *Query[V] {
if len(direction) > 0 {
o.orderBy[key] = direction[0]
} else {
o.orderBy[key] = 255
}
return o
}
// Search performs a search
func (o *Query[V]) Search(query string) *Query[V] {
o.search = query
return o
}
// Select defines the keys to request
func (o *Query[V]) Select(keys ...string) *Query[V] {
o.selectKeys = keys
return o
}
// Skip sets how many results should be skipped
func (o *Query[V]) Skip(num uint64) *Query[V] {
o.skip = num
return o
}
// Top limits the number of results
func (o *Query[V]) Top(num uint64) *Query[V] {
o.top = num
return o
}
// PathParam sets a path parameter for the OData query
func (o *Query[V]) PathParam(key, value string) *Query[V] {
o.pathParams[key] = value
return o
}
// Prepare creates a new OData request using the RequestProvider and sets the queries according to the builder functions
func (o *Query[V]) prepare() (*resty.Request, error) {
r, err := o.client.NewRequest()
if err != nil {
return nil, err
}
if o.count {
r.SetQueryParam("$count", "true")
}
if len(o.expand) > 0 {
r.SetQueryParam("$expand", strings.Join(o.expand, ","))
}
if len(o.filter) > 0 {
r.SetQueryParam("$filter", o.filter)
}
if len(o.orderBy) > 0 {
r.SetQueryParam("$orderby", o.orderBy.String())
}
if len(o.search) > 0 {
r.SetQueryParam("$search", o.search)
}
if len(o.selectKeys) > 0 {
r.SetQueryParam("$select", strings.Join(o.selectKeys, ","))
}
return r.SetPathParams(o.pathParams), nil
}
// Get performs a simple get on an OData API returning a single item
func (o *Query[V]) Get() (*V, error) {
r, err := o.prepare()
if err != nil {
return nil, err
}
result := new(V)
res, err := r.SetResult(result).Get(o.url)
if err != nil {
return nil, err
}
if res.IsError() {
return nil, &RequestError{res.StatusCode(), res.Status(), res.String()}
}
return result, nil
}
// GetAll performs an OData request for a set of items, iterating through all pages and collecting the results
func (o *Query[V]) GetAll() ([]V, error) {
r, err := o.prepare()
if err != nil {
return nil, err
}
result := Respose[V]{}
res, err := r.SetResult(&result).Get(o.url)
if err != nil {
return nil, err
}
if res.IsError() {
return nil, &RequestError{res.StatusCode(), res.Status(), res.String()}
}
err = result.Collect(o.client)
if err != nil {
return nil, err
}
return result.Value, nil
}