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

Support modular paging #1996

Merged
merged 71 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
df26e39
Re-generate to support the paging feature
MaryGao Aug 29, 2023
46a92cd
Update the page return type
MaryGao Aug 29, 2023
b9873b0
Refactor code
MaryGao Aug 29, 2023
ea2c7bc
Update the comments
MaryGao Aug 29, 2023
9fcd40a
Update the cadl-ranch version
MaryGao Aug 29, 2023
9642e55
Update the test cases and re-gen smoke testing
MaryGao Aug 29, 2023
18987a0
update paths
MaryGao Aug 29, 2023
e8be6ad
Regenerate rlc codes in int testing
MaryGao Aug 29, 2023
d4371cd
Merge remote-tracking branch 'origin/main' into support-modular-paging2
MaryGao Sep 8, 2023
8039014
Update the changes
MaryGao Sep 8, 2023
62bae6e
Merge remote-tracking branch 'origin/main' into support-modular-paging2
MaryGao Sep 14, 2023
a835f80
Fix the build error
MaryGao Sep 14, 2023
38cc90c
Update the page utils
MaryGao Sep 14, 2023
84b2a3d
Update the generation
MaryGao Sep 14, 2023
8df91b2
update the generated paging
MaryGao Sep 14, 2023
a35c600
Generate the smoke testing
MaryGao Sep 14, 2023
be9beb8
Fix the load testing issues
MaryGao Sep 14, 2023
8036a36
update the comments
MaryGao Sep 14, 2023
13c7892
Merge branch 'main' into support-modular-paging2
MaryGao Nov 7, 2023
aaf4eec
Merge remote-tracking branch 'origin/main' into support-modular-paging2
MaryGao Nov 8, 2023
52ea227
Rebuild and update the build issue
MaryGao Nov 8, 2023
960263f
Update the ranch version
MaryGao Nov 8, 2023
e7ba9ce
Merge branch 'main' into support-modular-paging2
MaryGao Nov 14, 2023
0c267e6
Merge remote-tracking branch 'origin/main' into support-modular-paging2
MaryGao Nov 14, 2023
068b10b
Refactor the paging codes
MaryGao Nov 15, 2023
28afe21
Update paging types
MaryGao Nov 15, 2023
33363f2
Update the batch generations
MaryGao Nov 15, 2023
a9c5b48
Update the codegen
MaryGao Nov 15, 2023
ea4b6aa
Re-generate the batch modular
MaryGao Nov 15, 2023
a10c6d3
Re-generate the integration testings
MaryGao Nov 15, 2023
e6c9c53
Update the integrate for paging
MaryGao Nov 15, 2023
04f6e24
Fix lint issues
MaryGao Nov 15, 2023
931cf4d
Fix lint issues
MaryGao Nov 15, 2023
448846d
Update the paging for smoke testings
MaryGao Nov 15, 2023
917e9bc
Refactor code
MaryGao Nov 15, 2023
37d5626
Refactor code
MaryGao Nov 15, 2023
963d5df
Refactor code
MaryGao Nov 15, 2023
494e616
Update packages/typespec-ts/test/commands/cadl-ranch-list.ts
MaryGao Nov 15, 2023
fa6c1de
Update packages/typespec-ts/test/commands/cadl-ranch-list.ts
MaryGao Nov 15, 2023
27c25d4
Update packages/typespec-ts/test/commands/cadl-ranch-list.ts
MaryGao Nov 15, 2023
e3fd177
Update packages/typespec-ts/test/commands/cadl-ranch-list.ts
MaryGao Nov 15, 2023
ee3b29a
Add comments for interface
MaryGao Nov 16, 2023
5dfb203
Merge remote-tracking branch 'origin/main' into support-modular-paging2
MaryGao Nov 16, 2023
dfd18ea
Resolve comments
MaryGao Nov 16, 2023
d7877c3
Update the modular paging regen
MaryGao Nov 16, 2023
7f6c9ab
Update the helpers and regenerate codes
MaryGao Nov 20, 2023
fa5c2fb
Remove useless types in pagings
MaryGao Nov 20, 2023
bd42574
Remove useless types in pagings
MaryGao Nov 20, 2023
4e95ced
Regen for paging in load testing modular
MaryGao Nov 20, 2023
c090215
Merge branch 'main' into support-modular-paging2
MaryGao Nov 20, 2023
e2aa5d9
Merge remote-tracking branch 'origin/main' into support-modular-paging2
MaryGao Nov 21, 2023
c7f7b7e
Regenerate the pagings not reference core's one
MaryGao Nov 21, 2023
e090a70
Update the paging cases in widget_dpg.api
MaryGao Nov 21, 2023
f8cf5a3
Resolve some comments
MaryGao Nov 21, 2023
677c526
Resolve some comments
MaryGao Nov 21, 2023
40dfbf1
Resolve some comments
MaryGao Nov 21, 2023
d39aa9e
Regenerate the paging helpers
MaryGao Nov 21, 2023
416064b
Report warning if no items defined
MaryGao Nov 21, 2023
e650d4b
Merge remote-tracking branch 'origin/main' into support-modular-paging2
MaryGao Nov 22, 2023
56c43ba
Resolve conflicts
MaryGao Nov 22, 2023
ec6444b
Update the changes
MaryGao Nov 22, 2023
08843fb
Update the extension TPageSettings extends PageSettings
MaryGao Nov 22, 2023
fd1b1ee
Update the no need export
MaryGao Nov 22, 2023
cf997fc
Remove useless comments
MaryGao Nov 22, 2023
1af1421
Regenerate the codes
MaryGao Nov 22, 2023
2539366
Update packages/typespec-test/test/contentsafety_modular/generated/ty…
MaryGao Nov 23, 2023
b002b3b
Update packages/typespec-test/test/contentsafety_modular/generated/ty…
MaryGao Nov 23, 2023
7d13c24
Regenerate codes to resolve comments
MaryGao Nov 23, 2023
0a4a75e
Merge remote-tracking branch 'origin/main' into support-modular-paging2
MaryGao Nov 27, 2023
88d3ffd
Re-generate the paging modular sdk
MaryGao Nov 27, 2023
09696c0
Add paging cases in modular
MaryGao Nov 27, 2023
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
8 changes: 4 additions & 4 deletions common/config/rush/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { ClientOptions } from '@azure-rest/core-client';
import { KeyCredential } from '@azure/core-auth';
import { OperationOptions } from '@azure-rest/core-client';
import { PagedAsyncIterableIterator } from '@azure/core-paging';
import { TokenCredential } from '@azure/core-auth';

