diff --git a/sdk/purview/purview-sharing-rest/CHANGELOG.md b/sdk/purview/purview-sharing-rest/CHANGELOG.md index 50a2b8747011..baeb8d70db0f 100644 --- a/sdk/purview/purview-sharing-rest/CHANGELOG.md +++ b/sdk/purview/purview-sharing-rest/CHANGELOG.md @@ -1,5 +1,10 @@ # Release History +## 1.0.0-beta.3 (2024-12-16) + +### Features Added +- refresh @azure-rest/purview-sharing + ## 1.0.0-beta.2 (2023-06-23) ### Features Added diff --git a/sdk/purview/purview-sharing-rest/package.json b/sdk/purview/purview-sharing-rest/package.json index d19efb6722db..0099893e9348 100644 --- a/sdk/purview/purview-sharing-rest/package.json +++ b/sdk/purview/purview-sharing-rest/package.json @@ -2,7 +2,7 @@ "name": "@azure-rest/purview-sharing", "sdk-type": "client", "author": "Microsoft Corporation", - "version": "1.0.0-beta.2", + "version": "1.0.0-beta.3", "description": "A generated SDK for PurviewSharing.", "keywords": [ "node", @@ -61,8 +61,7 @@ "@azure-rest/core-client": "^2.3.1", "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", - "@azure/core-lro": "^2.7.2", - "@azure/core-paging": "^1.6.2", + "@azure/core-lro": "^3.0.0", "@azure/core-rest-pipeline": "^1.18.0", "@azure/logger": "^1.1.4", "tslib": "^2.8.1" diff --git a/sdk/purview/purview-sharing-rest/review/purview-sharing.api.md b/sdk/purview/purview-sharing-rest/review/purview-sharing.api.md index 4addf4e91d6e..f4408c653e75 100644 --- a/sdk/purview/purview-sharing-rest/review/purview-sharing.api.md +++ b/sdk/purview/purview-sharing-rest/review/purview-sharing.api.md @@ -4,17 +4,17 @@ ```ts +import type { AbortSignalLike } from '@azure/abort-controller'; +import type { CancelOnProgress } from '@azure/core-lro'; import type { Client } from '@azure-rest/core-client'; import type { ClientOptions } from '@azure-rest/core-client'; import type { CreateHttpPollerOptions } from '@azure/core-lro'; import type { HttpResponse } from '@azure-rest/core-client'; import type { OperationState } from '@azure/core-lro'; -import type { PagedAsyncIterableIterator } from '@azure/core-paging'; import type { PathUncheckedResponse } from '@azure-rest/core-client'; import type { RawHttpHeaders } from '@azure/core-rest-pipeline'; import type { RawHttpHeadersInput } from '@azure/core-rest-pipeline'; import type { RequestParameters } from '@azure-rest/core-client'; -import type { SimplePollerLike } from '@azure/core-lro'; import type { StreamableMethod } from '@azure-rest/core-client'; import type { TokenCredential } from '@azure/core-auth'; @@ -147,7 +147,7 @@ export interface BlobStorageArtifactPropertiesOutput { } // @public -function createClient(endpoint: string, credentials: TokenCredential, options?: ClientOptions): PurviewSharingClient; +function createClient(endpoint: string, credentials: TokenCredential, { apiVersion, ...options }?: PurviewSharingClientOptions): PurviewSharingClient; export default createClient; // @public @@ -157,7 +157,7 @@ export type GetArrayType = T extends Array ? TData : never; export function getLongRunningPoller(client: Client, initialResponse: TResult, options?: CreateHttpPollerOptions>): Promise, TResult>>; // @public -export type GetPage = (pageLink: string, maxPageSize?: number) => Promise<{ +export type GetPage = (pageLink: string) => Promise<{ page: TPage; nextPageLink?: string; }>; @@ -298,6 +298,18 @@ export interface OperationResponseOutput { status: "Running" | "TransientFailure" | "Succeeded" | "Failed" | "NotStarted"; } +// @public +export interface PagedAsyncIterableIterator { + [Symbol.asyncIterator](): PagedAsyncIterableIterator; + byPage: (settings?: TPageSettings) => AsyncIterableIterator; + next(): Promise>; +} + +// @public +export interface PageSettings { + continuationToken?: string; +} + // @public export function paginate(client: Client, initialResponse: TResponse, options?: PagingOptions): PagedAsyncIterableIterator>; @@ -341,6 +353,11 @@ export type PurviewSharingClient = Client & { path: Routes; }; +// @public +export interface PurviewSharingClientOptions extends ClientOptions { + apiVersion?: string; +} + // @public export type ReceivedShare = InPlaceReceivedShare; @@ -1188,6 +1205,28 @@ export interface ShareResourcesGetAllShareResourcesQueryParamProperties { orderby?: string; } +// @public +export interface SimplePollerLike, TResult> { + getOperationState(): TState; + getResult(): TResult | undefined; + isDone(): boolean; + // @deprecated + isStopped(): boolean; + onProgress(callback: (state: TState) => void): CancelOnProgress; + poll(options?: { + abortSignal?: AbortSignalLike; + }): Promise; + pollUntilDone(pollOptions?: { + abortSignal?: AbortSignalLike; + }): Promise; + serialize(): Promise; + // @deprecated + stopPolling(): void; + submitted(): Promise; + // @deprecated + toString(): string; +} + // @public export type Sink = AdlsGen2AccountSink | BlobAccountSink; diff --git a/sdk/purview/purview-sharing-rest/src/logger.ts b/sdk/purview/purview-sharing-rest/src/logger.ts new file mode 100644 index 000000000000..6f256575d739 --- /dev/null +++ b/sdk/purview/purview-sharing-rest/src/logger.ts @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { createClientLogger } from "@azure/logger"; +export const logger = createClientLogger("purview-sharing"); diff --git a/sdk/purview/purview-sharing-rest/src/outputModels.ts b/sdk/purview/purview-sharing-rest/src/outputModels.ts index e0ac6e762d4b..391431b61d47 100644 --- a/sdk/purview/purview-sharing-rest/src/outputModels.ts +++ b/sdk/purview/purview-sharing-rest/src/outputModels.ts @@ -48,7 +48,11 @@ export interface OperationResponseOutput { /** List of received shares. */ export interface ReceivedShareListOutput { - /** The Url of next result page. */ + /** + * The Url of next result page. + * + * Value may contain a URL + */ nextLink?: string; /** Collection of items of type ReceivedShare */ value: Array; @@ -56,7 +60,11 @@ export interface ReceivedShareListOutput { /** List of sent shares. */ export interface SentShareListOutput { - /** The Url of next result page. */ + /** + * The Url of next result page. + * + * Value may contain a URL + */ nextLink?: string; /** Collection of items of type SentShare */ value: Array; @@ -69,7 +77,11 @@ export interface SentShareOutputParent extends ProxyResourceOutput { /** List of the sent share invitations */ export interface SentShareInvitationListOutput { - /** The Url of next result page. */ + /** + * The Url of next result page. + * + * Value may contain a URL + */ nextLink?: string; /** Collection of items of type SentShareInvitation */ value: Array; @@ -82,7 +94,11 @@ export interface SentShareInvitationOutputParent extends ProxyResourceOutput { /** A page of ShareResource results. */ export interface ShareResourceListOutput { - /** The Url of next result page. */ + /** + * The Url of next result page. + * + * Value may contain a URL + */ nextLink?: string; /** Collection of items of type ShareResource */ value: Array; diff --git a/sdk/purview/purview-sharing-rest/src/paginateHelper.ts b/sdk/purview/purview-sharing-rest/src/paginateHelper.ts index 5d541b4e406d..9ea946d9d6c5 100644 --- a/sdk/purview/purview-sharing-rest/src/paginateHelper.ts +++ b/sdk/purview/purview-sharing-rest/src/paginateHelper.ts @@ -1,11 +1,148 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import type { PagedAsyncIterableIterator, PagedResult } from "@azure/core-paging"; -import { getPagedAsyncIterator } from "@azure/core-paging"; import type { Client, PathUncheckedResponse } from "@azure-rest/core-client"; import { createRestError } from "@azure-rest/core-client"; +/** + * returns an async iterator that iterates over results. It also has a `byPage` + * method that returns pages of items at once. + * + * @param pagedResult - an object that specifies how to get pages. + * @returns a paged async iterator that iterates over results. + */ +function getPagedAsyncIterator< + TElement, + TPage = TElement[], + TPageSettings = PageSettings, + TLink = string, +>( + pagedResult: PagedResult, +): PagedAsyncIterableIterator { + const iter = getItemAsyncIterator(pagedResult); + return { + next() { + return iter.next(); + }, + [Symbol.asyncIterator]() { + return this; + }, + byPage: + pagedResult?.byPage ?? + (((settings?: PageSettings) => { + const { continuationToken } = settings ?? {}; + return getPageAsyncIterator(pagedResult, { + pageLink: continuationToken as unknown as TLink | undefined, + }); + }) as unknown as (settings?: TPageSettings) => AsyncIterableIterator), + }; +} + +async function* getItemAsyncIterator( + pagedResult: PagedResult, +): AsyncIterableIterator { + const pages = getPageAsyncIterator(pagedResult); + const firstVal = await pages.next(); + // if the result does not have an array shape, i.e. TPage = TElement, then we return it as is + if (!Array.isArray(firstVal.value)) { + // can extract elements from this page + const { toElements } = pagedResult; + if (toElements) { + yield* toElements(firstVal.value) as TElement[]; + for await (const page of pages) { + yield* toElements(page) as TElement[]; + } + } else { + yield firstVal.value; + // `pages` is of type `AsyncIterableIterator` but TPage = TElement in this case + yield* pages as unknown as AsyncIterableIterator; + } + } else { + yield* firstVal.value; + for await (const page of pages) { + // pages is of type `AsyncIterableIterator` so `page` is of type `TPage`. In this branch, + // it must be the case that `TPage = TElement[]` + yield* page as unknown as TElement[]; + } + } +} + +async function* getPageAsyncIterator( + pagedResult: PagedResult, + options: { + pageLink?: TLink; + } = {}, +): AsyncIterableIterator { + const { pageLink } = options; + let response = await pagedResult.getPage(pageLink ?? pagedResult.firstPageLink); + if (!response) { + return; + } + yield response.page; + while (response.nextPageLink) { + response = await pagedResult.getPage(response.nextPageLink); + if (!response) { + return; + } + yield response.page; + } +} + +/** + * An interface that tracks the settings for paged iteration + */ +export interface PageSettings { + /** + * The token that keeps track of where to continue the iterator + */ + continuationToken?: string; +} + +/** + * An interface that allows async iterable iteration both to completion and by page. + */ +export interface PagedAsyncIterableIterator< + TElement, + TPage = TElement[], + TPageSettings = PageSettings, +> { + /** + * The next method, part of the iteration protocol + */ + next(): Promise>; + /** + * The connection to the async iterator, part of the iteration protocol + */ + [Symbol.asyncIterator](): PagedAsyncIterableIterator; + /** + * Return an AsyncIterableIterator that works a page at a time + */ + byPage: (settings?: TPageSettings) => AsyncIterableIterator; +} + +/** + * An interface that describes how to communicate with the service. + */ +interface PagedResult { + /** + * Link to the first page of results. + */ + firstPageLink: TLink; + /** + * A method that returns a page of results. + */ + getPage: (pageLink: TLink) => Promise<{ page: TPage; nextPageLink?: TLink } | undefined>; + /** + * a function to implement the `byPage` method on the paged async iterator. + */ + byPage?: (settings?: TPageSettings) => AsyncIterableIterator; + + /** + * A function to extract elements from a page. + */ + toElements?: (page: TPage) => unknown[]; +} + /** * Helper type to extract the type of an array */ @@ -14,10 +151,7 @@ export type GetArrayType = T extends Array ? TData : never; /** * The type of a custom function that defines how to get a page and a link to the next one if any. */ -export type GetPage = ( - pageLink: string, - maxPageSize?: number, -) => Promise<{ +export type GetPage = (pageLink: string) => Promise<{ page: TPage; nextPageLink?: string; }>; diff --git a/sdk/purview/purview-sharing-rest/src/pollingHelper.ts b/sdk/purview/purview-sharing-rest/src/pollingHelper.ts index a54710cc33bd..a2668fc5d9c7 100644 --- a/sdk/purview/purview-sharing-rest/src/pollingHelper.ts +++ b/sdk/purview/purview-sharing-rest/src/pollingHelper.ts @@ -2,14 +2,82 @@ // Licensed under the MIT License. import type { Client, HttpResponse } from "@azure-rest/core-client"; +import type { AbortSignalLike } from "@azure/abort-controller"; import type { + CancelOnProgress, CreateHttpPollerOptions, - LongRunningOperation, - LroResponse, + RunningOperation, + OperationResponse, OperationState, - SimplePollerLike, } from "@azure/core-lro"; import { createHttpPoller } from "@azure/core-lro"; + +/** + * A simple poller that can be used to poll a long running operation. + */ +export interface SimplePollerLike, TResult> { + /** + * Returns true if the poller has finished polling. + */ + isDone(): boolean; + /** + * Returns the state of the operation. + */ + getOperationState(): TState; + /** + * Returns the result value of the operation, + * regardless of the state of the poller. + * It can return undefined or an incomplete form of the final TResult value + * depending on the implementation. + */ + getResult(): TResult | undefined; + /** + * Returns a promise that will resolve once a single polling request finishes. + * It does this by calling the update method of the Poller's operation. + */ + poll(options?: { abortSignal?: AbortSignalLike }): Promise; + /** + * Returns a promise that will resolve once the underlying operation is completed. + */ + pollUntilDone(pollOptions?: { abortSignal?: AbortSignalLike }): Promise; + /** + * Invokes the provided callback after each polling is completed, + * sending the current state of the poller's operation. + * + * It returns a method that can be used to stop receiving updates on the given callback function. + */ + onProgress(callback: (state: TState) => void): CancelOnProgress; + + /** + * Returns a promise that could be used for serialized version of the poller's operation + * by invoking the operation's serialize method. + */ + serialize(): Promise; + + /** + * Wait the poller to be submitted. + */ + submitted(): Promise; + + /** + * Returns a string representation of the poller's operation. Similar to serialize but returns a string. + * @deprecated Use serialize() instead. + */ + toString(): string; + + /** + * Stops the poller from continuing to poll. Please note this will only stop the client-side polling + * @deprecated Use abortSignal to stop polling instead. + */ + stopPolling(): void; + + /** + * Returns true if the poller is stopped. + * @deprecated Use abortSignal status to track this instead. + */ + isStopped(): boolean; +} + /** * Helper function that builds a Poller object to help polling a long running operation. * @param client - Client to use for sending the request to get additional pages. @@ -22,21 +90,39 @@ export async function getLongRunningPoller( initialResponse: TResult, options: CreateHttpPollerOptions> = {}, ): Promise, TResult>> { - const poller: LongRunningOperation = { - requestMethod: initialResponse.request.method, - requestPath: initialResponse.request.url, + const abortController = new AbortController(); + const poller: RunningOperation = { sendInitialRequest: async () => { // In the case of Rest Clients we are building the LRO poller object from a response that's the reason // we are not triggering the initial request here, just extracting the information from the // response we were provided. return getLroResponse(initialResponse); }, - sendPollRequest: async (path) => { + sendPollRequest: async (path: string, pollOptions?: { abortSignal?: AbortSignalLike }) => { // This is the callback that is going to be called to poll the service // to get the latest status. We use the client provided and the polling path // which is an opaque URL provided by caller, the service sends this in one of the following headers: operation-location, azure-asyncoperation or location // depending on the lro pattern that the service implements. If non is provided we default to the initial path. - const response = await client.pathUnchecked(path ?? initialResponse.request.url).get(); + function abortListener(): void { + abortController.abort(); + } + const inputAbortSignal = pollOptions?.abortSignal; + const abortSignal = abortController.signal; + if (inputAbortSignal?.aborted) { + abortController.abort(); + } else if (!abortSignal.aborted) { + inputAbortSignal?.addEventListener("abort", abortListener, { + once: true, + }); + } + let response; + try { + response = await client + .pathUnchecked(path ?? initialResponse.request.url) + .get({ abortSignal }); + } finally { + inputAbortSignal?.removeEventListener("abort", abortListener); + } const lroResponse = getLroResponse(response as TResult); lroResponse.rawResponse.headers["x-ms-original-url"] = initialResponse.request.url; return lroResponse; @@ -44,7 +130,45 @@ export async function getLongRunningPoller( }; options.resolveOnUnsuccessful = options.resolveOnUnsuccessful ?? true; - return createHttpPoller(poller, options); + const httpPoller = createHttpPoller(poller, options); + const simplePoller: SimplePollerLike, TResult> = { + isDone() { + return httpPoller.isDone; + }, + isStopped() { + return abortController.signal.aborted; + }, + getOperationState() { + if (!httpPoller.operationState) { + throw new Error( + "Operation state is not available. The poller may not have been started and you could await submitted() before calling getOperationState().", + ); + } + return httpPoller.operationState; + }, + getResult() { + return httpPoller.result; + }, + toString() { + if (!httpPoller.operationState) { + throw new Error( + "Operation state is not available. The poller may not have been started and you could await submitted() before calling getOperationState().", + ); + } + return JSON.stringify({ + state: httpPoller.operationState, + }); + }, + stopPolling() { + abortController.abort(); + }, + onProgress: httpPoller.onProgress, + poll: httpPoller.poll, + pollUntilDone: httpPoller.pollUntilDone, + serialize: httpPoller.serialize, + submitted: httpPoller.submitted, + }; + return simplePoller; } /** @@ -52,7 +176,9 @@ export async function getLongRunningPoller( * @param response - a rest client http response * @returns - An LRO response that the LRO implementation understands */ -function getLroResponse(response: TResult): LroResponse { +function getLroResponse( + response: TResult, +): OperationResponse { if (Number.isNaN(response.status)) { throw new TypeError(`Status code of the response is not a number. Value: ${response.status}`); } diff --git a/sdk/purview/purview-sharing-rest/src/purviewSharing.ts b/sdk/purview/purview-sharing-rest/src/purviewSharing.ts index 05fa482e5778..66fa2471581a 100644 --- a/sdk/purview/purview-sharing-rest/src/purviewSharing.ts +++ b/sdk/purview/purview-sharing-rest/src/purviewSharing.ts @@ -3,30 +3,29 @@ import type { ClientOptions } from "@azure-rest/core-client"; import { getClient } from "@azure-rest/core-client"; +import { logger } from "./logger.js"; import type { TokenCredential } from "@azure/core-auth"; import type { PurviewSharingClient } from "./clientDefinitions.js"; +/** The optional parameters for the client */ +export interface PurviewSharingClientOptions extends ClientOptions { + /** The api version option of the client */ + apiVersion?: string; +} + /** * Initialize a new instance of `PurviewSharingClient` - * @param endpoint type: string, The sharing endpoint of your purview account. Example: https://{accountName}.purview.azure.com/share - * @param credentials type: TokenCredential, uniquely identify client credential - * @param options type: ClientOptions, the parameter for all optional parameters + * @param endpoint - The sharing endpoint of your purview account. Example: https://{accountName}.purview.azure.com/share + * @param credentials - uniquely identify client credential + * @param options - the parameter for all optional parameters */ export default function createClient( endpoint: string, credentials: TokenCredential, - options: ClientOptions = {}, + { apiVersion = "2023-05-30-preview", ...options }: PurviewSharingClientOptions = {}, ): PurviewSharingClient { - const baseUrl = options.baseUrl ?? `${endpoint}`; - options.apiVersion = options.apiVersion ?? "2023-05-30-preview"; - options = { - ...options, - credentials: { - scopes: ["https://purview.azure.net/.default"], - }, - }; - - const userAgentInfo = `azsdk-js-purview-sharing-rest/1.0.0-beta.2`; + const endpointUrl = options.endpoint ?? options.baseUrl ?? `${endpoint}`; + const userAgentInfo = `azsdk-js-purview-sharing-rest/1.0.0-beta.3`; const userAgentPrefix = options.userAgentOptions && options.userAgentOptions.userAgentPrefix ? `${options.userAgentOptions.userAgentPrefix} ${userAgentInfo}` @@ -36,9 +35,31 @@ export default function createClient( userAgentOptions: { userAgentPrefix, }, + loggingOptions: { + logger: options.loggingOptions?.logger ?? logger.info, + }, + credentials: { + scopes: ["https://purview.azure.net/.default"], + }, }; + const client = getClient(endpointUrl, credentials, options) as PurviewSharingClient; + + client.pipeline.removePolicy({ name: "ApiVersionPolicy" }); + client.pipeline.addPolicy({ + name: "ClientApiVersionPolicy", + sendRequest: (req, next) => { + // Use the apiVersion defined in request url directly + // Append one if there is no apiVersion and we have one at client options + const url = new URL(req.url); + if (!url.searchParams.get("api-version") && apiVersion) { + req.url = `${req.url}${ + Array.from(url.searchParams.keys()).length > 0 ? "&" : "?" + }api-version=${apiVersion}`; + } - const client = getClient(baseUrl, credentials, options) as PurviewSharingClient; + return next(req); + }, + }); return client; } diff --git a/sdk/purview/purview-sharing-rest/swagger/README.md b/sdk/purview/purview-sharing-rest/swagger/README.md index 514444b35faa..4f9338963fd7 100644 --- a/sdk/purview/purview-sharing-rest/swagger/README.md +++ b/sdk/purview/purview-sharing-rest/swagger/README.md @@ -5,6 +5,8 @@ ## Configuration ```yaml +flavor: azure +openapi-type: data-plane package-name: "@azure-rest/purview-sharing" title: Purview Sharing description: Purview Sharing Client @@ -12,7 +14,7 @@ license-header: MICROSOFT_MIT_NO_VERSION output-folder: ../ source-code-folder-path: ./src input-file: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/40a953243ea428918de6e63758e853b7a24aa59a/specification/purview/data-plane/Azure.Analytics.Purview.Share/preview/2023-05-30-preview/share.json -package-version: 1.0.0-beta.2 +package-version: 1.0.0-beta.3 rest-level-client: true add-credentials: true credential-scopes: "https://purview.azure.net/.default"