Skip to content

Commit 0cb0d29

Browse files
feat(client): improve default client options support (#57)
1 parent dc521f6 commit 0cb0d29

File tree

3 files changed

+84
-45
lines changed

3 files changed

+84
-45
lines changed

client.go

+11-5
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,22 @@ type Client struct {
2929
Users *UserService
3030
}
3131

32+
// DefaultClientOptions read from the environment (GITPOD_API_KEY). This should be
33+
// used to initialize new clients.
34+
func DefaultClientOptions() []option.RequestOption {
35+
defaults := []option.RequestOption{option.WithEnvironmentProduction()}
36+
if o, ok := os.LookupEnv("GITPOD_API_KEY"); ok {
37+
defaults = append(defaults, option.WithBearerToken(o))
38+
}
39+
return defaults
40+
}
41+
3242
// NewClient generates a new client with the default option read from the
3343
// environment (GITPOD_API_KEY). The option passed in as arguments are applied
3444
// after these default arguments, and all option will be passed down to the
3545
// services and requests that this client makes.
3646
func NewClient(opts ...option.RequestOption) (r *Client) {
37-
defaults := []option.RequestOption{option.WithEnvironmentProduction()}
38-
if o, ok := os.LookupEnv("GITPOD_API_KEY"); ok {
39-
defaults = append(defaults, option.WithBearerToken(o))
40-
}
41-
opts = append(defaults, opts...)
47+
opts = append(DefaultClientOptions(), opts...)
4248

4349
r = &Client{Options: opts}
4450

internal/requestconfig/requestconfig.go

+38-5
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/gitpod-io/gitpod-sdk-go/internal/apierror"
2323
"github.com/gitpod-io/gitpod-sdk-go/internal/apiform"
2424
"github.com/gitpod-io/gitpod-sdk-go/internal/apiquery"
25+
"github.com/gitpod-io/gitpod-sdk-go/internal/param"
2526
)
2627

2728
func getDefaultHeaders() map[string]string {
@@ -77,7 +78,17 @@ func getPlatformProperties() map[string]string {
7778
}
7879
}
7980

80-
func NewRequestConfig(ctx context.Context, method string, u string, body interface{}, dst interface{}, opts ...func(*RequestConfig) error) (*RequestConfig, error) {
81+
type RequestOption interface {
82+
Apply(*RequestConfig) error
83+
}
84+
85+
type RequestOptionFunc func(*RequestConfig) error
86+
type PreRequestOptionFunc func(*RequestConfig) error
87+
88+
func (s RequestOptionFunc) Apply(r *RequestConfig) error { return s(r) }
89+
func (s PreRequestOptionFunc) Apply(r *RequestConfig) error { return s(r) }
90+
91+
func NewRequestConfig(ctx context.Context, method string, u string, body interface{}, dst interface{}, opts ...RequestOption) (*RequestConfig, error) {
8192
var reader io.Reader
8293

8394
contentType := "application/json"
@@ -174,10 +185,17 @@ func NewRequestConfig(ctx context.Context, method string, u string, body interfa
174185
return &cfg, nil
175186
}
176187

188+
func UseDefaultParam[T any](dst *param.Field[T], src *T) {
189+
if !dst.Present && src != nil {
190+
dst.Value = *src
191+
dst.Present = true
192+
}
193+
}
194+
177195
// RequestConfig represents all the state related to one request.
178196
//
179197
// Editing the variables inside RequestConfig directly is unstable api. Prefer
180-
// composing func(\*RequestConfig) error instead if possible.
198+
// composing the RequestOption instead if possible.
181199
type RequestConfig struct {
182200
MaxRetries int
183201
RequestTimeout time.Duration
@@ -517,7 +535,7 @@ func (cfg *RequestConfig) Execute() (err error) {
517535
return nil
518536
}
519537

520-
func ExecuteNewRequest(ctx context.Context, method string, u string, body interface{}, dst interface{}, opts ...func(*RequestConfig) error) error {
538+
func ExecuteNewRequest(ctx context.Context, method string, u string, body interface{}, dst interface{}, opts ...RequestOption) error {
521539
cfg, err := NewRequestConfig(ctx, method, u, body, dst, opts...)
522540
if err != nil {
523541
return err
@@ -551,12 +569,27 @@ func (cfg *RequestConfig) Clone(ctx context.Context) *RequestConfig {
551569
return new
552570
}
553571

554-
func (cfg *RequestConfig) Apply(opts ...func(*RequestConfig) error) error {
572+
func (cfg *RequestConfig) Apply(opts ...RequestOption) error {
555573
for _, opt := range opts {
556-
err := opt(cfg)
574+
err := opt.Apply(cfg)
557575
if err != nil {
558576
return err
559577
}
560578
}
561579
return nil
562580
}
581+
582+
func PreRequestOptions(opts ...RequestOption) (RequestConfig, error) {
583+
cfg := RequestConfig{}
584+
for _, opt := range opts {
585+
if _, ok := opt.(PreRequestOptionFunc); !ok {
586+
continue
587+
}
588+
589+
err := opt.Apply(&cfg)
590+
if err != nil {
591+
return cfg, err
592+
}
593+
}
594+
return cfg, nil
595+
}

option/requestoption.go

+35-35
Original file line numberDiff line numberDiff line change
@@ -21,30 +21,30 @@ import (
2121
// options pattern in our [README].
2222
//
2323
// [README]: https://pkg.go.dev/github.com/gitpod-io/gitpod-sdk-go#readme-requestoptions
24-
type RequestOption = func(*requestconfig.RequestConfig) error
24+
type RequestOption = requestconfig.RequestOption
2525

2626
// WithBaseURL returns a RequestOption that sets the BaseURL for the client.
2727
func WithBaseURL(base string) RequestOption {
2828
u, err := url.Parse(base)
2929
if err != nil {
3030
log.Fatalf("failed to parse BaseURL: %s\n", err)
3131
}
32-
return func(r *requestconfig.RequestConfig) error {
32+
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
3333
if u.Path != "" && !strings.HasSuffix(u.Path, "/") {
3434
u.Path += "/"
3535
}
3636
r.BaseURL = u
3737
return nil
38-
}
38+
})
3939
}
4040

4141
// WithHTTPClient returns a RequestOption that changes the underlying [http.Client] used to make this
4242
// request, which by default is [http.DefaultClient].
4343
func WithHTTPClient(client *http.Client) RequestOption {
44-
return func(r *requestconfig.RequestConfig) error {
44+
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
4545
r.HTTPClient = client
4646
return nil
47-
}
47+
})
4848
}
4949

5050
// MiddlewareNext is a function which is called by a middleware to pass an HTTP request
@@ -59,10 +59,10 @@ type Middleware = func(*http.Request, MiddlewareNext) (*http.Response, error)
5959
// WithMiddleware returns a RequestOption that applies the given middleware
6060
// to the requests made. Each middleware will execute in the order they were given.
6161
func WithMiddleware(middlewares ...Middleware) RequestOption {
62-
return func(r *requestconfig.RequestConfig) error {
62+
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
6363
r.Middlewares = append(r.Middlewares, middlewares...)
6464
return nil
65-
}
65+
})
6666
}
6767

6868
// WithMaxRetries returns a RequestOption that sets the maximum number of retries that the client
@@ -74,76 +74,76 @@ func WithMaxRetries(retries int) RequestOption {
7474
if retries < 0 {
7575
panic("option: cannot have fewer than 0 retries")
7676
}
77-
return func(r *requestconfig.RequestConfig) error {
77+
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
7878
r.MaxRetries = retries
7979
return nil
80-
}
80+
})
8181
}
8282

8383
// WithHeader returns a RequestOption that sets the header value to the associated key. It overwrites
8484
// any value if there was one already present.
8585
func WithHeader(key, value string) RequestOption {
86-
return func(r *requestconfig.RequestConfig) error {
86+
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
8787
r.Request.Header.Set(key, value)
8888
return nil
89-
}
89+
})
9090
}
9191

