forked from klauspost/dawa
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathquery.go
237 lines (209 loc) · 5.3 KB
/
query.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
231
232
233
234
235
236
237
package dawa
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"github.com/kpawlik/geojson"
)
// DefaultHost is the default host used for queries.
var DefaultHost = "http://dawa.aws.dk"
type parameter interface {
Param() string
Key() string
IsMulti() bool
AllValues() []string
Merge(parameter) error
}
// A generic query structure
type query struct {
httpClient *http.Client
host string
path string
params map[string]parameter
keys []string // Keys in the order they were added
warnings []error
}
type queryGeoJSON struct {
query
}
// Add a key/value pair as additional parameter. It will be added as key=value on the URL.
// The values should not be delivered URL-encoded, that will be handled by the library.
func (q *query) Add(key, value string) {
q.add(&textQuery{Name: key, Values: []string{value}, Multi: false, Null: true})
}
// This will return any warnings that may have been generated while building the query.
func (q query) Warnings() []error {
return q.warnings
}
// Returns true if any warnings have been generated.
func (q query) HasWarnings() bool {
return len(q.warnings) > 0
}
func (q *query) add(p parameter) {
if q.params == nil {
q.params = make(map[string]parameter)
}
key := p.Key()
pOld, ok := q.params[key]
if ok {
if !p.IsMulti() {
q.warnings = append(q.warnings, fmt.Errorf("Ignoring second value of key %s", key))
return
}
err := pOld.Merge(p)
if err != nil {
q.warnings = append(q.warnings, fmt.Errorf("Error while adding second value of key %s:%s", key, err.Error()))
}
q.params[key] = pOld
return
}
q.keys = append(q.keys, key)
q.params[key] = p
}
// WithHTTPClient allows overriding the HTTP Client for this query.
func (q *query) WithHTTPClient(httpClient *http.Client) {
q.httpClient = httpClient
}
// WithHost allows overriding the host for this query.
//
// The default value is http://dawa.aws.dk
func (q *query) WithHost(s string) {
q.host = s
}
// Replace the path of the query with something else.
func (q *query) OnPath(s string) {
q.path = s
}
// Returns the URL for the generated query.
func (q query) URL() string {
out := q.host + q.path
if len(q.params) == 0 {
return out
}
out += "?"
for i, key := range q.keys {
out += q.params[key].Param()
if i < len(q.keys)-1 {
out += "&"
}
}
return out
}
type textQuery struct {
Name string
Values []string
Multi bool
Null bool
}
// Merge other parameters into this one.
func (t *textQuery) Merge(other parameter) error {
if t.Key() != other.Key() {
return fmt.Errorf("merge: key value mismatch '%s' != '%s'", t.Key(), other.Key())
}
if !t.Multi {
return fmt.Errorf("merge: cannot merge multiple values of key %s", t.Key())
}
t.Values = append(t.Values, other.AllValues()...)
return nil
}
// Returns the entire parameter
func (t textQuery) Param() string {
out := url.QueryEscape(t.Name) + "="
if t.Null && len(t.Values) == 0 {
return out
}
for i, val := range t.Values {
out += url.QueryEscape(val)
if !t.Multi {
break
}
if i < len(t.Values)-1 {
out += "|"
}
}
return out
}
// Return values as string array, unencoded
func (t textQuery) AllValues() []string {
return t.Values
}
func (t textQuery) Key() string {
return t.Name
}
func (t textQuery) IsMulti() bool {
return t.Multi
}
type RequestError struct {
Type string `json:"type"`
Title string `json:"title"`
Details []interface{} `json:"details"`
URL string
}
func (r RequestError) Error() string {
if r.Type == "" {
return fmt.Sprintf("Error with request %s", r.URL)
}
return fmt.Sprintf("%s:%s. Details:%v. Request URL:%s", r.Type, r.Title, r.Details, r.URL)
}
// Perform the Request, and return the request result.
// If an error occurs during the request, or an error is reported
// this is returned.
// In some cases the error will be a RequestError type.
func (q query) Request() (io.ReadCloser, error) {
url := q.URL()
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
if err != nil {
return nil, err
}
resp, err := q.httpClient.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode < http.StatusBadRequest {
return resp.Body, nil
}
u, e2 := io.ReadAll(resp.Body)
resp.Body.Close()
if e2 != nil || len(u) == 0 {
return nil, fmt.Errorf("Error with request %s", url)
}
var rerr RequestError
_ = json.Unmarshal(u, &rerr)
rerr.URL = url
return nil, rerr
}
// Perform the Request, and return the request result as a geojson featurecollection.
// If an error occurs during the request, or an error is reported
// this is returned.
// In some cases the error will be a RequestError type.
func (q queryGeoJSON) GeoJSON() (*geojson.FeatureCollection, error) {
q.Add("format", "geojson")
url := q.URL()
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
if err != nil {
return nil, err
}
resp, err := q.httpClient.Do(req)
if err != nil {
return nil, err
}
u, err := io.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil || len(u) == 0 {
return nil, fmt.Errorf("Error with request %s", url)
}
if resp.StatusCode >= http.StatusBadRequest {
rerr := RequestError{URL: url}
_ = json.Unmarshal(u, &rerr)
return nil, rerr
}
var fc geojson.FeatureCollection
err = json.Unmarshal(u, &fc)
if err != nil {
return nil, err
}
return &fc, nil
}