// @public (undocumented)
Expand Down Expand Up @@ -59,8 +60,8 @@ export class ContentSafetyClient {
deleteTextBlocklist(blocklistName: string, options?: DeleteTextBlocklistOptions): Promise<void>;
getTextBlocklist(blocklistName: string, options?: GetTextBlocklistOptions): Promise<TextBlocklist>;
getTextBlocklistItem(blocklistName: string, blockItemId: string, options?: GetTextBlocklistItemOptions): Promise<TextBlockItem>;
listTextBlocklistItems(blocklistName: string, options?: ListTextBlocklistItemsOptions): Promise<PagedTextBlockItem>;
listTextBlocklists(options?: ListTextBlocklistsOptions): Promise<PagedTextBlocklist>;
listTextBlocklistItems(blocklistName: string, options?: ListTextBlocklistItemsOptions): PagedAsyncIterableIterator<TextBlockItem>;
listTextBlocklists(options?: ListTextBlocklistsOptions): PagedAsyncIterableIterator<TextBlocklist>;
removeBlockItems(blockItemIds: string[], blocklistName: string, options?: RemoveBlockItemsRequestOptions): Promise<void>;
}

Expand All @@ -78,6 +79,9 @@ export interface CreateOrUpdateTextBlocklistOptions extends OperationOptions {
export interface DeleteTextBlocklistOptions extends OperationOptions {
}

// @public
export function getContinuationToken(page: unknown): string | undefined;

// @public (undocumented)
export interface GetTextBlocklistItemOptions extends OperationOptions {
}
Expand Down Expand Up @@ -129,6 +133,9 @@ export interface PagedTextBlocklist {
export interface RemoveBlockItemsRequestOptions extends OperationOptions {
}

// @public (undocumented)
export function setContinuationToken(page: unknown, continuationToken: string | undefined): void;
MaryGao marked this conversation as resolved.
Show resolved Hide resolved

// @public
export interface TextAnalyzeSeverityResult {
category: TextCategory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import {
TextBlockItemInfo,
AddOrUpdateBlockItemsResult,
TextBlockItem,
PagedTextBlocklist,
PagedTextBlockItem,
} from "./models/models.js";
import {
AnalyzeTextRequestOptions,
Expand Down Expand Up @@ -40,6 +38,7 @@ import {
getTextBlocklistItem,
listTextBlocklistItems,
} from "./api/index.js";
import { PagedAsyncIterableIterator } from "@azure/core-paging";

export { ContentSafetyClientOptions } from "./api/ContentSafetyContext.js";

Expand Down Expand Up @@ -98,7 +97,7 @@ export class ContentSafetyClient {
/** Get all text blocklists details. */
listTextBlocklists(
options: ListTextBlocklistsOptions = { requestOptions: {} }
): Promise<PagedTextBlocklist> {
): PagedAsyncIterableIterator<TextBlocklist> {
return listTextBlocklists(this._client, options);
}

Expand Down Expand Up @@ -143,7 +142,7 @@ export class ContentSafetyClient {
listTextBlocklistItems(
blocklistName: string,
options: ListTextBlocklistItemsOptions = { requestOptions: {} }
): Promise<PagedTextBlockItem> {
): PagedAsyncIterableIterator<TextBlockItem> {
return listTextBlocklistItems(this._client, blocklistName, options);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
operationOptionsToRequestParameters,
} from "@azure-rest/core-client";
import { uint8ArrayToString } from "@azure/core-util";
import { PagedAsyncIterableIterator } from "@azure/core-paging";
import {
AnalyzeTextRequestOptions,
AnalyzeImageRequestOptions,
Expand All @@ -54,6 +55,7 @@ import {
GetTextBlocklistItemOptions,
ListTextBlocklistItemsOptions,
} from "../models/options.js";
import { buildPagedAsyncIterator } from "../util/pagingUtil.js";

export function _analyzeTextSend(
context: Client,
Expand Down Expand Up @@ -302,12 +304,16 @@ export async function _listTextBlocklistsDeserialize(
}

/** Get all text blocklists details. */
export async function listTextBlocklists(
export function listTextBlocklists(
context: Client,
options: ListTextBlocklistsOptions = { requestOptions: {} }
): Promise<PagedTextBlocklist> {
const result = await _listTextBlocklistsSend(context, options);
return _listTextBlocklistsDeserialize(result);
): PagedAsyncIterableIterator<TextBlocklist> {
return buildPagedAsyncIterator(
context,
_listTextBlocklistsSend,
_listTextBlocklistsDeserialize,
[context, options]
);
}

export function _addOrUpdateBlockItemsSend(
Expand Down Expand Up @@ -491,15 +497,15 @@ export async function _listTextBlocklistItemsDeserialize(
}

/** Get all blockItems in a text blocklist */
export async function listTextBlocklistItems(
export function listTextBlocklistItems(
context: Client,
blocklistName: string,
options: ListTextBlocklistItemsOptions = { requestOptions: {} }
): Promise<PagedTextBlockItem> {
const result = await _listTextBlocklistItemsSend(
): PagedAsyncIterableIterator<TextBlockItem> {
return buildPagedAsyncIterator(
context,
blocklistName,
options
_listTextBlocklistItemsSend,
_listTextBlocklistItemsDeserialize,
[context, blocklistName, options]
);
return _listTextBlocklistItemsDeserialize(result);
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ export {
GetTextBlocklistItemOptions,
ListTextBlocklistItemsOptions,
} from "./models/index.js";
export {
getContinuationToken,
setContinuationToken,
} from "./util/pagingUtil.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import {
getPagedAsyncIterator,
PagedAsyncIterableIterator,
PagedResult,
} from "@azure/core-paging";
import {
Client,
createRestError,
PathUncheckedResponse,
} from "@azure-rest/core-client";

export interface PageInfo {
continuationToken?: string;
}

const pageMap = new WeakMap<object, PageInfo>();

/**
* Given the last `.value` produced by the `byPage` iterator,
* returns a continuation token that can be used to begin paging from
* that point later.
* @param page An object from accessing `value` on the IteratorResult from a `byPage` iterator.
* @returns The continuation token that can be passed into byPage() during future calls.
*/
export function getContinuationToken(page: unknown): string | undefined {
if (typeof page !== "object" || page === null) {
return undefined;
}
return pageMap.get(page)?.continuationToken;
}

export function setContinuationToken(
page: unknown,
continuationToken: string | undefined
): void {
if (typeof page !== "object" || page === null || !continuationToken) {
return;
}
const pageInfo = pageMap.get(page) ?? {};
pageInfo.continuationToken = continuationToken;
pageMap.set(page, pageInfo);
}

/**
* Helper type to extract the type of an array
*/
export 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 is 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 strongly typed Iterator based on the response we get as second parameter
*/
export type PaginateReturn<TResult> = TResult extends
| {
body: { value?: infer TPage };
}
| {
body: { items?: infer TPage };
}
? GetArrayType<TPage>
: Array<unknown>;

export function buildPagedAsyncIterator<
TElement,
TResponse extends PathUncheckedResponse = PathUncheckedResponse
>(
client: Client,
initialSendFunction: (...args: any[]) => PromiseLike<TResponse>,
deserializeFunction: (result: TResponse) => Promise<unknown>,
sendFunctionArgs: any[] = []
): PagedAsyncIterableIterator<TElement> {
let firstRun = true;
let itemName: string, nextLinkName: string | undefined;
const firstPageLinkPlaceholder = "";
const pagedResult: PagedResult<TElement[]> = {
firstPageLink: firstPageLinkPlaceholder,
getPage: async (pageLink: string, maxPageSize?: number) => {
if (maxPageSize) {
throw new Error("maxPageSize is not supported by this operation.");
}
const result =
firstRun && pageLink === firstPageLinkPlaceholder
? await initialSendFunction(...sendFunctionArgs)
: await client.pathUnchecked(pageLink).get();
if (firstRun) {
const pageInfo = getPaginationProperties(result);
itemName = pageInfo.itemName;
nextLinkName = pageInfo.nextLinkName;
}
firstRun = false;
checkPagingRequest(result);
const results = await deserializeFunction(result as TResponse);
const nextLink = getNextLink(results, nextLinkName);
const values = getElements<TElement>(results, itemName);
setContinuationToken(values, nextLink);
return {
page: values,
nextPageLink: nextLink,
};
},
};

return getPagedAsyncIterator(pagedResult);
}

/**
* Gets for the value of nextLink in the body
*/
function getNextLink(body: unknown, nextLinkName?: string): string | undefined {
if (!nextLinkName) {
return undefined;
}

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`
);
}

return nextLink;
}

/**
* Gets the elements of the current request in the body.
*/
function getElements<T = unknown>(body: unknown, itemName: string): T[] {
const value = (body as Record<string, unknown>)[itemName] as T[];

// value has to be an array according to the x-ms-pageable extension.
// The fact that this must be an array is used above to calculate the
// type of elements in the page in PaginateReturn
if (!Array.isArray(value)) {
throw new Error(
`Couldn't paginate response\n Body doesn't contain an array property with name: ${itemName}`
);
}

return value ?? [];
}

/**
* Checks if a request failed
*/
function checkPagingRequest(response: PathUncheckedResponse): void {
const Http2xxStatusCodes = [
"200",
"201",
"202",
"203",
"204",
"205",
"206",
"207",
"208",
"226",
];
if (!Http2xxStatusCodes.includes(response.status)) {
throw createRestError(
`Pagination failed with unexpected statusCode ${response.status}`,
response
);
}
}

/**
* Extracts the itemName and nextLinkName from the initial response to use them for pagination
*/
function getPaginationProperties(initialResponse: PathUncheckedResponse) {
// Build a set with the passed custom nextLinkNames
const nextLinkNames = new Set(["nextLink"]);

// Build a set with the passed custom set of itemNames
const itemNames = new Set(["value", "items"]);

let nextLinkName: string | undefined;
let itemName: string | undefined;

for (const name of nextLinkNames) {
const nextLink = (initialResponse.body as Record<string, unknown>)[
name
] as string;
if (nextLink) {
nextLinkName = name;
break;
}
}

for (const name of itemNames) {
const item = (initialResponse.body as Record<string, unknown>)[
name
] as string;
if (item) {
itemName = name;
break;
}
}

if (!itemName) {
throw new Error(
`Couldn't paginate response\n Body doesn't contain an array property with name: ${[
...itemNames,
].join(" OR ")}`
);
}

return { itemName, nextLinkName };
}
Loading