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

[@azure-rest/core-paging] Use @azure/core-paging's getPagedAsyncIterator #16891

Merged
merged 7 commits into from
Aug 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions sdk/core/core-client-paging-rest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"docs": "typedoc --excludePrivate --excludeNotExported --excludeExternals --stripInternal --mode file --out ./dist/docs ./src",
"execute:samples": "echo skipped",
"extract-api": "tsc -p . && api-extractor run --local",
"format": "prettier --write --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"samples-dev/**/*.ts\" \"*.{js,json}\"",
"format": "prettier --write --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\"",
"integration-test:browser": "echo skipped",
"integration-test:node": "echo skipped",
"integration-test": "npm run integration-test:node && npm run integration-test:browser",
Expand Down Expand Up @@ -58,7 +58,7 @@
"sideEffects": false,
"prettier": "@azure/eslint-plugin-azure-sdk/prettier.json",
"dependencies": {
"@azure/core-paging": "^1.1.1",
"@azure/core-paging": "^1.2.0",
"@azure/core-rest-pipeline": "^1.1.0",
"@azure-rest/core-client": "1.0.0-beta.6",
"tslib": "^2.2.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export interface PaginateOptions {
}

// @public
export function paginateResponse<TReturn>(client: Client, initialResponse: HttpResponse, options?: PaginateOptions): PagedAsyncIterableIterator<TReturn, TReturn[]>;
export function paginateResponse<TElement>(client: Client, initialResponse: HttpResponse, options?: PaginateOptions): PagedAsyncIterableIterator<TElement>;


```
84 changes: 25 additions & 59 deletions sdk/core/core-client-paging-rest/src/paginate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
HttpResponse,
PathUncheckedResponse,
} from "@azure-rest/core-client";
import { PagedAsyncIterableIterator } from "@azure/core-paging";
import { getPagedAsyncIterator, PagedAsyncIterableIterator, PagedResult } from "@azure/core-paging";

const Http2xxStatusCodes = ["200", "201", "202", "203", "204", "205", "206", "207", "208", "226"];

Expand Down Expand Up @@ -42,67 +42,36 @@ export interface PaginateOptions {
* @param options - Options to use custom property names for pagination
* @returns - return a PagedAsyncIterableIterator that can be used to iterate the elements
*/
export function paginateResponse<TReturn>(
export function paginateResponse<TElement>(
client: Client,
initialResponse: HttpResponse,
options: PaginateOptions = {}
): PagedAsyncIterableIterator<TReturn, TReturn[]> {
const iter = listAll<TReturn>(client, initialResponse, options);
return {
next() {
return iter.next();
},
[Symbol.asyncIterator]() {
return this;
},
byPage: () => {
return listPage<TReturn>(client, initialResponse, options);
): PagedAsyncIterableIterator<TElement> {
let firstRun = true;
const pagedResult: PagedResult<TElement[]> = {
firstPageLink: "",
async getPage(pageLink: string) {
const result = firstRun ? initialResponse : await client.pathUnchecked(pageLink).get();
firstRun = false;
checkPagingRequest(result);
const nextLink = getNextLink(result.body, options);
const values = getElements<TElement>(result.body, options);
return {
page: values,
// According to x-ms-pageable is the nextLinkName is set to null we should only
// return the first page and skip any additional queries even if the initial response
// contains a nextLink.
nextPageLink: options.nextLinkName === null ? undefined : nextLink,
};
},
};
}

async function* listAll<T>(
client: Client,
initialResponse: PathUncheckedResponse,
paginateOptions: PaginateOptions
): AsyncIterableIterator<T> {
for await (const page of listPage<T>(client, initialResponse, paginateOptions)) {
yield* page;
}
}

async function* listPage<T = Record<string, unknown>[]>(
client: Client,
initialResponse: PathUncheckedResponse,
options: PaginateOptions
): AsyncIterableIterator<T[]> {
let result = initialResponse;
checkPagingRequest(result);
let nextLink = getNextLink(result.body, options);
let values = getElements<T>(result.body, options);

yield values;

// According to x-ms-pageable is the nextLinkName is set to null we should only
// return the first page and skip any additional queries even if the initial response
// contains a nextLink.
if (options.nextLinkName === null) {
return;
}

while (nextLink) {
result = await client.pathUnchecked(nextLink).get();
checkPagingRequest(result);
nextLink = getNextLink(result.body, options);
values = getElements<T>(result.body, options);
yield values;
}
return getPagedAsyncIterator(pagedResult);
}

/**
* Checks if a request failed
*/
function checkPagingRequest(response: PathUncheckedResponse) {
function checkPagingRequest(response: PathUncheckedResponse): void {
if (!Http2xxStatusCodes.includes(response.status)) {
throw createRestError(
`Pagination failed with unexpected statusCode ${response.status}`,
Expand All @@ -114,9 +83,9 @@ function checkPagingRequest(response: PathUncheckedResponse) {
/**
* Gets for the value of nextLink in the body. If a custom nextLinkName was provided, it will be used instead of default
*/
function getNextLink(body: Record<string, unknown>, paginateOptions: PaginateOptions = {}) {
function getNextLink(body: unknown, paginateOptions: PaginateOptions = {}): string | undefined {
const nextLinkName = paginateOptions.nextLinkName ?? DEFAULT_NEXTLINK;
const nextLink = body[nextLinkName];
const nextLink = (body as Record<string, unknown>)[nextLinkName];

if (typeof nextLink !== "string" && typeof nextLink !== "undefined") {
throw new Error(`Body Property ${nextLinkName} should be a string or undefined`);
Expand All @@ -129,12 +98,9 @@ function getNextLink(body: Record<string, unknown>, paginateOptions: PaginateOpt
* Gets the elements of the current request in the body. By default it will look in the `value` property unless
* a different value for itemName has been provided as part of the options.
*/
function getElements<T = unknown>(
body: Record<string, unknown>,
paginateOptions: PaginateOptions = {}
): T[] {
function getElements<T = unknown>(body: unknown, paginateOptions: PaginateOptions = {}): T[] {
const valueName = paginateOptions?.itemName ?? DEFAULT_VALUES;
const value = body[valueName];
const value = (body as Record<string, unknown>)[valueName];

if (!Array.isArray(value)) {
throw new Error(`Body Property ${valueName} is not an array`);
Expand Down
2 changes: 1 addition & 1 deletion sdk/core/core-client-paging-rest/test/paginate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ interface MockResponse {
* @param response - Responses to return, the actual request url is matched to one of the paths in the responses and the defined object is returned.
* if no path matches a 404 error is returned
*/
function mockResponse(client: Client, responses: MockResponse[]) {
function mockResponse(client: Client, responses: MockResponse[]): void {
let count = 0;

client.pipeline.addPolicy({
Expand Down