-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathurl_builder.go
202 lines (179 loc) · 5.36 KB
/
url_builder.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
package httputil
import (
"net/url"
"path"
"strconv"
"strings"
"github.com/sudo-suhas/xgo/errors"
)
// URLBuilderSource is used to create URLBuilder instances.
//
// The URLBuilder created using URLBuilderSource will include the base
// URL and query parameters present on the URLBuilderSource instance.
//
// Ideally, URLBuilderSource instance should be created only once for a
// given base URL.
type URLBuilderSource struct {
base *url.URL
qry url.Values
}
// NewURLBuilderSource builds a URLBuilderSource instance by parsing the
// baseURL.
//
// The baseURL is expected to specify the host. If no scheme is
// specified, it defaults to http scheme.
func NewURLBuilderSource(baseURL string) (URLBuilderSource, error) {
const op = "httputil.NewURLBuilderSource"
u, err := url.Parse(baseURL)
if err != nil {
return URLBuilderSource{}, errors.E(errors.WithOp(op), errors.InvalidInput, errors.WithErr(err))
}
if u.Scheme == "" {
return NewURLBuilderSource("http://" + baseURL)
}
return URLBuilderSource{
base: u,
qry: u.Query(),
}, nil
}
// NewURLBuilder creates a new instance of URLBuilder with the base URL
// and query parameters carried over from URLBuilderSource.
func (b URLBuilderSource) NewURLBuilder() *URLBuilder {
u := *b.base // create a copy
return &URLBuilder{
url: &u,
pathParams: make(map[string]string),
qry: urlValuesCopy(b.qry),
}
}
// URLBuilder is used to build a URL.
//
// URLBuilderSource should be used to create an instance of URLBuilder.
//
// b, err := httputil.NewURLBuilderSource("https://api.example.com/")
// if err != nil {
// // handle error
// }
//
// u := b.NewURLBuilder().
// Path("/users/{id}/posts").
// PathParam("id", id).
// QueryParam("limit", limit).
// QueryParam("offset", offset).
// URL()
//
// // { id: 123, limit: 10, offset: 120 }
// // https://api.example.com/users/123/posts?limit=10&offset=120
//
// r, err := http.NewRequestWithContext(context.Background(), http.MethodGet, u.String(), nil)
// if err != nil {
// // handle error
// }
//
// // send HTTP request.
type URLBuilder struct {
url *url.URL
path string
pathParams map[string]string
qry url.Values
}
// Path sets the path template for the URL.
//
// Path parameters of the format "{paramName}" are supported and can be
// substituted using PathParam*.
func (u *URLBuilder) Path(p string) *URLBuilder {
u.path = p
return u
}
// PathParam sets the path parameter name and value which needs to be
// substituted in the path template. Substitution happens when the URL
// is built using URLBuilder.URL()
func (u *URLBuilder) PathParam(name, value string) *URLBuilder {
u.pathParams[name] = value
return u
}
// PathParamInt sets the path parameter name and value which needs to be
// substituted in the path template. Substitution happens when the URL
// is built using URLBuilder.URL()
func (u *URLBuilder) PathParamInt(name string, value int64) *URLBuilder {
u.pathParams[name] = strconv.FormatInt(value, 10)
return u
}
// PathParams sets the path parameter names and values which need to be
// substituted in the path template. Substitution happens when the URL
// is built using URLBuilder.URL()
func (u *URLBuilder) PathParams(params map[string]string) *URLBuilder {
for name, value := range params {
u.pathParams[name] = value
}
return u
}
// QueryParam sets the query parameter with the given values. If a value
// was previously set, it is replaced.
func (u *URLBuilder) QueryParam(key string, values ...string) *URLBuilder {
u.qry.Del(key)
for _, v := range values {
u.qry.Add(key, v)
}
return u
}
// QueryParamInt sets the query parameter with the given values. If a
// value was previously set, it is replaced.
func (u *URLBuilder) QueryParamInt(key string, values ...int64) *URLBuilder {
u.qry.Del(key)
for _, v := range values {
u.qry.Add(key, strconv.FormatInt(v, 10))
}
return u
}
// QueryParamBool sets the query parameter with the given value. If a
// value was previously set, it is replaced.
func (u *URLBuilder) QueryParamBool(key string, value bool) *URLBuilder {
u.qry.Set(key, strconv.FormatBool(value))
return u
}
// QueryParamFloat sets the query parameter with the given values. If a
// value was previously set, it is replaced.
func (u *URLBuilder) QueryParamFloat(key string, values ...float64) *URLBuilder {
u.qry.Del(key)
for _, v := range values {
u.qry.Add(key, strconv.FormatFloat(v, 'f', -1, 64))
}
return u
}
// QueryParams sets the query parameters. If a value was previously set
// for any of the given parameters, it is replaced.
func (u *URLBuilder) QueryParams(params url.Values) *URLBuilder {
for key, values := range params {
u.qry.Del(key)
for _, v := range values {
u.qry.Add(key, v)
}
}
return u
}
// URL constructs and returns an instance of URL.
//
// The constructed URL has the complete path and query parameters setup.
// The path parameters are substituted before being joined with the base
// URL.
func (u *URLBuilder) URL() *url.URL {
urlv := u.url
p := u.path
for name, value := range u.pathParams {
p = strings.ReplaceAll(p, "{"+name+"}", url.PathEscape(value))
}
urlv.Path = path.Join(urlv.Path, p)
urlv.RawQuery = u.qry.Encode()
return urlv
}
func urlValuesCopy(src url.Values) url.Values {
dst := make(url.Values, len(src))
for key, values := range src {
dst[key] = make([]string, 0, len(values))
for _, v := range values {
dst.Add(key, v)
}
}
return dst
}