9292
// WithHeaderAdd returns a RequestOption that adds the header value to the associated key. It appends
9393
// onto any existing values.
9494
func WithHeaderAdd(key, value string) RequestOption {
95-
return func(r *requestconfig.RequestConfig) error {
95+
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
9696
r.Request.Header.Add(key, value)
9797
return nil
98-
}
98+
})
9999
}
100100

101101
// WithHeaderDel returns a RequestOption that deletes the header value(s) associated with the given key.
102102
func WithHeaderDel(key string) RequestOption {
103-
return func(r *requestconfig.RequestConfig) error {
103+
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
104104
r.Request.Header.Del(key)
105105
return nil
106-
}
106+
})
107107
}
108108

109109
// WithQuery returns a RequestOption that sets the query value to the associated key. It overwrites
110110
// any value if there was one already present.
111111
func WithQuery(key, value string) RequestOption {
112-
return func(r *requestconfig.RequestConfig) error {
112+
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
113113
query := r.Request.URL.Query()
114114
query.Set(key, value)
115115
r.Request.URL.RawQuery = query.Encode()
116116
return nil
117-
}
117+
})
118118
}
119119

120120
// WithQueryAdd returns a RequestOption that adds the query value to the associated key. It appends
121121
// onto any existing values.
122122
func WithQueryAdd(key, value string) RequestOption {
123-
return func(r *requestconfig.RequestConfig) error {
123+
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
124124
query := r.Request.URL.Query()
125125
query.Add(key, value)
126126
r.Request.URL.RawQuery = query.Encode()
127127
return nil
128-
}
128+
})
129129
}
130130

