Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Core Rest] Add pagination helper for rest clients @azure-rest/core-client-paging #15831

Merged
merged 11 commits into from Jun 24, 2021
Merged

[Core Rest] Add pagination helper for rest clients @azure-rest/core-client-paging #15831

merged 11 commits into from Jun 24, 2021

Conversation

joheredi
Copy link
Member

@joheredi joheredi commented Jun 17, 2021

Paginate response is a helper function to handle pagination for the user. Given a response that contains a body with a link to the next page and an array with the current page of results, this helper returns a PagebleAsyncIterator that can be used to get all the items or page by page.

This helper is intended to be wrapped by generated code to provide more context, such as types and pagination options.

The following code snippet shows the user experience consuming a generated client that re-exposes the pagination function with extra context.

const client = TestClient("https://example.org", new DefaultAzureCredentials());

const response = await client.path("/foo").get();
const items = paginate(client, response);

for await (const item of items) {
  // Item would be strongly typed here
  console.log(item.name);
}

Here's how the generated SDK would consume and export this helper.

In order to provide better typings, the library that consumes paginateResponse can wrap it providing additional types. For example a code generator may consume and export in the following way

/**
 * This is the wrapper function that would be exposed. It is hiding the Pagination Options because it can be
 * obtained in the case of a generator from the Swagger definition or by a developer context knowledge in case of a
 * hand written library.
 */
export function paginate<TReturn extends PathUncheckedResponse>(
  client: Client,
  initialResponse: TReturn
): PagedAsyncIterableIterator<PaginateReturn<TReturn>, PaginateReturn<TReturn>[], {}> {
  return paginateResponse<PaginateReturn<TReturn>>(client, initialResponse, {
    // For example these values could come from the swagger
    itemName: "items",
    nextLinkName: "continuationLink",
  });
}

// Helper type to extract the type of an array
type GetArrayType<T> = T extends Array<infer TData> ? TData : never;

// Helper type to infer the Type of the paged elements from the response type
// This type will be generated based on the swagger information for x-ms-pageable
// specifically on the itemName property which indicates the property of the response
// where the page items are found. The default value is `value`.
// This type will allow us to provide stronly typed Iterator based on the response we get as second parameter
export type PaginateReturn<TResult> = TResult extends {
  body: { items: infer TPage };
}
  ? GetArrayType<TPage>
  : Array<unknown>;

Note: This work implements the standard pagination strategy supported by Autorest x-ms-pageable extension,

If a Service uses a non-standard pagination strategy. The SDK author would be able to implement it's own pagination helper in a similar way as we do here for paginateRequest and export a paginate function

sdk/core/core-client-rest/src/getClient.ts Outdated Show resolved Hide resolved
sdk/agrifood/agrifood-farming-rest/src/farmBeats.ts Outdated Show resolved Hide resolved
*/
function checkPagingRequest(response: PathUncheckedResponse) {
if (!Http2xxStatusCodes.includes(response.status)) {
throw (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering - is there some centralized logic that already exists in core-client that's used for throwing based on the status of a response? If so - would it make sense to use that here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point, I'll be working on an LRO helper next. If I find we could leverage this, I'll add it as a core helper so it can be referenced centrally

sdk/core/core-client-rest/src/paginate.ts Outdated Show resolved Hide resolved
sdk/core/core-client-rest/src/paginate.ts Outdated Show resolved Hide resolved
Copy link
Member

@xirzec xirzec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this approach a lot! Per our offline discussion I think we can go ahead with creating the helper packages.

sdk/core/core-client-rest/README.md Outdated Show resolved Hide resolved
/// <reference lib="esnext.asynciterable" />

import { Client, HttpResponse, PathUncheckedResponse } from "./";
import "@azure/core-paging";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is to make sure the below type import doesn't get erased and we don't load the async iterator polyfill?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was following what other libraries did when using core-paging. I think it is just to load the polyfill before we try to import anything from core-paging. I'm removing it since we now support Node 10+ so the polyfill shouldn't be needed anymore

[Symbol.asyncIterator]() {
return this;
},
byPage: () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about PageSettings for controlling page size? Or is that not standard enough at the moment?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately autorest's x-ms-pageable doesn't provide a way to set PagingSettings :(

sdk/core/core-client-rest/src/paginate.ts Outdated Show resolved Hide resolved
@check-enforcer
Copy link

This pull request is protected by Check Enforcer.

What is Check Enforcer?

Check Enforcer helps ensure all pull requests are covered by at least one check-run (typically an Azure Pipeline). When all check-runs associated with this pull request pass then Check Enforcer itself will pass.

Why am I getting this message?

You are getting this message because Check Enforcer did not detect any check-runs being associated with this pull request within five minutes. This may indicate that your pull request is not covered by any pipelines and so Check Enforcer is correctly blocking the pull request being merged.

What should I do now?

If the check-enforcer check-run is not passing and all other check-runs associated with this PR are passing (excluding license-cla) then you could try telling Check Enforcer to evaluate your pull request again. You can do this by adding a comment to this pull request as follows:
/check-enforcer evaluate
Typically evaulation only takes a few seconds. If you know that your pull request is not covered by a pipeline and this is expected you can override Check Enforcer using the following command:
/check-enforcer override
Note that using the override command triggers alerts so that follow-up investigations can occur (PRs still need to be approved as normal).

What if I am onboarding a new service?

Often, new services do not have validation pipelines associated with them, in order to bootstrap pipelines for a new service, you can issue the following command as a pull request comment:
/azp run prepare-pipelines
This will run a pipeline that analyzes the source tree and creates the pipelines necessary to build and validate your pull request. Once the pipeline has been created you can trigger the pipeline using the following comment:
/azp run js - [service] - ci

@joheredi
Copy link
Member Author

/azp run js - core - ci

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@joheredi joheredi changed the title [Core Rest] Add pagination helper to @azure-rest/core-client [Core Rest] Add pagination helper for rest clients @azure-rest/core-client-paging Jun 21, 2021
@joheredi
Copy link
Member Author

/azp run js - core - ci

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Copy link
Member

@xirzec xirzec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few last small nits but looks good!

sdk/agrifood/agrifood-farming-rest/src/farmBeats.ts Outdated Show resolved Hide resolved
sdk/core/core-client-rest/src/restError.ts Outdated Show resolved Hide resolved
sdk/core/core-client-rest/src/restError.ts Outdated Show resolved Hide resolved
sdk/core/core-client-rest/src/restError.ts Outdated Show resolved Hide resolved
@joheredi
Copy link
Member Author

/azp run prepare-pipelines

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@joheredi joheredi merged commit 23bb270 into Azure:main Jun 24, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants