Skip to content

Commit cd1cd87

Browse files
authored
Add FetcherForNextLinkOptions for extensibility (Azure#21418)
* Add FetcherForNextLinkOptions for extensibility This includes support for paged operations that use next link method instead of a vanilla HTTP GET on the next link URL. * add error test case for NextReq
1 parent 072a486 commit cd1cd87

File tree

3 files changed

+51
-18
lines changed

3 files changed

+51
-18
lines changed

sdk/azcore/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
### Features Added
66

7-
* Added function `FetcherForNextLink` to the `runtime` package to centralize creation of `Pager[T].Fetcher` from a next link URL.
7+
* Added function `FetcherForNextLink` and `FetcherForNextLinkOptions` to the `runtime` package to centralize creation of `Pager[T].Fetcher` from a next link URL.
88

99
### Breaking Changes
1010

sdk/azcore/runtime/pager.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,30 @@ func (p *Pager[T]) UnmarshalJSON(data []byte) error {
9191
return json.Unmarshal(data, &p.current)
9292
}
9393

94+
// FetcherForNextLinkOptions contains the optional values for [FetcherForNextLink].
95+
type FetcherForNextLinkOptions struct {
96+
// NextReq is the func to be called when requesting subsequent pages.
97+
// Used for paged operations that have a custom next link operation.
98+
NextReq func(context.Context, string) (*policy.Request, error)
99+
}
100+
94101
// FetcherForNextLink is a helper containing boilerplate code to simplify creating a PagingHandler[T].Fetcher from a next link URL.
95-
func FetcherForNextLink(ctx context.Context, pl Pipeline, nextLink string, createReq func(context.Context) (*policy.Request, error)) (*http.Response, error) {
102+
// - ctx is the [context.Context] controlling the lifetime of the HTTP operation
103+
// - pl is the [Pipeline] used to dispatch the HTTP request
104+
// - nextLink is the URL used to fetch the next page. the empty string indicates the first page is to be requested
105+
// - firstReq is the func to be called when creating the request for the first page
106+
// - options contains any optional parameters, pass nil to accept the default values
107+
func FetcherForNextLink(ctx context.Context, pl Pipeline, nextLink string, firstReq func(context.Context) (*policy.Request, error), options *FetcherForNextLinkOptions) (*http.Response, error) {
96108
var req *policy.Request
97109
var err error
98110
if nextLink == "" {
99-
req, err = createReq(ctx)
111+
req, err = firstReq(ctx)
100112
} else if nextLink, err = EncodeQueryParams(nextLink); err == nil {
101-
req, err = NewRequest(ctx, http.MethodGet, nextLink)
113+
if options != nil && options.NextReq != nil {
114+
req, err = options.NextReq(ctx, nextLink)
115+
} else {
116+
req, err = NewRequest(ctx, http.MethodGet, nextLink)
117+
}
102118
}
103119
if err != nil {
104120
return nil, err

sdk/azcore/runtime/pager_test.go

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -264,52 +264,69 @@ func TestFetcherForNextLink(t *testing.T) {
264264
pl := exported.NewPipeline(srv)
265265

266266
srv.AppendResponse()
267-
createReqCalled := false
267+
firstReqCalled := false
268268
resp, err := FetcherForNextLink(context.Background(), pl, "", func(ctx context.Context) (*policy.Request, error) {
269-
createReqCalled = true
269+
firstReqCalled = true
270270
return NewRequest(ctx, http.MethodGet, srv.URL())
271-
})
271+
}, nil)
272272
require.NoError(t, err)
273-
require.True(t, createReqCalled)
273+
require.True(t, firstReqCalled)
274274
require.NotNil(t, resp)
275275
require.EqualValues(t, http.StatusOK, resp.StatusCode)
276276

277277
srv.AppendResponse()
278-
createReqCalled = false
278+
firstReqCalled = false
279+
nextReqCalled := false
279280
resp, err = FetcherForNextLink(context.Background(), pl, srv.URL(), func(ctx context.Context) (*policy.Request, error) {
280-
createReqCalled = true
281+
firstReqCalled = true
281282
return NewRequest(ctx, http.MethodGet, srv.URL())
283+
}, &FetcherForNextLinkOptions{
284+
NextReq: func(ctx context.Context, s string) (*policy.Request, error) {
285+
nextReqCalled = true
286+
return NewRequest(ctx, http.MethodGet, srv.URL())
287+
},
282288
})
283289
require.NoError(t, err)
284-
require.False(t, createReqCalled)
290+
require.False(t, firstReqCalled)
291+
require.True(t, nextReqCalled)
285292
require.NotNil(t, resp)
286293
require.EqualValues(t, http.StatusOK, resp.StatusCode)
287294

288295
resp, err = FetcherForNextLink(context.Background(), pl, "", func(ctx context.Context) (*policy.Request, error) {
289296
return nil, errors.New("failed")
297+
}, &FetcherForNextLinkOptions{})
298+
require.Error(t, err)
299+
require.Nil(t, resp)
300+
301+
resp, err = FetcherForNextLink(context.Background(), pl, srv.URL(), func(ctx context.Context) (*policy.Request, error) {
302+
return nil, nil
303+
}, &FetcherForNextLinkOptions{
304+
NextReq: func(ctx context.Context, s string) (*policy.Request, error) {
305+
return nil, errors.New("failed")
306+
},
290307
})
291308
require.Error(t, err)
292309
require.Nil(t, resp)
293310

294311
srv.AppendError(errors.New("failed"))
295312
resp, err = FetcherForNextLink(context.Background(), pl, "", func(ctx context.Context) (*policy.Request, error) {
296-
createReqCalled = true
313+
firstReqCalled = true
297314
return NewRequest(ctx, http.MethodGet, srv.URL())
298-
})
315+
}, &FetcherForNextLinkOptions{})
299316
require.Error(t, err)
300-
require.True(t, createReqCalled)
317+
require.True(t, firstReqCalled)
301318
require.Nil(t, resp)
302319

303320
srv.AppendResponse(mock.WithStatusCode(http.StatusBadRequest), mock.WithBody([]byte(`{ "error": { "code": "InvalidResource", "message": "doesn't exist" } }`)))
304-
createReqCalled = false
321+
firstReqCalled = false
305322
resp, err = FetcherForNextLink(context.Background(), pl, srv.URL(), func(ctx context.Context) (*policy.Request, error) {
306-
createReqCalled = true
323+
firstReqCalled = true
307324
return NewRequest(ctx, http.MethodGet, srv.URL())
308-
})
325+
}, nil)
309326
require.Error(t, err)
310327
var respErr *exported.ResponseError
311328
require.ErrorAs(t, err, &respErr)
312329
require.EqualValues(t, "InvalidResource", respErr.ErrorCode)
313-
require.False(t, createReqCalled)
330+
require.False(t, firstReqCalled)
314331
require.Nil(t, resp)
315332
}

0 commit comments

Comments
 (0)