131131
// WithQueryDel returns a RequestOption that deletes the query value(s) associated with the key.
132132
func WithQueryDel(key string) RequestOption {
133-
return func(r *requestconfig.RequestConfig) error {
133+
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
134134
query := r.Request.URL.Query()
135135
query.Del(key)
136136
r.Request.URL.RawQuery = query.Encode()
137137
return nil
138-
}
138+
})
139139
}
140140

141141
// WithJSONSet returns a RequestOption that sets the body's JSON value associated with the key.
142142
// The key accepts a string as defined by the [sjson format].
143143
//
144144
// [sjson format]: https://github.com/tidwall/sjson
145145
func WithJSONSet(key string, value interface{}) RequestOption {
146-
return func(r *requestconfig.RequestConfig) (err error) {
146+
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) (err error) {
147147
if buffer, ok := r.Body.(*bytes.Buffer); ok {
148148
b := buffer.Bytes()
149149
b, err = sjson.SetBytes(b, key, value)
@@ -155,15 +155,15 @@ func WithJSONSet(key string, value interface{}) RequestOption {
155155
}
156156

157157
return fmt.Errorf("cannot use WithJSONSet on a body that is not serialized as *bytes.Buffer")
158-
}
158+
})
159159
}
160160

161161
// WithJSONDel returns a RequestOption that deletes the body's JSON value associated with the key.
162162
// The key accepts a string as defined by the [sjson format].
163163
//
164164
// [sjson format]: https://github.com/tidwall/sjson
165165
func WithJSONDel(key string) RequestOption {
166-
return func(r *requestconfig.RequestConfig) (err error) {
166+
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) (err error) {
167167
if buffer, ok := r.Body.(*bytes.Buffer); ok {
168168
b := buffer.Bytes()
169169
b, err = sjson.DeleteBytes(b, key)
@@ -175,32 +175,32 @@ func WithJSONDel(key string) RequestOption {
175175
}
176176

177177
return fmt.Errorf("cannot use WithJSONDel on a body that is not serialized as *bytes.Buffer")
178-
}
178+
})
179179
}
180180

181181
// WithResponseBodyInto returns a RequestOption that overwrites the deserialization target with
182182
// the given destination. If provided, we don't deserialize into the default struct.
183183
func WithResponseBodyInto(dst any) RequestOption {
184-
return func(r *requestconfig.RequestConfig) error {
184+
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
185185
r.ResponseBodyInto = dst
186186
return nil
187-
}
187+
})
188188
}
189189

190190
// WithResponseInto returns a RequestOption that copies the [*http.Response] into the given address.
191191
func WithResponseInto(dst **http.Response) RequestOption {
192-
return func(r *requestconfig.RequestConfig) error {
192+
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
193193
r.ResponseInto = dst
194194
return nil
195-
}
195+
})
196196
}
197197

198198
// WithRequestBody returns a RequestOption that provides a custom serialized body with the given
199199
// content type.
200200
//
201201
// body accepts an io.Reader or raw []bytes.
202202
func WithRequestBody(contentType string, body any) RequestOption {
203-
return func(r *requestconfig.RequestConfig) error {
203+
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
204204
if reader, ok := body.(io.Reader); ok {
205205
r.Body = reader
206206
return r.Apply(WithHeader("Content-Type", contentType))
@@ -212,17 +212,17 @@ func WithRequestBody(contentType string, body any) RequestOption {
212212
}
213213

214214
return fmt.Errorf("body must be a byte slice or implement io.Reader")
215-
}
215+
})
216216
}
217217

218218
// WithRequestTimeout returns a RequestOption that sets the timeout for
219219
// each request attempt. This should be smaller than the timeout defined in
220220
// the context, which spans all retries.
221221
func WithRequestTimeout(dur time.Duration) RequestOption {
222-
return func(r *requestconfig.RequestConfig) error {
222+
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
223223
r.RequestTimeout = dur
224224
return nil
225-
}
225+
})
226226
}
227227

228228
// WithEnvironmentProduction returns a RequestOption that sets the current
@@ -234,8 +234,8 @@ func WithEnvironmentProduction() RequestOption {
234234

235235
// WithBearerToken returns a RequestOption that sets the client setting "bearer_token".
236236
func WithBearerToken(value string) RequestOption {
237-
return func(r *requestconfig.RequestConfig) error {
237+
return requestconfig.RequestOptionFunc(func(r *requestconfig.RequestConfig) error {
238238
r.BearerToken = value
239239
return r.Apply(WithHeader("authorization", fmt.Sprintf("Bearer %s", r.BearerToken)))
240-
}
240+
})
241241
}

0 commit comments

Comments
 (0)