diff --git a/packages/typespec-test/eng/smoke-test-main.js b/packages/typespec-test/eng/smoke-test-main.js deleted file mode 100644 index d4ffea9c9f..0000000000 --- a/packages/typespec-test/eng/smoke-test-main.js +++ /dev/null @@ -1,92 +0,0 @@ -import { readdir } from "fs/promises"; -import { join, dirname } from "path"; -import { fileURLToPath } from "url"; -import os from "os"; -import { Worker } from "worker_threads"; - -const failed = []; -async function generateSmokeTests() { - let folder; - if (process.argv.includes("--")) { - folder = process.argv[process.argv.length - 1]; - } else { - folder = process.argv[4]; - } - - const maxConcurrentWorkers = os.cpus().length; - let activeWorkers = 0; - - console.log(`Max concurrent workers: ${maxConcurrentWorkers}`); - - const __filename = fileURLToPath(import.meta.url); - const __dirname = dirname(__filename); - const root = join(__dirname, ".."); - - const folders = folder ? [folder] : await readdir(join(root, "test")); - - for (const folder of folders) { - // Wait for an available worker - while (activeWorkers >= maxConcurrentWorkers) { - await new Promise((resolve) => { - setTimeout(resolve, 1000); - }); - } - - const worker = new Worker("./eng/smoke-test-worker.js"); - activeWorkers++; - - // Send the worker the data it needs to do the work - worker.postMessage({ folder }); - - worker.on("message", ({ status, error, outputLog }) => { - if (status === "closed") { - activeWorkers--; - } - - if (error) { - outputLog += `Error occurred during execution\n${JSON.stringify( - error - )} \n`; - failed.push(folder); - } - - console.log(outputLog); - }); - - worker.on("error", (error) => { - console.error(`Error from worker: ${error}`); - activeWorkers--; - }); - - worker.on("exit", (code) => { - if (code !== 0) { - console.error(`Worker stopped with exit code ${code}`); - } - activeWorkers--; - }); - } - - while (activeWorkers > 0) { - await new Promise((resolve) => { - setTimeout(resolve, 1000); - }); - } - - if (failed.length > 0) { - console.error("\x1b[31m%s\x1b[0m", `Failed folders: ${failed.join(", ")}`); - } else { - console.log("\x1b[32m%s\x1b[0m", "All specs succeeded!"); - } -} - -let exitCode = 0; -try { - console.time("generateTypeSpecs"); - await generateSmokeTests(); -} catch (e) { - console.error(`Error occurred during execution\n${e.message}`); - exitCode = 1; -} finally { - console.timeEnd("generateTypeSpecs"); - process.exit(exitCode); -} diff --git a/packages/typespec-test/eng/smoke-test-worker.js b/packages/typespec-test/eng/smoke-test-worker.js deleted file mode 100644 index 8d0b0a4002..0000000000 --- a/packages/typespec-test/eng/smoke-test-worker.js +++ /dev/null @@ -1,124 +0,0 @@ -import { readdir, cp as copy } from "fs/promises"; -import { existsSync as exists } from "fs"; -import { runCommand } from "./runCommand.js"; -import { parentPort } from "worker_threads"; -import { join, dirname } from "path"; -import { fileURLToPath } from "url"; - -async function generate(path) { - let outputLog = ""; - let succeeded = false; - try { - let specPath = join(path, "spec"); - let compileArgs = ["tsp", "compile"]; - - if (exists(join(specPath, "tspconfig.yaml"))) { - compileArgs = ["tsp", "compile", "--config", "tspconfig.yaml"]; - } - - if (exists(join(specPath, "client.tsp"))) { - compileArgs.push("client.tsp"); - } else { - compileArgs.push("main.tsp"); - } - - // Run generate command - outputLog += `Run command: npx ${compileArgs.join(" ")}\n`; - const generateResult = await runCommand("npx", compileArgs, specPath); - outputLog += `Generated output: ${generateResult}\n`; - succeeded = true; - } catch (e) { - outputLog += `Error occurred during generation: ${e.message}\n`; - } finally { - return { outputLog, succeeded }; - } -} - -async function copyFiles(path) { - const hasCustomization = await hasCustomizationFolder(path); - if (!hasCustomization) { - return; - } - - const customizationPath = join( - path, - "generated/typespec-ts/sources/generated/src" - ); - const srcPath = join(path, "generated/typespec-ts/src"); - - await copy(customizationPath, srcPath, { - recursive: true, - force: true, - }); -} - -async function build(path) { - let outputLog = ""; - let succeeded = false; - - try { - const buildPath = join(path, "generated/typespec-ts"); - - outputLog = "Running npm install and build \n"; - await runCommand("npm", ["install"], buildPath); - await runCommand("npm", ["run", "build"], buildPath); - outputLog = "Build completed\n"; - succeeded = true; - } catch (e) { - outputLog = `Error occurred during build: ${e.message}\n`; - } finally { - return { outputLog, succeeded }; - } -} - -async function hasCustomizationFolder(path) { - const customizationPath = join(path, "generated/typespec-ts/sources"); - return exists(customizationPath); -} - -async function createSmokeTest(folder) { - const __filename = fileURLToPath(import.meta.url); - const __dirname = dirname(__filename); - const root = join(__dirname, ".."); - - const path = join(root, "test", folder); - const contents = await readdir(path); - - let outputLog = `================Start ${folder}===============\n`; - - if (contents.includes("skip")) { - outputLog += ` ##### Skipped ##### \n`; - } else { - const generateResult = await generate(path); - outputLog += generateResult.outputLog + "\n"; - - if (!generateResult.succeeded) { - outputLog += `Skipping build due to generation failure\n`; - return { outputLog, succeeded: false }; - } - await copyFiles(path); - const buildResult = await build(path); - outputLog += buildResult.outputLog + "\n"; - if (!buildResult.succeeded) { - outputLog += `Skipping build due to generation failure\n`; - return { outputLog, succeeded: false }; - } - } - outputLog += `================End ${folder}===============\n`; - - return { outputLog, succeeded: true }; -} - -parentPort.on("message", async ({ folder }) => { - try { - const { outputLog, succeeded } = await createSmokeTest(folder); - parentPort.postMessage({ status: "closed", outputLog, succeeded }); - } catch (e) { - parentPort.postMessage({ - status: "closed", - error: e, - outputLog, - succeeded: false, - }); - } -}); diff --git a/packages/typespec-test/eng/smoke-test.js b/packages/typespec-test/eng/smoke-test.js index fc26716184..a19e14546e 100644 --- a/packages/typespec-test/eng/smoke-test.js +++ b/packages/typespec-test/eng/smoke-test.js @@ -14,22 +14,7 @@ async function exists(filePath) { } } -const calculateMemoryLimit = () => { - const totalMemory = os.totalmem(); - const freeMemory = os.freemem(); - const minimumFreeMemory = 1024 * 1024 * 1024; // e.g., 1GB - - if (freeMemory > minimumFreeMemory) { - // Allow using up to 50% of total memory if there is at least 1GB free - return Math.floor(totalMemory / 1024 / 1024 / 1024 / 2); // in GB - } else { - // Default or lower memory limit if free memory is less than 1GB - return 512; // 512MB as a fallback - } -}; - -const memoryLimit = calculateMemoryLimit(); -console.log(`Memory limit: ${memoryLimit}GB`); +const memoryLimit = 4096; // 4GB function runCommand(command, args = [], workingDirectory, logger) { const isLinux = os.platform() === "linux"; @@ -145,15 +130,31 @@ async function main() { const root = join(__dirname, ".."); const folders = folder ? [folder] : await readdir(join(root, "test")); - const generatePromises = []; + const maxConcurrentWorkers = 4; + let activePromises = []; for (const folder of folders) { const path = join(root, "test", folder); - const generatePromise = generateSmokeTest(path); - generatePromises.push(generatePromise); + + const generatePromise = generateSmokeTest(path) + .then((result) => { + activePromises = activePromises.filter((p) => p !== generatePromise); + return result; + }) + .catch((error) => { + activePromises = activePromises.filter((p) => p !== generatePromise); + throw error; + }); + + activePromises.push(generatePromise); + + if (activePromises.length >= maxConcurrentWorkers) { + await Promise.race(activePromises); + } } - await Promise.all(generatePromises); + // Wait for all remaining promises to settle + await Promise.allSettled(activePromises); if (failed.length > 0) { console.error("\x1b[31m%s\x1b[0m", `Failed folders: ${failed.join(", ")}`); diff --git a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/api/dataProducts/index.ts b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/api/dataProducts/index.ts index 71d6925978..ffb44ad60e 100644 --- a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/api/dataProducts/index.ts +++ b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/api/dataProducts/index.ts @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { getLongRunningPoller } from "../pollingHelpers.js"; -import { PollerLike, OperationState } from "@azure/core-lro"; import { dataProductPropertiesSerializer, managedServiceIdentityV4Serializer, @@ -17,8 +15,6 @@ import { ListRoleAssignments, _DataProductListResult, } from "../../models/models.js"; -import { PagedAsyncIterableIterator } from "../../models/pagingTypes.js"; -import { buildPagedAsyncIterator } from "../pagingHelpers.js"; import { NetworkAnalyticsContext as Client } from "../index.js"; import { StreamableMethod, @@ -27,6 +23,12 @@ import { createRestError, } from "@azure-rest/core-client"; import { serializeRecord } from "../../helpers/serializerHelpers.js"; +import { + PagedAsyncIterableIterator, + buildPagedAsyncIterator, +} from "../../static-helpers/pagingHelpers.js"; +import { getLongRunningPoller } from "../../static-helpers/pollingHelpers.js"; +import { PollerLike, OperationState } from "@azure/core-lro"; import { DataProductsCreateOptionalParams, DataProductsGetOptionalParams, diff --git a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/api/dataProductsCatalogs/index.ts b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/api/dataProductsCatalogs/index.ts index e2afe24bde..862ff8cd7c 100644 --- a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/api/dataProductsCatalogs/index.ts +++ b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/api/dataProductsCatalogs/index.ts @@ -5,8 +5,6 @@ import { DataProductsCatalog, _DataProductsCatalogListResult, } from "../../models/models.js"; -import { PagedAsyncIterableIterator } from "../../models/pagingTypes.js"; -import { buildPagedAsyncIterator } from "../pagingHelpers.js"; import { NetworkAnalyticsContext as Client } from "../index.js"; import { StreamableMethod, @@ -14,6 +12,10 @@ import { PathUncheckedResponse, createRestError, } from "@azure-rest/core-client"; +import { + PagedAsyncIterableIterator, + buildPagedAsyncIterator, +} from "../../static-helpers/pagingHelpers.js"; import { DataProductsCatalogsGetOptionalParams, DataProductsCatalogsListByResourceGroupOptionalParams, diff --git a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/api/dataTypes/index.ts b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/api/dataTypes/index.ts index 6d38511ed9..3be137716a 100644 --- a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/api/dataTypes/index.ts +++ b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/api/dataTypes/index.ts @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { getLongRunningPoller } from "../pollingHelpers.js"; -import { PollerLike, OperationState } from "@azure/core-lro"; import { dataTypePropertiesSerializer, dataTypeUpdatePropertiesSerializer, @@ -12,8 +10,6 @@ import { ContainerSasToken, _DataTypeListResult, } from "../../models/models.js"; -import { PagedAsyncIterableIterator } from "../../models/pagingTypes.js"; -import { buildPagedAsyncIterator } from "../pagingHelpers.js"; import { NetworkAnalyticsContext as Client } from "../index.js"; import { StreamableMethod, @@ -21,6 +17,12 @@ import { PathUncheckedResponse, createRestError, } from "@azure-rest/core-client"; +import { + PagedAsyncIterableIterator, + buildPagedAsyncIterator, +} from "../../static-helpers/pagingHelpers.js"; +import { getLongRunningPoller } from "../../static-helpers/pollingHelpers.js"; +import { PollerLike, OperationState } from "@azure/core-lro"; import { DataTypesCreateOptionalParams, DataTypesGetOptionalParams, diff --git a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/api/operations/index.ts b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/api/operations/index.ts index 3e75f1a4e9..e8eec2fbb4 100644 --- a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/api/operations/index.ts +++ b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/api/operations/index.ts @@ -2,8 +2,6 @@ // Licensed under the MIT license. import { Operation, _OperationListResult } from "../../models/models.js"; -import { PagedAsyncIterableIterator } from "../../models/pagingTypes.js"; -import { buildPagedAsyncIterator } from "../pagingHelpers.js"; import { NetworkAnalyticsContext as Client } from "../index.js"; import { StreamableMethod, @@ -11,6 +9,10 @@ import { PathUncheckedResponse, createRestError, } from "@azure-rest/core-client"; +import { + PagedAsyncIterableIterator, + buildPagedAsyncIterator, +} from "../../static-helpers/pagingHelpers.js"; import { OperationsListOptionalParams } from "../../models/options.js"; export function _listSend( diff --git a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/classic/dataProducts/index.ts b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/classic/dataProducts/index.ts index 64cd38d2e9..249936f9d8 100644 --- a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/classic/dataProducts/index.ts +++ b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/classic/dataProducts/index.ts @@ -25,7 +25,7 @@ import { listByResourceGroup, listBySubscription, } from "../../api/dataProducts/index.js"; -import { PagedAsyncIterableIterator } from "../../models/pagingTypes.js"; +import { PagedAsyncIterableIterator } from "../../static-helpers/pagingHelpers.js"; import { PollerLike, OperationState } from "@azure/core-lro"; import { DataProductsCreateOptionalParams, diff --git a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/classic/dataProductsCatalogs/index.ts b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/classic/dataProductsCatalogs/index.ts index 45b70334c9..ebd9b4a1cb 100644 --- a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/classic/dataProductsCatalogs/index.ts +++ b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/classic/dataProductsCatalogs/index.ts @@ -8,7 +8,7 @@ import { listByResourceGroup, listBySubscription, } from "../../api/dataProductsCatalogs/index.js"; -import { PagedAsyncIterableIterator } from "../../models/pagingTypes.js"; +import { PagedAsyncIterableIterator } from "../../static-helpers/pagingHelpers.js"; import { DataProductsCatalogsGetOptionalParams, DataProductsCatalogsListByResourceGroupOptionalParams, diff --git a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/classic/dataTypes/index.ts b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/classic/dataTypes/index.ts index fac5453937..09a0a92875 100644 --- a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/classic/dataTypes/index.ts +++ b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/classic/dataTypes/index.ts @@ -17,7 +17,7 @@ import { generateStorageContainerSasToken, listByDataProduct, } from "../../api/dataTypes/index.js"; -import { PagedAsyncIterableIterator } from "../../models/pagingTypes.js"; +import { PagedAsyncIterableIterator } from "../../static-helpers/pagingHelpers.js"; import { PollerLike, OperationState } from "@azure/core-lro"; import { DataTypesCreateOptionalParams, diff --git a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/classic/operations/index.ts b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/classic/operations/index.ts index 93aa25b4f1..df1beab677 100644 --- a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/classic/operations/index.ts +++ b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/classic/operations/index.ts @@ -4,7 +4,7 @@ import { NetworkAnalyticsContext } from "../../api/networkAnalyticsContext.js"; import { Operation } from "../../models/models.js"; import { list } from "../../api/operations/index.js"; -import { PagedAsyncIterableIterator } from "../../models/pagingTypes.js"; +import { PagedAsyncIterableIterator } from "../../static-helpers/pagingHelpers.js"; import { OperationsListOptionalParams } from "../../models/options.js"; /** Interface representing a Operations operations. */ diff --git a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/helpers/serializerHelpers.ts b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/helpers/serializerHelpers.ts +++ b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/index.ts b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/index.ts index f5b0a4bd3e..ebbc70bdff 100644 --- a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/index.ts +++ b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/index.ts @@ -1,6 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { + PageSettings, + ContinuablePage, + PagedAsyncIterableIterator, +} from "./static-helpers/pagingHelpers.js"; + export { NetworkAnalyticsClient, NetworkAnalyticsClientOptionalParams, @@ -86,9 +92,6 @@ export { DataProductsListRolesAssignmentsOptionalParams, DataProductsListByResourceGroupOptionalParams, DataProductsListBySubscriptionOptionalParams, - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, } from "./models/index.js"; export { DataProductsOperations, @@ -96,3 +99,4 @@ export { DataTypesOperations, OperationsOperations, } from "./classic/index.js"; +export { PageSettings, ContinuablePage, PagedAsyncIterableIterator }; diff --git a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/models/index.ts b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/models/index.ts index 1ea22786b9..1d70677cf0 100644 --- a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/models/index.ts +++ b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/models/index.ts @@ -84,8 +84,3 @@ export { DataProductsListByResourceGroupOptionalParams, DataProductsListBySubscriptionOptionalParams, } from "./options.js"; -export { - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, -} from "./pagingTypes.js"; diff --git a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/models/pagingTypes.ts b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/models/pagingTypes.ts deleted file mode 100644 index f734b48e62..0000000000 --- a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/models/pagingTypes.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -/** - * Options for the byPage method - */ -export interface PageSettings { - /** - * A reference to a specific page to start iterating from. - */ - continuationToken?: string; -} - -/** - * An interface that describes a page of results. - */ -export type ContinuablePage = TPage & { - /** - * 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 extends PageSettings = 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< - TElement, - TPage, - TPageSettings - >; - /** - * Return an AsyncIterableIterator that works a page at a time - */ - byPage: ( - settings?: TPageSettings, - ) => AsyncIterableIterator>; -} - -/** - * An interface that describes how to communicate with the service. - */ -export interface PagedResult< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, -> { - /** - * Link to the first page of results. - */ - firstPageLink?: string; - /** - * A method that returns a page of results. - */ - getPage: ( - pageLink?: string, - ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; -} - -/** - * Options for the paging helper - */ -export interface BuildPagedAsyncIteratorOptions { - itemName?: string; - nextLinkName?: string; -} diff --git a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/restorePollerHelpers.ts b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/restorePollerHelpers.ts index f0a0b88d5c..21e2a828e2 100644 --- a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/restorePollerHelpers.ts +++ b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/restorePollerHelpers.ts @@ -1,14 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { - PollerLike, - OperationState, - deserializeState, - ResourceLocationConfig, -} from "@azure/core-lro"; import { NetworkAnalyticsClient } from "./networkAnalyticsClient.js"; -import { getLongRunningPoller } from "./api/pollingHelpers.js"; import { _createDeserialize, _updateDeserialize, @@ -20,11 +13,18 @@ import { _updateDeserialize as _updateDeserializeDataProducts, _$deleteDeserialize as _$deleteDeserializeDataProducts, } from "./api/dataProducts/index.js"; +import { getLongRunningPoller } from "./static-helpers/pollingHelpers.js"; import { - PathUncheckedResponse, OperationOptions, + PathUncheckedResponse, } from "@azure-rest/core-client"; import { AbortSignalLike } from "@azure/abort-controller"; +import { + PollerLike, + OperationState, + deserializeState, + ResourceLocationConfig, +} from "@azure/core-lro"; export interface RestorePollerOptions< TResult, diff --git a/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/api/pagingHelpers.ts b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/static-helpers/pagingHelpers.ts similarity index 71% rename from packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/api/pagingHelpers.ts rename to packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/static-helpers/pagingHelpers.ts index 0fc574c83c..5855ada5a8 100644 --- a/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/api/pagingHelpers.ts +++ b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/static-helpers/pagingHelpers.ts @@ -1,19 +1,102 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { Client, createRestError, PathUncheckedResponse, } from "@azure-rest/core-client"; import { RestError } from "@azure/core-rest-pipeline"; -import { - BuildPagedAsyncIteratorOptions, - ContinuablePage, - PageSettings, - PagedAsyncIterableIterator, - PagedResult, -} from "../models/pagingTypes.js"; + +/** + * Options for the byPage method + */ +export interface PageSettings { + /** + * A reference to a specific page to start iterating from. + */ + continuationToken?: string; +} + +/** + * An interface that describes a page of results. + */ +export type ContinuablePage = TPage & { + /** + * 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 extends PageSettings = 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< + TElement, + TPage, + TPageSettings + >; + /** + * Return an AsyncIterableIterator that works a page at a time + */ + byPage: ( + settings?: TPageSettings, + ) => AsyncIterableIterator>; +} + +/** + * An interface that describes how to communicate with the service. + */ +export interface PagedResult< + TElement, + TPage = TElement[], + TPageSettings extends PageSettings = PageSettings, +> { + /** + * Link to the first page of results. + */ + firstPageLink?: string; + /** + * A method that returns a page of results. + */ + getPage: ( + pageLink?: string, + ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; +} + +/** + * Options for the paging helper + */ +export interface BuildPagedAsyncIteratorOptions { + itemName?: string; + nextLinkName?: string; +} /** * Helper to paginate results in a generic way and return a PagedAsyncIterableIterator diff --git a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/api/pollingHelpers.ts b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/static-helpers/pollingHelpers.ts similarity index 98% rename from packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/api/pollingHelpers.ts rename to packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/static-helpers/pollingHelpers.ts index 47c259e79f..e130b18d89 100644 --- a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/api/pollingHelpers.ts +++ b/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/static-helpers/pollingHelpers.ts @@ -1,6 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { PollerLike, OperationState, diff --git a/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/api/multivariate/index.ts b/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/api/multivariate/index.ts index ed5498f4da..a8000be6dd 100644 --- a/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/api/multivariate/index.ts +++ b/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/api/multivariate/index.ts @@ -13,8 +13,6 @@ import { MultivariateMultivariateLastDetectionResult, _MultivariateModelList, } from "../../models/models.js"; -import { PagedAsyncIterableIterator } from "../../models/pagingTypes.js"; -import { buildPagedAsyncIterator } from "../pagingHelpers.js"; import { AnomalyDetectorContext as Client } from "../index.js"; import { StreamableMethod, @@ -22,6 +20,10 @@ import { PathUncheckedResponse, createRestError, } from "@azure-rest/core-client"; +import { + PagedAsyncIterableIterator, + buildPagedAsyncIterator, +} from "../../static-helpers/pagingHelpers.js"; import { MultivariateGetMultivariateBatchDetectionResultOptionalParams, MultivariateTrainMultivariateModelOptionalParams, diff --git a/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/classic/multivariate/index.ts b/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/classic/multivariate/index.ts index aad8be0984..1ceeaaa93f 100644 --- a/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/classic/multivariate/index.ts +++ b/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/classic/multivariate/index.ts @@ -19,7 +19,7 @@ import { detectMultivariateBatchAnomaly, detectMultivariateLastAnomaly, } from "../../api/multivariate/index.js"; -import { PagedAsyncIterableIterator } from "../../models/pagingTypes.js"; +import { PagedAsyncIterableIterator } from "../../static-helpers/pagingHelpers.js"; import { MultivariateGetMultivariateBatchDetectionResultOptionalParams, MultivariateTrainMultivariateModelOptionalParams, diff --git a/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/helpers/serializerHelpers.ts b/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/helpers/serializerHelpers.ts +++ b/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/index.ts b/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/index.ts index 3094d68a87..1fb46bd1cf 100644 --- a/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/index.ts +++ b/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/index.ts @@ -1,6 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { + PageSettings, + ContinuablePage, + PagedAsyncIterableIterator, +} from "./static-helpers/pagingHelpers.js"; + export { AnomalyDetectorClient, AnomalyDetectorClientOptionalParams, @@ -49,11 +55,9 @@ export { MultivariateGetMultivariateModelOptionalParams, MultivariateDetectMultivariateBatchAnomalyOptionalParams, MultivariateDetectMultivariateLastAnomalyOptionalParams, - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, } from "./models/index.js"; export { MultivariateOperations, UnivariateOperations, } from "./classic/index.js"; +export { PageSettings, ContinuablePage, PagedAsyncIterableIterator }; diff --git a/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/models/index.ts b/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/models/index.ts index 133a6b264d..2210bf4222 100644 --- a/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/models/index.ts +++ b/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/models/index.ts @@ -48,8 +48,3 @@ export { MultivariateDetectMultivariateBatchAnomalyOptionalParams, MultivariateDetectMultivariateLastAnomalyOptionalParams, } from "./options.js"; -export { - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, -} from "./pagingTypes.js"; diff --git a/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/models/pagingTypes.ts b/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/models/pagingTypes.ts deleted file mode 100644 index f734b48e62..0000000000 --- a/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/models/pagingTypes.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -/** - * Options for the byPage method - */ -export interface PageSettings { - /** - * A reference to a specific page to start iterating from. - */ - continuationToken?: string; -} - -/** - * An interface that describes a page of results. - */ -export type ContinuablePage = TPage & { - /** - * 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 extends PageSettings = 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< - TElement, - TPage, - TPageSettings - >; - /** - * Return an AsyncIterableIterator that works a page at a time - */ - byPage: ( - settings?: TPageSettings, - ) => AsyncIterableIterator>; -} - -/** - * An interface that describes how to communicate with the service. - */ -export interface PagedResult< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, -> { - /** - * Link to the first page of results. - */ - firstPageLink?: string; - /** - * A method that returns a page of results. - */ - getPage: ( - pageLink?: string, - ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; -} - -/** - * Options for the paging helper - */ -export interface BuildPagedAsyncIteratorOptions { - itemName?: string; - nextLinkName?: string; -} diff --git a/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/api/pagingHelpers.ts b/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/static-helpers/pagingHelpers.ts similarity index 71% rename from packages/typespec-test/test/batch_modular/generated/typespec-ts/src/api/pagingHelpers.ts rename to packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/static-helpers/pagingHelpers.ts index 0fc574c83c..5855ada5a8 100644 --- a/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/api/pagingHelpers.ts +++ b/packages/typespec-test/test/anomalyDetector/generated/typespec-ts/src/static-helpers/pagingHelpers.ts @@ -1,19 +1,102 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { Client, createRestError, PathUncheckedResponse, } from "@azure-rest/core-client"; import { RestError } from "@azure/core-rest-pipeline"; -import { - BuildPagedAsyncIteratorOptions, - ContinuablePage, - PageSettings, - PagedAsyncIterableIterator, - PagedResult, -} from "../models/pagingTypes.js"; + +/** + * Options for the byPage method + */ +export interface PageSettings { + /** + * A reference to a specific page to start iterating from. + */ + continuationToken?: string; +} + +/** + * An interface that describes a page of results. + */ +export type ContinuablePage = TPage & { + /** + * 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 extends PageSettings = 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< + TElement, + TPage, + TPageSettings + >; + /** + * Return an AsyncIterableIterator that works a page at a time + */ + byPage: ( + settings?: TPageSettings, + ) => AsyncIterableIterator>; +} + +/** + * An interface that describes how to communicate with the service. + */ +export interface PagedResult< + TElement, + TPage = TElement[], + TPageSettings extends PageSettings = PageSettings, +> { + /** + * Link to the first page of results. + */ + firstPageLink?: string; + /** + * A method that returns a page of results. + */ + getPage: ( + pageLink?: string, + ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; +} + +/** + * Options for the paging helper + */ +export interface BuildPagedAsyncIteratorOptions { + itemName?: string; + nextLinkName?: string; +} /** * Helper to paginate results in a generic way and return a PagedAsyncIterableIterator diff --git a/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/api/operations.ts b/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/api/operations.ts index 1c4681c343..0b08718242 100644 --- a/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/api/operations.ts +++ b/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/api/operations.ts @@ -87,8 +87,6 @@ import { _PoolListUsageMetricsResult, _PoolNodeCountsListResult, } from "../models/models.js"; -import { PagedAsyncIterableIterator } from "../models/pagingTypes.js"; -import { buildPagedAsyncIterator } from "./pagingHelpers.js"; import { BatchContext as Client } from "./index.js"; import { StreamableMethod, @@ -97,6 +95,10 @@ import { createRestError, } from "@azure-rest/core-client"; import { uint8ArrayToString, stringToUint8Array } from "@azure/core-util"; +import { + PagedAsyncIterableIterator, + buildPagedAsyncIterator, +} from "../static-helpers/pagingHelpers.js"; import { ListApplicationsOptionalParams, GetApplicationOptionalParams, diff --git a/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/batchClient.ts b/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/batchClient.ts index 60c3fdffba..83490080cd 100644 --- a/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/batchClient.ts +++ b/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/batchClient.ts @@ -123,7 +123,6 @@ import { GetNodeFilePropertiesOptionalParams, ListNodeFilesOptionalParams, } from "./models/options.js"; -import { PagedAsyncIterableIterator } from "./models/pagingTypes.js"; import { createBatch, BatchContext, @@ -205,6 +204,7 @@ import { getNodeFileProperties, listNodeFiles, } from "./api/index.js"; +import { PagedAsyncIterableIterator } from "./static-helpers/pagingHelpers.js"; export { BatchClientOptionalParams } from "./api/batchContext.js"; diff --git a/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts b/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts +++ b/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/index.ts b/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/index.ts index 597d3628c6..189252ed07 100644 --- a/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/index.ts +++ b/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/index.ts @@ -1,6 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { + PageSettings, + ContinuablePage, + PagedAsyncIterableIterator, +} from "./static-helpers/pagingHelpers.js"; + export { BatchClient, BatchClientOptionalParams } from "./batchClient.js"; export { BatchNodeUserCreateOptions, @@ -271,7 +277,5 @@ export { GetNodeFileOptionalParams, GetNodeFilePropertiesOptionalParams, ListNodeFilesOptionalParams, - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, } from "./models/index.js"; +export { PageSettings, ContinuablePage, PagedAsyncIterableIterator }; diff --git a/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/models/index.ts b/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/models/index.ts index d5f78b8449..7222ebb6bb 100644 --- a/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/models/index.ts +++ b/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/models/index.ts @@ -273,8 +273,3 @@ export { GetNodeFilePropertiesOptionalParams, ListNodeFilesOptionalParams, } from "./options.js"; -export { - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, -} from "./pagingTypes.js"; diff --git a/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/models/pagingTypes.ts b/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/models/pagingTypes.ts deleted file mode 100644 index f734b48e62..0000000000 --- a/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/models/pagingTypes.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -/** - * Options for the byPage method - */ -export interface PageSettings { - /** - * A reference to a specific page to start iterating from. - */ - continuationToken?: string; -} - -/** - * An interface that describes a page of results. - */ -export type ContinuablePage = TPage & { - /** - * 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 extends PageSettings = 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< - TElement, - TPage, - TPageSettings - >; - /** - * Return an AsyncIterableIterator that works a page at a time - */ - byPage: ( - settings?: TPageSettings, - ) => AsyncIterableIterator>; -} - -/** - * An interface that describes how to communicate with the service. - */ -export interface PagedResult< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, -> { - /** - * Link to the first page of results. - */ - firstPageLink?: string; - /** - * A method that returns a page of results. - */ - getPage: ( - pageLink?: string, - ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; -} - -/** - * Options for the paging helper - */ -export interface BuildPagedAsyncIteratorOptions { - itemName?: string; - nextLinkName?: string; -} diff --git a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/api/pagingHelpers.ts b/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/static-helpers/pagingHelpers.ts similarity index 71% rename from packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/api/pagingHelpers.ts rename to packages/typespec-test/test/batch_modular/generated/typespec-ts/src/static-helpers/pagingHelpers.ts index 0fc574c83c..5855ada5a8 100644 --- a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/api/pagingHelpers.ts +++ b/packages/typespec-test/test/batch_modular/generated/typespec-ts/src/static-helpers/pagingHelpers.ts @@ -1,19 +1,102 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { Client, createRestError, PathUncheckedResponse, } from "@azure-rest/core-client"; import { RestError } from "@azure/core-rest-pipeline"; -import { - BuildPagedAsyncIteratorOptions, - ContinuablePage, - PageSettings, - PagedAsyncIterableIterator, - PagedResult, -} from "../models/pagingTypes.js"; + +/** + * Options for the byPage method + */ +export interface PageSettings { + /** + * A reference to a specific page to start iterating from. + */ + continuationToken?: string; +} + +/** + * An interface that describes a page of results. + */ +export type ContinuablePage = TPage & { + /** + * 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 extends PageSettings = 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< + TElement, + TPage, + TPageSettings + >; + /** + * Return an AsyncIterableIterator that works a page at a time + */ + byPage: ( + settings?: TPageSettings, + ) => AsyncIterableIterator>; +} + +/** + * An interface that describes how to communicate with the service. + */ +export interface PagedResult< + TElement, + TPage = TElement[], + TPageSettings extends PageSettings = PageSettings, +> { + /** + * Link to the first page of results. + */ + firstPageLink?: string; + /** + * A method that returns a page of results. + */ + getPage: ( + pageLink?: string, + ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; +} + +/** + * Options for the paging helper + */ +export interface BuildPagedAsyncIteratorOptions { + itemName?: string; + nextLinkName?: string; +} /** * Helper to paginate results in a generic way and return a PagedAsyncIterableIterator diff --git a/packages/typespec-test/test/chatApi_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts b/packages/typespec-test/test/chatApi_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-test/test/chatApi_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts +++ b/packages/typespec-test/test/chatApi_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/api/operations.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/api/operations.ts index aa84a31f51..6bf53a1507 100644 --- a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/api/operations.ts +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/api/operations.ts @@ -16,8 +16,6 @@ import { _PagedTextBlockItem, _PagedTextBlocklist, } from "../models/models.js"; -import { PagedAsyncIterableIterator } from "../models/pagingTypes.js"; -import { buildPagedAsyncIterator } from "./pagingHelpers.js"; import { ContentSafetyContext as Client } from "./index.js"; import { StreamableMethod, @@ -25,6 +23,10 @@ import { PathUncheckedResponse, createRestError, } from "@azure-rest/core-client"; +import { + PagedAsyncIterableIterator, + buildPagedAsyncIterator, +} from "../static-helpers/pagingHelpers.js"; import { AnalyzeTextOptionalParams, AnalyzeImageOptionalParams, diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/contentSafetyClient.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/contentSafetyClient.ts index 3afbfd2219..69edc1d10d 100644 --- a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/contentSafetyClient.ts +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/contentSafetyClient.ts @@ -26,7 +26,6 @@ import { GetTextBlocklistItemOptionalParams, ListTextBlocklistItemsOptionalParams, } from "./models/options.js"; -import { PagedAsyncIterableIterator } from "./models/pagingTypes.js"; import { createContentSafety, ContentSafetyContext, @@ -42,6 +41,7 @@ import { getTextBlocklistItem, listTextBlocklistItems, } from "./api/index.js"; +import { PagedAsyncIterableIterator } from "./static-helpers/pagingHelpers.js"; export { ContentSafetyClientOptionalParams } from "./api/contentSafetyContext.js"; diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/index.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/index.ts index f13998a0ed..1583101c8b 100644 --- a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/index.ts +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/index.ts @@ -1,6 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { + PageSettings, + ContinuablePage, + PagedAsyncIterableIterator, +} from "./static-helpers/pagingHelpers.js"; + export { ContentSafetyClient, ContentSafetyClientOptionalParams, @@ -35,7 +41,5 @@ export { RemoveBlockItemsOptionalParams, GetTextBlocklistItemOptionalParams, ListTextBlocklistItemsOptionalParams, - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, } from "./models/index.js"; +export { PageSettings, ContinuablePage, PagedAsyncIterableIterator }; diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/models/index.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/models/index.ts index d408658ac3..eb04c7c5b2 100644 --- a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/models/index.ts +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/models/index.ts @@ -34,8 +34,3 @@ export { GetTextBlocklistItemOptionalParams, ListTextBlocklistItemsOptionalParams, } from "./options.js"; -export { - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, -} from "./pagingTypes.js"; diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/models/pagingTypes.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/models/pagingTypes.ts deleted file mode 100644 index f734b48e62..0000000000 --- a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/models/pagingTypes.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -/** - * Options for the byPage method - */ -export interface PageSettings { - /** - * A reference to a specific page to start iterating from. - */ - continuationToken?: string; -} - -/** - * An interface that describes a page of results. - */ -export type ContinuablePage = TPage & { - /** - * 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 extends PageSettings = 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< - TElement, - TPage, - TPageSettings - >; - /** - * Return an AsyncIterableIterator that works a page at a time - */ - byPage: ( - settings?: TPageSettings, - ) => AsyncIterableIterator>; -} - -/** - * An interface that describes how to communicate with the service. - */ -export interface PagedResult< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, -> { - /** - * Link to the first page of results. - */ - firstPageLink?: string; - /** - * A method that returns a page of results. - */ - getPage: ( - pageLink?: string, - ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; -} - -/** - * Options for the paging helper - */ -export interface BuildPagedAsyncIteratorOptions { - itemName?: string; - nextLinkName?: string; -} diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/api/pagingHelpers.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/static-helpers/pagingHelpers.ts similarity index 71% rename from packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/api/pagingHelpers.ts rename to packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/static-helpers/pagingHelpers.ts index 0fc574c83c..5855ada5a8 100644 --- a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/api/pagingHelpers.ts +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/static-helpers/pagingHelpers.ts @@ -1,19 +1,102 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { Client, createRestError, PathUncheckedResponse, } from "@azure-rest/core-client"; import { RestError } from "@azure/core-rest-pipeline"; -import { - BuildPagedAsyncIteratorOptions, - ContinuablePage, - PageSettings, - PagedAsyncIterableIterator, - PagedResult, -} from "../models/pagingTypes.js"; + +/** + * Options for the byPage method + */ +export interface PageSettings { + /** + * A reference to a specific page to start iterating from. + */ + continuationToken?: string; +} + +/** + * An interface that describes a page of results. + */ +export type ContinuablePage = TPage & { + /** + * 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 extends PageSettings = 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< + TElement, + TPage, + TPageSettings + >; + /** + * Return an AsyncIterableIterator that works a page at a time + */ + byPage: ( + settings?: TPageSettings, + ) => AsyncIterableIterator>; +} + +/** + * An interface that describes how to communicate with the service. + */ +export interface PagedResult< + TElement, + TPage = TElement[], + TPageSettings extends PageSettings = PageSettings, +> { + /** + * Link to the first page of results. + */ + firstPageLink?: string; + /** + * A method that returns a page of results. + */ + getPage: ( + pageLink?: string, + ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; +} + +/** + * Options for the paging helper + */ +export interface BuildPagedAsyncIteratorOptions { + itemName?: string; + nextLinkName?: string; +} /** * Helper to paginate results in a generic way and return a PagedAsyncIterableIterator diff --git a/packages/typespec-test/test/eventgrid_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts b/packages/typespec-test/test/eventgrid_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-test/test/eventgrid_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts +++ b/packages/typespec-test/test/eventgrid_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-test/test/hierarchy_generic/generated/typespec-ts/src/helpers/serializerHelpers.ts b/packages/typespec-test/test/hierarchy_generic/generated/typespec-ts/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-test/test/hierarchy_generic/generated/typespec-ts/src/helpers/serializerHelpers.ts +++ b/packages/typespec-test/test/hierarchy_generic/generated/typespec-ts/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/review/load-testing.api.md b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/review/load-testing.api.md index 111103fb54..a54d037375 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/review/load-testing.api.md +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/review/load-testing.api.md @@ -687,7 +687,7 @@ export class TestProfileAdministrationOperationsClient { createOrUpdateTestProfile(testProfileId: string, body: TestProfileAdministrationOperationsClientTestProfile, options?: CreateOrUpdateTestProfileOptionalParams): Promise; deleteTestProfile(testProfileId: string, options?: DeleteTestProfileOptionalParams): Promise; getTestProfile(testProfileId: string, options?: GetTestProfileOptionalParams): Promise; - listTestProfiles(options?: ListTestProfilesOptionalParams): TestProfileAdministrationOperationsClientPagedAsyncIterableIterator; + listTestProfiles(options?: ListTestProfilesOptionalParams): PagedAsyncIterableIterator; readonly pipeline: Pipeline; } @@ -731,11 +731,6 @@ export interface TestProfileAdministrationOperationsClientCertificateMetadata { // @public export type TestProfileAdministrationOperationsClientCertificateType = string; -// @public -export type TestProfileAdministrationOperationsClientContinuablePage = TPage & { - continuationToken?: string; -}; - // @public export interface TestProfileAdministrationOperationsClientDimensionFilter { name?: string; @@ -1026,18 +1021,6 @@ export interface TestProfileAdministrationOperationsClientOptionalParams extends apiVersion?: string; } -// @public -export interface TestProfileAdministrationOperationsClientPagedAsyncIterableIterator { - [Symbol.asyncIterator](): TestProfileAdministrationOperationsClientPagedAsyncIterableIterator; - byPage: (settings?: TPageSettings) => AsyncIterableIterator>; - next(): Promise>; -} - -// @public -export interface TestProfileAdministrationOperationsClientPageSettings { - continuationToken?: string; -} - // @public export interface TestProfileAdministrationOperationsClientPassFailCriteria { passFailMetrics?: Record; @@ -1384,7 +1367,7 @@ export class TestProfileRunOperationsClient { createOrUpdateTestProfileRun(testProfileRunId: string, body: TestProfileRunOperationsClientTestProfileRun, options?: CreateOrUpdateTestProfileRunOptionalParams): Promise; deleteTestProfileRun(testProfileRunId: string, options?: DeleteTestProfileRunOptionalParams): Promise; getTestProfileRun(testProfileRunId: string, options?: GetTestProfileRunOptionalParams): Promise; - listTestProfileRuns(options?: ListTestProfileRunsOptionalParams): TestProfileRunOperationsClientPagedAsyncIterableIterator; + listTestProfileRuns(options?: ListTestProfileRunsOptionalParams): PagedAsyncIterableIterator; readonly pipeline: Pipeline; stopTestProfileRun(testProfileRunId: string, options?: StopTestProfileRunOptionalParams): Promise; } @@ -1429,11 +1412,6 @@ export interface TestProfileRunOperationsClientCertificateMetadata { // @public export type TestProfileRunOperationsClientCertificateType = string; -// @public -export type TestProfileRunOperationsClientContinuablePage = TPage & { - continuationToken?: string; -}; - // @public export interface TestProfileRunOperationsClientDimensionFilter { name?: string; @@ -1724,18 +1702,6 @@ export interface TestProfileRunOperationsClientOptionalParams extends ClientOpti apiVersion?: string; } -// @public -export interface TestProfileRunOperationsClientPagedAsyncIterableIterator { - [Symbol.asyncIterator](): TestProfileRunOperationsClientPagedAsyncIterableIterator; - byPage: (settings?: TPageSettings) => AsyncIterableIterator>; - next(): Promise>; -} - -// @public -export interface TestProfileRunOperationsClientPageSettings { - continuationToken?: string; -} - // @public export interface TestProfileRunOperationsClientPassFailCriteria { passFailMetrics?: Record; @@ -2156,8 +2122,8 @@ export class TestRunOperationsClient { listMetricDefinitions(testRunId: string, metricNamespace: string, options?: ListMetricDefinitionsOptionalParams): Promise; listMetricDimensionValues(testRunId: string, name: string, metricname: string, metricNamespace: string, timespan: string, options?: ListMetricDimensionValuesOptionalParams): Promise; listMetricNamespaces(testRunId: string, options?: ListMetricNamespacesOptionalParams): Promise; - listMetrics(testRunId: string, metricname: string, metricNamespace: string, timespan: string, body?: TestRunOperationsClientMetricRequestPayload, options?: ListMetricsOptionalParams): TestRunOperationsClientPagedAsyncIterableIterator; - listTestRuns(options?: ListTestRunsOptionalParams): TestRunOperationsClientPagedAsyncIterableIterator; + listMetrics(testRunId: string, metricname: string, metricNamespace: string, timespan: string, body?: TestRunOperationsClientMetricRequestPayload, options?: ListMetricsOptionalParams): PagedAsyncIterableIterator; + listTestRuns(options?: ListTestRunsOptionalParams): PagedAsyncIterableIterator; readonly pipeline: Pipeline; stopTestRun(testRunId: string, options?: StopTestRunOptionalParams): Promise; } @@ -2202,11 +2168,6 @@ export interface TestRunOperationsClientCertificateMetadata { // @public export type TestRunOperationsClientCertificateType = string; -// @public -export type TestRunOperationsClientContinuablePage = TPage & { - continuationToken?: string; -}; - // @public export interface TestRunOperationsClientCreateOrUpdateAppComponentsOptionalParams extends OperationOptions { contentType?: string; @@ -2515,18 +2476,6 @@ export interface TestRunOperationsClientOptionalParams extends ClientOptions { apiVersion?: string; } -// @public -export interface TestRunOperationsClientPagedAsyncIterableIterator { - [Symbol.asyncIterator](): TestRunOperationsClientPagedAsyncIterableIterator; - byPage: (settings?: TPageSettings) => AsyncIterableIterator>; - next(): Promise>; -} - -// @public -export interface TestRunOperationsClientPageSettings { - continuationToken?: string; -} - // @public export interface TestRunOperationsClientPassFailCriteria { passFailMetrics?: Record; diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/administrationOperations/administrationOperationsClient.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/administrationOperations/administrationOperationsClient.ts index 0e229a73aa..94df36782e 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/administrationOperations/administrationOperationsClient.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/administrationOperations/administrationOperationsClient.ts @@ -23,7 +23,6 @@ import { DeleteTestFileOptionalParams, DeleteTestOptionalParams, } from "./models/options.js"; -import { PagedAsyncIterableIterator } from "./models/pagingTypes.js"; import { createAdministrationOperations, LoadTestServiceContext, @@ -41,6 +40,7 @@ import { deleteTestFile, deleteTest, } from "./api/index.js"; +import { PagedAsyncIterableIterator } from "../static-helpers/pagingHelpers.js"; export { AdministrationOperationsClientOptionalParams } from "./api/administrationOperationsContext.js"; diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/administrationOperations/api/operations.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/administrationOperations/api/operations.ts index bf2832955e..36bf73a3d4 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/administrationOperations/api/operations.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/administrationOperations/api/operations.ts @@ -16,8 +16,6 @@ import { _PagedTest, _PagedTestFileInfo, } from "../models/models.js"; -import { PagedAsyncIterableIterator } from "../models/pagingTypes.js"; -import { buildPagedAsyncIterator } from "./pagingHelpers.js"; import { LoadTestServiceContext as Client } from "./index.js"; import { StreamableMethod, @@ -26,6 +24,10 @@ import { createRestError, } from "@azure-rest/core-client"; import { serializeRecord } from "../../helpers/serializerHelpers.js"; +import { + PagedAsyncIterableIterator, + buildPagedAsyncIterator, +} from "../../static-helpers/pagingHelpers.js"; import { CreateOrUpdateTestOptionalParams, CreateOrUpdateAppComponentsOptionalParams, diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/administrationOperations/api/pagingHelpers.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/administrationOperations/api/pagingHelpers.ts deleted file mode 100644 index 0fc574c83c..0000000000 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/administrationOperations/api/pagingHelpers.ts +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { - Client, - createRestError, - PathUncheckedResponse, -} from "@azure-rest/core-client"; -import { RestError } from "@azure/core-rest-pipeline"; -import { - BuildPagedAsyncIteratorOptions, - ContinuablePage, - PageSettings, - PagedAsyncIterableIterator, - PagedResult, -} from "../models/pagingTypes.js"; - -/** - * Helper to paginate results in a generic way and return a PagedAsyncIterableIterator - */ -export function buildPagedAsyncIterator< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, - TResponse extends PathUncheckedResponse = PathUncheckedResponse, ->( - client: Client, - getInitialResponse: () => PromiseLike, - processResponseBody: (result: TResponse) => PromiseLike, - expectedStatuses: string[], - options: BuildPagedAsyncIteratorOptions = {}, -): PagedAsyncIterableIterator { - const itemName = options.itemName ?? "value"; - const nextLinkName = options.nextLinkName ?? "nextLink"; - const pagedResult: PagedResult = { - getPage: async (pageLink?: string) => { - const result = - pageLink === undefined - ? await getInitialResponse() - : await client.pathUnchecked(pageLink).get(); - checkPagingRequest(result, expectedStatuses); - const results = await processResponseBody(result as TResponse); - const nextLink = getNextLink(results, nextLinkName); - const values = getElements(results, itemName) as TPage; - return { - page: values, - nextPageLink: nextLink, - }; - }, - byPage: (settings?: TPageSettings) => { - const { continuationToken } = settings ?? {}; - return getPageAsyncIterator(pagedResult, { - pageLink: continuationToken, - }); - }, - }; - return getPagedAsyncIterator(pagedResult); -} - -/** - * 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 extends PageSettings = PageSettings, ->( - pagedResult: PagedResult, -): PagedAsyncIterableIterator { - const iter = getItemAsyncIterator( - pagedResult, - ); - return { - next() { - return iter.next(); - }, - [Symbol.asyncIterator]() { - return this; - }, - byPage: - pagedResult?.byPage ?? - ((settings?: TPageSettings) => { - const { continuationToken } = settings ?? {}; - return getPageAsyncIterator(pagedResult, { - pageLink: continuationToken, - }); - }), - }; -} - -async function* getItemAsyncIterator< - TElement, - TPage, - TPageSettings extends PageSettings, ->( - pagedResult: PagedResult, -): AsyncIterableIterator { - const pages = getPageAsyncIterator(pagedResult); - for await (const page of pages) { - yield* page as unknown as TElement[]; - } -} - -async function* getPageAsyncIterator< - TElement, - TPage, - TPageSettings extends PageSettings, ->( - pagedResult: PagedResult, - options: { - pageLink?: string; - } = {}, -): AsyncIterableIterator> { - const { pageLink } = options; - let response = await pagedResult.getPage( - pageLink ?? pagedResult.firstPageLink, - ); - if (!response) { - return; - } - let result = response.page as ContinuablePage; - result.continuationToken = response.nextPageLink; - yield result; - while (response.nextPageLink) { - response = await pagedResult.getPage(response.nextPageLink); - if (!response) { - return; - } - result = response.page as ContinuablePage; - result.continuationToken = response.nextPageLink; - yield result; - } -} - -/** - * 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)[nextLinkName]; - - if ( - typeof nextLink !== "string" && - typeof nextLink !== "undefined" && - nextLink !== null - ) { - throw new RestError( - `Body Property ${nextLinkName} should be a string or undefined or null but got ${typeof nextLink}`, - ); - } - - if (nextLink === null) { - return undefined; - } - - return nextLink; -} - -/** - * Gets the elements of the current request in the body. - */ -function getElements(body: unknown, itemName: string): T[] { - const value = (body as Record)[itemName] as T[]; - if (!Array.isArray(value)) { - throw new RestError( - `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, - expectedStatuses: string[], -): void { - if (!expectedStatuses.includes(response.status)) { - throw createRestError( - `Pagination failed with unexpected statusCode ${response.status}`, - response, - ); - } -} diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/administrationOperations/index.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/administrationOperations/index.ts index dee5083a2d..37fa3b22a6 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/administrationOperations/index.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/administrationOperations/index.ts @@ -100,7 +100,4 @@ export { UploadTestFileOptionalParams, DeleteTestFileOptionalParams, DeleteTestOptionalParams, - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, } from "./models/index.js"; diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/administrationOperations/models/index.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/administrationOperations/models/index.ts index 5968849e35..46f8efaa38 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/administrationOperations/models/index.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/administrationOperations/models/index.ts @@ -99,8 +99,3 @@ export { DeleteTestFileOptionalParams, DeleteTestOptionalParams, } from "./options.js"; -export { - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, -} from "./pagingTypes.js"; diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/administrationOperations/models/pagingTypes.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/administrationOperations/models/pagingTypes.ts deleted file mode 100644 index f734b48e62..0000000000 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/administrationOperations/models/pagingTypes.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -/** - * Options for the byPage method - */ -export interface PageSettings { - /** - * A reference to a specific page to start iterating from. - */ - continuationToken?: string; -} - -/** - * An interface that describes a page of results. - */ -export type ContinuablePage = TPage & { - /** - * 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 extends PageSettings = 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< - TElement, - TPage, - TPageSettings - >; - /** - * Return an AsyncIterableIterator that works a page at a time - */ - byPage: ( - settings?: TPageSettings, - ) => AsyncIterableIterator>; -} - -/** - * An interface that describes how to communicate with the service. - */ -export interface PagedResult< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, -> { - /** - * Link to the first page of results. - */ - firstPageLink?: string; - /** - * A method that returns a page of results. - */ - getPage: ( - pageLink?: string, - ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; -} - -/** - * Options for the paging helper - */ -export interface BuildPagedAsyncIteratorOptions { - itemName?: string; - nextLinkName?: string; -} diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/index.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/index.ts index cefaf7b134..b1197ea79a 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/index.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/index.ts @@ -1,6 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { + PageSettings, + ContinuablePage, + PagedAsyncIterableIterator, +} from "./static-helpers/pagingHelpers.js"; + export { AdministrationOperationsClient, AdministrationOperationsClientOptionalParams, @@ -100,10 +106,8 @@ export { UploadTestFileOptionalParams, DeleteTestFileOptionalParams, DeleteTestOptionalParams, - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, } from "./administrationOperations/models/index.js"; +export { PageSettings, ContinuablePage, PagedAsyncIterableIterator }; export { TestRunOperationsClient, TestRunOperationsClientOptionalParams, @@ -205,9 +209,6 @@ export { ListMetricsOptionalParams, ListTestRunsOptionalParams, StopTestRunOptionalParams, - PageSettings as TestRunOperationsClientPageSettings, - ContinuablePage as TestRunOperationsClientContinuablePage, - PagedAsyncIterableIterator as TestRunOperationsClientPagedAsyncIterableIterator, } from "./testRunOperations/models/index.js"; export { TestProfileAdministrationOperationsClient, @@ -300,9 +301,6 @@ export { DeleteTestProfileOptionalParams, GetTestProfileOptionalParams, ListTestProfilesOptionalParams, - PageSettings as TestProfileAdministrationOperationsClientPageSettings, - ContinuablePage as TestProfileAdministrationOperationsClientContinuablePage, - PagedAsyncIterableIterator as TestProfileAdministrationOperationsClientPagedAsyncIterableIterator, } from "./testProfileAdministrationOperations/models/index.js"; export { TestProfileRunOperationsClient, @@ -396,7 +394,4 @@ export { GetTestProfileRunOptionalParams, ListTestProfileRunsOptionalParams, StopTestProfileRunOptionalParams, - PageSettings as TestProfileRunOperationsClientPageSettings, - ContinuablePage as TestProfileRunOperationsClientContinuablePage, - PagedAsyncIterableIterator as TestProfileRunOperationsClientPagedAsyncIterableIterator, } from "./testProfileRunOperations/models/index.js"; diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/static-helpers/pagingHelpers.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/static-helpers/pagingHelpers.ts new file mode 100644 index 0000000000..5855ada5a8 --- /dev/null +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/static-helpers/pagingHelpers.ts @@ -0,0 +1,277 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { + Client, + createRestError, + PathUncheckedResponse, +} from "@azure-rest/core-client"; +import { RestError } from "@azure/core-rest-pipeline"; + +/** + * Options for the byPage method + */ +export interface PageSettings { + /** + * A reference to a specific page to start iterating from. + */ + continuationToken?: string; +} + +/** + * An interface that describes a page of results. + */ +export type ContinuablePage = TPage & { + /** + * 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 extends PageSettings = 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< + TElement, + TPage, + TPageSettings + >; + /** + * Return an AsyncIterableIterator that works a page at a time + */ + byPage: ( + settings?: TPageSettings, + ) => AsyncIterableIterator>; +} + +/** + * An interface that describes how to communicate with the service. + */ +export interface PagedResult< + TElement, + TPage = TElement[], + TPageSettings extends PageSettings = PageSettings, +> { + /** + * Link to the first page of results. + */ + firstPageLink?: string; + /** + * A method that returns a page of results. + */ + getPage: ( + pageLink?: string, + ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; +} + +/** + * Options for the paging helper + */ +export interface BuildPagedAsyncIteratorOptions { + itemName?: string; + nextLinkName?: string; +} + +/** + * Helper to paginate results in a generic way and return a PagedAsyncIterableIterator + */ +export function buildPagedAsyncIterator< + TElement, + TPage = TElement[], + TPageSettings extends PageSettings = PageSettings, + TResponse extends PathUncheckedResponse = PathUncheckedResponse, +>( + client: Client, + getInitialResponse: () => PromiseLike, + processResponseBody: (result: TResponse) => PromiseLike, + expectedStatuses: string[], + options: BuildPagedAsyncIteratorOptions = {}, +): PagedAsyncIterableIterator { + const itemName = options.itemName ?? "value"; + const nextLinkName = options.nextLinkName ?? "nextLink"; + const pagedResult: PagedResult = { + getPage: async (pageLink?: string) => { + const result = + pageLink === undefined + ? await getInitialResponse() + : await client.pathUnchecked(pageLink).get(); + checkPagingRequest(result, expectedStatuses); + const results = await processResponseBody(result as TResponse); + const nextLink = getNextLink(results, nextLinkName); + const values = getElements(results, itemName) as TPage; + return { + page: values, + nextPageLink: nextLink, + }; + }, + byPage: (settings?: TPageSettings) => { + const { continuationToken } = settings ?? {}; + return getPageAsyncIterator(pagedResult, { + pageLink: continuationToken, + }); + }, + }; + return getPagedAsyncIterator(pagedResult); +} + +/** + * 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 extends PageSettings = PageSettings, +>( + pagedResult: PagedResult, +): PagedAsyncIterableIterator { + const iter = getItemAsyncIterator( + pagedResult, + ); + return { + next() { + return iter.next(); + }, + [Symbol.asyncIterator]() { + return this; + }, + byPage: + pagedResult?.byPage ?? + ((settings?: TPageSettings) => { + const { continuationToken } = settings ?? {}; + return getPageAsyncIterator(pagedResult, { + pageLink: continuationToken, + }); + }), + }; +} + +async function* getItemAsyncIterator< + TElement, + TPage, + TPageSettings extends PageSettings, +>( + pagedResult: PagedResult, +): AsyncIterableIterator { + const pages = getPageAsyncIterator(pagedResult); + for await (const page of pages) { + yield* page as unknown as TElement[]; + } +} + +async function* getPageAsyncIterator< + TElement, + TPage, + TPageSettings extends PageSettings, +>( + pagedResult: PagedResult, + options: { + pageLink?: string; + } = {}, +): AsyncIterableIterator> { + const { pageLink } = options; + let response = await pagedResult.getPage( + pageLink ?? pagedResult.firstPageLink, + ); + if (!response) { + return; + } + let result = response.page as ContinuablePage; + result.continuationToken = response.nextPageLink; + yield result; + while (response.nextPageLink) { + response = await pagedResult.getPage(response.nextPageLink); + if (!response) { + return; + } + result = response.page as ContinuablePage; + result.continuationToken = response.nextPageLink; + yield result; + } +} + +/** + * 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)[nextLinkName]; + + if ( + typeof nextLink !== "string" && + typeof nextLink !== "undefined" && + nextLink !== null + ) { + throw new RestError( + `Body Property ${nextLinkName} should be a string or undefined or null but got ${typeof nextLink}`, + ); + } + + if (nextLink === null) { + return undefined; + } + + return nextLink; +} + +/** + * Gets the elements of the current request in the body. + */ +function getElements(body: unknown, itemName: string): T[] { + const value = (body as Record)[itemName] as T[]; + if (!Array.isArray(value)) { + throw new RestError( + `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, + expectedStatuses: string[], +): void { + if (!expectedStatuses.includes(response.status)) { + throw createRestError( + `Pagination failed with unexpected statusCode ${response.status}`, + response, + ); + } +} diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileAdministrationOperations/api/operations.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileAdministrationOperations/api/operations.ts index eae7fda220..730ee59b5c 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileAdministrationOperations/api/operations.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileAdministrationOperations/api/operations.ts @@ -6,8 +6,6 @@ import { TestProfile, _PagedTestProfile, } from "../models/models.js"; -import { PagedAsyncIterableIterator } from "../models/pagingTypes.js"; -import { buildPagedAsyncIterator } from "./pagingHelpers.js"; import { LoadTestServiceContext as Client } from "./index.js"; import { StreamableMethod, @@ -15,6 +13,10 @@ import { PathUncheckedResponse, createRestError, } from "@azure-rest/core-client"; +import { + PagedAsyncIterableIterator, + buildPagedAsyncIterator, +} from "../../static-helpers/pagingHelpers.js"; import { CreateOrUpdateTestProfileOptionalParams, DeleteTestProfileOptionalParams, diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileAdministrationOperations/api/pagingHelpers.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileAdministrationOperations/api/pagingHelpers.ts deleted file mode 100644 index 0fc574c83c..0000000000 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileAdministrationOperations/api/pagingHelpers.ts +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { - Client, - createRestError, - PathUncheckedResponse, -} from "@azure-rest/core-client"; -import { RestError } from "@azure/core-rest-pipeline"; -import { - BuildPagedAsyncIteratorOptions, - ContinuablePage, - PageSettings, - PagedAsyncIterableIterator, - PagedResult, -} from "../models/pagingTypes.js"; - -/** - * Helper to paginate results in a generic way and return a PagedAsyncIterableIterator - */ -export function buildPagedAsyncIterator< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, - TResponse extends PathUncheckedResponse = PathUncheckedResponse, ->( - client: Client, - getInitialResponse: () => PromiseLike, - processResponseBody: (result: TResponse) => PromiseLike, - expectedStatuses: string[], - options: BuildPagedAsyncIteratorOptions = {}, -): PagedAsyncIterableIterator { - const itemName = options.itemName ?? "value"; - const nextLinkName = options.nextLinkName ?? "nextLink"; - const pagedResult: PagedResult = { - getPage: async (pageLink?: string) => { - const result = - pageLink === undefined - ? await getInitialResponse() - : await client.pathUnchecked(pageLink).get(); - checkPagingRequest(result, expectedStatuses); - const results = await processResponseBody(result as TResponse); - const nextLink = getNextLink(results, nextLinkName); - const values = getElements(results, itemName) as TPage; - return { - page: values, - nextPageLink: nextLink, - }; - }, - byPage: (settings?: TPageSettings) => { - const { continuationToken } = settings ?? {}; - return getPageAsyncIterator(pagedResult, { - pageLink: continuationToken, - }); - }, - }; - return getPagedAsyncIterator(pagedResult); -} - -/** - * 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 extends PageSettings = PageSettings, ->( - pagedResult: PagedResult, -): PagedAsyncIterableIterator { - const iter = getItemAsyncIterator( - pagedResult, - ); - return { - next() { - return iter.next(); - }, - [Symbol.asyncIterator]() { - return this; - }, - byPage: - pagedResult?.byPage ?? - ((settings?: TPageSettings) => { - const { continuationToken } = settings ?? {}; - return getPageAsyncIterator(pagedResult, { - pageLink: continuationToken, - }); - }), - }; -} - -async function* getItemAsyncIterator< - TElement, - TPage, - TPageSettings extends PageSettings, ->( - pagedResult: PagedResult, -): AsyncIterableIterator { - const pages = getPageAsyncIterator(pagedResult); - for await (const page of pages) { - yield* page as unknown as TElement[]; - } -} - -async function* getPageAsyncIterator< - TElement, - TPage, - TPageSettings extends PageSettings, ->( - pagedResult: PagedResult, - options: { - pageLink?: string; - } = {}, -): AsyncIterableIterator> { - const { pageLink } = options; - let response = await pagedResult.getPage( - pageLink ?? pagedResult.firstPageLink, - ); - if (!response) { - return; - } - let result = response.page as ContinuablePage; - result.continuationToken = response.nextPageLink; - yield result; - while (response.nextPageLink) { - response = await pagedResult.getPage(response.nextPageLink); - if (!response) { - return; - } - result = response.page as ContinuablePage; - result.continuationToken = response.nextPageLink; - yield result; - } -} - -/** - * 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)[nextLinkName]; - - if ( - typeof nextLink !== "string" && - typeof nextLink !== "undefined" && - nextLink !== null - ) { - throw new RestError( - `Body Property ${nextLinkName} should be a string or undefined or null but got ${typeof nextLink}`, - ); - } - - if (nextLink === null) { - return undefined; - } - - return nextLink; -} - -/** - * Gets the elements of the current request in the body. - */ -function getElements(body: unknown, itemName: string): T[] { - const value = (body as Record)[itemName] as T[]; - if (!Array.isArray(value)) { - throw new RestError( - `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, - expectedStatuses: string[], -): void { - if (!expectedStatuses.includes(response.status)) { - throw createRestError( - `Pagination failed with unexpected statusCode ${response.status}`, - response, - ); - } -} diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileAdministrationOperations/index.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileAdministrationOperations/index.ts index e380cf9d16..4523732658 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileAdministrationOperations/index.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileAdministrationOperations/index.ts @@ -92,7 +92,4 @@ export { DeleteTestProfileOptionalParams, GetTestProfileOptionalParams, ListTestProfilesOptionalParams, - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, } from "./models/index.js"; diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileAdministrationOperations/models/index.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileAdministrationOperations/models/index.ts index e2aecc161b..a7bb9ddbcc 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileAdministrationOperations/models/index.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileAdministrationOperations/models/index.ts @@ -91,8 +91,3 @@ export { GetTestProfileOptionalParams, ListTestProfilesOptionalParams, } from "./options.js"; -export { - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, -} from "./pagingTypes.js"; diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileAdministrationOperations/models/pagingTypes.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileAdministrationOperations/models/pagingTypes.ts deleted file mode 100644 index f734b48e62..0000000000 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileAdministrationOperations/models/pagingTypes.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -/** - * Options for the byPage method - */ -export interface PageSettings { - /** - * A reference to a specific page to start iterating from. - */ - continuationToken?: string; -} - -/** - * An interface that describes a page of results. - */ -export type ContinuablePage = TPage & { - /** - * 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 extends PageSettings = 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< - TElement, - TPage, - TPageSettings - >; - /** - * Return an AsyncIterableIterator that works a page at a time - */ - byPage: ( - settings?: TPageSettings, - ) => AsyncIterableIterator>; -} - -/** - * An interface that describes how to communicate with the service. - */ -export interface PagedResult< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, -> { - /** - * Link to the first page of results. - */ - firstPageLink?: string; - /** - * A method that returns a page of results. - */ - getPage: ( - pageLink?: string, - ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; -} - -/** - * Options for the paging helper - */ -export interface BuildPagedAsyncIteratorOptions { - itemName?: string; - nextLinkName?: string; -} diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileAdministrationOperations/testProfileAdministrationOperationsClient.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileAdministrationOperations/testProfileAdministrationOperationsClient.ts index 7da38bb4c4..c96c9b5729 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileAdministrationOperations/testProfileAdministrationOperationsClient.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileAdministrationOperations/testProfileAdministrationOperationsClient.ts @@ -10,7 +10,6 @@ import { GetTestProfileOptionalParams, ListTestProfilesOptionalParams, } from "./models/options.js"; -import { PagedAsyncIterableIterator } from "./models/pagingTypes.js"; import { createOrUpdateTestProfile, deleteTestProfile, @@ -20,6 +19,7 @@ import { LoadTestServiceContext, TestProfileAdministrationOperationsClientOptionalParams, } from "./api/index.js"; +import { PagedAsyncIterableIterator } from "../static-helpers/pagingHelpers.js"; export { TestProfileAdministrationOperationsClientOptionalParams } from "./api/testProfileAdministrationOperationsContext.js"; diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileRunOperations/api/operations.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileRunOperations/api/operations.ts index 6ccd9e3e1e..98031bfc22 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileRunOperations/api/operations.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileRunOperations/api/operations.ts @@ -2,8 +2,6 @@ // Licensed under the MIT license. import { TestProfileRun, _PagedTestProfileRun } from "../models/models.js"; -import { PagedAsyncIterableIterator } from "../models/pagingTypes.js"; -import { buildPagedAsyncIterator } from "./pagingHelpers.js"; import { LoadTestServiceContext as Client } from "./index.js"; import { StreamableMethod, @@ -11,6 +9,10 @@ import { PathUncheckedResponse, createRestError, } from "@azure-rest/core-client"; +import { + PagedAsyncIterableIterator, + buildPagedAsyncIterator, +} from "../../static-helpers/pagingHelpers.js"; import { CreateOrUpdateTestProfileRunOptionalParams, DeleteTestProfileRunOptionalParams, diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileRunOperations/api/pagingHelpers.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileRunOperations/api/pagingHelpers.ts deleted file mode 100644 index 0fc574c83c..0000000000 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileRunOperations/api/pagingHelpers.ts +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { - Client, - createRestError, - PathUncheckedResponse, -} from "@azure-rest/core-client"; -import { RestError } from "@azure/core-rest-pipeline"; -import { - BuildPagedAsyncIteratorOptions, - ContinuablePage, - PageSettings, - PagedAsyncIterableIterator, - PagedResult, -} from "../models/pagingTypes.js"; - -/** - * Helper to paginate results in a generic way and return a PagedAsyncIterableIterator - */ -export function buildPagedAsyncIterator< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, - TResponse extends PathUncheckedResponse = PathUncheckedResponse, ->( - client: Client, - getInitialResponse: () => PromiseLike, - processResponseBody: (result: TResponse) => PromiseLike, - expectedStatuses: string[], - options: BuildPagedAsyncIteratorOptions = {}, -): PagedAsyncIterableIterator { - const itemName = options.itemName ?? "value"; - const nextLinkName = options.nextLinkName ?? "nextLink"; - const pagedResult: PagedResult = { - getPage: async (pageLink?: string) => { - const result = - pageLink === undefined - ? await getInitialResponse() - : await client.pathUnchecked(pageLink).get(); - checkPagingRequest(result, expectedStatuses); - const results = await processResponseBody(result as TResponse); - const nextLink = getNextLink(results, nextLinkName); - const values = getElements(results, itemName) as TPage; - return { - page: values, - nextPageLink: nextLink, - }; - }, - byPage: (settings?: TPageSettings) => { - const { continuationToken } = settings ?? {}; - return getPageAsyncIterator(pagedResult, { - pageLink: continuationToken, - }); - }, - }; - return getPagedAsyncIterator(pagedResult); -} - -/** - * 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 extends PageSettings = PageSettings, ->( - pagedResult: PagedResult, -): PagedAsyncIterableIterator { - const iter = getItemAsyncIterator( - pagedResult, - ); - return { - next() { - return iter.next(); - }, - [Symbol.asyncIterator]() { - return this; - }, - byPage: - pagedResult?.byPage ?? - ((settings?: TPageSettings) => { - const { continuationToken } = settings ?? {}; - return getPageAsyncIterator(pagedResult, { - pageLink: continuationToken, - }); - }), - }; -} - -async function* getItemAsyncIterator< - TElement, - TPage, - TPageSettings extends PageSettings, ->( - pagedResult: PagedResult, -): AsyncIterableIterator { - const pages = getPageAsyncIterator(pagedResult); - for await (const page of pages) { - yield* page as unknown as TElement[]; - } -} - -async function* getPageAsyncIterator< - TElement, - TPage, - TPageSettings extends PageSettings, ->( - pagedResult: PagedResult, - options: { - pageLink?: string; - } = {}, -): AsyncIterableIterator> { - const { pageLink } = options; - let response = await pagedResult.getPage( - pageLink ?? pagedResult.firstPageLink, - ); - if (!response) { - return; - } - let result = response.page as ContinuablePage; - result.continuationToken = response.nextPageLink; - yield result; - while (response.nextPageLink) { - response = await pagedResult.getPage(response.nextPageLink); - if (!response) { - return; - } - result = response.page as ContinuablePage; - result.continuationToken = response.nextPageLink; - yield result; - } -} - -/** - * 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)[nextLinkName]; - - if ( - typeof nextLink !== "string" && - typeof nextLink !== "undefined" && - nextLink !== null - ) { - throw new RestError( - `Body Property ${nextLinkName} should be a string or undefined or null but got ${typeof nextLink}`, - ); - } - - if (nextLink === null) { - return undefined; - } - - return nextLink; -} - -/** - * Gets the elements of the current request in the body. - */ -function getElements(body: unknown, itemName: string): T[] { - const value = (body as Record)[itemName] as T[]; - if (!Array.isArray(value)) { - throw new RestError( - `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, - expectedStatuses: string[], -): void { - if (!expectedStatuses.includes(response.status)) { - throw createRestError( - `Pagination failed with unexpected statusCode ${response.status}`, - response, - ); - } -} diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileRunOperations/index.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileRunOperations/index.ts index 3de72b65d9..94e4d0fd64 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileRunOperations/index.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileRunOperations/index.ts @@ -93,7 +93,4 @@ export { GetTestProfileRunOptionalParams, ListTestProfileRunsOptionalParams, StopTestProfileRunOptionalParams, - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, } from "./models/index.js"; diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileRunOperations/models/index.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileRunOperations/models/index.ts index 704ef57adc..de0f445826 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileRunOperations/models/index.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileRunOperations/models/index.ts @@ -92,8 +92,3 @@ export { ListTestProfileRunsOptionalParams, StopTestProfileRunOptionalParams, } from "./options.js"; -export { - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, -} from "./pagingTypes.js"; diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileRunOperations/models/pagingTypes.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileRunOperations/models/pagingTypes.ts deleted file mode 100644 index f734b48e62..0000000000 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileRunOperations/models/pagingTypes.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -/** - * Options for the byPage method - */ -export interface PageSettings { - /** - * A reference to a specific page to start iterating from. - */ - continuationToken?: string; -} - -/** - * An interface that describes a page of results. - */ -export type ContinuablePage = TPage & { - /** - * 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 extends PageSettings = 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< - TElement, - TPage, - TPageSettings - >; - /** - * Return an AsyncIterableIterator that works a page at a time - */ - byPage: ( - settings?: TPageSettings, - ) => AsyncIterableIterator>; -} - -/** - * An interface that describes how to communicate with the service. - */ -export interface PagedResult< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, -> { - /** - * Link to the first page of results. - */ - firstPageLink?: string; - /** - * A method that returns a page of results. - */ - getPage: ( - pageLink?: string, - ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; -} - -/** - * Options for the paging helper - */ -export interface BuildPagedAsyncIteratorOptions { - itemName?: string; - nextLinkName?: string; -} diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileRunOperations/testProfileRunOperationsClient.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileRunOperations/testProfileRunOperationsClient.ts index 4c1090f0c6..2a11abbd2d 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileRunOperations/testProfileRunOperationsClient.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testProfileRunOperations/testProfileRunOperationsClient.ts @@ -11,7 +11,6 @@ import { ListTestProfileRunsOptionalParams, StopTestProfileRunOptionalParams, } from "./models/options.js"; -import { PagedAsyncIterableIterator } from "./models/pagingTypes.js"; import { createOrUpdateTestProfileRun, deleteTestProfileRun, @@ -22,6 +21,7 @@ import { LoadTestServiceContext, TestProfileRunOperationsClientOptionalParams, } from "./api/index.js"; +import { PagedAsyncIterableIterator } from "../static-helpers/pagingHelpers.js"; export { TestProfileRunOperationsClientOptionalParams } from "./api/testProfileRunOperationsContext.js"; diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testRunOperations/api/operations.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testRunOperations/api/operations.ts index 23b49ff3c3..1af8db209b 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testRunOperations/api/operations.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testRunOperations/api/operations.ts @@ -22,8 +22,6 @@ import { _Metrics, _PagedTestRun, } from "../models/models.js"; -import { PagedAsyncIterableIterator } from "../models/pagingTypes.js"; -import { buildPagedAsyncIterator } from "./pagingHelpers.js"; import { LoadTestServiceContext as Client } from "./index.js"; import { StreamableMethod, @@ -32,6 +30,10 @@ import { createRestError, } from "@azure-rest/core-client"; import { serializeRecord } from "../../helpers/serializerHelpers.js"; +import { + PagedAsyncIterableIterator, + buildPagedAsyncIterator, +} from "../../static-helpers/pagingHelpers.js"; import { CreateOrUpdateTestRunOptionalParams, CreateOrUpdateAppComponentsOptionalParams, diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testRunOperations/api/pagingHelpers.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testRunOperations/api/pagingHelpers.ts deleted file mode 100644 index 0fc574c83c..0000000000 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testRunOperations/api/pagingHelpers.ts +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { - Client, - createRestError, - PathUncheckedResponse, -} from "@azure-rest/core-client"; -import { RestError } from "@azure/core-rest-pipeline"; -import { - BuildPagedAsyncIteratorOptions, - ContinuablePage, - PageSettings, - PagedAsyncIterableIterator, - PagedResult, -} from "../models/pagingTypes.js"; - -/** - * Helper to paginate results in a generic way and return a PagedAsyncIterableIterator - */ -export function buildPagedAsyncIterator< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, - TResponse extends PathUncheckedResponse = PathUncheckedResponse, ->( - client: Client, - getInitialResponse: () => PromiseLike, - processResponseBody: (result: TResponse) => PromiseLike, - expectedStatuses: string[], - options: BuildPagedAsyncIteratorOptions = {}, -): PagedAsyncIterableIterator { - const itemName = options.itemName ?? "value"; - const nextLinkName = options.nextLinkName ?? "nextLink"; - const pagedResult: PagedResult = { - getPage: async (pageLink?: string) => { - const result = - pageLink === undefined - ? await getInitialResponse() - : await client.pathUnchecked(pageLink).get(); - checkPagingRequest(result, expectedStatuses); - const results = await processResponseBody(result as TResponse); - const nextLink = getNextLink(results, nextLinkName); - const values = getElements(results, itemName) as TPage; - return { - page: values, - nextPageLink: nextLink, - }; - }, - byPage: (settings?: TPageSettings) => { - const { continuationToken } = settings ?? {}; - return getPageAsyncIterator(pagedResult, { - pageLink: continuationToken, - }); - }, - }; - return getPagedAsyncIterator(pagedResult); -} - -/** - * 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 extends PageSettings = PageSettings, ->( - pagedResult: PagedResult, -): PagedAsyncIterableIterator { - const iter = getItemAsyncIterator( - pagedResult, - ); - return { - next() { - return iter.next(); - }, - [Symbol.asyncIterator]() { - return this; - }, - byPage: - pagedResult?.byPage ?? - ((settings?: TPageSettings) => { - const { continuationToken } = settings ?? {}; - return getPageAsyncIterator(pagedResult, { - pageLink: continuationToken, - }); - }), - }; -} - -async function* getItemAsyncIterator< - TElement, - TPage, - TPageSettings extends PageSettings, ->( - pagedResult: PagedResult, -): AsyncIterableIterator { - const pages = getPageAsyncIterator(pagedResult); - for await (const page of pages) { - yield* page as unknown as TElement[]; - } -} - -async function* getPageAsyncIterator< - TElement, - TPage, - TPageSettings extends PageSettings, ->( - pagedResult: PagedResult, - options: { - pageLink?: string; - } = {}, -): AsyncIterableIterator> { - const { pageLink } = options; - let response = await pagedResult.getPage( - pageLink ?? pagedResult.firstPageLink, - ); - if (!response) { - return; - } - let result = response.page as ContinuablePage; - result.continuationToken = response.nextPageLink; - yield result; - while (response.nextPageLink) { - response = await pagedResult.getPage(response.nextPageLink); - if (!response) { - return; - } - result = response.page as ContinuablePage; - result.continuationToken = response.nextPageLink; - yield result; - } -} - -/** - * 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)[nextLinkName]; - - if ( - typeof nextLink !== "string" && - typeof nextLink !== "undefined" && - nextLink !== null - ) { - throw new RestError( - `Body Property ${nextLinkName} should be a string or undefined or null but got ${typeof nextLink}`, - ); - } - - if (nextLink === null) { - return undefined; - } - - return nextLink; -} - -/** - * Gets the elements of the current request in the body. - */ -function getElements(body: unknown, itemName: string): T[] { - const value = (body as Record)[itemName] as T[]; - if (!Array.isArray(value)) { - throw new RestError( - `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, - expectedStatuses: string[], -): void { - if (!expectedStatuses.includes(response.status)) { - throw createRestError( - `Pagination failed with unexpected statusCode ${response.status}`, - response, - ); - } -} diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testRunOperations/index.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testRunOperations/index.ts index 7a52e5b992..1c5dacfb22 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testRunOperations/index.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testRunOperations/index.ts @@ -102,7 +102,4 @@ export { ListMetricsOptionalParams, ListTestRunsOptionalParams, StopTestRunOptionalParams, - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, } from "./models/index.js"; diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testRunOperations/models/index.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testRunOperations/models/index.ts index 894168a7ff..d665c2068f 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testRunOperations/models/index.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testRunOperations/models/index.ts @@ -101,8 +101,3 @@ export { ListTestRunsOptionalParams, StopTestRunOptionalParams, } from "./options.js"; -export { - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, -} from "./pagingTypes.js"; diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testRunOperations/models/pagingTypes.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testRunOperations/models/pagingTypes.ts deleted file mode 100644 index f734b48e62..0000000000 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testRunOperations/models/pagingTypes.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -/** - * Options for the byPage method - */ -export interface PageSettings { - /** - * A reference to a specific page to start iterating from. - */ - continuationToken?: string; -} - -/** - * An interface that describes a page of results. - */ -export type ContinuablePage = TPage & { - /** - * 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 extends PageSettings = 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< - TElement, - TPage, - TPageSettings - >; - /** - * Return an AsyncIterableIterator that works a page at a time - */ - byPage: ( - settings?: TPageSettings, - ) => AsyncIterableIterator>; -} - -/** - * An interface that describes how to communicate with the service. - */ -export interface PagedResult< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, -> { - /** - * Link to the first page of results. - */ - firstPageLink?: string; - /** - * A method that returns a page of results. - */ - getPage: ( - pageLink?: string, - ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; -} - -/** - * Options for the paging helper - */ -export interface BuildPagedAsyncIteratorOptions { - itemName?: string; - nextLinkName?: string; -} diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testRunOperations/testRunOperationsClient.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testRunOperations/testRunOperationsClient.ts index 21c6e52566..ae8e1b17e7 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testRunOperations/testRunOperationsClient.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/testRunOperations/testRunOperationsClient.ts @@ -30,7 +30,6 @@ import { ListTestRunsOptionalParams, StopTestRunOptionalParams, } from "./models/options.js"; -import { PagedAsyncIterableIterator } from "./models/pagingTypes.js"; import { createOrUpdateTestRun, createOrUpdateAppComponents, @@ -50,6 +49,7 @@ import { LoadTestServiceContext, TestRunOperationsClientOptionalParams, } from "./api/index.js"; +import { PagedAsyncIterableIterator } from "../static-helpers/pagingHelpers.js"; export { TestRunOperationsClientOptionalParams } from "./api/testRunOperationsContext.js"; diff --git a/packages/typespec-test/test/openai_generic/generated/typespec-ts/src/helpers/serializerHelpers.ts b/packages/typespec-test/test/openai_generic/generated/typespec-ts/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-test/test/openai_generic/generated/typespec-ts/src/helpers/serializerHelpers.ts +++ b/packages/typespec-test/test/openai_generic/generated/typespec-ts/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-test/test/openai_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts b/packages/typespec-test/test/openai_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-test/test/openai_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts +++ b/packages/typespec-test/test/openai_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-test/test/openai_non_branded/generated/typespec-ts/src/helpers/serializerHelpers.ts b/packages/typespec-test/test/openai_non_branded/generated/typespec-ts/src/helpers/serializerHelpers.ts index 4608af90fd..9c73b7fc55 100644 --- a/packages/typespec-test/test/openai_non_branded/generated/typespec-ts/src/helpers/serializerHelpers.ts +++ b/packages/typespec-test/test/openai_non_branded/generated/typespec-ts/src/helpers/serializerHelpers.ts @@ -37,33 +37,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-test/test/overloads_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts b/packages/typespec-test/test/overloads_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-test/test/overloads_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts +++ b/packages/typespec-test/test/overloads_modular/generated/typespec-ts/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-test/test/parametrizedHost/generated/typespec-ts/src/helpers/serializerHelpers.ts b/packages/typespec-test/test/parametrizedHost/generated/typespec-ts/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-test/test/parametrizedHost/generated/typespec-ts/src/helpers/serializerHelpers.ts +++ b/packages/typespec-test/test/parametrizedHost/generated/typespec-ts/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/api/pagingHelpers.ts b/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/api/pagingHelpers.ts deleted file mode 100644 index 0fc574c83c..0000000000 --- a/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/api/pagingHelpers.ts +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { - Client, - createRestError, - PathUncheckedResponse, -} from "@azure-rest/core-client"; -import { RestError } from "@azure/core-rest-pipeline"; -import { - BuildPagedAsyncIteratorOptions, - ContinuablePage, - PageSettings, - PagedAsyncIterableIterator, - PagedResult, -} from "../models/pagingTypes.js"; - -/** - * Helper to paginate results in a generic way and return a PagedAsyncIterableIterator - */ -export function buildPagedAsyncIterator< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, - TResponse extends PathUncheckedResponse = PathUncheckedResponse, ->( - client: Client, - getInitialResponse: () => PromiseLike, - processResponseBody: (result: TResponse) => PromiseLike, - expectedStatuses: string[], - options: BuildPagedAsyncIteratorOptions = {}, -): PagedAsyncIterableIterator { - const itemName = options.itemName ?? "value"; - const nextLinkName = options.nextLinkName ?? "nextLink"; - const pagedResult: PagedResult = { - getPage: async (pageLink?: string) => { - const result = - pageLink === undefined - ? await getInitialResponse() - : await client.pathUnchecked(pageLink).get(); - checkPagingRequest(result, expectedStatuses); - const results = await processResponseBody(result as TResponse); - const nextLink = getNextLink(results, nextLinkName); - const values = getElements(results, itemName) as TPage; - return { - page: values, - nextPageLink: nextLink, - }; - }, - byPage: (settings?: TPageSettings) => { - const { continuationToken } = settings ?? {}; - return getPageAsyncIterator(pagedResult, { - pageLink: continuationToken, - }); - }, - }; - return getPagedAsyncIterator(pagedResult); -} - -/** - * 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 extends PageSettings = PageSettings, ->( - pagedResult: PagedResult, -): PagedAsyncIterableIterator { - const iter = getItemAsyncIterator( - pagedResult, - ); - return { - next() { - return iter.next(); - }, - [Symbol.asyncIterator]() { - return this; - }, - byPage: - pagedResult?.byPage ?? - ((settings?: TPageSettings) => { - const { continuationToken } = settings ?? {}; - return getPageAsyncIterator(pagedResult, { - pageLink: continuationToken, - }); - }), - }; -} - -async function* getItemAsyncIterator< - TElement, - TPage, - TPageSettings extends PageSettings, ->( - pagedResult: PagedResult, -): AsyncIterableIterator { - const pages = getPageAsyncIterator(pagedResult); - for await (const page of pages) { - yield* page as unknown as TElement[]; - } -} - -async function* getPageAsyncIterator< - TElement, - TPage, - TPageSettings extends PageSettings, ->( - pagedResult: PagedResult, - options: { - pageLink?: string; - } = {}, -): AsyncIterableIterator> { - const { pageLink } = options; - let response = await pagedResult.getPage( - pageLink ?? pagedResult.firstPageLink, - ); - if (!response) { - return; - } - let result = response.page as ContinuablePage; - result.continuationToken = response.nextPageLink; - yield result; - while (response.nextPageLink) { - response = await pagedResult.getPage(response.nextPageLink); - if (!response) { - return; - } - result = response.page as ContinuablePage; - result.continuationToken = response.nextPageLink; - yield result; - } -} - -/** - * 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)[nextLinkName]; - - if ( - typeof nextLink !== "string" && - typeof nextLink !== "undefined" && - nextLink !== null - ) { - throw new RestError( - `Body Property ${nextLinkName} should be a string or undefined or null but got ${typeof nextLink}`, - ); - } - - if (nextLink === null) { - return undefined; - } - - return nextLink; -} - -/** - * Gets the elements of the current request in the body. - */ -function getElements(body: unknown, itemName: string): T[] { - const value = (body as Record)[itemName] as T[]; - if (!Array.isArray(value)) { - throw new RestError( - `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, - expectedStatuses: string[], -): void { - if (!expectedStatuses.includes(response.status)) { - throw createRestError( - `Pagination failed with unexpected statusCode ${response.status}`, - response, - ); - } -} diff --git a/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/api/schemaOperations/index.ts b/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/api/schemaOperations/index.ts index 69b9047511..255aec606b 100644 --- a/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/api/schemaOperations/index.ts +++ b/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/api/schemaOperations/index.ts @@ -8,8 +8,6 @@ import { _PagedSchemaGroup, _PagedVersion, } from "../../models/models.js"; -import { PagedAsyncIterableIterator } from "../../models/pagingTypes.js"; -import { buildPagedAsyncIterator } from "../pagingHelpers.js"; import { SchemaRegistryContext as Client } from "../index.js"; import { StreamableMethod, @@ -18,6 +16,10 @@ import { createRestError, } from "@azure-rest/core-client"; import { stringToUint8Array, uint8ArrayToString } from "@azure/core-util"; +import { + PagedAsyncIterableIterator, + buildPagedAsyncIterator, +} from "../../static-helpers/pagingHelpers.js"; import { SchemaOperationsListSchemaGroupsOptionalParams, SchemaOperationsGetSchemaByIdOptionalParams, diff --git a/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/classic/schemaOperations/index.ts b/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/classic/schemaOperations/index.ts index a492a84997..899c9c7dfa 100644 --- a/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/classic/schemaOperations/index.ts +++ b/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/classic/schemaOperations/index.ts @@ -15,7 +15,7 @@ import { getSchemaIdByContent, registerSchema, } from "../../api/schemaOperations/index.js"; -import { PagedAsyncIterableIterator } from "../../models/pagingTypes.js"; +import { PagedAsyncIterableIterator } from "../../static-helpers/pagingHelpers.js"; import { SchemaOperationsListSchemaGroupsOptionalParams, SchemaOperationsGetSchemaByIdOptionalParams, diff --git a/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/helpers/serializerHelpers.ts b/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/helpers/serializerHelpers.ts +++ b/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/index.ts b/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/index.ts index d4b4b3daba..399c499152 100644 --- a/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/index.ts +++ b/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/index.ts @@ -1,6 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { + PageSettings, + ContinuablePage, + PagedAsyncIterableIterator, +} from "./static-helpers/pagingHelpers.js"; + export { SchemaRegistryClient, SchemaRegistryClientOptionalParams, @@ -20,8 +26,6 @@ export { SchemaOperationsGetSchemaByVersionOptionalParams, SchemaOperationsGetSchemaIdByContentOptionalParams, SchemaOperationsRegisterSchemaOptionalParams, - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, } from "./models/index.js"; export { SchemaOperationsOperations } from "./classic/index.js"; +export { PageSettings, ContinuablePage, PagedAsyncIterableIterator }; diff --git a/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/models/index.ts b/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/models/index.ts index 089dffa3a0..7042bee062 100644 --- a/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/models/index.ts +++ b/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/models/index.ts @@ -19,8 +19,3 @@ export { SchemaOperationsGetSchemaIdByContentOptionalParams, SchemaOperationsRegisterSchemaOptionalParams, } from "./options.js"; -export { - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, -} from "./pagingTypes.js"; diff --git a/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/models/pagingTypes.ts b/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/models/pagingTypes.ts deleted file mode 100644 index f734b48e62..0000000000 --- a/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/models/pagingTypes.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -/** - * Options for the byPage method - */ -export interface PageSettings { - /** - * A reference to a specific page to start iterating from. - */ - continuationToken?: string; -} - -/** - * An interface that describes a page of results. - */ -export type ContinuablePage = TPage & { - /** - * 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 extends PageSettings = 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< - TElement, - TPage, - TPageSettings - >; - /** - * Return an AsyncIterableIterator that works a page at a time - */ - byPage: ( - settings?: TPageSettings, - ) => AsyncIterableIterator>; -} - -/** - * An interface that describes how to communicate with the service. - */ -export interface PagedResult< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, -> { - /** - * Link to the first page of results. - */ - firstPageLink?: string; - /** - * A method that returns a page of results. - */ - getPage: ( - pageLink?: string, - ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; -} - -/** - * Options for the paging helper - */ -export interface BuildPagedAsyncIteratorOptions { - itemName?: string; - nextLinkName?: string; -} diff --git a/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/static-helpers/pagingHelpers.ts b/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/static-helpers/pagingHelpers.ts new file mode 100644 index 0000000000..5855ada5a8 --- /dev/null +++ b/packages/typespec-test/test/schemaRegistry/generated/typespec-ts/src/static-helpers/pagingHelpers.ts @@ -0,0 +1,277 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { + Client, + createRestError, + PathUncheckedResponse, +} from "@azure-rest/core-client"; +import { RestError } from "@azure/core-rest-pipeline"; + +/** + * Options for the byPage method + */ +export interface PageSettings { + /** + * A reference to a specific page to start iterating from. + */ + continuationToken?: string; +} + +/** + * An interface that describes a page of results. + */ +export type ContinuablePage = TPage & { + /** + * 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 extends PageSettings = 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< + TElement, + TPage, + TPageSettings + >; + /** + * Return an AsyncIterableIterator that works a page at a time + */ + byPage: ( + settings?: TPageSettings, + ) => AsyncIterableIterator>; +} + +/** + * An interface that describes how to communicate with the service. + */ +export interface PagedResult< + TElement, + TPage = TElement[], + TPageSettings extends PageSettings = PageSettings, +> { + /** + * Link to the first page of results. + */ + firstPageLink?: string; + /** + * A method that returns a page of results. + */ + getPage: ( + pageLink?: string, + ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; +} + +/** + * Options for the paging helper + */ +export interface BuildPagedAsyncIteratorOptions { + itemName?: string; + nextLinkName?: string; +} + +/** + * Helper to paginate results in a generic way and return a PagedAsyncIterableIterator + */ +export function buildPagedAsyncIterator< + TElement, + TPage = TElement[], + TPageSettings extends PageSettings = PageSettings, + TResponse extends PathUncheckedResponse = PathUncheckedResponse, +>( + client: Client, + getInitialResponse: () => PromiseLike, + processResponseBody: (result: TResponse) => PromiseLike, + expectedStatuses: string[], + options: BuildPagedAsyncIteratorOptions = {}, +): PagedAsyncIterableIterator { + const itemName = options.itemName ?? "value"; + const nextLinkName = options.nextLinkName ?? "nextLink"; + const pagedResult: PagedResult = { + getPage: async (pageLink?: string) => { + const result = + pageLink === undefined + ? await getInitialResponse() + : await client.pathUnchecked(pageLink).get(); + checkPagingRequest(result, expectedStatuses); + const results = await processResponseBody(result as TResponse); + const nextLink = getNextLink(results, nextLinkName); + const values = getElements(results, itemName) as TPage; + return { + page: values, + nextPageLink: nextLink, + }; + }, + byPage: (settings?: TPageSettings) => { + const { continuationToken } = settings ?? {}; + return getPageAsyncIterator(pagedResult, { + pageLink: continuationToken, + }); + }, + }; + return getPagedAsyncIterator(pagedResult); +} + +/** + * 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 extends PageSettings = PageSettings, +>( + pagedResult: PagedResult, +): PagedAsyncIterableIterator { + const iter = getItemAsyncIterator( + pagedResult, + ); + return { + next() { + return iter.next(); + }, + [Symbol.asyncIterator]() { + return this; + }, + byPage: + pagedResult?.byPage ?? + ((settings?: TPageSettings) => { + const { continuationToken } = settings ?? {}; + return getPageAsyncIterator(pagedResult, { + pageLink: continuationToken, + }); + }), + }; +} + +async function* getItemAsyncIterator< + TElement, + TPage, + TPageSettings extends PageSettings, +>( + pagedResult: PagedResult, +): AsyncIterableIterator { + const pages = getPageAsyncIterator(pagedResult); + for await (const page of pages) { + yield* page as unknown as TElement[]; + } +} + +async function* getPageAsyncIterator< + TElement, + TPage, + TPageSettings extends PageSettings, +>( + pagedResult: PagedResult, + options: { + pageLink?: string; + } = {}, +): AsyncIterableIterator> { + const { pageLink } = options; + let response = await pagedResult.getPage( + pageLink ?? pagedResult.firstPageLink, + ); + if (!response) { + return; + } + let result = response.page as ContinuablePage; + result.continuationToken = response.nextPageLink; + yield result; + while (response.nextPageLink) { + response = await pagedResult.getPage(response.nextPageLink); + if (!response) { + return; + } + result = response.page as ContinuablePage; + result.continuationToken = response.nextPageLink; + yield result; + } +} + +/** + * 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)[nextLinkName]; + + if ( + typeof nextLink !== "string" && + typeof nextLink !== "undefined" && + nextLink !== null + ) { + throw new RestError( + `Body Property ${nextLinkName} should be a string or undefined or null but got ${typeof nextLink}`, + ); + } + + if (nextLink === null) { + return undefined; + } + + return nextLink; +} + +/** + * Gets the elements of the current request in the body. + */ +function getElements(body: unknown, itemName: string): T[] { + const value = (body as Record)[itemName] as T[]; + if (!Array.isArray(value)) { + throw new RestError( + `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, + expectedStatuses: string[], +): void { + if (!expectedStatuses.includes(response.status)) { + throw createRestError( + `Pagination failed with unexpected statusCode ${response.status}`, + response, + ); + } +} diff --git a/packages/typespec-test/test/spread/generated/typespec-ts/src/helpers/serializerHelpers.ts b/packages/typespec-test/test/spread/generated/typespec-ts/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-test/test/spread/generated/typespec-ts/src/helpers/serializerHelpers.ts +++ b/packages/typespec-test/test/spread/generated/typespec-ts/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/api/budgets/index.ts b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/api/budgets/index.ts index 820cba60ff..2bafb9fb76 100644 --- a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/api/budgets/index.ts +++ b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/api/budgets/index.ts @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { getLongRunningPoller } from "../pollingHelpers.js"; -import { PollerLike, OperationState } from "@azure/core-lro"; import { User } from "../../models/models.js"; import { WidgetServiceContext as Client } from "../index.js"; import { @@ -11,6 +9,8 @@ import { PathUncheckedResponse, createRestError, } from "@azure-rest/core-client"; +import { getLongRunningPoller } from "../../static-helpers/pollingHelpers.js"; +import { PollerLike, OperationState } from "@azure/core-lro"; import { BudgetsCreateOrReplaceOptionalParams } from "../../models/options.js"; export function _createOrReplaceSend( diff --git a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/api/pagingHelpers.ts b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/api/pagingHelpers.ts deleted file mode 100644 index 0fc574c83c..0000000000 --- a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/api/pagingHelpers.ts +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { - Client, - createRestError, - PathUncheckedResponse, -} from "@azure-rest/core-client"; -import { RestError } from "@azure/core-rest-pipeline"; -import { - BuildPagedAsyncIteratorOptions, - ContinuablePage, - PageSettings, - PagedAsyncIterableIterator, - PagedResult, -} from "../models/pagingTypes.js"; - -/** - * Helper to paginate results in a generic way and return a PagedAsyncIterableIterator - */ -export function buildPagedAsyncIterator< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, - TResponse extends PathUncheckedResponse = PathUncheckedResponse, ->( - client: Client, - getInitialResponse: () => PromiseLike, - processResponseBody: (result: TResponse) => PromiseLike, - expectedStatuses: string[], - options: BuildPagedAsyncIteratorOptions = {}, -): PagedAsyncIterableIterator { - const itemName = options.itemName ?? "value"; - const nextLinkName = options.nextLinkName ?? "nextLink"; - const pagedResult: PagedResult = { - getPage: async (pageLink?: string) => { - const result = - pageLink === undefined - ? await getInitialResponse() - : await client.pathUnchecked(pageLink).get(); - checkPagingRequest(result, expectedStatuses); - const results = await processResponseBody(result as TResponse); - const nextLink = getNextLink(results, nextLinkName); - const values = getElements(results, itemName) as TPage; - return { - page: values, - nextPageLink: nextLink, - }; - }, - byPage: (settings?: TPageSettings) => { - const { continuationToken } = settings ?? {}; - return getPageAsyncIterator(pagedResult, { - pageLink: continuationToken, - }); - }, - }; - return getPagedAsyncIterator(pagedResult); -} - -/** - * 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 extends PageSettings = PageSettings, ->( - pagedResult: PagedResult, -): PagedAsyncIterableIterator { - const iter = getItemAsyncIterator( - pagedResult, - ); - return { - next() { - return iter.next(); - }, - [Symbol.asyncIterator]() { - return this; - }, - byPage: - pagedResult?.byPage ?? - ((settings?: TPageSettings) => { - const { continuationToken } = settings ?? {}; - return getPageAsyncIterator(pagedResult, { - pageLink: continuationToken, - }); - }), - }; -} - -async function* getItemAsyncIterator< - TElement, - TPage, - TPageSettings extends PageSettings, ->( - pagedResult: PagedResult, -): AsyncIterableIterator { - const pages = getPageAsyncIterator(pagedResult); - for await (const page of pages) { - yield* page as unknown as TElement[]; - } -} - -async function* getPageAsyncIterator< - TElement, - TPage, - TPageSettings extends PageSettings, ->( - pagedResult: PagedResult, - options: { - pageLink?: string; - } = {}, -): AsyncIterableIterator> { - const { pageLink } = options; - let response = await pagedResult.getPage( - pageLink ?? pagedResult.firstPageLink, - ); - if (!response) { - return; - } - let result = response.page as ContinuablePage; - result.continuationToken = response.nextPageLink; - yield result; - while (response.nextPageLink) { - response = await pagedResult.getPage(response.nextPageLink); - if (!response) { - return; - } - result = response.page as ContinuablePage; - result.continuationToken = response.nextPageLink; - yield result; - } -} - -/** - * 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)[nextLinkName]; - - if ( - typeof nextLink !== "string" && - typeof nextLink !== "undefined" && - nextLink !== null - ) { - throw new RestError( - `Body Property ${nextLinkName} should be a string or undefined or null but got ${typeof nextLink}`, - ); - } - - if (nextLink === null) { - return undefined; - } - - return nextLink; -} - -/** - * Gets the elements of the current request in the body. - */ -function getElements(body: unknown, itemName: string): T[] { - const value = (body as Record)[itemName] as T[]; - if (!Array.isArray(value)) { - throw new RestError( - `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, - expectedStatuses: string[], -): void { - if (!expectedStatuses.includes(response.status)) { - throw createRestError( - `Pagination failed with unexpected statusCode ${response.status}`, - response, - ); - } -} diff --git a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/api/widgets/index.ts b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/api/widgets/index.ts index 73d02a01eb..bb6281ed66 100644 --- a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/api/widgets/index.ts +++ b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/api/widgets/index.ts @@ -1,16 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { getLongRunningPoller } from "../pollingHelpers.js"; -import { PollerLike, OperationState } from "@azure/core-lro"; import { User, Widget, AnalyzeResult, _ListWidgetsPagesResults, } from "../../models/models.js"; -import { PagedAsyncIterableIterator } from "../../models/pagingTypes.js"; -import { buildPagedAsyncIterator } from "../pagingHelpers.js"; import { WidgetServiceContext as Client } from "../index.js"; import { StreamableMethod, @@ -19,7 +15,13 @@ import { createRestError, } from "@azure-rest/core-client"; import { uint8ArrayToString } from "@azure/core-util"; -import { buildCsvCollection } from "../../helpers/serializerHelpers.js"; +import { + PagedAsyncIterableIterator, + buildPagedAsyncIterator, +} from "../../static-helpers/pagingHelpers.js"; +import { getLongRunningPoller } from "../../static-helpers/pollingHelpers.js"; +import { buildCsvCollection } from "../../static-helpers/serialization/build-csv-collection.js"; +import { PollerLike, OperationState } from "@azure/core-lro"; import { WidgetsListWidgetsOptionalParams, WidgetsListWidgetsPagesOptionalParams, diff --git a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/classic/widgets/index.ts b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/classic/widgets/index.ts index c56ccd35ea..ea2b1e499b 100644 --- a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/classic/widgets/index.ts +++ b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/classic/widgets/index.ts @@ -14,7 +14,7 @@ import { deleteWidget, analyzeWidget, } from "../../api/widgets/index.js"; -import { PagedAsyncIterableIterator } from "../../models/pagingTypes.js"; +import { PagedAsyncIterableIterator } from "../../static-helpers/pagingHelpers.js"; import { PollerLike, OperationState } from "@azure/core-lro"; import { WidgetsListWidgetsOptionalParams, diff --git a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/helpers/serializerHelpers.ts b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/helpers/serializerHelpers.ts +++ b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/index.ts b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/index.ts index 1e424a44fa..9452493ae2 100644 --- a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/index.ts +++ b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/index.ts @@ -1,6 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { + PageSettings, + ContinuablePage, + PagedAsyncIterableIterator, +} from "./static-helpers/pagingHelpers.js"; + export { WidgetServiceClient, WidgetServiceClientOptionalParams, @@ -25,8 +31,6 @@ export { WidgetsDeleteWidgetOptionalParams, WidgetsAnalyzeWidgetOptionalParams, BudgetsCreateOrReplaceOptionalParams, - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, } from "./models/index.js"; export { BudgetsOperations, WidgetsOperations } from "./classic/index.js"; +export { PageSettings, ContinuablePage, PagedAsyncIterableIterator }; diff --git a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/models/index.ts b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/models/index.ts index 62aef0d529..cc5787116c 100644 --- a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/models/index.ts +++ b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/models/index.ts @@ -23,8 +23,3 @@ export { WidgetsAnalyzeWidgetOptionalParams, BudgetsCreateOrReplaceOptionalParams, } from "./options.js"; -export { - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, -} from "./pagingTypes.js"; diff --git a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/models/pagingTypes.ts b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/models/pagingTypes.ts deleted file mode 100644 index f734b48e62..0000000000 --- a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/models/pagingTypes.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -/** - * Options for the byPage method - */ -export interface PageSettings { - /** - * A reference to a specific page to start iterating from. - */ - continuationToken?: string; -} - -/** - * An interface that describes a page of results. - */ -export type ContinuablePage = TPage & { - /** - * 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 extends PageSettings = 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< - TElement, - TPage, - TPageSettings - >; - /** - * Return an AsyncIterableIterator that works a page at a time - */ - byPage: ( - settings?: TPageSettings, - ) => AsyncIterableIterator>; -} - -/** - * An interface that describes how to communicate with the service. - */ -export interface PagedResult< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, -> { - /** - * Link to the first page of results. - */ - firstPageLink?: string; - /** - * A method that returns a page of results. - */ - getPage: ( - pageLink?: string, - ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; -} - -/** - * Options for the paging helper - */ -export interface BuildPagedAsyncIteratorOptions { - itemName?: string; - nextLinkName?: string; -} diff --git a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/restorePollerHelpers.ts b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/restorePollerHelpers.ts index a5e52a1a71..9bd141fe0f 100644 --- a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/restorePollerHelpers.ts +++ b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/restorePollerHelpers.ts @@ -1,21 +1,21 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { - PollerLike, - OperationState, - deserializeState, - ResourceLocationConfig, -} from "@azure/core-lro"; import { WidgetServiceClient } from "./widgetServiceClient.js"; -import { getLongRunningPoller } from "./api/pollingHelpers.js"; import { _createOrReplaceDeserialize } from "./api/widgets/index.js"; import { _createOrReplaceDeserialize as _createOrReplaceDeserializeBudgets } from "./api/budgets/index.js"; +import { getLongRunningPoller } from "./static-helpers/pollingHelpers.js"; import { - PathUncheckedResponse, OperationOptions, + PathUncheckedResponse, } from "@azure-rest/core-client"; import { AbortSignalLike } from "@azure/abort-controller"; +import { + PollerLike, + OperationState, + deserializeState, + ResourceLocationConfig, +} from "@azure/core-lro"; export interface RestorePollerOptions< TResult, diff --git a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/static-helpers/pagingHelpers.ts b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/static-helpers/pagingHelpers.ts new file mode 100644 index 0000000000..5855ada5a8 --- /dev/null +++ b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/static-helpers/pagingHelpers.ts @@ -0,0 +1,277 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { + Client, + createRestError, + PathUncheckedResponse, +} from "@azure-rest/core-client"; +import { RestError } from "@azure/core-rest-pipeline"; + +/** + * Options for the byPage method + */ +export interface PageSettings { + /** + * A reference to a specific page to start iterating from. + */ + continuationToken?: string; +} + +/** + * An interface that describes a page of results. + */ +export type ContinuablePage = TPage & { + /** + * 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 extends PageSettings = 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< + TElement, + TPage, + TPageSettings + >; + /** + * Return an AsyncIterableIterator that works a page at a time + */ + byPage: ( + settings?: TPageSettings, + ) => AsyncIterableIterator>; +} + +/** + * An interface that describes how to communicate with the service. + */ +export interface PagedResult< + TElement, + TPage = TElement[], + TPageSettings extends PageSettings = PageSettings, +> { + /** + * Link to the first page of results. + */ + firstPageLink?: string; + /** + * A method that returns a page of results. + */ + getPage: ( + pageLink?: string, + ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; +} + +/** + * Options for the paging helper + */ +export interface BuildPagedAsyncIteratorOptions { + itemName?: string; + nextLinkName?: string; +} + +/** + * Helper to paginate results in a generic way and return a PagedAsyncIterableIterator + */ +export function buildPagedAsyncIterator< + TElement, + TPage = TElement[], + TPageSettings extends PageSettings = PageSettings, + TResponse extends PathUncheckedResponse = PathUncheckedResponse, +>( + client: Client, + getInitialResponse: () => PromiseLike, + processResponseBody: (result: TResponse) => PromiseLike, + expectedStatuses: string[], + options: BuildPagedAsyncIteratorOptions = {}, +): PagedAsyncIterableIterator { + const itemName = options.itemName ?? "value"; + const nextLinkName = options.nextLinkName ?? "nextLink"; + const pagedResult: PagedResult = { + getPage: async (pageLink?: string) => { + const result = + pageLink === undefined + ? await getInitialResponse() + : await client.pathUnchecked(pageLink).get(); + checkPagingRequest(result, expectedStatuses); + const results = await processResponseBody(result as TResponse); + const nextLink = getNextLink(results, nextLinkName); + const values = getElements(results, itemName) as TPage; + return { + page: values, + nextPageLink: nextLink, + }; + }, + byPage: (settings?: TPageSettings) => { + const { continuationToken } = settings ?? {}; + return getPageAsyncIterator(pagedResult, { + pageLink: continuationToken, + }); + }, + }; + return getPagedAsyncIterator(pagedResult); +} + +/** + * 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 extends PageSettings = PageSettings, +>( + pagedResult: PagedResult, +): PagedAsyncIterableIterator { + const iter = getItemAsyncIterator( + pagedResult, + ); + return { + next() { + return iter.next(); + }, + [Symbol.asyncIterator]() { + return this; + }, + byPage: + pagedResult?.byPage ?? + ((settings?: TPageSettings) => { + const { continuationToken } = settings ?? {}; + return getPageAsyncIterator(pagedResult, { + pageLink: continuationToken, + }); + }), + }; +} + +async function* getItemAsyncIterator< + TElement, + TPage, + TPageSettings extends PageSettings, +>( + pagedResult: PagedResult, +): AsyncIterableIterator { + const pages = getPageAsyncIterator(pagedResult); + for await (const page of pages) { + yield* page as unknown as TElement[]; + } +} + +async function* getPageAsyncIterator< + TElement, + TPage, + TPageSettings extends PageSettings, +>( + pagedResult: PagedResult, + options: { + pageLink?: string; + } = {}, +): AsyncIterableIterator> { + const { pageLink } = options; + let response = await pagedResult.getPage( + pageLink ?? pagedResult.firstPageLink, + ); + if (!response) { + return; + } + let result = response.page as ContinuablePage; + result.continuationToken = response.nextPageLink; + yield result; + while (response.nextPageLink) { + response = await pagedResult.getPage(response.nextPageLink); + if (!response) { + return; + } + result = response.page as ContinuablePage; + result.continuationToken = response.nextPageLink; + yield result; + } +} + +/** + * 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)[nextLinkName]; + + if ( + typeof nextLink !== "string" && + typeof nextLink !== "undefined" && + nextLink !== null + ) { + throw new RestError( + `Body Property ${nextLinkName} should be a string or undefined or null but got ${typeof nextLink}`, + ); + } + + if (nextLink === null) { + return undefined; + } + + return nextLink; +} + +/** + * Gets the elements of the current request in the body. + */ +function getElements(body: unknown, itemName: string): T[] { + const value = (body as Record)[itemName] as T[]; + if (!Array.isArray(value)) { + throw new RestError( + `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, + expectedStatuses: string[], +): void { + if (!expectedStatuses.includes(response.status)) { + throw createRestError( + `Pagination failed with unexpected statusCode ${response.status}`, + response, + ); + } +} diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/rpc/generated/src/api/pollingHelpers.ts b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/static-helpers/pollingHelpers.ts similarity index 98% rename from packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/rpc/generated/src/api/pollingHelpers.ts rename to packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/static-helpers/pollingHelpers.ts index 47c259e79f..e130b18d89 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/rpc/generated/src/api/pollingHelpers.ts +++ b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/static-helpers/pollingHelpers.ts @@ -1,6 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { PollerLike, OperationState, diff --git a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/static-helpers/serialization/build-csv-collection.ts b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/static-helpers/serialization/build-csv-collection.ts new file mode 100644 index 0000000000..d3b752635d --- /dev/null +++ b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/static-helpers/serialization/build-csv-collection.ts @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +export function buildCsvCollection(items: string[] | number[]): string { + return items.join(","); +} diff --git a/packages/typespec-ts/package.json b/packages/typespec-ts/package.json index 3486d59718..2e1f5e3ac5 100644 --- a/packages/typespec-ts/package.json +++ b/packages/typespec-ts/package.json @@ -128,7 +128,8 @@ "dist/**", "README.md", "CHANGELOG.md", - "LICENSE" + "LICENSE", + "static/**" ], "mocha": { "extension": [ diff --git a/packages/typespec-ts/src/azure/dependency.ts b/packages/typespec-ts/src/azure/dependency.ts deleted file mode 100644 index 07f568eb09..0000000000 --- a/packages/typespec-ts/src/azure/dependency.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { CoreDependencies } from "../framework/dependency.js"; - -export const AzureCoreDependencies: CoreDependencies = { - Client: { - kind: "externalDependency", - module: "@azure-rest/core-client", - name: "Client" - }, - ClientOptions: { - kind: "externalDependency", - module: "@azure-rest/core-client", - name: "ClientOptions" - }, - Pipeline: { - kind: "externalDependency", - module: "@azure-rest/core-client", - name: "Pipeline" - }, - getClient: { - kind: "externalDependency", - module: "@azure-rest/core-client", - name: "getClient" - }, - RestError: { - kind: "externalDependency", - module: "@azure-rest/core-client", - name: "RestError" - }, - OperationOptions: { - kind: "externalDependency", - module: "@azure-rest/core-client", - name: "OperationOptions" - }, - PathUnckeckedResponse: { - kind: "externalDependency", - module: "@azure-rest/core-client", - name: "PathUnckeckedResponse" - }, - AbortSignalLike: { - kind: "externalDependency", - module: "@azure/abort-controller", - name: "AbortSignalLike" - }, - createRestError: { - kind: "externalDependency", - module: "@azure-rest/core-client", - name: "createRestError" - }, - operationOptionsToRequestParameters: { - kind: "externalDependency", - module: "@azure-rest/core-client", - name: "operationOptionsToRequestParameters" - }, - uint8ArrayToString: { - kind: "externalDependency", - module: "@azure/core-util", - name: "uint8ArrayToString" - } -}; diff --git a/packages/typespec-ts/src/framework/dependency.ts b/packages/typespec-ts/src/framework/dependency.ts index fcb685bda6..b25a6f2a55 100644 --- a/packages/typespec-ts/src/framework/dependency.ts +++ b/packages/typespec-ts/src/framework/dependency.ts @@ -2,6 +2,7 @@ export interface ReferenceableSymbol { kind: string; name: string; module: string; + visibility?: "internal" | "public"; } export type ExternalDependencies = CoreDependencies & @@ -25,9 +26,9 @@ export interface CoreDependencies extends Record { name: "OperationOptions"; module: string; }; - PathUnckeckedResponse: { + PathUncheckedResponse: { kind: "externalDependency"; - name: "PathUnckeckedResponse"; + name: "PathUncheckedResponse"; module: string; }; AbortSignalLike: { @@ -52,64 +53,4 @@ export interface CoreDependencies extends Record { }; } -const _CoreDependencies: CoreDependencies = { - Client: { - kind: "externalDependency", - name: "Client", - module: "@typespec/ts-http-runtime" - }, - ClientOptions: { - kind: "externalDependency", - name: "ClientOptions", - module: "@typespec/ts-http-runtime" - }, - Pipeline: { - kind: "externalDependency", - name: "Pipeline", - module: "@typespec/ts-http-runtime" - }, - getClient: { - kind: "externalDependency", - name: "getClient", - module: "@typespec/ts-http-runtime" - }, - RestError: { - kind: "externalDependency", - name: "RestError", - module: "@typespec/ts-http-runtime" - }, - OperationOptions: { - kind: "externalDependency", - name: "OperationOptions", - module: "@typespec/ts-http-runtime" - }, - PathUnckeckedResponse: { - kind: "externalDependency", - name: "PathUnckeckedResponse", - module: "@typespec/ts-http-runtime" - }, - AbortSignalLike: { - kind: "externalDependency", - name: "AbortSignalLike", - module: "@typespec/ts-http-runtime" - }, - createRestError: { - kind: "externalDependency", - name: "createRestError", - module: "@typespec/ts-http-runtime" - }, - operationOptionsToRequestParameters: { - kind: "externalDependency", - name: "operationOptionsToRequestParameters", - module: "@typespec/ts-http-runtime" - }, - uint8ArrayToString: { - kind: "externalDependency", - name: "uint8ArrayToString", - module: "@typespec/ts-http-runtime" - } -} as const; - -export const DEFAULT_DEPENDENCIES = _CoreDependencies; - export type CoreDependency = keyof CoreDependencies; diff --git a/packages/typespec-ts/src/framework/hooks/binder.ts b/packages/typespec-ts/src/framework/hooks/binder.ts index 51510edaa5..94dc1961a2 100644 --- a/packages/typespec-ts/src/framework/hooks/binder.ts +++ b/packages/typespec-ts/src/framework/hooks/binder.ts @@ -9,6 +9,11 @@ import { provideContext, useContext } from "../../contextManager.js"; import { ReferenceableSymbol } from "../dependency.js"; import { provideDependencies, useDependencies } from "./useDependencies.js"; import { refkey } from "../refkey.js"; +import { + isStaticHelperMetadata, + SourceFileSymbol, + StaticHelperMetadata +} from "../load-static-helpers.js"; export interface DeclarationInfo { name: string; @@ -17,6 +22,7 @@ export interface DeclarationInfo { } export interface BinderOptions { + staticHelpers?: Map; dependencies?: Record; } @@ -37,17 +43,22 @@ export interface Binder { resolveAllReferences(): void; } +const PLACEHOLDER_PREFIX = "_PLACEHOLDER_"; + class BinderImp implements Binder { private declarations = new Map(); + private references = new Map>(); private imports = new Map(); private symbolsBySourceFile = new Map>(); private project: Project; private dependencies: Record; + private staticHelpers: Map; constructor(project: Project, options: BinderOptions = {}) { this.project = project; provideDependencies(options.dependencies); + this.staticHelpers = options.staticHelpers ?? new Map(); this.dependencies = useDependencies(); } @@ -146,7 +157,7 @@ class BinderImp implements Binder { * @returns The serialized placeholder string. */ private serializePlaceholder(refkey: unknown): string { - return `""`; + return `${PLACEHOLDER_PREFIX}${String(refkey)}_`; } /** @@ -172,7 +183,7 @@ class BinderImp implements Binder { moduleSpecifier = fileWhereImportIsAdded.getRelativePathAsModuleSpecifierTo( fileWhereImportPointsTo - ); + ) + ".js"; } const importStructures = this.imports.get(fileWhereImportIsAdded) || []; @@ -222,6 +233,8 @@ class BinderImp implements Binder { sourceFile.addImportDeclaration(importStructure); } } + + this.cleanUnreferencedHelpers(); } private resolveDependencyReferences(file: SourceFile) { @@ -244,28 +257,77 @@ class BinderImp implements Binder { if (!hasAnyPlaceholders(file)) { return; } - for (const [declarationKey, declaration] of this.declarations) { + + for (const [declarationKey, declaration] of [ + ...this.declarations, + ...this.staticHelpers + ]) { const placeholderKey = this.serializePlaceholder(declarationKey); + if ( + isStaticHelperMetadata(declaration) && + !countPlaceholderOccurrences(file, placeholderKey) + ) { + continue; + } + let name = declaration.name; - if (file !== declaration.sourceFile) { - const importDec = this.addImport( - file, - declaration.sourceFile, - declaration.name - ); - name = importDec.alias ?? declaration.name; + let declarationSourceFile: SourceFile; + + if ("sourceFile" in declaration) { + declarationSourceFile = declaration.sourceFile; + } else { + declarationSourceFile = declaration[SourceFileSymbol]!; + } + + if (file !== declarationSourceFile) { + this.trackReference(declarationKey, file); + const importDec = this.addImport(file, declarationSourceFile, name); + name = importDec.alias ?? name; } replacePlaceholder(file, placeholderKey, name); } } + + private trackReference(refkey: unknown, sourceFile: SourceFile): void { + if (!this.references.has(refkey)) { + this.references.set(refkey, new Set()); + } + + this.references.get(refkey)!.add(sourceFile); + } + + private cleanUnreferencedHelpers() { + const usedHelperFiles = new Set(); + for (const helper of this.staticHelpers.values()) { + const sourceFile = helper[SourceFileSymbol]; + if (!sourceFile) { + // This should be unreachable + throw new Error( + `Static helper ${helper.name} does not have a source file. Make sure that loadStaticHelpers has been correctly initialized in index.ts` + ); + } + const referencedHelper = this.references.get(refkey(helper)); + + if (referencedHelper?.size) { + usedHelperFiles.add(sourceFile); + } + } + + this.project + .getSourceFiles("**/static-helpers/**/*.ts") + .filter((helperFile) => !usedHelperFiles.has(helperFile)) + .forEach((helperFile) => helperFile.delete()); + } } // Provide the binder context to be used globally export function provideBinder( project: Project, options: BinderOptions = {} -): void { - return provideContext("binder", new BinderImp(project, options)); +): Binder { + const binder = new BinderImp(project, options); + provideContext("binder", binder); + return binder; } /** @@ -310,5 +372,5 @@ function escapeRegExp(string: string): string { } function hasAnyPlaceholders(sourceFile: SourceFile): boolean { - return sourceFile.getFullText().includes(` = {} ) { const dependencies = { - ...DEFAULT_DEPENDENCIES, + ...DefaultCoreDependencies, ...customDependencies } as ExternalDependencies; diff --git a/packages/typespec-ts/src/framework/load-static-helpers.ts b/packages/typespec-ts/src/framework/load-static-helpers.ts new file mode 100644 index 0000000000..6558f19c5d --- /dev/null +++ b/packages/typespec-ts/src/framework/load-static-helpers.ts @@ -0,0 +1,174 @@ +import { readdir, stat, readFile } from "fs/promises"; +import * as path from "path"; +import { + ClassDeclaration, + FunctionDeclaration, + InterfaceDeclaration, + Project, + SourceFile, + TypeAliasDeclaration +} from "ts-morph"; +import { refkey } from "./refkey.js"; +import { resolveProjectRoot } from "../utils/resolve-project-root.js"; +export const SourceFileSymbol = Symbol("SourceFile"); +export interface StaticHelperMetadata { + name: string; + kind: "function" | "interface" | "typeAlias" | "class"; + location: string; + [SourceFileSymbol]?: SourceFile; +} + +export function isStaticHelperMetadata( + metadata: any +): metadata is StaticHelperMetadata { + return Boolean( + metadata && + metadata.name && + metadata.kind && + metadata.location && + metadata[SourceFileSymbol] + ); +} + +export type StaticHelpers = Record; + +const DEFAULT_STATIC_HELPERS_PATH = "static/static-helpers"; + +export interface LoadStaticHelpersOptions { + helpersAssetDirectory?: string; + sourcesDir?: string; +} + +export async function loadStaticHelpers( + project: Project, + helpers: StaticHelpers, + options: LoadStaticHelpersOptions = {} +): Promise> { + const sourcesDir = options.sourcesDir ?? ""; + const helpersMap = new Map(); + const defaultStaticHelpersPath = path.join( + resolveProjectRoot(), + DEFAULT_STATIC_HELPERS_PATH + ); + const files = await traverseDirectory( + options.helpersAssetDirectory ?? defaultStaticHelpersPath + ); + + for (const file of files) { + const targetPath = path.join(sourcesDir, file.target); + const contents = await readFile(file.source, "utf-8"); + const addedFile = project.createSourceFile(targetPath, contents, { + overwrite: true + }); + + for (const entry of Object.values(helpers)) { + if (!addedFile.getFilePath().endsWith(entry.location)) { + continue; + } + + const declaration = getDeclarationByMetadata(addedFile, entry); + if (!declaration) { + throw new Error( + `Declaration ${ + entry.name + } not found in file ${addedFile.getFilePath()}\n This is an Emitter bug, make sure that the map of static helpers passed to loadStaticHelpers matches what is in the file.` + ); + } + + entry[SourceFileSymbol] = addedFile; + helpersMap.set(refkey(entry), entry); + } + } + + return assertAllHelpersLoadedPresent(helpersMap); +} + +function assertAllHelpersLoadedPresent( + helpers: Map +) { + const missingHelpers = []; + for (const helper of helpers.values()) { + if (!helper[SourceFileSymbol]) { + missingHelpers.push(helper); + } + } + + if (missingHelpers.length > 0) { + const missingHelpersString = missingHelpers + .map((helper) => `${helper.name} - ${helper.location}`) + .join("\n"); + + throw new Error( + `The following helpers were not found in the project, make sure they are defined in the expected static helper file: ${missingHelpersString}` + ); + } + + return helpers; +} + +function getDeclarationByMetadata( + file: SourceFile, + declaration: StaticHelperMetadata +): + | ClassDeclaration + | FunctionDeclaration + | TypeAliasDeclaration + | InterfaceDeclaration + | undefined { + switch (declaration.kind) { + case "class": + return file.getClass(declaration.name); + case "function": + return file.getFunction(declaration.name); + case "interface": + return file.getInterface(declaration.name); + case "typeAlias": + return file.getTypeAlias(declaration.name); + default: + throw new Error( + `invalid helper kind ${declaration.kind}\nAll helpers provided to loadStaticHelpers are of kind: function, interface, typeAlias, class` + ); + } +} + +const _targetStaticHelpersBaseDir = "static-helpers"; +async function traverseDirectory( + directory: string, + result: { source: string; target: string }[] = [], + relativePath: string = "" +): Promise<{ source: string; target: string }[]> { + try { + const files = await readdir(directory); + + await Promise.all( + files.map(async (file) => { + const filePath = path.join(directory, file); + const fileStat = await stat(filePath); + + if (fileStat.isDirectory()) { + await traverseDirectory( + filePath, + result, + path.join(relativePath, file) + ); + } else if ( + fileStat.isFile() && + !file.endsWith(".d.ts") && + file.endsWith(".ts") + ) { + const target = path.join( + _targetStaticHelpersBaseDir, + relativePath, + file + ); + result.push({ source: filePath, target }); + } + }) + ); + + return result; + } catch (error) { + console.error(`Error traversing directory ${directory}:`, error); + throw error; + } +} diff --git a/packages/typespec-ts/src/framework/reference.ts b/packages/typespec-ts/src/framework/reference.ts index d73817ce92..3caca1194a 100644 --- a/packages/typespec-ts/src/framework/reference.ts +++ b/packages/typespec-ts/src/framework/reference.ts @@ -1,19 +1,33 @@ import { useBinder } from "./hooks/binder.js"; import { refkey as getRefkey } from "./refkey.js"; import { ReferenceableSymbol } from "./dependency.js"; +import { + SourceFileSymbol, + StaticHelperMetadata +} from "./load-static-helpers.js"; -export function resolveReference(refkey: unknown): string | undefined { +export function resolveReference(refkey: unknown): string { const binder = useBinder(); let key = refkey; + if (isReferenceableSymbol(key)) { key = getRefkey(key); } + if (isStaticHelperMetadata(key)) { + key = getRefkey(key); + } + const stringRefkey = typeof key === "string" ? key : getRefkey(key); return binder.resolveReference(stringRefkey); } + function isReferenceableSymbol(obj: any): obj is ReferenceableSymbol { return obj?.kind === "externalDependency"; } + +function isStaticHelperMetadata(obj: any): obj is StaticHelperMetadata { + return Boolean(obj[SourceFileSymbol]); +} diff --git a/packages/typespec-ts/src/index.ts b/packages/typespec-ts/src/index.ts index 8d4df13736..ec1554051a 100644 --- a/packages/typespec-ts/src/index.ts +++ b/packages/typespec-ts/src/index.ts @@ -42,15 +42,7 @@ import { buildClassicalClient } from "./modular/buildClassicalClient.js"; import { buildClassicOperationFiles } from "./modular/buildClassicalOperationGroups.js"; import { buildClientContext } from "./modular/buildClientContext.js"; import { emitCodeModel } from "./modular/buildCodeModel.js"; -import { - buildGetPollerHelper, - buildRestorePollerHelper -} from "./modular/buildLroFiles.js"; import { buildOperationFiles } from "./modular/buildOperations.js"; -import { - buildPagingHelpers as buildModularPagingHelpers, - buildPagingTypes -} from "./modular/buildPagingFiles.js"; import { getModuleExports } from "./modular/buildProjectFiles.js"; import { buildRootIndex, @@ -68,15 +60,33 @@ import { GenerationDirDetail, SdkContext } from "./utils/interfaces.js"; import { provideContext, useContext } from "./contextManager.js"; import { emitSerializerHelpersFile } from "./modular/buildHelperSerializers.js"; import { provideSdkTypes } from "./framework/hooks/sdkTypes.js"; +import { provideBinder } from "./framework/hooks/binder.js"; +import { loadStaticHelpers } from "./framework/load-static-helpers.js"; +import { + PagingHelpers, + PollingHelpers, + SerializationHelpers +} from "./modular/static-helpers-metadata.js"; +import { + AzureCoreDependencies, + AzurePollingDependencies +} from "./modular/external-dependencies.js"; import { emitLoggerFile } from "./modular/emitLoggerFile.js"; +import { buildRestorePoller } from "./modular/buildRestorePoller.js"; export * from "./lib.js"; export async function $onEmit(context: EmitContext) { /** Shared status */ + const outputProject = new Project(); const program: Program = context.program; const emitterOptions: EmitterOptions = context.options; const dpgContext = await createContextWithDefaultOptions(context); + const rlcOptions: RLCOptions = transformRLCOptions( + emitterOptions, + dpgContext + ); + const needUnexpectedHelper: Map = new Map(); const serviceNameToRlcModelsMap: Map = new Map< string, @@ -85,12 +95,32 @@ export async function $onEmit(context: EmitContext) { provideContext("rlcMetaTree", new Map()); provideContext("symbolMap", new Map()); provideContext("modularMetaTree", new Map()); - provideContext("outputProject", new Project()); + provideContext("outputProject", outputProject); provideContext("emitContext", { compilerContext: context, tcgcContext: dpgContext }); provideSdkTypes(dpgContext.sdkPackage); + const { modularSourcesDir } = await calculateGenerationDir(); + const staticHelpers = await loadStaticHelpers( + outputProject, + { + ...SerializationHelpers, + ...PagingHelpers, + ...PollingHelpers + }, + { sourcesDir: modularSourcesDir } + ); + const extraDependencies = + rlcOptions?.flavor === "azure" + ? { ...AzurePollingDependencies, ...AzureCoreDependencies } + : {}; + const binder = provideBinder(outputProject, { + staticHelpers, + dependencies: { + ...extraDependencies + } + }); const rlcCodeModels: RLCModel[] = []; let modularCodeModel: ModularCodeModel; @@ -116,16 +146,15 @@ export async function $onEmit(context: EmitContext) { const generationPathDetail: GenerationDirDetail = await calculateGenerationDir(); dpgContext.generationPathDetail = generationPathDetail; - const options: RLCOptions = transformRLCOptions(emitterOptions, dpgContext); const hasTestFolder = await fsextra.pathExists( join(dpgContext.generationPathDetail?.metadataDir ?? "", "test") ); - options.generateTest = - options.generateTest === true || - (options.generateTest === undefined && + rlcOptions.generateTest = + rlcOptions.generateTest === true || + (rlcOptions.generateTest === undefined && !hasTestFolder && - options.flavor === "azure"); - dpgContext.rlcOptions = options; + rlcOptions.flavor === "azure"); + dpgContext.rlcOptions = rlcOptions; } async function calculateGenerationDir(): Promise { @@ -224,16 +253,11 @@ export async function $onEmit(context: EmitContext) { buildModelsOptions(subClient, modularCodeModel); if (!env["EXPERIMENTAL_TYPESPEC_TS_SERIALIZATION"]) buildSerializeUtils(modularCodeModel); - // build paging files - buildPagingTypes(subClient, modularCodeModel); - buildModularPagingHelpers(subClient, modularCodeModel); // build operation files buildOperationFiles(subClient, dpgContext, modularCodeModel); buildClientContext(subClient, dpgContext, modularCodeModel); buildSubpathIndexFile(subClient, modularCodeModel, "models"); - // build lro files - buildGetPollerHelper(modularCodeModel, subClient); - buildRestorePollerHelper(modularCodeModel, subClient); + buildRestorePoller(modularCodeModel, subClient); if (dpgContext.rlcOptions?.hierarchyClient) { buildSubpathIndexFile(subClient, modularCodeModel, "api"); } else { @@ -254,7 +278,11 @@ export async function $onEmit(context: EmitContext) { buildRootIndex(subClient, modularCodeModel, rootIndexFile); } + binder.resolveAllReferences(); + for (const file of project.getSourceFiles()) { + file.fixMissingImports({}, { importModuleSpecifierEnding: "js" }); + file.fixUnusedIdentifiers(); await emitContentByBuilder( program, () => ({ content: file.getFullText(), path: file.getFilePath() }), diff --git a/packages/typespec-ts/src/modular/buildClassicalClient.ts b/packages/typespec-ts/src/modular/buildClassicalClient.ts index 9c984b6e5a..410fb55c53 100644 --- a/packages/typespec-ts/src/modular/buildClassicalClient.ts +++ b/packages/typespec-ts/src/modular/buildClassicalClient.ts @@ -13,7 +13,6 @@ import { } from "ts-morph"; import { isRLCMultiEndpoint } from "../utils/clientUtils.js"; import { SdkContext } from "../utils/interfaces.js"; -import { importLroCoreDependencies } from "./buildLroFiles.js"; import { buildUserAgentOptions, getClientParameters, @@ -108,14 +107,11 @@ export function buildClassicalClient( `this._client = create${modularClientName}(${paramNames.join(",")})` ]); constructor.addStatements(`this.pipeline = this._client.pipeline`); - importLroCoreDependencies(clientFile); importCredential(codeModel.runtimeImports, clientFile); importPipeline(codeModel.runtimeImports, clientFile); importAllModels(clientFile, srcPath, subfolder); buildClientOperationGroups(clientFile, client, dpgContext, clientClass); importAllApis(clientFile, srcPath, subfolder); - clientFile.fixMissingImports(); - clientFile.fixUnusedIdentifiers(); return clientFile; } diff --git a/packages/typespec-ts/src/modular/buildClassicalOperationGroups.ts b/packages/typespec-ts/src/modular/buildClassicalOperationGroups.ts index 81eb8462a8..4f56ddd447 100644 --- a/packages/typespec-ts/src/modular/buildClassicalOperationGroups.ts +++ b/packages/typespec-ts/src/modular/buildClassicalOperationGroups.ts @@ -1,7 +1,6 @@ import { NameType } from "@azure-tools/rlc-common"; import { SourceFile } from "ts-morph"; -import { importLroCoreDependencies } from "./buildLroFiles.js"; -import { importModels, importPagingDependencies } from "./buildOperations.js"; +import { importModels } from "./buildOperations.js"; import { getClassicalOperation } from "./helpers/classicalOperationHelpers.js"; import { getClassicalLayerPrefix } from "./helpers/namingHelpers.js"; import { @@ -56,16 +55,6 @@ export function buildClassicOperationFiles( ); importApis(classicFile, client, codeModel, operationGroup); // We need to import the paging helpers and types explicitly because ts-morph may not be able to find them. - importPagingDependencies( - srcPath, - classicFile, - codeModel.project, - subfolder, - operationGroup.namespaceHierarchies.length - ); - importLroCoreDependencies(classicFile); - classicFile.fixMissingImports(); - classicFile.fixUnusedIdentifiers(); classicOperationFiles.set(classicOperationFileName, classicFile); } } @@ -109,18 +98,6 @@ export function buildClassicOperationFiles( // We SHOULD keep this because otherwise ts-morph will "helpfully" try to import models from the rest layer when we call fixMissingImports(). importModels(srcPath, classicFile, codeModel.project, subfolder, layer); importApis(classicFile, client, codeModel, operationGroup, layer); - // We need to import the paging helpers and types explicitly because ts-morph may not be able to find them. - importPagingDependencies( - srcPath, - classicFile, - codeModel.project, - subfolder, - operationGroup.namespaceHierarchies.length - ); - importLroCoreDependencies(classicFile); - - classicFile.fixMissingImports(); - classicFile.fixUnusedIdentifiers(); classicOperationFiles.set(classicOperationFileName, classicFile); } } diff --git a/packages/typespec-ts/src/modular/buildHelperSerializers.ts b/packages/typespec-ts/src/modular/buildHelperSerializers.ts index 04d063b8c9..2a97caf318 100644 --- a/packages/typespec-ts/src/modular/buildHelperSerializers.ts +++ b/packages/typespec-ts/src/modular/buildHelperSerializers.ts @@ -34,6 +34,7 @@ export function serializeRecord( ); }` }; + const isRecordElementSupportedFunction = { symbol: "isSupportedRecordType", content: ` @@ -43,52 +44,6 @@ function isSupportedRecordType(t: any) { ` }; -const buildMultiCollection = { - symbol: "buildMultiCollection", - content: `export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return \`\${parameterName}=\${item}\`; - }) - .join("&"); -}` -}; - -const buildPipeCollection = { - symbol: "buildPipeCollection", - content: ` -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -}` -}; - -const buildTsvCollection = { - symbol: "buildTsvCollection", - content: `export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\\t"); -}` -}; - -const buildSsvCollection = { - symbol: "buildSsvCollection", - content: `export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -}` -}; - -const buildCsvCollection = { - symbol: "buildCsvCollection", - content: `export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -}` -}; - export function emitSerializerHelpersFile( project: Project, srcPath: string = "src" @@ -110,33 +65,8 @@ export function emitSerializerHelpersFile( symbolMap.set(isRecordElementSupportedFunction.symbol, sourceFile); } - if (!symbolMap.has(buildMultiCollection.symbol)) { - symbolMap.set(buildMultiCollection.symbol, sourceFile); - } - - if (!symbolMap.has(buildPipeCollection.symbol)) { - symbolMap.set(buildPipeCollection.symbol, sourceFile); - } - - if (!symbolMap.has(buildSsvCollection.symbol)) { - symbolMap.set(buildSsvCollection.symbol, sourceFile); - } - - if (!symbolMap.has(buildTsvCollection.symbol)) { - symbolMap.set(buildTsvCollection.symbol, sourceFile); - } - - if (!symbolMap.has(buildCsvCollection.symbol)) { - symbolMap.set(buildCsvCollection.symbol, sourceFile); - } - sourceFile.addStatements([ serializeRecordFunction.content, - isRecordElementSupportedFunction.content, - buildMultiCollection.content, - buildPipeCollection.content, - buildSsvCollection.content, - buildTsvCollection.content, - buildCsvCollection.content + isRecordElementSupportedFunction.content ]); } diff --git a/packages/typespec-ts/src/modular/buildLroFiles.ts b/packages/typespec-ts/src/modular/buildLroFiles.ts deleted file mode 100644 index ff23265292..0000000000 --- a/packages/typespec-ts/src/modular/buildLroFiles.ts +++ /dev/null @@ -1,420 +0,0 @@ -import { NameType, normalizeName } from "@azure-tools/rlc-common"; -import path from "path"; -import { SourceFile } from "ts-morph"; -import { buildLroDeserDetailMap } from "./buildOperations.js"; -import { getClientName } from "./helpers/namingHelpers.js"; -import { isLroOnlyOperation } from "./helpers/operationHelpers.js"; -import { Client, ModularCodeModel } from "./modularCodeModel.js"; - -/** - * Always import LRO dependencies and remember to remove if unused. - * @param clientFile - The file to add the imports to. - */ -export function importLroCoreDependencies(clientFile: SourceFile) { - clientFile.addImportDeclaration({ - moduleSpecifier: `@azure/core-lro`, - namedImports: [ - "PollerLike", - "OperationState", - "deserializeState", - "ResourceLocationConfig", - "RunningOperation", - "createHttpPoller", - "OperationResponse" - ] - }); -} - -/** - * Generate `restorePollerHelpers.ts` file - * @param codeModel - The code model. - * @param client - The client. - * @returns - */ -export function buildRestorePollerHelper( - codeModel: ModularCodeModel, - client: Client -) { - const lros = client.operationGroups - .flatMap((op) => op.operations) - .filter(isLroOnlyOperation); - if (lros.length === 0) { - return; - } - const srcPath = codeModel.modularOptions.sourceRoot; - const subfolder = client.subfolder ?? ""; - const filePath = path.join( - `${srcPath}/${ - subfolder !== "" ? subfolder + "/" : "" - }restorePollerHelpers.ts` - ); - const restorePollerFile = codeModel.project.createSourceFile( - filePath, - undefined, - { - overwrite: true - } - ); - - importLroCoreDependencies(restorePollerFile); - const clientNames = importClassicalClient(client, restorePollerFile); - importGetPollerHelper(restorePollerFile); - const deserializeMap = importDeserializeHelpers(client, restorePollerFile); - const restorePollerHelperContent = `import { - PathUncheckedResponse, - OperationOptions - } from "@azure-rest/core-client"; - import { AbortSignalLike } from "@azure/abort-controller"; - - export interface RestorePollerOptions< - TResult, - TResponse extends PathUncheckedResponse = PathUncheckedResponse - > extends OperationOptions { - /** Delay to wait until next poll, in milliseconds. */ - updateIntervalInMs?: number; - /** - * The signal which can be used to abort requests. - */ - abortSignal?: AbortSignalLike; - /** Deserialization function for raw response body */ - processResponseBody?: (result: TResponse) => Promise; - } - - /** - * Creates a poller from the serialized state of another poller. This can be - * useful when you want to create pollers on a different host or a poller - * needs to be constructed after the original one is not in scope. - */ - export function restorePoller( - client: ${clientNames.join(" | ")}, - serializedState: string, - sourceOperation: ( - ...args: any[] - ) => PollerLike, TResult>, - options?: RestorePollerOptions - ): PollerLike, TResult> { - const pollerConfig = deserializeState(serializedState).config; - const { initialRequestUrl, requestMethod, metadata } = pollerConfig; - if (!initialRequestUrl || !requestMethod) { - throw new Error( - \`Invalid serialized state: \${serializedState} for sourceOperation \${sourceOperation?.name}\` - ); - } - const resourceLocationConfig = metadata?.["resourceLocationConfig"] as - | ResourceLocationConfig - | undefined; - const { deserializer, expectedStatuses = [] } = - getDeserializationHelper(initialRequestUrl, requestMethod) ?? {}; - const deserializeHelper = options?.processResponseBody ?? deserializer; - if (!deserializeHelper) { - throw new Error( - \`Please ensure the operation is in this client! We can't find its deserializeHelper for \${sourceOperation?.name}.\` - ); - } - return getLongRunningPoller( - (client as any)["_client"] ?? client, - deserializeHelper as (result: TResponse) => Promise, - expectedStatuses, - { - updateIntervalInMs: options?.updateIntervalInMs, - abortSignal: options?.abortSignal, - resourceLocationConfig, - restoreFrom: serializedState, - initialRequestUrl - } - ); - } - - interface DeserializationHelper { - deserializer: Function; - expectedStatuses: string[]; - } - - const deserializeMap: Record = { - ${deserializeMap.join(",\n")} - }; - - function getDeserializationHelper( - urlStr: string, - method: string - ): DeserializationHelper | undefined { - const path = new URL(urlStr).pathname; - const pathParts = path.split("/"); - - // Traverse list to match the longest candidate - // matchedLen: the length of candidate path - // matchedValue: the matched status code array - let matchedLen = -1, - matchedValue: DeserializationHelper | undefined; - - // Iterate the responseMap to find a match - for (const [key, value] of Object.entries(deserializeMap)) { - // Extracting the path from the map key which is in format - // GET /path/foo - if (!key.startsWith(method)) { - continue; - } - const candidatePath = getPathFromMapKey(key); - // Get each part of the url path - const candidateParts = candidatePath.split("/"); - - // track if we have found a match to return the values found. - let found = true; - for ( - let i = candidateParts.length - 1, j = pathParts.length - 1; - i >= 1 && j >= 1; - i--, j-- - ) { - if ( - candidateParts[i]?.startsWith("{") && - candidateParts[i]?.indexOf("}") !== -1 - ) { - const start = candidateParts[i]!.indexOf("}") + 1, - end = candidateParts[i]?.length; - // If the current part of the candidate is a "template" part - // Try to use the suffix of pattern to match the path - // {guid} ==> $ - // {guid}:export ==> :export$ - const isMatched = new RegExp( - \`\${candidateParts[i]?.slice(start, end)}\` - ).test(pathParts[j] || ""); - - if (!isMatched) { - found = false; - break; - } - continue; - } - - // If the candidate part is not a template and - // the parts don't match mark the candidate as not found - // to move on with the next candidate path. - if (candidateParts[i] !== pathParts[j]) { - found = false; - break; - } - } - - // We finished evaluating the current candidate parts - // Update the matched value if and only if we found the longer pattern - if (found && candidatePath.length > matchedLen) { - matchedLen = candidatePath.length; - matchedValue = value; - } - } - - return matchedValue; - } - - function getPathFromMapKey(mapKey: string): string { - const pathStart = mapKey.indexOf("/"); - return mapKey.slice(pathStart); - } - `; - restorePollerFile.addStatements(restorePollerHelperContent); - restorePollerFile.fixMissingImports(); - restorePollerFile.fixUnusedIdentifiers(); -} - -function importClassicalClient( - client: Client, - sourceFile: SourceFile -): string[] { - const classicalClientName = `${getClientName(client)}Client`; - sourceFile.addImportDeclaration({ - namedImports: [`${classicalClientName}`], - moduleSpecifier: `./${normalizeName(classicalClientName, NameType.File)}.js` - }); - return [classicalClientName]; -} - -function importGetPollerHelper(sourceFile: SourceFile) { - sourceFile.addImportDeclaration({ - namedImports: [`getLongRunningPoller`], - moduleSpecifier: `./api/pollingHelpers.js` - }); -} - -function importDeserializeHelpers(client: Client, sourceFile: SourceFile) { - const deserializeDetails = buildLroDeserDetailMap(client); - const deserializeMap: string[] = []; - for (const [key, value] of deserializeDetails.entries()) { - sourceFile.addImportDeclaration({ - namedImports: value.map((detail) => - detail.renamedDeserName - ? `${detail.deserName} as ${detail.renamedDeserName}` - : detail.deserName - ), - moduleSpecifier: key - }); - value.forEach((detail) => { - deserializeMap.push( - `"${detail.path}": { deserializer: ${ - detail.renamedDeserName ?? detail.deserName - }, expectedStatuses: ${detail.expectedStatusesExpression} }` - ); - }); - } - return deserializeMap; -} - -/** - * Generate `api/pollingHelpers.ts` file - * @param codeModel - The code model. - * @param client - The client. - * @param needUnexpectedHelper - Whether need to import unexpected helper. - * @param isMultiClients - Whether the client is multi-clients. - * @returns - */ -export function buildGetPollerHelper( - codeModel: ModularCodeModel, - client: Client -) { - const lroOperstions = client.operationGroups - .flatMap((op) => op.operations) - .filter(isLroOnlyOperation); - if (lroOperstions.length === 0) { - return; - } - - const getLroPollerContent = ` - import { - Client, - PathUncheckedResponse, - createRestError - } from "@azure-rest/core-client"; - import { AbortSignalLike } from "@azure/abort-controller"; - - export interface GetLongRunningPollerOptions { - /** Delay to wait until next poll, in milliseconds. */ - updateIntervalInMs?: number; - /** - * The signal which can be used to abort requests. - */ - abortSignal?: AbortSignalLike; - /** - * The potential location of the result of the LRO if specified by the LRO extension in the swagger. - */ - resourceLocationConfig?: ResourceLocationConfig; - /** - * The original url of the LRO - * Should not be null when restoreFrom is set - */ - initialRequestUrl?: string; - /** - * A serialized poller which can be used to resume an existing paused Long-Running-Operation. - */ - restoreFrom?: string; - /** - * The function to get the initial response - */ - getInitialResponse?: () => PromiseLike; - } - export function getLongRunningPoller< - TResponse extends PathUncheckedResponse, - TResult = void - >( - client: Client, - processResponseBody: (result: TResponse) => Promise, - expectedStatuses: string[], - options: GetLongRunningPollerOptions - ): PollerLike, TResult> { - const { restoreFrom, getInitialResponse } = options; - if (!restoreFrom && !getInitialResponse) { - throw new Error( - "Either restoreFrom or getInitialResponse must be specified" - ); - } - let initialResponse: TResponse | undefined = undefined; - const pollAbortController = new AbortController(); - const poller: RunningOperation = { - sendInitialRequest: async () => { - if (!getInitialResponse) { - throw new Error( - "getInitialResponse is required when initializing a new poller" - ); - } - initialResponse = await getInitialResponse(); - return getLroResponse(initialResponse, expectedStatuses); - }, - sendPollRequest: async ( - path: string, - pollOptions?: { - abortSignal?: AbortSignalLike; - } - ) => { - // The poll request would both listen to the user provided abort signal and the poller's own abort signal - function abortListener(): void { - pollAbortController.abort(); - } - const abortSignal = pollAbortController.signal; - if (options.abortSignal?.aborted) { - pollAbortController.abort(); - } else if (pollOptions?.abortSignal?.aborted) { - pollAbortController.abort(); - } else if (!abortSignal.aborted) { - options.abortSignal?.addEventListener("abort", abortListener, { - once: true - }); - pollOptions?.abortSignal?.addEventListener("abort", abortListener, { - once: true - }); - } - let response; - try { - response = await client.pathUnchecked(path).get({ abortSignal }); - } finally { - options.abortSignal?.removeEventListener("abort", abortListener); - pollOptions?.abortSignal?.removeEventListener("abort", abortListener); - } - - return getLroResponse(response as TResponse, expectedStatuses); - } - }; - return createHttpPoller(poller, { - intervalInMs: options?.updateIntervalInMs, - resourceLocationConfig: options?.resourceLocationConfig, - restoreFrom: options?.restoreFrom, - processResult: (result: unknown) => { - return processResponseBody(result as TResponse); - } - }); - } - /** - * Converts a Rest Client response to a response that the LRO implementation understands - * @param response - a rest client http response - * @param deserializeFn - deserialize function to convert Rest response to modular output - * @returns - An LRO response that the LRO implementation understands - */ - function getLroResponse( - response: TResponse, - expectedStatuses: string[] - ): OperationResponse { - if(!expectedStatuses.includes(response.status)) { - throw createRestError(response); - } - - return { - flatResponse: response, - rawResponse: { - ...response, - statusCode: Number.parseInt(response.status), - body: response.body - } - }; - } - `; - const filePath = path.join( - codeModel.modularOptions.sourceRoot, - client.subfolder ?? "", - `api/pollingHelpers.ts` - ); - - const fileContent = codeModel.project.createSourceFile(filePath, undefined, { - overwrite: true - }); - importLroCoreDependencies(fileContent); - fileContent.addStatements(getLroPollerContent); - fileContent.fixMissingImports(); - fileContent.fixUnusedIdentifiers(); -} diff --git a/packages/typespec-ts/src/modular/buildOperations.ts b/packages/typespec-ts/src/modular/buildOperations.ts index ff191b7b50..21b8e03b41 100644 --- a/packages/typespec-ts/src/modular/buildOperations.ts +++ b/packages/typespec-ts/src/modular/buildOperations.ts @@ -8,7 +8,6 @@ import { import { Project, SourceFile } from "ts-morph"; import { isRLCMultiEndpoint } from "../utils/clientUtils.js"; import { SdkContext } from "../utils/interfaces.js"; -import { importLroCoreDependencies } from "./buildLroFiles.js"; import { getDocsFromDescription } from "./helpers/docsHelpers.js"; import { getOperationName } from "./helpers/namingHelpers.js"; import { @@ -58,11 +57,6 @@ export function buildOperationFiles( subfolder && subfolder !== "" ? subfolder + "/" : "" }api/${operationFileName}.ts` ); - // We need to import the lro helpers and types explicitly because ts-morph may not be able to find correct ones. - importLroDependencies( - operationGroupFile, - operationGroup.namespaceHierarchies.length - ); // Import models used from ./models.ts // We SHOULD keep this because otherwise ts-morph will "helpfully" try to import models from the rest layer when we call fixMissingImports(). @@ -94,15 +88,6 @@ export function buildOperationFiles( operationGroup.namespaceHierarchies.length ); - // We need to import the paging helpers and types explicitly because ts-morph may not be able to find them. - importPagingDependencies( - srcPath, - operationGroupFile, - codeModel.project, - subfolder, - operationGroup.namespaceHierarchies.length - ); - const indexPathPrefix = "../".repeat(operationGroup.namespaceHierarchies.length) || "./"; operationGroupFile.addImportDeclaration({ @@ -144,15 +129,7 @@ export function buildOperationFiles( addImportsToFiles(codeModel.runtimeImports, operationGroupFile); addImportBySymbol("serializeRecord", operationGroupFile); - addImportBySymbol("buildMultiCollection", operationGroupFile); - addImportBySymbol("buildPipeCollection", operationGroupFile); - addImportBySymbol("buildCsvCollection", operationGroupFile); - addImportBySymbol("buildTsvCollection", operationGroupFile); - addImportBySymbol("buildSsvCollection", operationGroupFile); - operationGroupFile.fixMissingImports(); - // have to fixUnusedIdentifiers after everything get generated. - operationGroupFile.fixUnusedIdentifiers(); operationFiles.push(operationGroupFile); } return operationFiles; @@ -225,64 +202,6 @@ export function importDeserializeUtils( }); } } -// Import all deserializeUtil and then let ts-morph clean up the unused ones -// we can't fixUnusedIdentifiers here because the operaiton files are still being generated. -// sourceFile.fixUnusedIdentifiers(); -export function importPagingDependencies( - srcPath: string, - sourceFile: SourceFile, - project: Project, - subfolder: string = "", - importLayer: number = 0 -) { - const pagingTypes = project.getSourceFile( - `${srcPath}/${subfolder !== "" ? subfolder + "/" : ""}models/pagingTypes.ts` - ); - - if (!pagingTypes) { - return; - } - - const exportedPaingTypes = [...pagingTypes.getExportedDeclarations().keys()]; - - sourceFile.addImportDeclaration({ - moduleSpecifier: `${"../".repeat(importLayer + 1)}models/pagingTypes.js`, - namedImports: exportedPaingTypes - }); - - const pagingHelper = project.getSourceFile( - `${srcPath}/${subfolder !== "" ? subfolder + "/" : ""}api/pagingHelpers.ts` - ); - - if (!pagingHelper) { - return; - } - - const exportedPaingHelpers = [ - ...pagingHelper.getExportedDeclarations().keys() - ]; - - sourceFile.addImportDeclaration({ - moduleSpecifier: `${ - importLayer === 0 ? "./" : "../".repeat(importLayer) - }pagingHelpers.js`, - namedImports: exportedPaingHelpers - }); -} - -export function importLroDependencies( - sourceFile: SourceFile, - importLayer: number = 0 -) { - sourceFile.addImportDeclaration({ - moduleSpecifier: `${ - importLayer === 0 ? "./" : "../".repeat(importLayer) - }pollingHelpers.js`, - namedImports: ["getLongRunningPoller"] - }); - - importLroCoreDependencies(sourceFile); -} /** * This function generates the interfaces for each operation options diff --git a/packages/typespec-ts/src/modular/buildPagingFiles.ts b/packages/typespec-ts/src/modular/buildPagingFiles.ts deleted file mode 100644 index 4cfc5f1a96..0000000000 --- a/packages/typespec-ts/src/modular/buildPagingFiles.ts +++ /dev/null @@ -1,325 +0,0 @@ -import path from "path"; -import { - hasPagingOnlyOperation, - isPagingOnlyOperation -} from "./helpers/operationHelpers.js"; -import { Client, ModularCodeModel } from "./modularCodeModel.js"; - -export function buildPagingTypes(client: Client, codeModel: ModularCodeModel) { - if (!hasPagingOnlyOperation(client)) { - return; - } - const filePath = path.join( - codeModel.modularOptions.sourceRoot, - client.subfolder ?? "", - `models/pagingTypes.ts` - ); - const fileContent = codeModel.project.createSourceFile(filePath, undefined, { - overwrite: true - }); - fileContent.addStatements([ - ` - /** - * Options for the byPage method - */ - export interface PageSettings { - /** - * A reference to a specific page to start iterating from. - */ - continuationToken?: string; - } - - /** - * An interface that describes a page of results. - */ - export type ContinuablePage = TPage & { - /** - * 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 extends PageSettings = 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< - TElement, - TPage, - TPageSettings - >; - /** - * Return an AsyncIterableIterator that works a page at a time - */ - byPage: ( - settings?: TPageSettings - ) => AsyncIterableIterator>; - } - - /** - * An interface that describes how to communicate with the service. - */ - export interface PagedResult< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings - > { - /** - * Link to the first page of results. - */ - firstPageLink?: string; - /** - * A method that returns a page of results. - */ - getPage: ( - pageLink?: string - ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; - } - - /** - * Options for the paging helper - */ - export interface BuildPagedAsyncIteratorOptions { - itemName?: string; - nextLinkName?: string; - } - ` - ]); - - return fileContent; -} - -export function buildPagingHelpers( - client: Client, - codeModel: ModularCodeModel -) { - const pagingOperstions = client.operationGroups - .flatMap((op) => op.operations) - .filter(isPagingOnlyOperation); - if (!pagingOperstions || pagingOperstions.length === 0) { - return; - } - - const pagingTypesPath = `../models/pagingTypes.js`; - const filePath = path.join( - codeModel.modularOptions.sourceRoot, - client.subfolder ?? "", - `api/pagingHelpers.ts` - ); - - const fileContent = codeModel.project.createSourceFile(filePath, undefined, { - overwrite: true - }); - - fileContent.addStatements([ - ` - import { - Client, - createRestError, - PathUncheckedResponse - } from "@azure-rest/core-client"; - import { RestError } from "@azure/core-rest-pipeline"; - import { - BuildPagedAsyncIteratorOptions, - ContinuablePage, - PageSettings, - PagedAsyncIterableIterator, - PagedResult, - } from "${pagingTypesPath}"; - - /** - * Helper to paginate results in a generic way and return a PagedAsyncIterableIterator - */ - export function buildPagedAsyncIterator< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, - TResponse extends PathUncheckedResponse = PathUncheckedResponse - >( - client: Client, - getInitialResponse: () => PromiseLike, - processResponseBody: (result: TResponse) => PromiseLike, - expectedStatuses: string[], - options: BuildPagedAsyncIteratorOptions = {} - ): PagedAsyncIterableIterator { - const itemName = options.itemName ?? "value"; - const nextLinkName = options.nextLinkName ?? "nextLink"; - const pagedResult: PagedResult = { - getPage: async (pageLink?: string) => { - const result = - pageLink === undefined - ? await getInitialResponse() - : await client.pathUnchecked(pageLink).get(); - checkPagingRequest(result, expectedStatuses); - const results = await processResponseBody(result as TResponse); - const nextLink = getNextLink(results, nextLinkName); - const values = getElements(results, itemName) as TPage; - return { - page: values, - nextPageLink: nextLink, - }; - }, - byPage: (settings?: TPageSettings) => { - const { continuationToken } = settings ?? {}; - return getPageAsyncIterator(pagedResult, { - pageLink: continuationToken, - }); - }, - }; - return getPagedAsyncIterator(pagedResult); - } - - /** - * 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 extends PageSettings = PageSettings - >( - pagedResult: PagedResult - ): PagedAsyncIterableIterator { - const iter = getItemAsyncIterator( - pagedResult - ); - return { - next() { - return iter.next(); - }, - [Symbol.asyncIterator]() { - return this; - }, - byPage: - pagedResult?.byPage ?? - ((settings?: TPageSettings) => { - const { continuationToken } = settings ?? {}; - return getPageAsyncIterator(pagedResult, { - pageLink: continuationToken, - }); - }), - }; - } - - async function* getItemAsyncIterator< - TElement, - TPage, - TPageSettings extends PageSettings - >( - pagedResult: PagedResult - ): AsyncIterableIterator { - const pages = getPageAsyncIterator(pagedResult); - for await (const page of pages) { - yield* page as unknown as TElement[]; - } - } - - async function* getPageAsyncIterator< - TElement, - TPage, - TPageSettings extends PageSettings - >( - pagedResult: PagedResult, - options: { - pageLink?: string; - } = {} - ): AsyncIterableIterator> { - const { pageLink } = options; - let response = await pagedResult.getPage( - pageLink ?? pagedResult.firstPageLink - ); - if (!response) { - return; - } - let result = response.page as ContinuablePage; - result.continuationToken = response.nextPageLink; - yield result; - while (response.nextPageLink) { - response = await pagedResult.getPage(response.nextPageLink); - if (!response) { - return; - } - result = response.page as ContinuablePage; - result.continuationToken = response.nextPageLink; - yield result; - } - } - - /** - * 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)[nextLinkName]; - - if ( - typeof nextLink !== "string" && - typeof nextLink !== "undefined" && - nextLink !== null - ) { - throw new RestError( - \`Body Property \${nextLinkName} should be a string or undefined or null but got \${typeof nextLink}\` - ); - } - - if (nextLink === null) { - return undefined; - } - - return nextLink; - } - - /** - * Gets the elements of the current request in the body. - */ - function getElements(body: unknown, itemName: string): T[] { - const value = (body as Record)[itemName] as T[]; - if (!Array.isArray(value)) { - throw new RestError( - \`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, expectedStatuses: string[]): void { - if(!expectedStatuses.includes(response.status)) { - throw createRestError(\`Pagination failed with unexpected statusCode \${response.status}\`, response); - } - } - ` - ]); -} diff --git a/packages/typespec-ts/src/modular/buildRestorePoller.ts b/packages/typespec-ts/src/modular/buildRestorePoller.ts new file mode 100644 index 0000000000..f1e98c7933 --- /dev/null +++ b/packages/typespec-ts/src/modular/buildRestorePoller.ts @@ -0,0 +1,239 @@ +import { SourceFile } from "ts-morph"; +import { isLroOnlyOperation } from "./helpers/operationHelpers.js"; +import { ModularCodeModel, Client } from "./modularCodeModel.js"; +import path from "path"; +import { buildLroDeserDetailMap } from "./buildOperations.js"; +import { getClientName } from "./helpers/namingHelpers.js"; +import { NameType, normalizeName } from "@azure-tools/rlc-common"; +import { resolveReference } from "../framework/reference.js"; +import { + AzureCoreDependencies, + AzurePollingDependencies +} from "./external-dependencies.js"; +import { PollingHelpers } from "./static-helpers-metadata.js"; + +export function buildRestorePoller( + codeModel: ModularCodeModel, + client: Client +) { + const lros = client.operationGroups + .flatMap((op) => op.operations) + .filter(isLroOnlyOperation); + if (lros.length === 0) { + return; + } + const srcPath = codeModel.modularOptions.sourceRoot; + const subfolder = client.subfolder ?? ""; + const filePath = path.join( + `${srcPath}/${ + subfolder !== "" ? subfolder + "/" : "" + }restorePollerHelpers.ts` + ); + const restorePollerFile = codeModel.project.createSourceFile( + filePath, + undefined, + { + overwrite: true + } + ); + + const clientNames = importClassicalClient(client, restorePollerFile); + const deserializeMap = importDeserializeHelpers(client, restorePollerFile); + const pathUncheckedReference = resolveReference( + AzureCoreDependencies.PathUncheckedResponse + ); + const pollerLikeReference = resolveReference( + AzurePollingDependencies.PollerLike + ); + const operationStateReference = resolveReference( + AzurePollingDependencies.OperationState + ); + const operationOptionsReference = resolveReference( + AzureCoreDependencies.OperationOptions + ); + const deserializeStateReference = resolveReference( + AzurePollingDependencies.DeserializeState + ); + const restorePollerHelperContent = ` + export interface RestorePollerOptions< + TResult, + TResponse extends ${pathUncheckedReference} = ${pathUncheckedReference} + > extends ${operationOptionsReference} { + /** Delay to wait until next poll, in milliseconds. */ + updateIntervalInMs?: number; + /** + * The signal which can be used to abort requests. + */ + abortSignal?: ${resolveReference(AzureCoreDependencies.AbortSignalLike)}; + /** Deserialization function for raw response body */ + processResponseBody?: (result: TResponse) => Promise; + } + + /** + * Creates a poller from the serialized state of another poller. This can be + * useful when you want to create pollers on a different host or a poller + * needs to be constructed after the original one is not in scope. + */ + export function restorePoller( + client: ${clientNames.join(" | ")}, + serializedState: string, + sourceOperation: ( + ...args: any[] + ) => ${pollerLikeReference}<${operationStateReference}, TResult>, + options?: RestorePollerOptions + ): ${pollerLikeReference}<${operationStateReference}, TResult> { + const pollerConfig = ${deserializeStateReference}(serializedState).config; + const { initialRequestUrl, requestMethod, metadata } = pollerConfig; + if (!initialRequestUrl || !requestMethod) { + throw new Error( + \`Invalid serialized state: \${serializedState} for sourceOperation \${sourceOperation?.name}\` + ); + } + const resourceLocationConfig = metadata?.["resourceLocationConfig"] as + | ${resolveReference(AzurePollingDependencies.ResourceLocationConfig)} + | undefined; + const { deserializer, expectedStatuses = [] } = + getDeserializationHelper(initialRequestUrl, requestMethod) ?? {}; + const deserializeHelper = options?.processResponseBody ?? deserializer; + if (!deserializeHelper) { + throw new Error( + \`Please ensure the operation is in this client! We can't find its deserializeHelper for \${sourceOperation?.name}.\` + ); + } + return ${resolveReference(PollingHelpers.GetLongRunningPoller)}( + (client as any)["_client"] ?? client, + deserializeHelper as (result: TResponse) => Promise, + expectedStatuses, + { + updateIntervalInMs: options?.updateIntervalInMs, + abortSignal: options?.abortSignal, + resourceLocationConfig, + restoreFrom: serializedState, + initialRequestUrl + } + ); + } + + interface DeserializationHelper { + deserializer: Function; + expectedStatuses: string[]; + } + + const deserializeMap: Record = { + ${deserializeMap.join(",\n")} + }; + + function getDeserializationHelper( + urlStr: string, + method: string + ): DeserializationHelper | undefined { + const path = new URL(urlStr).pathname; + const pathParts = path.split("/"); + + // Traverse list to match the longest candidate + // matchedLen: the length of candidate path + // matchedValue: the matched status code array + let matchedLen = -1, + matchedValue: DeserializationHelper | undefined; + + // Iterate the responseMap to find a match + for (const [key, value] of Object.entries(deserializeMap)) { + // Extracting the path from the map key which is in format + // GET /path/foo + if (!key.startsWith(method)) { + continue; + } + const candidatePath = getPathFromMapKey(key); + // Get each part of the url path + const candidateParts = candidatePath.split("/"); + + // track if we have found a match to return the values found. + let found = true; + for ( + let i = candidateParts.length - 1, j = pathParts.length - 1; + i >= 1 && j >= 1; + i--, j-- + ) { + if ( + candidateParts[i]?.startsWith("{") && + candidateParts[i]?.indexOf("}") !== -1 + ) { + const start = candidateParts[i]!.indexOf("}") + 1, + end = candidateParts[i]?.length; + // If the current part of the candidate is a "template" part + // Try to use the suffix of pattern to match the path + // {guid} ==> $ + // {guid}:export ==> :export$ + const isMatched = new RegExp( + \`\${candidateParts[i]?.slice(start, end)}\` + ).test(pathParts[j] || ""); + + if (!isMatched) { + found = false; + break; + } + continue; + } + + // If the candidate part is not a template and + // the parts don't match mark the candidate as not found + // to move on with the next candidate path. + if (candidateParts[i] !== pathParts[j]) { + found = false; + break; + } + } + + // We finished evaluating the current candidate parts + // Update the matched value if and only if we found the longer pattern + if (found && candidatePath.length > matchedLen) { + matchedLen = candidatePath.length; + matchedValue = value; + } + } + + return matchedValue; + } + + function getPathFromMapKey(mapKey: string): string { + const pathStart = mapKey.indexOf("/"); + return mapKey.slice(pathStart); + } + `; + restorePollerFile.addStatements(restorePollerHelperContent); +} + +function importDeserializeHelpers(client: Client, sourceFile: SourceFile) { + const deserializeDetails = buildLroDeserDetailMap(client); + const deserializeMap: string[] = []; + for (const [key, value] of deserializeDetails.entries()) { + sourceFile.addImportDeclaration({ + namedImports: value.map((detail) => + detail.renamedDeserName + ? `${detail.deserName} as ${detail.renamedDeserName}` + : detail.deserName + ), + moduleSpecifier: key + }); + value.forEach((detail) => { + deserializeMap.push( + `"${detail.path}": { deserializer: ${ + detail.renamedDeserName ?? detail.deserName + }, expectedStatuses: ${detail.expectedStatusesExpression} }` + ); + }); + } + return deserializeMap; +} + +function importClassicalClient( + client: Client, + sourceFile: SourceFile +): string[] { + const classicalClientName = `${getClientName(client)}Client`; + sourceFile.addImportDeclaration({ + namedImports: [`${classicalClientName}`], + moduleSpecifier: `./${normalizeName(classicalClientName, NameType.File)}.js` + }); + return [classicalClientName]; +} diff --git a/packages/typespec-ts/src/modular/buildRootIndex.ts b/packages/typespec-ts/src/modular/buildRootIndex.ts index dba37733ef..236db37d93 100644 --- a/packages/typespec-ts/src/modular/buildRootIndex.ts +++ b/packages/typespec-ts/src/modular/buildRootIndex.ts @@ -2,6 +2,8 @@ import { NameType, normalizeName } from "@azure-tools/rlc-common"; import { Project, SourceFile } from "ts-morph"; import { getClientName } from "./helpers/namingHelpers.js"; import { Client, ModularCodeModel } from "./modularCodeModel.js"; +import { resolveReference } from "../framework/reference.js"; +import { PagingHelpers } from "./static-helpers-metadata.js"; export function buildRootIndex( client: Client, @@ -55,6 +57,72 @@ export function buildRootIndex( subfolder, true ); + + exportPagingTypes(codeModel, rootIndexFile); +} + +/** + * This is a temporary solution for adding paging exports. Eventually we will have the binder generate the exports automatically. + */ +function exportPagingTypes( + codeModel: ModularCodeModel, + rootIndexFile: SourceFile +) { + if (!hasPaging(codeModel)) { + return; + } + + const existingExports = getExistingExports(rootIndexFile); + const namedExports = [ + resolveReference(PagingHelpers.PageSettings), + resolveReference(PagingHelpers.ContinuablePage), + resolveReference(PagingHelpers.PagedAsyncIterableIterator) + ]; + + const newNamedExports = getNewNamedExports(namedExports, existingExports); + + if (newNamedExports.length > 0) { + addExportsToRootIndexFile(rootIndexFile, newNamedExports); + } +} + +function hasPaging(codeModel: ModularCodeModel): boolean { + return codeModel.clients.some((c) => + c.operationGroups.some((og) => + og.operations.some( + (op) => + op.discriminator === "paging" || op.discriminator === "lropaging" + ) + ) + ); +} + +function getExistingExports(rootIndexFile: SourceFile): Set { + return new Set( + rootIndexFile + .getExportDeclarations() + .flatMap((exportDecl) => + exportDecl.getNamedExports().map((namedExport) => namedExport.getName()) + ) + ); +} + +function getNewNamedExports( + namedExports: string[], + existingExports: Set +): string[] { + return namedExports.filter( + (namedExport) => !existingExports.has(namedExport) + ); +} + +function addExportsToRootIndexFile( + rootIndexFile: SourceFile, + newNamedExports: string[] +) { + rootIndexFile.addExportDeclaration({ + namedExports: newNamedExports + }); } function exportRestoreHelpers( diff --git a/packages/typespec-ts/src/modular/emitModels.ts b/packages/typespec-ts/src/modular/emitModels.ts index ca545853ba..fa9ff92364 100644 --- a/packages/typespec-ts/src/modular/emitModels.ts +++ b/packages/typespec-ts/src/modular/emitModels.ts @@ -393,13 +393,6 @@ export function buildModelsOptions( } ]); - modelOptionsFile.fixMissingImports( - {}, - { - importModuleSpecifierPreference: "shortest", - importModuleSpecifierEnding: "js" - } - ); modelOptionsFile .getImportDeclarations() .filter((id) => { diff --git a/packages/typespec-ts/src/modular/external-dependencies.ts b/packages/typespec-ts/src/modular/external-dependencies.ts new file mode 100644 index 0000000000..53013eb0d8 --- /dev/null +++ b/packages/typespec-ts/src/modular/external-dependencies.ts @@ -0,0 +1,140 @@ +import { CoreDependencies } from "../framework/dependency.js"; + +export const DefaultCoreDependencies: CoreDependencies = { + Client: { + kind: "externalDependency", + name: "Client", + module: "@typespec/ts-http-runtime" + }, + ClientOptions: { + kind: "externalDependency", + name: "ClientOptions", + module: "@typespec/ts-http-runtime" + }, + Pipeline: { + kind: "externalDependency", + name: "Pipeline", + module: "@typespec/ts-http-runtime" + }, + getClient: { + kind: "externalDependency", + name: "getClient", + module: "@typespec/ts-http-runtime" + }, + RestError: { + kind: "externalDependency", + name: "RestError", + module: "@typespec/ts-http-runtime" + }, + OperationOptions: { + kind: "externalDependency", + name: "OperationOptions", + module: "@typespec/ts-http-runtime" + }, + PathUncheckedResponse: { + kind: "externalDependency", + name: "PathUncheckedResponse", + module: "@typespec/ts-http-runtime" + }, + AbortSignalLike: { + kind: "externalDependency", + name: "AbortSignalLike", + module: "@typespec/ts-http-runtime" + }, + createRestError: { + kind: "externalDependency", + name: "createRestError", + module: "@typespec/ts-http-runtime" + }, + operationOptionsToRequestParameters: { + kind: "externalDependency", + name: "operationOptionsToRequestParameters", + module: "@typespec/ts-http-runtime" + }, + uint8ArrayToString: { + kind: "externalDependency", + name: "uint8ArrayToString", + module: "@typespec/ts-http-runtime" + } +} as const; + +export const AzurePollingDependencies = { + PollerLike: { + kind: "externalDependency", + module: "@azure/core-lro", + name: "PollerLike" + }, + OperationState: { + kind: "externalDependency", + module: "@azure/core-lro", + name: "OperationState" + }, + DeserializeState: { + kind: "externalDependency", + module: "@azure/core-lro", + name: "deserializeState" + }, + ResourceLocationConfig: { + kind: "externalDependency", + module: "@azure/core-lro", + name: "ResourceLocationConfig" + } +}; + +export const AzureCoreDependencies: CoreDependencies = { + Client: { + kind: "externalDependency", + module: "@azure-rest/core-client", + name: "Client" + }, + ClientOptions: { + kind: "externalDependency", + module: "@azure-rest/core-client", + name: "ClientOptions" + }, + Pipeline: { + kind: "externalDependency", + module: "@azure-rest/core-client", + name: "Pipeline" + }, + getClient: { + kind: "externalDependency", + module: "@azure-rest/core-client", + name: "getClient" + }, + RestError: { + kind: "externalDependency", + module: "@azure-rest/core-client", + name: "RestError" + }, + OperationOptions: { + kind: "externalDependency", + module: "@azure-rest/core-client", + name: "OperationOptions" + }, + PathUncheckedResponse: { + kind: "externalDependency", + module: "@azure-rest/core-client", + name: "PathUncheckedResponse" + }, + AbortSignalLike: { + kind: "externalDependency", + module: "@azure/abort-controller", + name: "AbortSignalLike" + }, + createRestError: { + kind: "externalDependency", + module: "@azure-rest/core-client", + name: "createRestError" + }, + operationOptionsToRequestParameters: { + kind: "externalDependency", + module: "@azure-rest/core-client", + name: "operationOptionsToRequestParameters" + }, + uint8ArrayToString: { + kind: "externalDependency", + module: "@azure/core-util", + name: "uint8ArrayToString" + } +}; diff --git a/packages/typespec-ts/src/modular/helpers/operationHelpers.ts b/packages/typespec-ts/src/modular/helpers/operationHelpers.ts index a034c6b19f..1c0e5f9dac 100644 --- a/packages/typespec-ts/src/modular/helpers/operationHelpers.ts +++ b/packages/typespec-ts/src/modular/helpers/operationHelpers.ts @@ -42,6 +42,9 @@ import { } from "./docsHelpers.js"; import { getClassicalLayerPrefix, getOperationName } from "./namingHelpers.js"; import { buildType, isTypeNullable } from "./typeHelpers.js"; +import { resolveReference } from "../../framework/reference.js"; +import { PagingHelpers, PollingHelpers } from "../static-helpers-metadata.js"; +import { AzurePollingDependencies } from "../external-dependencies.js"; export function getSendPrivateFunction( dpgContext: SdkContext, @@ -312,6 +315,12 @@ function getLroOnlyOperationFunction(operation: Operation, clientType: string) { getOperationSignatureParameters(operation, clientType); const returnType = buildLroReturnType(operation); const { name, fixme = [] } = getOperationName(operation); + const pollerLikeReference = resolveReference( + AzurePollingDependencies.PollerLike + ); + const operationStateReference = resolveReference( + AzurePollingDependencies.OperationState + ); const functionStatement = { docs: [ ...getDocsFromDescription(operation.description), @@ -322,12 +331,17 @@ function getLroOnlyOperationFunction(operation: Operation, clientType: string) { name, propertyName: operation.name, parameters, - returnType: `PollerLike, ${returnType.type}>` + returnType: `${pollerLikeReference}<${operationStateReference}<${returnType.type}>, ${returnType.type}>` }; + const getLongRunningPollerReference = resolveReference( + PollingHelpers.GetLongRunningPoller + ); + const statements: string[] = []; statements.push(` - return getLongRunningPoller(context, _${name}Deserialize, ${getExpectedStatuses( + + return ${getLongRunningPollerReference}(context, _${name}Deserialize, ${getExpectedStatuses( operation )}, { updateIntervalInMs: options?.updateIntervalInMs, @@ -335,7 +349,9 @@ function getLroOnlyOperationFunction(operation: Operation, clientType: string) { getInitialResponse: () => _${name}Send(${parameters .map((p) => p.name) .join(", ")}) - }) as PollerLike, ${returnType.type}>; + }) as ${pollerLikeReference}<${operationStateReference}<${ + returnType.type + }>, ${returnType.type}>; `); return { @@ -370,6 +386,12 @@ function getPagingOnlyOperationFunction( returnType = buildType(type.name, type, type.format); } const { name, fixme = [] } = getOperationName(operation); + const pagedAsyncIterableIteratorReference = resolveReference( + PagingHelpers.PagedAsyncIterableIterator + ); + const buildPagedAsyncIteratorReference = resolveReference( + PagingHelpers.BuildPagedAsyncIterator + ); const functionStatement = { docs: [ ...getDocsFromDescription(operation.description), @@ -380,7 +402,7 @@ function getPagingOnlyOperationFunction( name, propertyName: operation.name, parameters, - returnType: `PagedAsyncIterableIterator<${returnType.type}>` + returnType: `${pagedAsyncIterableIteratorReference}<${returnType.type}>` }; const statements: string[] = []; @@ -392,7 +414,7 @@ function getPagingOnlyOperationFunction( options.push(`nextLinkName: "${operation.continuationTokenName}"`); } statements.push( - `return buildPagedAsyncIterator( + `return ${buildPagedAsyncIteratorReference}( context, () => _${name}Send(${parameters.map((p) => p.name).join(", ")}), _${name}Deserialize, diff --git a/packages/typespec-ts/src/modular/static-helpers-metadata.ts b/packages/typespec-ts/src/modular/static-helpers-metadata.ts new file mode 100644 index 0000000000..360cd35e80 --- /dev/null +++ b/packages/typespec-ts/src/modular/static-helpers-metadata.ts @@ -0,0 +1,73 @@ +export const SerializationHelpers = { + buildMultiCollection: { + kind: "function", + name: "buildMultiCollection", + location: "serialization/build-multi-collection.ts" + }, + buildCsvCollection: { + kind: "function", + name: "buildCsvCollection", + location: "serialization/build-csv-collection.ts" + }, + buildPipeCollection: { + kind: "function", + name: "buildPipeCollection", + location: "serialization/build-pipe-collection.ts" + }, + buildSsvCollection: { + kind: "function", + name: "buildSsvCollection", + location: "serialization/build-ssv-collection.ts" + }, + buildTsvCollection: { + kind: "function", + name: "buildTsvCollection", + location: "serialization/build-tsv-collection.ts" + } +} as const; + +export const PagingHelpers = { + PageSettings: { + kind: "interface", + name: "PageSettings", + location: "pagingHelpers.ts" + }, + ContinuablePage: { + kind: "typeAlias", + name: "ContinuablePage", + location: "pagingHelpers.ts" + }, + PagedAsyncIterableIterator: { + kind: "interface", + name: "PagedAsyncIterableIterator", + location: "pagingHelpers.ts" + }, + PagedResult: { + kind: "interface", + name: "PagedResult", + location: "pagingHelpers.ts" + }, + BuildPagedAsyncIteratorOptions: { + kind: "interface", + name: "BuildPagedAsyncIteratorOptions", + location: "pagingHelpers.ts" + }, + BuildPagedAsyncIterator: { + kind: "function", + name: "buildPagedAsyncIterator", + location: "pagingHelpers.ts" + } +} as const; + +export const PollingHelpers = { + GetLongRunningPollerOptions: { + kind: "interface", + name: "GetLongRunningPollerOptions", + location: "pollingHelpers.ts" + }, + GetLongRunningPoller: { + kind: "function", + name: "getLongRunningPoller", + location: "pollingHelpers.ts" + } +} as const; diff --git a/packages/typespec-ts/src/utils/dirname.ts b/packages/typespec-ts/src/utils/dirname.ts new file mode 100644 index 0000000000..db91cc22e7 --- /dev/null +++ b/packages/typespec-ts/src/utils/dirname.ts @@ -0,0 +1,12 @@ +import path from "path"; +import { fileURLToPath } from "url"; + +/** + * + * @param metaUrl usually import.meta.url + */ +export function getDirname(metaUrl: string) { + const __filename = fileURLToPath(metaUrl); + const __dirname = path.dirname(__filename); + return { __dirname, __filename }; +} diff --git a/packages/typespec-ts/src/utils/operationUtil.ts b/packages/typespec-ts/src/utils/operationUtil.ts index 05203fa3c6..fd46ee08ec 100644 --- a/packages/typespec-ts/src/utils/operationUtil.ts +++ b/packages/typespec-ts/src/utils/operationUtil.ts @@ -36,6 +36,8 @@ import { SdkContext } from "./interfaces.js"; import { KnownMediaType, knownMediaType } from "./mediaTypes.js"; import { isByteOrByteUnion } from "./modelUtils.js"; import { getOperationNamespaceInterfaceName } from "./namespaceUtils.js"; +import { resolveReference } from "../framework/reference.js"; +import { SerializationHelpers } from "../modular/static-helpers-metadata.js"; // Sorts the responses by status code export function sortedOperationResponses(responses: HttpOperationResponse[]) { @@ -489,8 +491,29 @@ export function getCollectionFormatHelper( paramType: string, paramFormat: string ) { - const detail = getSpecialSerializeInfo(paramType, paramFormat); - return detail.descriptions.length > 0 ? detail.descriptions[0] : undefined; + // const detail = getSpecialSerializeInfo(paramType, paramFormat); + // return detail.descriptions.length > 0 ? detail.descriptions[0] : undefined; + if (getHasMultiCollection(paramType, paramFormat)) { + return resolveReference(SerializationHelpers.buildMultiCollection); + } + + if (getHasPipeCollection(paramType, paramFormat)) { + return resolveReference(SerializationHelpers.buildPipeCollection); + } + + if (getHasSsvCollection(paramType, paramFormat)) { + return resolveReference(SerializationHelpers.buildSsvCollection); + } + + if (getHasTsvCollection(paramType, paramFormat)) { + return resolveReference(SerializationHelpers.buildTsvCollection); + } + + if (getHasCsvCollection(paramType, paramFormat)) { + return resolveReference(SerializationHelpers.buildCsvCollection); + } + + return undefined; } export function getCustomRequestHeaderNameForOperation( diff --git a/packages/typespec-ts/src/utils/resolve-project-root.ts b/packages/typespec-ts/src/utils/resolve-project-root.ts new file mode 100644 index 0000000000..86d4037e1f --- /dev/null +++ b/packages/typespec-ts/src/utils/resolve-project-root.ts @@ -0,0 +1,28 @@ +import { fileURLToPath } from "url"; +import { dirname, resolve } from "path"; +import { existsSync } from "fs"; + +/** + * Recursively finds the nearest package.json file starting from the specified directory. + * @param {string} currentDir - The directory to start searching from. Defaults to the directory of the current module. + * @returns {string} path to the directory containing the package.json file. + */ +export function resolveProjectRoot( + currentDir: string = dirname(fileURLToPath(import.meta.url)) +): string { + const packageJsonPath = resolve(currentDir, "package.json"); + + if (existsSync(packageJsonPath)) { + return currentDir; // Return the directory containing the package.json + } + + const parentDir = resolve(currentDir, ".."); + + if (parentDir === currentDir) { + // If we've reached the root directory and haven't found package.json + throw new Error("Could not find package.json"); + } + + // Recursively search the parent directory + return resolveProjectRoot(parentDir); +} diff --git a/packages/typespec-ts/static/static-helpers/pagingHelpers.ts b/packages/typespec-ts/static/static-helpers/pagingHelpers.ts new file mode 100644 index 0000000000..a50acf5ca9 --- /dev/null +++ b/packages/typespec-ts/static/static-helpers/pagingHelpers.ts @@ -0,0 +1,274 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { + Client, + createRestError, + PathUncheckedResponse +} from "@azure-rest/core-client"; +import { RestError } from "@azure/core-rest-pipeline"; + +/** + * Options for the byPage method + */ +export interface PageSettings { + /** + * A reference to a specific page to start iterating from. + */ + continuationToken?: string; +} + +/** + * An interface that describes a page of results. + */ +export type ContinuablePage = TPage & { + /** + * 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 extends PageSettings = 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< + TElement, + TPage, + TPageSettings + >; + /** + * Return an AsyncIterableIterator that works a page at a time + */ + byPage: ( + settings?: TPageSettings + ) => AsyncIterableIterator>; +} + +/** + * An interface that describes how to communicate with the service. + */ +export interface PagedResult< + TElement, + TPage = TElement[], + TPageSettings extends PageSettings = PageSettings +> { + /** + * Link to the first page of results. + */ + firstPageLink?: string; + /** + * A method that returns a page of results. + */ + getPage: ( + pageLink?: string + ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; +} + +/** + * Options for the paging helper + */ +export interface BuildPagedAsyncIteratorOptions { + itemName?: string; + nextLinkName?: string; +} + +/** + * Helper to paginate results in a generic way and return a PagedAsyncIterableIterator + */ +export function buildPagedAsyncIterator< + TElement, + TPage = TElement[], + TPageSettings extends PageSettings = PageSettings, + TResponse extends PathUncheckedResponse = PathUncheckedResponse +>( + client: Client, + getInitialResponse: () => PromiseLike, + processResponseBody: (result: TResponse) => PromiseLike, + expectedStatuses: string[], + options: BuildPagedAsyncIteratorOptions = {} +): PagedAsyncIterableIterator { + const itemName = options.itemName ?? "value"; + const nextLinkName = options.nextLinkName ?? "nextLink"; + const pagedResult: PagedResult = { + getPage: async (pageLink?: string) => { + const result = + pageLink === undefined + ? await getInitialResponse() + : await client.pathUnchecked(pageLink).get(); + checkPagingRequest(result, expectedStatuses); + const results = await processResponseBody(result as TResponse); + const nextLink = getNextLink(results, nextLinkName); + const values = getElements(results, itemName) as TPage; + return { + page: values, + nextPageLink: nextLink + }; + }, + byPage: (settings?: TPageSettings) => { + const { continuationToken } = settings ?? {}; + return getPageAsyncIterator(pagedResult, { + pageLink: continuationToken + }); + } + }; + return getPagedAsyncIterator(pagedResult); +} + +/** + * 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 extends PageSettings = PageSettings +>( + pagedResult: PagedResult +): PagedAsyncIterableIterator { + const iter = getItemAsyncIterator( + pagedResult + ); + return { + next() { + return iter.next(); + }, + [Symbol.asyncIterator]() { + return this; + }, + byPage: + pagedResult?.byPage ?? + ((settings?: TPageSettings) => { + const { continuationToken } = settings ?? {}; + return getPageAsyncIterator(pagedResult, { + pageLink: continuationToken + }); + }) + }; +} + +async function* getItemAsyncIterator< + TElement, + TPage, + TPageSettings extends PageSettings +>( + pagedResult: PagedResult +): AsyncIterableIterator { + const pages = getPageAsyncIterator(pagedResult); + for await (const page of pages) { + yield* page as unknown as TElement[]; + } +} + +async function* getPageAsyncIterator< + TElement, + TPage, + TPageSettings extends PageSettings +>( + pagedResult: PagedResult, + options: { + pageLink?: string; + } = {} +): AsyncIterableIterator> { + const { pageLink } = options; + let response = await pagedResult.getPage( + pageLink ?? pagedResult.firstPageLink + ); + if (!response) { + return; + } + let result = response.page as ContinuablePage; + result.continuationToken = response.nextPageLink; + yield result; + while (response.nextPageLink) { + response = await pagedResult.getPage(response.nextPageLink); + if (!response) { + return; + } + result = response.page as ContinuablePage; + result.continuationToken = response.nextPageLink; + yield result; + } +} + +/** + * 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)[nextLinkName]; + + if ( + typeof nextLink !== "string" && + typeof nextLink !== "undefined" && + nextLink !== null + ) { + throw new RestError( + `Body Property ${nextLinkName} should be a string or undefined or null but got ${typeof nextLink}` + ); + } + + if (nextLink === null) { + return undefined; + } + + return nextLink; +} + +/** + * Gets the elements of the current request in the body. + */ +function getElements(body: unknown, itemName: string): T[] { + const value = (body as Record)[itemName] as T[]; + if (!Array.isArray(value)) { + throw new RestError( + `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, + expectedStatuses: string[] +): void { + if (!expectedStatuses.includes(response.status)) { + throw createRestError( + `Pagination failed with unexpected statusCode ${response.status}`, + response + ); + } +} diff --git a/packages/typespec-ts/static/static-helpers/pollingHelpers.ts b/packages/typespec-ts/static/static-helpers/pollingHelpers.ts new file mode 100644 index 0000000000..6b4fa97b7f --- /dev/null +++ b/packages/typespec-ts/static/static-helpers/pollingHelpers.ts @@ -0,0 +1,137 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { + PollerLike, + OperationState, + ResourceLocationConfig, + RunningOperation, + createHttpPoller, + OperationResponse +} from "@azure/core-lro"; + +import { + Client, + PathUncheckedResponse, + createRestError +} from "@azure-rest/core-client"; +import { AbortSignalLike } from "@azure/abort-controller"; + +export interface GetLongRunningPollerOptions { + /** Delay to wait until next poll, in milliseconds. */ + updateIntervalInMs?: number; + /** + * The signal which can be used to abort requests. + */ + abortSignal?: AbortSignalLike; + /** + * The potential location of the result of the LRO if specified by the LRO extension in the swagger. + */ + resourceLocationConfig?: ResourceLocationConfig; + /** + * The original url of the LRO + * Should not be null when restoreFrom is set + */ + initialRequestUrl?: string; + /** + * A serialized poller which can be used to resume an existing paused Long-Running-Operation. + */ + restoreFrom?: string; + /** + * The function to get the initial response + */ + getInitialResponse?: () => PromiseLike; +} +export function getLongRunningPoller< + TResponse extends PathUncheckedResponse, + TResult = void +>( + client: Client, + processResponseBody: (result: TResponse) => Promise, + expectedStatuses: string[], + options: GetLongRunningPollerOptions +): PollerLike, TResult> { + const { restoreFrom, getInitialResponse } = options; + if (!restoreFrom && !getInitialResponse) { + throw new Error( + "Either restoreFrom or getInitialResponse must be specified" + ); + } + let initialResponse: TResponse | undefined = undefined; + const pollAbortController = new AbortController(); + const poller: RunningOperation = { + sendInitialRequest: async () => { + if (!getInitialResponse) { + throw new Error( + "getInitialResponse is required when initializing a new poller" + ); + } + initialResponse = await getInitialResponse(); + return getLroResponse(initialResponse, expectedStatuses); + }, + sendPollRequest: async ( + path: string, + pollOptions?: { + abortSignal?: AbortSignalLike; + } + ) => { + // The poll request would both listen to the user provided abort signal and the poller's own abort signal + function abortListener(): void { + pollAbortController.abort(); + } + const abortSignal = pollAbortController.signal; + if (options.abortSignal?.aborted) { + pollAbortController.abort(); + } else if (pollOptions?.abortSignal?.aborted) { + pollAbortController.abort(); + } else if (!abortSignal.aborted) { + options.abortSignal?.addEventListener("abort", abortListener, { + once: true + }); + pollOptions?.abortSignal?.addEventListener("abort", abortListener, { + once: true + }); + } + let response; + try { + response = await client.pathUnchecked(path).get({ abortSignal }); + } finally { + options.abortSignal?.removeEventListener("abort", abortListener); + pollOptions?.abortSignal?.removeEventListener("abort", abortListener); + } + + return getLroResponse(response as TResponse, expectedStatuses); + } + }; + return createHttpPoller(poller, { + intervalInMs: options?.updateIntervalInMs, + resourceLocationConfig: options?.resourceLocationConfig, + restoreFrom: options?.restoreFrom, + processResult: (result: unknown) => { + return processResponseBody(result as TResponse); + } + }); +} +/** + * Converts a Rest Client response to a response that the LRO implementation understands + * @param response - a rest client http response + * @param deserializeFn - deserialize function to convert Rest response to modular output + * @returns - An LRO response that the LRO implementation understands + */ +function getLroResponse( + response: TResponse, + expectedStatuses: string[] +): OperationResponse { + if (!expectedStatuses.includes(response.status)) { + throw createRestError(response); + } + + return { + flatResponse: response, + rawResponse: { + ...response, + statusCode: Number.parseInt(response.status), + body: response.body + } + }; +} diff --git a/packages/typespec-ts/static/static-helpers/serialization/build-csv-collection.ts b/packages/typespec-ts/static/static-helpers/serialization/build-csv-collection.ts new file mode 100644 index 0000000000..370dfa5e3e --- /dev/null +++ b/packages/typespec-ts/static/static-helpers/serialization/build-csv-collection.ts @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +export function buildCsvCollection(items: string[] | number[]): string { + return items.join(","); +} diff --git a/packages/typespec-ts/static/static-helpers/serialization/build-multi-collection.ts b/packages/typespec-ts/static/static-helpers/serialization/build-multi-collection.ts new file mode 100644 index 0000000000..c05fb24649 --- /dev/null +++ b/packages/typespec-ts/static/static-helpers/serialization/build-multi-collection.ts @@ -0,0 +1,13 @@ +export function buildMultiCollection( + items: string[], + parameterName: string +): string { + return items + .map((item, index) => { + if (index === 0) { + return item; + } + return `${parameterName}=${item}`; + }) + .join("&"); +} diff --git a/packages/typespec-ts/static/static-helpers/serialization/build-pipe-collection.ts b/packages/typespec-ts/static/static-helpers/serialization/build-pipe-collection.ts new file mode 100644 index 0000000000..d6ae9b87e2 --- /dev/null +++ b/packages/typespec-ts/static/static-helpers/serialization/build-pipe-collection.ts @@ -0,0 +1,3 @@ +export function buildPipeCollection(items: string[] | number[]): string { + return items.join("|"); +} diff --git a/packages/typespec-ts/static/static-helpers/serialization/build-ssv-collection.ts b/packages/typespec-ts/static/static-helpers/serialization/build-ssv-collection.ts new file mode 100644 index 0000000000..f425fa9b97 --- /dev/null +++ b/packages/typespec-ts/static/static-helpers/serialization/build-ssv-collection.ts @@ -0,0 +1,3 @@ +export function buildSsvCollection(items: string[] | number[]): string { + return items.join(" "); +} diff --git a/packages/typespec-ts/static/static-helpers/serialization/build-tsv-collection.ts b/packages/typespec-ts/static/static-helpers/serialization/build-tsv-collection.ts new file mode 100644 index 0000000000..b4400d9a89 --- /dev/null +++ b/packages/typespec-ts/static/static-helpers/serialization/build-tsv-collection.ts @@ -0,0 +1,3 @@ +export function buildTsvCollection(items: string[] | number[]): string { + return items.join("\t"); +} diff --git a/packages/typespec-ts/test-next/integration/assets/static-helpers/utils.ts b/packages/typespec-ts/test-next/integration/assets/static-helpers/utils.ts new file mode 100644 index 0000000000..370dfa5e3e --- /dev/null +++ b/packages/typespec-ts/test-next/integration/assets/static-helpers/utils.ts @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +export function buildCsvCollection(items: string[] | number[]): string { + return items.join(","); +} diff --git a/packages/typespec-ts/test-next/integration/hooks/binder.test.ts b/packages/typespec-ts/test-next/integration/hooks/binder.test.ts index 59d9f22af1..f020c1d41b 100644 --- a/packages/typespec-ts/test-next/integration/hooks/binder.test.ts +++ b/packages/typespec-ts/test-next/integration/hooks/binder.test.ts @@ -2,7 +2,8 @@ import { FunctionDeclarationStructure, InterfaceDeclarationStructure, Project, - StructureKind + StructureKind, + TypeAliasDeclarationStructure } from "ts-morph"; import { describe, it, expect, beforeEach, assert } from "vitest"; import { @@ -15,13 +16,26 @@ import { addDeclaration } from "../../../src/framework/declaration.js"; import { resolveReference } from "../../../src/framework/reference.js"; import { assertGetFunctionDeclaration, + assertGetFunctionParameter, + assertGetFunctionReturnType, assertGetImportStatements, assertGetInterfaceDeclaration, assertGetInterfaceProperty, + assertGetStatement, + assertGetTypealiasDeclaration, assertGetVariableDeclaration } from "../../utils/tsmorph-utils.js"; import { useDependencies } from "../../../src/framework/hooks/useDependencies.js"; import { ExternalDependencies } from "../../../src/framework/dependency.js"; +import { + loadStaticHelpers, + StaticHelpers +} from "../../../src/framework/load-static-helpers.js"; +import path from "path"; +import { AzurePollingDependencies } from "../../../src/modular/external-dependencies.js"; +import { getDirname } from "../../../src/utils/dirname.js"; + +const __dirname = getDirname(import.meta.url).__dirname; describe("Binder", () => { let project: Project; @@ -30,11 +44,357 @@ describe("Binder", () => { beforeEach(() => { project = new Project(); - provideBinder(project); - binder = useBinder(); + binder = provideBinder(project); Dependencies = useDependencies(); }); + describe("References", () => { + it("should handle interface property reference", () => { + const sourceFile = project.createSourceFile("test1.ts", "", { + overwrite: true + }); + + const interfaceDeclaration: InterfaceDeclarationStructure = { + kind: StructureKind.Interface, + name: "TestInterface", + properties: [{ name: "foo", type: "string" }] + }; + + const interfaceWithReference: InterfaceDeclarationStructure = { + kind: StructureKind.Interface, + name: "TestModel", + properties: [{ name: "bar", type: resolveReference("TestInterface") }] + }; + + addDeclaration(sourceFile, interfaceDeclaration, "TestInterface"); + addDeclaration(sourceFile, interfaceWithReference, "TestModel"); + binder.resolveAllReferences(); + + assertGetInterfaceDeclaration(sourceFile, "TestInterface"); + assertGetInterfaceDeclaration(sourceFile, "TestModel"); + + console.log("// test1.ts"); + console.log(sourceFile.getFullText()); + // test1.ts + // interface TestInterface { + // foo: string; + // } + // interface TestModel { + // bar: TestInterface; + // } + }); + + it("should handle interface property reference where referenced has type parameters", () => { + const sourceFile = project.createSourceFile("test1.ts", "", { + overwrite: true + }); + + const interfaceDeclaration: InterfaceDeclarationStructure = { + kind: StructureKind.Interface, + name: "TestInterface", + properties: [{ name: "foo", type: "T" }], + typeParameters: ["T"] + }; + + const interfaceWithReference: InterfaceDeclarationStructure = { + kind: StructureKind.Interface, + name: "TestModel", + properties: [ + { name: "bar", type: `${resolveReference("TestInterface")}` } + ] + }; + + addDeclaration(sourceFile, interfaceDeclaration, "TestInterface"); + addDeclaration(sourceFile, interfaceWithReference, "TestModel"); + binder.resolveAllReferences(); + + assertGetInterfaceDeclaration(sourceFile, "TestInterface"); + assertGetInterfaceDeclaration(sourceFile, "TestModel"); + + console.log("// test1.ts"); + console.log(sourceFile.getFullText()); + // test1.ts + // interface TestInterface { + // foo: string; + // } + // interface TestModel { + // bar: TestInterface; + // } + }); + + it("should handle function parameter with reference", () => { + const sourceFile = project.createSourceFile("test1.ts", "", { + overwrite: true + }); + + const interfaceDeclaration: InterfaceDeclarationStructure = { + kind: StructureKind.Interface, + name: "TestInterface", + properties: [{ name: "foo", type: "string" }] + }; + + const functionDeclaration: FunctionDeclarationStructure = { + kind: StructureKind.Function, + name: "testFunction", + parameters: [{ name: "param", type: resolveReference("TestInterface") }] + }; + + addDeclaration(sourceFile, interfaceDeclaration, "TestInterface"); + addDeclaration(sourceFile, functionDeclaration, "testFunction"); + binder.resolveAllReferences(); + + assertGetInterfaceDeclaration(sourceFile, "TestInterface"); + const functionDec = assertGetFunctionDeclaration( + sourceFile, + "testFunction" + ); + const param = assertGetFunctionParameter(functionDec, "param"); + expect(param.getType().getText(), "TestInterface"); + + console.log("// test1.ts"); + console.log(sourceFile.getFullText()); + // test1.ts + // interface TestInterface { + // foo: string; + // } + // function testFunction(param: TestInterface) { + // } + }); + + it("should handle function parameter with reference where referenced has type parameters", () => { + const sourceFile = project.createSourceFile("test1.ts", "", { + overwrite: true + }); + + const interfaceDeclaration: InterfaceDeclarationStructure = { + kind: StructureKind.Interface, + name: "TestInterface", + properties: [{ name: "foo", type: "T" }], + typeParameters: ["T"] + }; + + const functionDeclaration: FunctionDeclarationStructure = { + kind: StructureKind.Function, + name: "testFunction", + parameters: [ + { + name: "param", + type: `${resolveReference("TestInterface")}` + } + ] + }; + + addDeclaration(sourceFile, interfaceDeclaration, "TestInterface"); + addDeclaration(sourceFile, functionDeclaration, "testFunction"); + binder.resolveAllReferences(); + + assertGetInterfaceDeclaration(sourceFile, "TestInterface"); + const functionDec = assertGetFunctionDeclaration( + sourceFile, + "testFunction" + ); + const param = assertGetFunctionParameter(functionDec, "param"); + expect(param.getType().getText(), "TestInterface"); + + console.log("// test1.ts"); + console.log(sourceFile.getFullText()); + // test1.ts + // interface TestInterface { + // foo: string; + // } + // function testFunction(param: TestInterface) { + // } + }); + + it("should handle function return type with reference", () => { + const sourceFile = project.createSourceFile("test1.ts", "", { + overwrite: true + }); + + const interfaceDeclaration: InterfaceDeclarationStructure = { + kind: StructureKind.Interface, + name: "TestInterface", + properties: [{ name: "foo", type: "string" }] + }; + + const functionDeclaration: FunctionDeclarationStructure = { + kind: StructureKind.Function, + name: "testFunction", + returnType: resolveReference("TestInterface") + }; + + addDeclaration(sourceFile, interfaceDeclaration, "TestInterface"); + addDeclaration(sourceFile, functionDeclaration, "testFunction"); + binder.resolveAllReferences(); + + assertGetInterfaceDeclaration(sourceFile, "TestInterface"); + const fnDeclaration = assertGetFunctionDeclaration( + sourceFile, + "testFunction" + ); + assertGetFunctionReturnType(fnDeclaration, "TestInterface"); + + console.log("// test1.ts"); + console.log(sourceFile.getFullText()); + // test1.ts + // interface TestInterface { + // foo: string; + // } + // function testFunction(): TestInterface { + // } + }); + + it("should handle function return type with reference with type params", () => { + const sourceFile = project.createSourceFile("test1.ts", "", { + overwrite: true + }); + + const interfaceDeclaration: InterfaceDeclarationStructure = { + kind: StructureKind.Interface, + name: "TestInterface", + properties: [{ name: "foo", type: "T" }], + typeParameters: ["T"] + }; + + const functionDeclaration: FunctionDeclarationStructure = { + kind: StructureKind.Function, + name: "testFunction", + returnType: `${resolveReference("TestInterface")}` + }; + + addDeclaration(sourceFile, interfaceDeclaration, "TestInterface"); + addDeclaration(sourceFile, functionDeclaration, "testFunction"); + binder.resolveAllReferences(); + + assertGetInterfaceDeclaration(sourceFile, "TestInterface"); + const fnDeclaration = assertGetFunctionDeclaration( + sourceFile, + "testFunction" + ); + assertGetFunctionReturnType(fnDeclaration, "TestInterface"); + + console.log("// test1.ts"); + console.log(sourceFile.getFullText()); + // test1.ts + // interface TestInterface { + // foo: string; + // } + // function testFunction(): TestInterface { + // } + }); + + it("should handle a type alias", () => { + const sourceFile = project.createSourceFile("test1.ts", "", { + overwrite: true + }); + + const interfaceDeclaration: InterfaceDeclarationStructure = { + kind: StructureKind.Interface, + name: "TestInterface", + properties: [{ name: "foo", type: "string" }] + }; + + const typeDeclaration: TypeAliasDeclarationStructure = { + kind: StructureKind.TypeAlias, + name: "TestType", + type: resolveReference("TestInterface") + }; + + addDeclaration(sourceFile, interfaceDeclaration, "TestInterface"); + addDeclaration(sourceFile, typeDeclaration, "TestType"); + binder.resolveAllReferences(); + + assertGetInterfaceDeclaration(sourceFile, "TestInterface"); + assertGetTypealiasDeclaration(sourceFile, "TestType"); + + console.log("// test1.ts"); + console.log(sourceFile.getFullText()); + // test1.ts + // interface TestInterface { + // foo: string; + // } + // function testFunction(): TestInterface { + // } + }); + + it("should handle a type alias with interface and type parameters", () => { + const sourceFile = project.createSourceFile("test1.ts", "", { + overwrite: true + }); + + const interfaceDeclaration: InterfaceDeclarationStructure = { + kind: StructureKind.Interface, + name: "TestInterface", + properties: [{ name: "foo", type: "T" }], + typeParameters: ["T"] + }; + + const typeDeclaration: TypeAliasDeclarationStructure = { + kind: StructureKind.TypeAlias, + name: "TestType", + type: `${resolveReference("TestInterface")}` + }; + + addDeclaration(sourceFile, interfaceDeclaration, "TestInterface"); + addDeclaration(sourceFile, typeDeclaration, "TestType"); + binder.resolveAllReferences(); + + assertGetInterfaceDeclaration(sourceFile, "TestInterface"); + assertGetTypealiasDeclaration(sourceFile, "TestType"); + + console.log("// test1.ts"); + console.log(sourceFile.getFullText()); + // test1.ts + // interface TestInterface { + // foo: string; + // } + // function testFunction(): TestInterface { + // } + }); + }); + + describe("Static Helpers", () => { + let staticHelpers: StaticHelpers; + let helpersDirectory: string; + + beforeEach(async () => { + helpersDirectory = path.resolve(__dirname, "../assets/static-helpers"); + staticHelpers = { + buildCsvCollection: { + kind: "function", + name: "buildCsvCollection", + location: "utils.ts" + } + }; + const staticHelperMap = await loadStaticHelpers(project, staticHelpers, { + helpersAssetDirectory: helpersDirectory + }); + binder = provideBinder(project, { staticHelpers: staticHelperMap }); + }); + + it("should resolve reference to static helper", () => { + const sourceFile = project.createSourceFile("src/test1.ts", "", { + overwrite: true + }); + + sourceFile.addStatements( + `${resolveReference(staticHelpers.buildCsvCollection)}();` + ); + + binder.resolveAllReferences(); + + assertGetImportStatements(sourceFile, "./static-helpers/utils.js"); + assertGetStatement(sourceFile, "buildCsvCollection();"); + + console.log("// test1.ts"); + console.log(sourceFile.getFullText()); + // test1.ts + // import { buildCsvCollection } from "./static-helpers/utils.js"; + // + // buildCsvCollection(); + }); + }); + describe("External Dependencies Override", () => { beforeEach(() => { const customDependencies = { @@ -42,7 +402,8 @@ describe("Binder", () => { kind: "externalDependency", name: "ClientOptions", module: "@azure-rest/core-client" - } + }, + ...AzurePollingDependencies }; provideBinder(project, { dependencies: customDependencies }); binder = useBinder(); diff --git a/packages/typespec-ts/test-next/integration/load-static-files.test.ts b/packages/typespec-ts/test-next/integration/load-static-files.test.ts new file mode 100644 index 0000000000..12db3f151d --- /dev/null +++ b/packages/typespec-ts/test-next/integration/load-static-files.test.ts @@ -0,0 +1,67 @@ +import { describe, it, beforeEach, expect, assert } from "vitest"; +import { loadStaticHelpers } from "../../src/framework/load-static-helpers.js"; +import { Project } from "ts-morph"; +import path from "path"; +import { refkey } from "../../src/framework/refkey.js"; +import { getDirname } from "../../src/utils/dirname.js"; + +const __dirname = getDirname(import.meta.url).__dirname; + +describe("loadStaticHelpers", () => { + let project: Project; + let helpersAssetDirectory: string; + beforeEach(() => { + project = new Project(); + helpersAssetDirectory = path.resolve(__dirname, "assets/static-helpers"); + }); + + it("should load static helpers", async () => { + const helpers = { + buildCsvCollection: { + kind: "function", + name: "buildCsvCollection", + location: "utils.ts" + } + } as const; + const helperDeclarations = await loadStaticHelpers(project, helpers, { + helpersAssetDirectory + }); + expect(project.getSourceFiles()).to.toHaveLength(1); + const buildCsvCollectionDeclaration = helperDeclarations.get( + refkey(helpers.buildCsvCollection) + ); + expect(buildCsvCollectionDeclaration).toEqual(helpers.buildCsvCollection); + }); + + it("should handle missing helpers gracefully", async () => { + const helpers = { + buildCsvCollection: { + kind: "function", + name: "nonExisting", + location: "utils.ts" + } + } as const; + + await expect( + loadStaticHelpers(project, helpers, { + helpersAssetDirectory + }) + ).rejects.toThrowError(/not found/); + }); + + it("should handle invalid helper kind gracefully", async () => { + const helpers = { + buildCsvCollection: { + kind: "invalid", + name: "buildCsvCollection", + location: "utils.ts" + } + } as any; + + await expect( + loadStaticHelpers(project, helpers, { + helpersAssetDirectory + }) + ).rejects.toThrowError(/invalid helper kind/); + }); +}); diff --git a/packages/typespec-ts/test-next/unit/static-helpers/header-collection.test.ts b/packages/typespec-ts/test-next/unit/static-helpers/header-collection.test.ts new file mode 100644 index 0000000000..22c80b8544 --- /dev/null +++ b/packages/typespec-ts/test-next/unit/static-helpers/header-collection.test.ts @@ -0,0 +1,50 @@ +import { describe, it, expect } from "vitest"; +import { buildCsvCollection } from "../../../static/static-helpers/serialization/build-csv-collection.js"; +import { buildPipeCollection } from "../../../static/static-helpers/serialization/build-pipe-collection.js"; +import { buildSsvCollection } from "../../../static/static-helpers/serialization/build-ssv-collection.js"; +import { buildTsvCollection } from "../../../static/static-helpers/serialization/build-tsv-collection.js"; + +describe("Collection Functions", () => { + const items = [1, 2, 3]; + const stringItems = ["a", "b", "c"]; + + describe("buildCsvCollection", () => { + it("should join numbers with a comma", () => { + expect(buildCsvCollection(items)).toBe("1,2,3"); + }); + + it("should join strings with a comma", () => { + expect(buildCsvCollection(stringItems)).toBe("a,b,c"); + }); + }); + + describe("buildPipeCollection", () => { + it("should join numbers with a pipe", () => { + expect(buildPipeCollection(items)).toBe("1|2|3"); + }); + + it("should join strings with a pipe", () => { + expect(buildPipeCollection(stringItems)).toBe("a|b|c"); + }); + }); + + describe("buildSsvCollection", () => { + it("should join numbers with a space", () => { + expect(buildSsvCollection(items)).toBe("1 2 3"); + }); + + it("should join strings with a space", () => { + expect(buildSsvCollection(stringItems)).toBe("a b c"); + }); + }); + + describe("buildTsvCollection", () => { + it("should join numbers with a tab", () => { + expect(buildTsvCollection(items)).toBe("1\t2\t3"); + }); + + it("should join strings with a tab", () => { + expect(buildTsvCollection(stringItems)).toBe("a\tb\tc"); + }); + }); +}); diff --git a/packages/typespec-ts/test-next/utils/tsmorph-utils.ts b/packages/typespec-ts/test-next/utils/tsmorph-utils.ts index f01c369762..e9c5ea2048 100644 --- a/packages/typespec-ts/test-next/utils/tsmorph-utils.ts +++ b/packages/typespec-ts/test-next/utils/tsmorph-utils.ts @@ -1,5 +1,11 @@ import { assert } from "chai"; -import { InterfaceDeclaration, SourceFile, SyntaxKind } from "ts-morph"; +import exp from "constants"; +import { + FunctionDeclaration, + InterfaceDeclaration, + SourceFile, + TypeAliasDeclaration +} from "ts-morph"; export function assertGetInterfaceDeclaration( sourceFile: SourceFile, @@ -10,6 +16,15 @@ export function assertGetInterfaceDeclaration( return interfaceDeclaration; } +export function assertGetTypealiasDeclaration( + sourceFile: SourceFile, + name: string +): TypeAliasDeclaration { + const typeAliasDeclaration = sourceFile.getTypeAlias(name); + assert(typeAliasDeclaration, `Type alias ${name} not found`); + return typeAliasDeclaration; +} + export function assertGetFunctionDeclaration( sourceFile: SourceFile, name: string @@ -19,6 +34,32 @@ export function assertGetFunctionDeclaration( return functionDeclaration; } +export function assertGetFunctionParameter( + functionDeclaration: FunctionDeclaration, + name: string +) { + const parameter = functionDeclaration.getParameter(name); + assert(parameter, `Parameter ${name} not found`); + return parameter; +} + +export function assertGetFunctionReturnType( + functionDeclaration: FunctionDeclaration, + expected: string +) { + const returnType = functionDeclaration.getReturnType().getText(); + assert.equal(returnType, expected); +} + +export function assertGetStatement(sourceFile: SourceFile, expected: string) { + const statement = sourceFile + .getStatements() + .find((statement) => statement.getText() === expected); + + assert(statement, `Statement \`${expected}\` not found`); + return statement; +} + export function assertGetInterfaceProperty( interfaceDeclaration: InterfaceDeclaration, name: string diff --git a/packages/typespec-ts/test/commands/gen-cadl-ranch.js b/packages/typespec-ts/test/commands/gen-cadl-ranch.js index 6e81210c41..13ede97e64 100644 --- a/packages/typespec-ts/test/commands/gen-cadl-ranch.js +++ b/packages/typespec-ts/test/commands/gen-cadl-ranch.js @@ -5,7 +5,6 @@ import { rlcTsps } from "./cadl-ranch-list.js"; import { runTypespec } from "./run.js"; -import os from "os"; async function generateTypeSpecs(tag = "rlc", isDebugging, pathFilter) { let list = rlcTsps; @@ -33,23 +32,29 @@ async function generateTypeSpecs(tag = "rlc", isDebugging, pathFilter) { } const maxConcurrentWorkers = 4; - let generatePromises = []; - let count = 0; + let activePromises = []; for (const tsp of list) { if (isDebugging === true && tsp.debug !== true) { continue; } - const generatePromise = runTypespec(tsp, tag); - generatePromises.push(generatePromise); - count++; - if (count % maxConcurrentWorkers === 0) { - await Promise.allSettled(generatePromises); - generatePromises = []; + const generatePromise = runTypespec(tsp, tag) + .then((result) => { + activePromises = activePromises.filter((p) => p !== generatePromise); + return result; + }) + .catch((error) => { + activePromises = activePromises.filter((p) => p !== generatePromise); + throw error; + }); + + activePromises.push(generatePromise); + + if (activePromises.length >= maxConcurrentWorkers) { + await Promise.race(activePromises); } } - if (generatePromises.length > 0) { - await Promise.allSettled(generatePromises); - } + + await Promise.allSettled(activePromises); } async function main() { @@ -63,10 +68,12 @@ async function main() { let exitCode = 0; try { + console.time("generate-cadl-ranch"); await main(); } catch (e) { console.error(e); exitCode = 1; } finally { + console.timeEnd("generate-cadl-ranch"); process.exit(exitCode); } diff --git a/packages/typespec-ts/test/commands/run.js b/packages/typespec-ts/test/commands/run.js index 854356a228..09f8472f64 100644 --- a/packages/typespec-ts/test/commands/run.js +++ b/packages/typespec-ts/test/commands/run.js @@ -55,13 +55,17 @@ export async function runTypespec(config, mode) { `${typespecPath}`, "--config tspconfig.yaml " ]; + let error; try { await npxCommand("tsp", commandArguments, workingDir, logger); logger.log(`=== End ${targetFolder} ===`); } catch (e) { logger.error(e.toString()); + error = e; } finally { logger.flush(); - throw e; + if (error) { + throw error; + } } } diff --git a/packages/typespec-ts/test/modularIntegration/generated/authentication/api-key/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/authentication/api-key/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/authentication/api-key/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/authentication/api-key/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/authentication/http/custom/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/authentication/http/custom/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/authentication/http/custom/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/authentication/http/custom/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/authentication/oauth2/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/authentication/oauth2/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/authentication/oauth2/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/authentication/oauth2/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/authentication/union/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/authentication/union/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/authentication/union/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/authentication/union/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/client-generator-core/access/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/client-generator-core/access/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/client-generator-core/access/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/client-generator-core/access/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/client-generator-core/usage/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/client-generator-core/usage/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/client-generator-core/usage/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/client-generator-core/usage/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/api/operations.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/api/operations.ts index 801dcb39b1..7e5527cb36 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/api/operations.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/api/operations.ts @@ -12,8 +12,6 @@ import { _PagedUser, _UserListResults, } from "../models/models.js"; -import { PagedAsyncIterableIterator } from "../models/pagingTypes.js"; -import { buildPagedAsyncIterator } from "./pagingHelpers.js"; import { BasicContext as Client } from "./index.js"; import { StreamableMethod, @@ -21,7 +19,11 @@ import { PathUncheckedResponse, createRestError, } from "@azure-rest/core-client"; -import { buildMultiCollection } from "../helpers/serializerHelpers.js"; +import { + PagedAsyncIterableIterator, + buildPagedAsyncIterator, +} from "../static-helpers/pagingHelpers.js"; +import { buildMultiCollection } from "../static-helpers/serialization/build-multi-collection.js"; import { CreateOrUpdateOptionalParams, CreateOrReplaceOptionalParams, diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/api/pagingHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/api/pagingHelpers.ts deleted file mode 100644 index 0fc574c83c..0000000000 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/api/pagingHelpers.ts +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { - Client, - createRestError, - PathUncheckedResponse, -} from "@azure-rest/core-client"; -import { RestError } from "@azure/core-rest-pipeline"; -import { - BuildPagedAsyncIteratorOptions, - ContinuablePage, - PageSettings, - PagedAsyncIterableIterator, - PagedResult, -} from "../models/pagingTypes.js"; - -/** - * Helper to paginate results in a generic way and return a PagedAsyncIterableIterator - */ -export function buildPagedAsyncIterator< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, - TResponse extends PathUncheckedResponse = PathUncheckedResponse, ->( - client: Client, - getInitialResponse: () => PromiseLike, - processResponseBody: (result: TResponse) => PromiseLike, - expectedStatuses: string[], - options: BuildPagedAsyncIteratorOptions = {}, -): PagedAsyncIterableIterator { - const itemName = options.itemName ?? "value"; - const nextLinkName = options.nextLinkName ?? "nextLink"; - const pagedResult: PagedResult = { - getPage: async (pageLink?: string) => { - const result = - pageLink === undefined - ? await getInitialResponse() - : await client.pathUnchecked(pageLink).get(); - checkPagingRequest(result, expectedStatuses); - const results = await processResponseBody(result as TResponse); - const nextLink = getNextLink(results, nextLinkName); - const values = getElements(results, itemName) as TPage; - return { - page: values, - nextPageLink: nextLink, - }; - }, - byPage: (settings?: TPageSettings) => { - const { continuationToken } = settings ?? {}; - return getPageAsyncIterator(pagedResult, { - pageLink: continuationToken, - }); - }, - }; - return getPagedAsyncIterator(pagedResult); -} - -/** - * 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 extends PageSettings = PageSettings, ->( - pagedResult: PagedResult, -): PagedAsyncIterableIterator { - const iter = getItemAsyncIterator( - pagedResult, - ); - return { - next() { - return iter.next(); - }, - [Symbol.asyncIterator]() { - return this; - }, - byPage: - pagedResult?.byPage ?? - ((settings?: TPageSettings) => { - const { continuationToken } = settings ?? {}; - return getPageAsyncIterator(pagedResult, { - pageLink: continuationToken, - }); - }), - }; -} - -async function* getItemAsyncIterator< - TElement, - TPage, - TPageSettings extends PageSettings, ->( - pagedResult: PagedResult, -): AsyncIterableIterator { - const pages = getPageAsyncIterator(pagedResult); - for await (const page of pages) { - yield* page as unknown as TElement[]; - } -} - -async function* getPageAsyncIterator< - TElement, - TPage, - TPageSettings extends PageSettings, ->( - pagedResult: PagedResult, - options: { - pageLink?: string; - } = {}, -): AsyncIterableIterator> { - const { pageLink } = options; - let response = await pagedResult.getPage( - pageLink ?? pagedResult.firstPageLink, - ); - if (!response) { - return; - } - let result = response.page as ContinuablePage; - result.continuationToken = response.nextPageLink; - yield result; - while (response.nextPageLink) { - response = await pagedResult.getPage(response.nextPageLink); - if (!response) { - return; - } - result = response.page as ContinuablePage; - result.continuationToken = response.nextPageLink; - yield result; - } -} - -/** - * 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)[nextLinkName]; - - if ( - typeof nextLink !== "string" && - typeof nextLink !== "undefined" && - nextLink !== null - ) { - throw new RestError( - `Body Property ${nextLinkName} should be a string or undefined or null but got ${typeof nextLink}`, - ); - } - - if (nextLink === null) { - return undefined; - } - - return nextLink; -} - -/** - * Gets the elements of the current request in the body. - */ -function getElements(body: unknown, itemName: string): T[] { - const value = (body as Record)[itemName] as T[]; - if (!Array.isArray(value)) { - throw new RestError( - `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, - expectedStatuses: string[], -): void { - if (!expectedStatuses.includes(response.status)) { - throw createRestError( - `Pagination failed with unexpected statusCode ${response.status}`, - response, - ); - } -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/basicClient.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/basicClient.ts index 8bba591ef5..5fcf8e0be2 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/basicClient.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/basicClient.ts @@ -21,7 +21,6 @@ import { ListFirstItemOptionalParams, ListSecondItemOptionalParams, } from "./models/options.js"; -import { PagedAsyncIterableIterator } from "./models/pagingTypes.js"; import { createBasic, BasicContext, @@ -38,6 +37,7 @@ import { listFirstItem, listSecondItem, } from "./api/index.js"; +import { PagedAsyncIterableIterator } from "./static-helpers/pagingHelpers.js"; export { BasicClientOptionalParams } from "./api/basicContext.js"; diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/index.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/index.ts index 36a1132d4f..db4767a012 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/index.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/index.ts @@ -1,6 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { + PageSettings, + ContinuablePage, + PagedAsyncIterableIterator, +} from "./static-helpers/pagingHelpers.js"; + export { BasicClient, BasicClientOptionalParams } from "./basicClient.js"; export { User, @@ -21,7 +27,5 @@ export { ExportOptionalParams, ListFirstItemOptionalParams, ListSecondItemOptionalParams, - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, } from "./models/index.js"; +export { PageSettings, ContinuablePage, PagedAsyncIterableIterator }; diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/models/index.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/models/index.ts index 5504635e56..274cd44721 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/models/index.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/models/index.ts @@ -23,8 +23,3 @@ export { ListFirstItemOptionalParams, ListSecondItemOptionalParams, } from "./options.js"; -export { - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, -} from "./pagingTypes.js"; diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/models/pagingTypes.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/models/pagingTypes.ts deleted file mode 100644 index f734b48e62..0000000000 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/models/pagingTypes.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -/** - * Options for the byPage method - */ -export interface PageSettings { - /** - * A reference to a specific page to start iterating from. - */ - continuationToken?: string; -} - -/** - * An interface that describes a page of results. - */ -export type ContinuablePage = TPage & { - /** - * 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 extends PageSettings = 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< - TElement, - TPage, - TPageSettings - >; - /** - * Return an AsyncIterableIterator that works a page at a time - */ - byPage: ( - settings?: TPageSettings, - ) => AsyncIterableIterator>; -} - -/** - * An interface that describes how to communicate with the service. - */ -export interface PagedResult< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, -> { - /** - * Link to the first page of results. - */ - firstPageLink?: string; - /** - * A method that returns a page of results. - */ - getPage: ( - pageLink?: string, - ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; -} - -/** - * Options for the paging helper - */ -export interface BuildPagedAsyncIteratorOptions { - itemName?: string; - nextLinkName?: string; -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/static-helpers/pagingHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/static-helpers/pagingHelpers.ts new file mode 100644 index 0000000000..5855ada5a8 --- /dev/null +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/static-helpers/pagingHelpers.ts @@ -0,0 +1,277 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { + Client, + createRestError, + PathUncheckedResponse, +} from "@azure-rest/core-client"; +import { RestError } from "@azure/core-rest-pipeline"; + +/** + * Options for the byPage method + */ +export interface PageSettings { + /** + * A reference to a specific page to start iterating from. + */ + continuationToken?: string; +} + +/** + * An interface that describes a page of results. + */ +export type ContinuablePage = TPage & { + /** + * 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 extends PageSettings = 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< + TElement, + TPage, + TPageSettings + >; + /** + * Return an AsyncIterableIterator that works a page at a time + */ + byPage: ( + settings?: TPageSettings, + ) => AsyncIterableIterator>; +} + +/** + * An interface that describes how to communicate with the service. + */ +export interface PagedResult< + TElement, + TPage = TElement[], + TPageSettings extends PageSettings = PageSettings, +> { + /** + * Link to the first page of results. + */ + firstPageLink?: string; + /** + * A method that returns a page of results. + */ + getPage: ( + pageLink?: string, + ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; +} + +/** + * Options for the paging helper + */ +export interface BuildPagedAsyncIteratorOptions { + itemName?: string; + nextLinkName?: string; +} + +/** + * Helper to paginate results in a generic way and return a PagedAsyncIterableIterator + */ +export function buildPagedAsyncIterator< + TElement, + TPage = TElement[], + TPageSettings extends PageSettings = PageSettings, + TResponse extends PathUncheckedResponse = PathUncheckedResponse, +>( + client: Client, + getInitialResponse: () => PromiseLike, + processResponseBody: (result: TResponse) => PromiseLike, + expectedStatuses: string[], + options: BuildPagedAsyncIteratorOptions = {}, +): PagedAsyncIterableIterator { + const itemName = options.itemName ?? "value"; + const nextLinkName = options.nextLinkName ?? "nextLink"; + const pagedResult: PagedResult = { + getPage: async (pageLink?: string) => { + const result = + pageLink === undefined + ? await getInitialResponse() + : await client.pathUnchecked(pageLink).get(); + checkPagingRequest(result, expectedStatuses); + const results = await processResponseBody(result as TResponse); + const nextLink = getNextLink(results, nextLinkName); + const values = getElements(results, itemName) as TPage; + return { + page: values, + nextPageLink: nextLink, + }; + }, + byPage: (settings?: TPageSettings) => { + const { continuationToken } = settings ?? {}; + return getPageAsyncIterator(pagedResult, { + pageLink: continuationToken, + }); + }, + }; + return getPagedAsyncIterator(pagedResult); +} + +/** + * 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 extends PageSettings = PageSettings, +>( + pagedResult: PagedResult, +): PagedAsyncIterableIterator { + const iter = getItemAsyncIterator( + pagedResult, + ); + return { + next() { + return iter.next(); + }, + [Symbol.asyncIterator]() { + return this; + }, + byPage: + pagedResult?.byPage ?? + ((settings?: TPageSettings) => { + const { continuationToken } = settings ?? {}; + return getPageAsyncIterator(pagedResult, { + pageLink: continuationToken, + }); + }), + }; +} + +async function* getItemAsyncIterator< + TElement, + TPage, + TPageSettings extends PageSettings, +>( + pagedResult: PagedResult, +): AsyncIterableIterator { + const pages = getPageAsyncIterator(pagedResult); + for await (const page of pages) { + yield* page as unknown as TElement[]; + } +} + +async function* getPageAsyncIterator< + TElement, + TPage, + TPageSettings extends PageSettings, +>( + pagedResult: PagedResult, + options: { + pageLink?: string; + } = {}, +): AsyncIterableIterator> { + const { pageLink } = options; + let response = await pagedResult.getPage( + pageLink ?? pagedResult.firstPageLink, + ); + if (!response) { + return; + } + let result = response.page as ContinuablePage; + result.continuationToken = response.nextPageLink; + yield result; + while (response.nextPageLink) { + response = await pagedResult.getPage(response.nextPageLink); + if (!response) { + return; + } + result = response.page as ContinuablePage; + result.continuationToken = response.nextPageLink; + yield result; + } +} + +/** + * 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)[nextLinkName]; + + if ( + typeof nextLink !== "string" && + typeof nextLink !== "undefined" && + nextLink !== null + ) { + throw new RestError( + `Body Property ${nextLinkName} should be a string or undefined or null but got ${typeof nextLink}`, + ); + } + + if (nextLink === null) { + return undefined; + } + + return nextLink; +} + +/** + * Gets the elements of the current request in the body. + */ +function getElements(body: unknown, itemName: string): T[] { + const value = (body as Record)[itemName] as T[]; + if (!Array.isArray(value)) { + throw new RestError( + `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, + expectedStatuses: string[], +): void { + if (!expectedStatuses.includes(response.status)) { + throw createRestError( + `Pagination failed with unexpected statusCode ${response.status}`, + response, + ); + } +} diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/static-helpers/serialization/build-multi-collection.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/static-helpers/serialization/build-multi-collection.ts new file mode 100644 index 0000000000..951b4a54e9 --- /dev/null +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/core/basic/src/static-helpers/serialization/build-multi-collection.ts @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +export function buildMultiCollection( + items: string[], + parameterName: string, +): string { + return items + .map((item, index) => { + if (index === 0) { + return item; + } + return `${parameterName}=${item}`; + }) + .join("&"); +} diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/rpc/generated/src/api/operations.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/rpc/generated/src/api/operations.ts index 240c2e6b0c..6024f56f50 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/rpc/generated/src/api/operations.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/rpc/generated/src/api/operations.ts @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { getLongRunningPoller } from "./pollingHelpers.js"; -import { PollerLike, OperationState } from "@azure/core-lro"; import { GenerationOptions, GenerationResult } from "../models/models.js"; import { RpcContext as Client } from "./index.js"; import { @@ -11,6 +9,8 @@ import { PathUncheckedResponse, createRestError, } from "@azure-rest/core-client"; +import { getLongRunningPoller } from "../static-helpers/pollingHelpers.js"; +import { PollerLike, OperationState } from "@azure/core-lro"; import { LongRunningRpcOptionalParams } from "../models/options.js"; export function _longRunningRpcSend( diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/rpc/generated/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/rpc/generated/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/rpc/generated/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/rpc/generated/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/rpc/generated/src/restorePollerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/rpc/generated/src/restorePollerHelpers.ts index 02dfde2fd4..e3b61d53f4 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/rpc/generated/src/restorePollerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/rpc/generated/src/restorePollerHelpers.ts @@ -1,20 +1,20 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { - PollerLike, - OperationState, - deserializeState, - ResourceLocationConfig, -} from "@azure/core-lro"; import { RpcClient } from "./rpcClient.js"; -import { getLongRunningPoller } from "./api/pollingHelpers.js"; import { _longRunningRpcDeserialize } from "./api/operations.js"; +import { getLongRunningPoller } from "./static-helpers/pollingHelpers.js"; import { - PathUncheckedResponse, OperationOptions, + PathUncheckedResponse, } from "@azure-rest/core-client"; import { AbortSignalLike } from "@azure/abort-controller"; +import { + PollerLike, + OperationState, + deserializeState, + ResourceLocationConfig, +} from "@azure/core-lro"; export interface RestorePollerOptions< TResult, diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/rpc/generated/src/rpcClient.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/rpc/generated/src/rpcClient.ts index 3357d68cd7..fcf0158650 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/rpc/generated/src/rpcClient.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/rpc/generated/src/rpcClient.ts @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { PollerLike, OperationState } from "@azure/core-lro"; import { Pipeline } from "@azure/core-rest-pipeline"; import { GenerationOptions, GenerationResult } from "./models/models.js"; import { LongRunningRpcOptionalParams } from "./models/options.js"; @@ -11,6 +10,7 @@ import { RpcContext, RpcClientOptionalParams, } from "./api/index.js"; +import { PollerLike, OperationState } from "@azure/core-lro"; export { RpcClientOptionalParams } from "./api/rpcContext.js"; diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/standard/generated/src/api/pollingHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/rpc/generated/src/static-helpers/pollingHelpers.ts similarity index 98% rename from packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/standard/generated/src/api/pollingHelpers.ts rename to packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/rpc/generated/src/static-helpers/pollingHelpers.ts index 47c259e79f..e130b18d89 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/standard/generated/src/api/pollingHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/rpc/generated/src/static-helpers/pollingHelpers.ts @@ -1,6 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { PollerLike, OperationState, diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/standard/generated/src/api/operations.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/standard/generated/src/api/operations.ts index f2b9ef2bb2..d87be41723 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/standard/generated/src/api/operations.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/standard/generated/src/api/operations.ts @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { getLongRunningPoller } from "./pollingHelpers.js"; -import { PollerLike, OperationState } from "@azure/core-lro"; import { User, ExportedUser } from "../models/models.js"; import { StandardContext as Client } from "./index.js"; import { @@ -11,6 +9,8 @@ import { PathUncheckedResponse, createRestError, } from "@azure-rest/core-client"; +import { getLongRunningPoller } from "../static-helpers/pollingHelpers.js"; +import { PollerLike, OperationState } from "@azure/core-lro"; import { CreateOrReplaceOptionalParams, DeleteOptionalParams, diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/standard/generated/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/standard/generated/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/standard/generated/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/standard/generated/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/standard/generated/src/restorePollerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/standard/generated/src/restorePollerHelpers.ts index 1f72bd4a7d..7aae4b90bc 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/standard/generated/src/restorePollerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/standard/generated/src/restorePollerHelpers.ts @@ -1,24 +1,24 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { - PollerLike, - OperationState, - deserializeState, - ResourceLocationConfig, -} from "@azure/core-lro"; import { StandardClient } from "./standardClient.js"; -import { getLongRunningPoller } from "./api/pollingHelpers.js"; import { _createOrReplaceDeserialize, _$deleteDeserialize, _$exportDeserialize, } from "./api/operations.js"; +import { getLongRunningPoller } from "./static-helpers/pollingHelpers.js"; import { - PathUncheckedResponse, OperationOptions, + PathUncheckedResponse, } from "@azure-rest/core-client"; import { AbortSignalLike } from "@azure/abort-controller"; +import { + PollerLike, + OperationState, + deserializeState, + ResourceLocationConfig, +} from "@azure/core-lro"; export interface RestorePollerOptions< TResult, diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/standard/generated/src/standardClient.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/standard/generated/src/standardClient.ts index d64485b397..1321e02c4a 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/standard/generated/src/standardClient.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/standard/generated/src/standardClient.ts @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { PollerLike, OperationState } from "@azure/core-lro"; import { Pipeline } from "@azure/core-rest-pipeline"; import { User, ExportedUser } from "./models/models.js"; import { @@ -17,6 +16,7 @@ import { StandardContext, StandardClientOptionalParams, } from "./api/index.js"; +import { PollerLike, OperationState } from "@azure/core-lro"; export { StandardClientOptionalParams } from "./api/standardContext.js"; diff --git a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/api/pollingHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/standard/generated/src/static-helpers/pollingHelpers.ts similarity index 98% rename from packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/api/pollingHelpers.ts rename to packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/standard/generated/src/static-helpers/pollingHelpers.ts index 47c259e79f..e130b18d89 100644 --- a/packages/typespec-test/test/NetworkAnalytics.Management/generated/typespec-ts/src/api/pollingHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/core/lro/standard/generated/src/static-helpers/pollingHelpers.ts @@ -1,6 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import { PollerLike, OperationState, diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/core/model/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/core/model/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/core/model/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/core/model/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/core/scalar/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/core/scalar/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/core/scalar/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/core/scalar/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/core/traits/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/core/traits/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/core/traits/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/core/traits/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/common-types/managed-identity/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/common-types/managed-identity/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/common-types/managed-identity/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/common-types/managed-identity/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/api/nestedProxyResources/index.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/api/nestedProxyResources/index.ts index 07f498ef00..47b013933d 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/api/nestedProxyResources/index.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/api/nestedProxyResources/index.ts @@ -1,15 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { getLongRunningPoller } from "../pollingHelpers.js"; -import { PollerLike, OperationState } from "@azure/core-lro"; import { nestedProxyResourcePropertiesSerializer, NestedProxyResource, _NestedProxyResourceListResult, } from "../../models/models.js"; -import { PagedAsyncIterableIterator } from "../../models/pagingTypes.js"; -import { buildPagedAsyncIterator } from "../pagingHelpers.js"; import { ResourcesContext as Client } from "../index.js"; import { StreamableMethod, @@ -17,6 +13,12 @@ import { PathUncheckedResponse, createRestError, } from "@azure-rest/core-client"; +import { + PagedAsyncIterableIterator, + buildPagedAsyncIterator, +} from "../../static-helpers/pagingHelpers.js"; +import { getLongRunningPoller } from "../../static-helpers/pollingHelpers.js"; +import { PollerLike, OperationState } from "@azure/core-lro"; import { NestedProxyResourcesGetOptionalParams, NestedProxyResourcesCreateOrReplaceOptionalParams, diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/api/pagingHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/api/pagingHelpers.ts deleted file mode 100644 index 0fc574c83c..0000000000 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/api/pagingHelpers.ts +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { - Client, - createRestError, - PathUncheckedResponse, -} from "@azure-rest/core-client"; -import { RestError } from "@azure/core-rest-pipeline"; -import { - BuildPagedAsyncIteratorOptions, - ContinuablePage, - PageSettings, - PagedAsyncIterableIterator, - PagedResult, -} from "../models/pagingTypes.js"; - -/** - * Helper to paginate results in a generic way and return a PagedAsyncIterableIterator - */ -export function buildPagedAsyncIterator< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, - TResponse extends PathUncheckedResponse = PathUncheckedResponse, ->( - client: Client, - getInitialResponse: () => PromiseLike, - processResponseBody: (result: TResponse) => PromiseLike, - expectedStatuses: string[], - options: BuildPagedAsyncIteratorOptions = {}, -): PagedAsyncIterableIterator { - const itemName = options.itemName ?? "value"; - const nextLinkName = options.nextLinkName ?? "nextLink"; - const pagedResult: PagedResult = { - getPage: async (pageLink?: string) => { - const result = - pageLink === undefined - ? await getInitialResponse() - : await client.pathUnchecked(pageLink).get(); - checkPagingRequest(result, expectedStatuses); - const results = await processResponseBody(result as TResponse); - const nextLink = getNextLink(results, nextLinkName); - const values = getElements(results, itemName) as TPage; - return { - page: values, - nextPageLink: nextLink, - }; - }, - byPage: (settings?: TPageSettings) => { - const { continuationToken } = settings ?? {}; - return getPageAsyncIterator(pagedResult, { - pageLink: continuationToken, - }); - }, - }; - return getPagedAsyncIterator(pagedResult); -} - -/** - * 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 extends PageSettings = PageSettings, ->( - pagedResult: PagedResult, -): PagedAsyncIterableIterator { - const iter = getItemAsyncIterator( - pagedResult, - ); - return { - next() { - return iter.next(); - }, - [Symbol.asyncIterator]() { - return this; - }, - byPage: - pagedResult?.byPage ?? - ((settings?: TPageSettings) => { - const { continuationToken } = settings ?? {}; - return getPageAsyncIterator(pagedResult, { - pageLink: continuationToken, - }); - }), - }; -} - -async function* getItemAsyncIterator< - TElement, - TPage, - TPageSettings extends PageSettings, ->( - pagedResult: PagedResult, -): AsyncIterableIterator { - const pages = getPageAsyncIterator(pagedResult); - for await (const page of pages) { - yield* page as unknown as TElement[]; - } -} - -async function* getPageAsyncIterator< - TElement, - TPage, - TPageSettings extends PageSettings, ->( - pagedResult: PagedResult, - options: { - pageLink?: string; - } = {}, -): AsyncIterableIterator> { - const { pageLink } = options; - let response = await pagedResult.getPage( - pageLink ?? pagedResult.firstPageLink, - ); - if (!response) { - return; - } - let result = response.page as ContinuablePage; - result.continuationToken = response.nextPageLink; - yield result; - while (response.nextPageLink) { - response = await pagedResult.getPage(response.nextPageLink); - if (!response) { - return; - } - result = response.page as ContinuablePage; - result.continuationToken = response.nextPageLink; - yield result; - } -} - -/** - * 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)[nextLinkName]; - - if ( - typeof nextLink !== "string" && - typeof nextLink !== "undefined" && - nextLink !== null - ) { - throw new RestError( - `Body Property ${nextLinkName} should be a string or undefined or null but got ${typeof nextLink}`, - ); - } - - if (nextLink === null) { - return undefined; - } - - return nextLink; -} - -/** - * Gets the elements of the current request in the body. - */ -function getElements(body: unknown, itemName: string): T[] { - const value = (body as Record)[itemName] as T[]; - if (!Array.isArray(value)) { - throw new RestError( - `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, - expectedStatuses: string[], -): void { - if (!expectedStatuses.includes(response.status)) { - throw createRestError( - `Pagination failed with unexpected statusCode ${response.status}`, - response, - ); - } -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/api/pollingHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/api/pollingHelpers.ts deleted file mode 100644 index 47c259e79f..0000000000 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/api/pollingHelpers.ts +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { - PollerLike, - OperationState, - ResourceLocationConfig, - RunningOperation, - createHttpPoller, - OperationResponse, -} from "@azure/core-lro"; - -import { - Client, - PathUncheckedResponse, - createRestError, -} from "@azure-rest/core-client"; -import { AbortSignalLike } from "@azure/abort-controller"; - -export interface GetLongRunningPollerOptions { - /** Delay to wait until next poll, in milliseconds. */ - updateIntervalInMs?: number; - /** - * The signal which can be used to abort requests. - */ - abortSignal?: AbortSignalLike; - /** - * The potential location of the result of the LRO if specified by the LRO extension in the swagger. - */ - resourceLocationConfig?: ResourceLocationConfig; - /** - * The original url of the LRO - * Should not be null when restoreFrom is set - */ - initialRequestUrl?: string; - /** - * A serialized poller which can be used to resume an existing paused Long-Running-Operation. - */ - restoreFrom?: string; - /** - * The function to get the initial response - */ - getInitialResponse?: () => PromiseLike; -} -export function getLongRunningPoller< - TResponse extends PathUncheckedResponse, - TResult = void, ->( - client: Client, - processResponseBody: (result: TResponse) => Promise, - expectedStatuses: string[], - options: GetLongRunningPollerOptions, -): PollerLike, TResult> { - const { restoreFrom, getInitialResponse } = options; - if (!restoreFrom && !getInitialResponse) { - throw new Error( - "Either restoreFrom or getInitialResponse must be specified", - ); - } - let initialResponse: TResponse | undefined = undefined; - const pollAbortController = new AbortController(); - const poller: RunningOperation = { - sendInitialRequest: async () => { - if (!getInitialResponse) { - throw new Error( - "getInitialResponse is required when initializing a new poller", - ); - } - initialResponse = await getInitialResponse(); - return getLroResponse(initialResponse, expectedStatuses); - }, - sendPollRequest: async ( - path: string, - pollOptions?: { - abortSignal?: AbortSignalLike; - }, - ) => { - // The poll request would both listen to the user provided abort signal and the poller's own abort signal - function abortListener(): void { - pollAbortController.abort(); - } - const abortSignal = pollAbortController.signal; - if (options.abortSignal?.aborted) { - pollAbortController.abort(); - } else if (pollOptions?.abortSignal?.aborted) { - pollAbortController.abort(); - } else if (!abortSignal.aborted) { - options.abortSignal?.addEventListener("abort", abortListener, { - once: true, - }); - pollOptions?.abortSignal?.addEventListener("abort", abortListener, { - once: true, - }); - } - let response; - try { - response = await client.pathUnchecked(path).get({ abortSignal }); - } finally { - options.abortSignal?.removeEventListener("abort", abortListener); - pollOptions?.abortSignal?.removeEventListener("abort", abortListener); - } - - return getLroResponse(response as TResponse, expectedStatuses); - }, - }; - return createHttpPoller(poller, { - intervalInMs: options?.updateIntervalInMs, - resourceLocationConfig: options?.resourceLocationConfig, - restoreFrom: options?.restoreFrom, - processResult: (result: unknown) => { - return processResponseBody(result as TResponse); - }, - }); -} -/** - * Converts a Rest Client response to a response that the LRO implementation understands - * @param response - a rest client http response - * @param deserializeFn - deserialize function to convert Rest response to modular output - * @returns - An LRO response that the LRO implementation understands - */ -function getLroResponse( - response: TResponse, - expectedStatuses: string[], -): OperationResponse { - if (!expectedStatuses.includes(response.status)) { - throw createRestError(response); - } - - return { - flatResponse: response, - rawResponse: { - ...response, - statusCode: Number.parseInt(response.status), - body: response.body, - }, - }; -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/api/topLevelTrackedResources/index.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/api/topLevelTrackedResources/index.ts index 8535c488aa..73546c3672 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/api/topLevelTrackedResources/index.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/api/topLevelTrackedResources/index.ts @@ -1,15 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { getLongRunningPoller } from "../pollingHelpers.js"; -import { PollerLike, OperationState } from "@azure/core-lro"; import { topLevelTrackedResourcePropertiesSerializer, TopLevelTrackedResource, _TopLevelTrackedResourceListResult, } from "../../models/models.js"; -import { PagedAsyncIterableIterator } from "../../models/pagingTypes.js"; -import { buildPagedAsyncIterator } from "../pagingHelpers.js"; import { ResourcesContext as Client } from "../index.js"; import { StreamableMethod, @@ -18,6 +14,12 @@ import { createRestError, } from "@azure-rest/core-client"; import { serializeRecord } from "../../helpers/serializerHelpers.js"; +import { + PagedAsyncIterableIterator, + buildPagedAsyncIterator, +} from "../../static-helpers/pagingHelpers.js"; +import { getLongRunningPoller } from "../../static-helpers/pollingHelpers.js"; +import { PollerLike, OperationState } from "@azure/core-lro"; import { TopLevelTrackedResourcesGetOptionalParams, TopLevelTrackedResourcesCreateOrReplaceOptionalParams, diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/classic/nestedProxyResources/index.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/classic/nestedProxyResources/index.ts index b0bad9d496..09899061ec 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/classic/nestedProxyResources/index.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/classic/nestedProxyResources/index.ts @@ -10,7 +10,7 @@ import { nestedProxyResourcesDelete, nestedProxyResourcesListByTopLevelTrackedResource, } from "../../api/nestedProxyResources/index.js"; -import { PagedAsyncIterableIterator } from "../../models/pagingTypes.js"; +import { PagedAsyncIterableIterator } from "../../static-helpers/pagingHelpers.js"; import { PollerLike, OperationState } from "@azure/core-lro"; import { NestedProxyResourcesGetOptionalParams, diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/classic/topLevelTrackedResources/index.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/classic/topLevelTrackedResources/index.ts index 6c476068c9..c25e356075 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/classic/topLevelTrackedResources/index.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/classic/topLevelTrackedResources/index.ts @@ -11,7 +11,7 @@ import { topLevelTrackedResourcesListByResourceGroup, topLevelTrackedResourcesListBySubscription, } from "../../api/topLevelTrackedResources/index.js"; -import { PagedAsyncIterableIterator } from "../../models/pagingTypes.js"; +import { PagedAsyncIterableIterator } from "../../static-helpers/pagingHelpers.js"; import { PollerLike, OperationState } from "@azure/core-lro"; import { TopLevelTrackedResourcesGetOptionalParams, diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/index.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/index.ts index f808222977..09869413d9 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/index.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/index.ts @@ -1,6 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { + PageSettings, + ContinuablePage, + PagedAsyncIterableIterator, +} from "./static-helpers/pagingHelpers.js"; + export { ResourcesClient, ResourcesClientOptionalParams, @@ -35,11 +41,9 @@ export { NestedProxyResourcesUpdateOptionalParams, NestedProxyResourcesDeleteOptionalParams, NestedProxyResourcesListByTopLevelTrackedResourceOptionalParams, - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, } from "./models/index.js"; export { NestedProxyResourcesOperations, TopLevelTrackedResourcesOperations, } from "./classic/index.js"; +export { PageSettings, ContinuablePage, PagedAsyncIterableIterator }; diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/models/index.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/models/index.ts index e6469947af..762f7b8372 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/models/index.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/models/index.ts @@ -33,8 +33,3 @@ export { NestedProxyResourcesDeleteOptionalParams, NestedProxyResourcesListByTopLevelTrackedResourceOptionalParams, } from "./options.js"; -export { - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, -} from "./pagingTypes.js"; diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/models/pagingTypes.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/models/pagingTypes.ts deleted file mode 100644 index f734b48e62..0000000000 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/models/pagingTypes.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -/** - * Options for the byPage method - */ -export interface PageSettings { - /** - * A reference to a specific page to start iterating from. - */ - continuationToken?: string; -} - -/** - * An interface that describes a page of results. - */ -export type ContinuablePage = TPage & { - /** - * 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 extends PageSettings = 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< - TElement, - TPage, - TPageSettings - >; - /** - * Return an AsyncIterableIterator that works a page at a time - */ - byPage: ( - settings?: TPageSettings, - ) => AsyncIterableIterator>; -} - -/** - * An interface that describes how to communicate with the service. - */ -export interface PagedResult< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, -> { - /** - * Link to the first page of results. - */ - firstPageLink?: string; - /** - * A method that returns a page of results. - */ - getPage: ( - pageLink?: string, - ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; -} - -/** - * Options for the paging helper - */ -export interface BuildPagedAsyncIteratorOptions { - itemName?: string; - nextLinkName?: string; -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/restorePollerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/restorePollerHelpers.ts index b0213645cb..97f716e336 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/restorePollerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/restorePollerHelpers.ts @@ -1,14 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { - PollerLike, - OperationState, - deserializeState, - ResourceLocationConfig, -} from "@azure/core-lro"; import { ResourcesClient } from "./resourcesClient.js"; -import { getLongRunningPoller } from "./api/pollingHelpers.js"; import { _topLevelTrackedResourcesCreateOrReplaceDeserialize, _topLevelTrackedResourcesUpdateDeserialize, @@ -19,11 +12,18 @@ import { _nestedProxyResourcesUpdateDeserialize, _nestedProxyResourcesDeleteDeserialize, } from "./api/nestedProxyResources/index.js"; +import { getLongRunningPoller } from "./static-helpers/pollingHelpers.js"; import { - PathUncheckedResponse, OperationOptions, + PathUncheckedResponse, } from "@azure-rest/core-client"; import { AbortSignalLike } from "@azure/abort-controller"; +import { + PollerLike, + OperationState, + deserializeState, + ResourceLocationConfig, +} from "@azure/core-lro"; export interface RestorePollerOptions< TResult, diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/static-helpers/pagingHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/static-helpers/pagingHelpers.ts new file mode 100644 index 0000000000..5855ada5a8 --- /dev/null +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/static-helpers/pagingHelpers.ts @@ -0,0 +1,277 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { + Client, + createRestError, + PathUncheckedResponse, +} from "@azure-rest/core-client"; +import { RestError } from "@azure/core-rest-pipeline"; + +/** + * Options for the byPage method + */ +export interface PageSettings { + /** + * A reference to a specific page to start iterating from. + */ + continuationToken?: string; +} + +/** + * An interface that describes a page of results. + */ +export type ContinuablePage = TPage & { + /** + * 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 extends PageSettings = 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< + TElement, + TPage, + TPageSettings + >; + /** + * Return an AsyncIterableIterator that works a page at a time + */ + byPage: ( + settings?: TPageSettings, + ) => AsyncIterableIterator>; +} + +/** + * An interface that describes how to communicate with the service. + */ +export interface PagedResult< + TElement, + TPage = TElement[], + TPageSettings extends PageSettings = PageSettings, +> { + /** + * Link to the first page of results. + */ + firstPageLink?: string; + /** + * A method that returns a page of results. + */ + getPage: ( + pageLink?: string, + ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; +} + +/** + * Options for the paging helper + */ +export interface BuildPagedAsyncIteratorOptions { + itemName?: string; + nextLinkName?: string; +} + +/** + * Helper to paginate results in a generic way and return a PagedAsyncIterableIterator + */ +export function buildPagedAsyncIterator< + TElement, + TPage = TElement[], + TPageSettings extends PageSettings = PageSettings, + TResponse extends PathUncheckedResponse = PathUncheckedResponse, +>( + client: Client, + getInitialResponse: () => PromiseLike, + processResponseBody: (result: TResponse) => PromiseLike, + expectedStatuses: string[], + options: BuildPagedAsyncIteratorOptions = {}, +): PagedAsyncIterableIterator { + const itemName = options.itemName ?? "value"; + const nextLinkName = options.nextLinkName ?? "nextLink"; + const pagedResult: PagedResult = { + getPage: async (pageLink?: string) => { + const result = + pageLink === undefined + ? await getInitialResponse() + : await client.pathUnchecked(pageLink).get(); + checkPagingRequest(result, expectedStatuses); + const results = await processResponseBody(result as TResponse); + const nextLink = getNextLink(results, nextLinkName); + const values = getElements(results, itemName) as TPage; + return { + page: values, + nextPageLink: nextLink, + }; + }, + byPage: (settings?: TPageSettings) => { + const { continuationToken } = settings ?? {}; + return getPageAsyncIterator(pagedResult, { + pageLink: continuationToken, + }); + }, + }; + return getPagedAsyncIterator(pagedResult); +} + +/** + * 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 extends PageSettings = PageSettings, +>( + pagedResult: PagedResult, +): PagedAsyncIterableIterator { + const iter = getItemAsyncIterator( + pagedResult, + ); + return { + next() { + return iter.next(); + }, + [Symbol.asyncIterator]() { + return this; + }, + byPage: + pagedResult?.byPage ?? + ((settings?: TPageSettings) => { + const { continuationToken } = settings ?? {}; + return getPageAsyncIterator(pagedResult, { + pageLink: continuationToken, + }); + }), + }; +} + +async function* getItemAsyncIterator< + TElement, + TPage, + TPageSettings extends PageSettings, +>( + pagedResult: PagedResult, +): AsyncIterableIterator { + const pages = getPageAsyncIterator(pagedResult); + for await (const page of pages) { + yield* page as unknown as TElement[]; + } +} + +async function* getPageAsyncIterator< + TElement, + TPage, + TPageSettings extends PageSettings, +>( + pagedResult: PagedResult, + options: { + pageLink?: string; + } = {}, +): AsyncIterableIterator> { + const { pageLink } = options; + let response = await pagedResult.getPage( + pageLink ?? pagedResult.firstPageLink, + ); + if (!response) { + return; + } + let result = response.page as ContinuablePage; + result.continuationToken = response.nextPageLink; + yield result; + while (response.nextPageLink) { + response = await pagedResult.getPage(response.nextPageLink); + if (!response) { + return; + } + result = response.page as ContinuablePage; + result.continuationToken = response.nextPageLink; + yield result; + } +} + +/** + * 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)[nextLinkName]; + + if ( + typeof nextLink !== "string" && + typeof nextLink !== "undefined" && + nextLink !== null + ) { + throw new RestError( + `Body Property ${nextLinkName} should be a string or undefined or null but got ${typeof nextLink}`, + ); + } + + if (nextLink === null) { + return undefined; + } + + return nextLink; +} + +/** + * Gets the elements of the current request in the body. + */ +function getElements(body: unknown, itemName: string): T[] { + const value = (body as Record)[itemName] as T[]; + if (!Array.isArray(value)) { + throw new RestError( + `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, + expectedStatuses: string[], +): void { + if (!expectedStatuses.includes(response.status)) { + throw createRestError( + `Pagination failed with unexpected statusCode ${response.status}`, + response, + ); + } +} diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/static-helpers/pollingHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/static-helpers/pollingHelpers.ts new file mode 100644 index 0000000000..e130b18d89 --- /dev/null +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/resource-manager/models/resources/src/static-helpers/pollingHelpers.ts @@ -0,0 +1,140 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { + PollerLike, + OperationState, + ResourceLocationConfig, + RunningOperation, + createHttpPoller, + OperationResponse, +} from "@azure/core-lro"; + +import { + Client, + PathUncheckedResponse, + createRestError, +} from "@azure-rest/core-client"; +import { AbortSignalLike } from "@azure/abort-controller"; + +export interface GetLongRunningPollerOptions { + /** Delay to wait until next poll, in milliseconds. */ + updateIntervalInMs?: number; + /** + * The signal which can be used to abort requests. + */ + abortSignal?: AbortSignalLike; + /** + * The potential location of the result of the LRO if specified by the LRO extension in the swagger. + */ + resourceLocationConfig?: ResourceLocationConfig; + /** + * The original url of the LRO + * Should not be null when restoreFrom is set + */ + initialRequestUrl?: string; + /** + * A serialized poller which can be used to resume an existing paused Long-Running-Operation. + */ + restoreFrom?: string; + /** + * The function to get the initial response + */ + getInitialResponse?: () => PromiseLike; +} +export function getLongRunningPoller< + TResponse extends PathUncheckedResponse, + TResult = void, +>( + client: Client, + processResponseBody: (result: TResponse) => Promise, + expectedStatuses: string[], + options: GetLongRunningPollerOptions, +): PollerLike, TResult> { + const { restoreFrom, getInitialResponse } = options; + if (!restoreFrom && !getInitialResponse) { + throw new Error( + "Either restoreFrom or getInitialResponse must be specified", + ); + } + let initialResponse: TResponse | undefined = undefined; + const pollAbortController = new AbortController(); + const poller: RunningOperation = { + sendInitialRequest: async () => { + if (!getInitialResponse) { + throw new Error( + "getInitialResponse is required when initializing a new poller", + ); + } + initialResponse = await getInitialResponse(); + return getLroResponse(initialResponse, expectedStatuses); + }, + sendPollRequest: async ( + path: string, + pollOptions?: { + abortSignal?: AbortSignalLike; + }, + ) => { + // The poll request would both listen to the user provided abort signal and the poller's own abort signal + function abortListener(): void { + pollAbortController.abort(); + } + const abortSignal = pollAbortController.signal; + if (options.abortSignal?.aborted) { + pollAbortController.abort(); + } else if (pollOptions?.abortSignal?.aborted) { + pollAbortController.abort(); + } else if (!abortSignal.aborted) { + options.abortSignal?.addEventListener("abort", abortListener, { + once: true, + }); + pollOptions?.abortSignal?.addEventListener("abort", abortListener, { + once: true, + }); + } + let response; + try { + response = await client.pathUnchecked(path).get({ abortSignal }); + } finally { + options.abortSignal?.removeEventListener("abort", abortListener); + pollOptions?.abortSignal?.removeEventListener("abort", abortListener); + } + + return getLroResponse(response as TResponse, expectedStatuses); + }, + }; + return createHttpPoller(poller, { + intervalInMs: options?.updateIntervalInMs, + resourceLocationConfig: options?.resourceLocationConfig, + restoreFrom: options?.restoreFrom, + processResult: (result: unknown) => { + return processResponseBody(result as TResponse); + }, + }); +} +/** + * Converts a Rest Client response to a response that the LRO implementation understands + * @param response - a rest client http response + * @param deserializeFn - deserialize function to convert Rest response to modular output + * @returns - An LRO response that the LRO implementation understands + */ +function getLroResponse( + response: TResponse, + expectedStatuses: string[], +): OperationResponse { + if (!expectedStatuses.includes(response.status)) { + throw createRestError(response); + } + + return { + flatResponse: response, + rawResponse: { + ...response, + statusCode: Number.parseInt(response.status), + body: response.body, + }, + }; +} diff --git a/packages/typespec-ts/test/modularIntegration/generated/azure/special-headers/client-request-id/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/azure/special-headers/client-request-id/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/azure/special-headers/client-request-id/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/azure/special-headers/client-request-id/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/client/naming/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/client/naming/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/client/naming/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/client/naming/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/client/structure/default/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/client/structure/default/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/client/structure/default/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/client/structure/default/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/client/structure/multi-client/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/client/structure/multi-client/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/client/structure/multi-client/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/client/structure/multi-client/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/client/structure/renamed-operation/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/client/structure/renamed-operation/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/client/structure/renamed-operation/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/client/structure/renamed-operation/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/client/structure/two-operation-group/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/client/structure/two-operation-group/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/client/structure/two-operation-group/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/client/structure/two-operation-group/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/encode/bytes/src/api/header/index.ts b/packages/typespec-ts/test/modularIntegration/generated/encode/bytes/src/api/header/index.ts index ce0ab7a336..1dae62602f 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/encode/bytes/src/api/header/index.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/encode/bytes/src/api/header/index.ts @@ -9,7 +9,7 @@ import { createRestError, } from "@azure-rest/core-client"; import { uint8ArrayToString } from "@azure/core-util"; -import { buildCsvCollection } from "../../helpers/serializerHelpers.js"; +import { buildCsvCollection } from "../../static-helpers/serialization/build-csv-collection.js"; import { HeaderDefaultOptionalParams, HeaderBase64OptionalParams, diff --git a/packages/typespec-ts/test/modularIntegration/generated/encode/bytes/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/encode/bytes/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/encode/bytes/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/encode/bytes/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/encode/bytes/src/static-helpers/serialization/build-csv-collection.ts b/packages/typespec-ts/test/modularIntegration/generated/encode/bytes/src/static-helpers/serialization/build-csv-collection.ts new file mode 100644 index 0000000000..d3b752635d --- /dev/null +++ b/packages/typespec-ts/test/modularIntegration/generated/encode/bytes/src/static-helpers/serialization/build-csv-collection.ts @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +export function buildCsvCollection(items: string[] | number[]): string { + return items.join(","); +} diff --git a/packages/typespec-ts/test/modularIntegration/generated/encode/datetime/src/api/header/index.ts b/packages/typespec-ts/test/modularIntegration/generated/encode/datetime/src/api/header/index.ts index 2aaec8fcb5..81996235fc 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/encode/datetime/src/api/header/index.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/encode/datetime/src/api/header/index.ts @@ -8,7 +8,7 @@ import { PathUncheckedResponse, createRestError, } from "@azure-rest/core-client"; -import { buildCsvCollection } from "../../helpers/serializerHelpers.js"; +import { buildCsvCollection } from "../../static-helpers/serialization/build-csv-collection.js"; import { HeaderDefaultOptionalParams, HeaderRfc3339OptionalParams, diff --git a/packages/typespec-ts/test/modularIntegration/generated/encode/datetime/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/encode/datetime/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/encode/datetime/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/encode/datetime/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/encode/datetime/src/static-helpers/serialization/build-csv-collection.ts b/packages/typespec-ts/test/modularIntegration/generated/encode/datetime/src/static-helpers/serialization/build-csv-collection.ts new file mode 100644 index 0000000000..d3b752635d --- /dev/null +++ b/packages/typespec-ts/test/modularIntegration/generated/encode/datetime/src/static-helpers/serialization/build-csv-collection.ts @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +export function buildCsvCollection(items: string[] | number[]): string { + return items.join(","); +} diff --git a/packages/typespec-ts/test/modularIntegration/generated/encode/duration/src/api/header/index.ts b/packages/typespec-ts/test/modularIntegration/generated/encode/duration/src/api/header/index.ts index db9eae42f1..bf8eb1e8da 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/encode/duration/src/api/header/index.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/encode/duration/src/api/header/index.ts @@ -8,7 +8,7 @@ import { PathUncheckedResponse, createRestError, } from "@azure-rest/core-client"; -import { buildCsvCollection } from "../../helpers/serializerHelpers.js"; +import { buildCsvCollection } from "../../static-helpers/serialization/build-csv-collection.js"; import { HeaderDefaultOptionalParams, HeaderIso8601OptionalParams, diff --git a/packages/typespec-ts/test/modularIntegration/generated/encode/duration/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/encode/duration/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/encode/duration/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/encode/duration/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/encode/duration/src/static-helpers/serialization/build-csv-collection.ts b/packages/typespec-ts/test/modularIntegration/generated/encode/duration/src/static-helpers/serialization/build-csv-collection.ts new file mode 100644 index 0000000000..d3b752635d --- /dev/null +++ b/packages/typespec-ts/test/modularIntegration/generated/encode/duration/src/static-helpers/serialization/build-csv-collection.ts @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +export function buildCsvCollection(items: string[] | number[]): string { + return items.join(","); +} diff --git a/packages/typespec-ts/test/modularIntegration/generated/parameters/basic/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/parameters/basic/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/parameters/basic/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/parameters/basic/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/parameters/body-optionality/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/parameters/body-optionality/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/parameters/body-optionality/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/parameters/body-optionality/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/api/header/index.ts b/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/api/header/index.ts index 417348618c..c7345ec480 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/api/header/index.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/api/header/index.ts @@ -8,7 +8,7 @@ import { PathUncheckedResponse, createRestError, } from "@azure-rest/core-client"; -import { buildCsvCollection } from "../../helpers/serializerHelpers.js"; +import { buildCsvCollection } from "../../static-helpers/serialization/build-csv-collection.js"; import { HeaderCsvOptionalParams } from "../../models/options.js"; export function _headerCsvSend( diff --git a/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/api/query/index.ts b/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/api/query/index.ts index 54c01e17b6..1e70cd8b4b 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/api/query/index.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/api/query/index.ts @@ -8,12 +8,10 @@ import { PathUncheckedResponse, createRestError, } from "@azure-rest/core-client"; -import { - buildMultiCollection, - buildPipeCollection, - buildTsvCollection, - buildSsvCollection, -} from "../../helpers/serializerHelpers.js"; +import { buildMultiCollection } from "../../static-helpers/serialization/build-multi-collection.js"; +import { buildPipeCollection } from "../../static-helpers/serialization/build-pipe-collection.js"; +import { buildSsvCollection } from "../../static-helpers/serialization/build-ssv-collection.js"; +import { buildTsvCollection } from "../../static-helpers/serialization/build-tsv-collection.js"; import { QueryMultiOptionalParams, QuerySsvOptionalParams, diff --git a/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/static-helpers/serialization/build-csv-collection.ts b/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/static-helpers/serialization/build-csv-collection.ts new file mode 100644 index 0000000000..d3b752635d --- /dev/null +++ b/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/static-helpers/serialization/build-csv-collection.ts @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +export function buildCsvCollection(items: string[] | number[]): string { + return items.join(","); +} diff --git a/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/static-helpers/serialization/build-multi-collection.ts b/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/static-helpers/serialization/build-multi-collection.ts new file mode 100644 index 0000000000..951b4a54e9 --- /dev/null +++ b/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/static-helpers/serialization/build-multi-collection.ts @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +export function buildMultiCollection( + items: string[], + parameterName: string, +): string { + return items + .map((item, index) => { + if (index === 0) { + return item; + } + return `${parameterName}=${item}`; + }) + .join("&"); +} diff --git a/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/static-helpers/serialization/build-pipe-collection.ts b/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/static-helpers/serialization/build-pipe-collection.ts new file mode 100644 index 0000000000..4a203b121d --- /dev/null +++ b/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/static-helpers/serialization/build-pipe-collection.ts @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +export function buildPipeCollection(items: string[] | number[]): string { + return items.join("|"); +} diff --git a/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/static-helpers/serialization/build-ssv-collection.ts b/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/static-helpers/serialization/build-ssv-collection.ts new file mode 100644 index 0000000000..945007fa71 --- /dev/null +++ b/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/static-helpers/serialization/build-ssv-collection.ts @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +export function buildSsvCollection(items: string[] | number[]): string { + return items.join(" "); +} diff --git a/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/static-helpers/serialization/build-tsv-collection.ts b/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/static-helpers/serialization/build-tsv-collection.ts new file mode 100644 index 0000000000..17f33c0ddc --- /dev/null +++ b/packages/typespec-ts/test/modularIntegration/generated/parameters/collection-format/src/static-helpers/serialization/build-tsv-collection.ts @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +export function buildTsvCollection(items: string[] | number[]): string { + return items.join("\t"); +} diff --git a/packages/typespec-ts/test/modularIntegration/generated/parameters/spread/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/parameters/spread/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/parameters/spread/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/parameters/spread/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/payload/content-negotiation/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/payload/content-negotiation/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/payload/content-negotiation/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/payload/content-negotiation/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/payload/media-type/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/payload/media-type/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/payload/media-type/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/payload/media-type/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/api/operations.ts b/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/api/operations.ts index 42f3d68ebb..8dbdb33caa 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/api/operations.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/api/operations.ts @@ -2,8 +2,6 @@ // Licensed under the MIT license. import { _PagedUser, User } from "../models/models.js"; -import { PagedAsyncIterableIterator } from "../models/pagingTypes.js"; -import { buildPagedAsyncIterator } from "./pagingHelpers.js"; import { PageableContext as Client } from "./index.js"; import { StreamableMethod, @@ -11,6 +9,10 @@ import { PathUncheckedResponse, createRestError, } from "@azure-rest/core-client"; +import { + PagedAsyncIterableIterator, + buildPagedAsyncIterator, +} from "../static-helpers/pagingHelpers.js"; import { ListOptionalParams } from "../models/options.js"; export function _listSend( diff --git a/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/api/pagingHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/api/pagingHelpers.ts deleted file mode 100644 index 0fc574c83c..0000000000 --- a/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/api/pagingHelpers.ts +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { - Client, - createRestError, - PathUncheckedResponse, -} from "@azure-rest/core-client"; -import { RestError } from "@azure/core-rest-pipeline"; -import { - BuildPagedAsyncIteratorOptions, - ContinuablePage, - PageSettings, - PagedAsyncIterableIterator, - PagedResult, -} from "../models/pagingTypes.js"; - -/** - * Helper to paginate results in a generic way and return a PagedAsyncIterableIterator - */ -export function buildPagedAsyncIterator< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, - TResponse extends PathUncheckedResponse = PathUncheckedResponse, ->( - client: Client, - getInitialResponse: () => PromiseLike, - processResponseBody: (result: TResponse) => PromiseLike, - expectedStatuses: string[], - options: BuildPagedAsyncIteratorOptions = {}, -): PagedAsyncIterableIterator { - const itemName = options.itemName ?? "value"; - const nextLinkName = options.nextLinkName ?? "nextLink"; - const pagedResult: PagedResult = { - getPage: async (pageLink?: string) => { - const result = - pageLink === undefined - ? await getInitialResponse() - : await client.pathUnchecked(pageLink).get(); - checkPagingRequest(result, expectedStatuses); - const results = await processResponseBody(result as TResponse); - const nextLink = getNextLink(results, nextLinkName); - const values = getElements(results, itemName) as TPage; - return { - page: values, - nextPageLink: nextLink, - }; - }, - byPage: (settings?: TPageSettings) => { - const { continuationToken } = settings ?? {}; - return getPageAsyncIterator(pagedResult, { - pageLink: continuationToken, - }); - }, - }; - return getPagedAsyncIterator(pagedResult); -} - -/** - * 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 extends PageSettings = PageSettings, ->( - pagedResult: PagedResult, -): PagedAsyncIterableIterator { - const iter = getItemAsyncIterator( - pagedResult, - ); - return { - next() { - return iter.next(); - }, - [Symbol.asyncIterator]() { - return this; - }, - byPage: - pagedResult?.byPage ?? - ((settings?: TPageSettings) => { - const { continuationToken } = settings ?? {}; - return getPageAsyncIterator(pagedResult, { - pageLink: continuationToken, - }); - }), - }; -} - -async function* getItemAsyncIterator< - TElement, - TPage, - TPageSettings extends PageSettings, ->( - pagedResult: PagedResult, -): AsyncIterableIterator { - const pages = getPageAsyncIterator(pagedResult); - for await (const page of pages) { - yield* page as unknown as TElement[]; - } -} - -async function* getPageAsyncIterator< - TElement, - TPage, - TPageSettings extends PageSettings, ->( - pagedResult: PagedResult, - options: { - pageLink?: string; - } = {}, -): AsyncIterableIterator> { - const { pageLink } = options; - let response = await pagedResult.getPage( - pageLink ?? pagedResult.firstPageLink, - ); - if (!response) { - return; - } - let result = response.page as ContinuablePage; - result.continuationToken = response.nextPageLink; - yield result; - while (response.nextPageLink) { - response = await pagedResult.getPage(response.nextPageLink); - if (!response) { - return; - } - result = response.page as ContinuablePage; - result.continuationToken = response.nextPageLink; - yield result; - } -} - -/** - * 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)[nextLinkName]; - - if ( - typeof nextLink !== "string" && - typeof nextLink !== "undefined" && - nextLink !== null - ) { - throw new RestError( - `Body Property ${nextLinkName} should be a string or undefined or null but got ${typeof nextLink}`, - ); - } - - if (nextLink === null) { - return undefined; - } - - return nextLink; -} - -/** - * Gets the elements of the current request in the body. - */ -function getElements(body: unknown, itemName: string): T[] { - const value = (body as Record)[itemName] as T[]; - if (!Array.isArray(value)) { - throw new RestError( - `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, - expectedStatuses: string[], -): void { - if (!expectedStatuses.includes(response.status)) { - throw createRestError( - `Pagination failed with unexpected statusCode ${response.status}`, - response, - ); - } -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/index.ts b/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/index.ts index 6c2d8402a2..cfdd203110 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/index.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/index.ts @@ -1,14 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { + PageSettings, + ContinuablePage, + PagedAsyncIterableIterator, +} from "./static-helpers/pagingHelpers.js"; + export { PageableClient, PageableClientOptionalParams, } from "./pageableClient.js"; -export { - User, - ListOptionalParams, - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, -} from "./models/index.js"; +export { User, ListOptionalParams } from "./models/index.js"; +export { PageSettings, ContinuablePage, PagedAsyncIterableIterator }; diff --git a/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/models/index.ts b/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/models/index.ts index 9cbfa6c472..9acd55b6e3 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/models/index.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/models/index.ts @@ -3,8 +3,3 @@ export { User } from "./models.js"; export { ListOptionalParams } from "./options.js"; -export { - PageSettings, - ContinuablePage, - PagedAsyncIterableIterator, -} from "./pagingTypes.js"; diff --git a/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/models/pagingTypes.ts b/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/models/pagingTypes.ts deleted file mode 100644 index f734b48e62..0000000000 --- a/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/models/pagingTypes.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -/** - * Options for the byPage method - */ -export interface PageSettings { - /** - * A reference to a specific page to start iterating from. - */ - continuationToken?: string; -} - -/** - * An interface that describes a page of results. - */ -export type ContinuablePage = TPage & { - /** - * 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 extends PageSettings = 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< - TElement, - TPage, - TPageSettings - >; - /** - * Return an AsyncIterableIterator that works a page at a time - */ - byPage: ( - settings?: TPageSettings, - ) => AsyncIterableIterator>; -} - -/** - * An interface that describes how to communicate with the service. - */ -export interface PagedResult< - TElement, - TPage = TElement[], - TPageSettings extends PageSettings = PageSettings, -> { - /** - * Link to the first page of results. - */ - firstPageLink?: string; - /** - * A method that returns a page of results. - */ - getPage: ( - pageLink?: string, - ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; -} - -/** - * Options for the paging helper - */ -export interface BuildPagedAsyncIteratorOptions { - itemName?: string; - nextLinkName?: string; -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/pageableClient.ts b/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/pageableClient.ts index dd74a1d696..853cff07fa 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/pageableClient.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/pageableClient.ts @@ -4,13 +4,13 @@ import { Pipeline } from "@azure/core-rest-pipeline"; import { User } from "./models/models.js"; import { ListOptionalParams } from "./models/options.js"; -import { PagedAsyncIterableIterator } from "./models/pagingTypes.js"; import { list, createPageable, PageableContext, PageableClientOptionalParams, } from "./api/index.js"; +import { PagedAsyncIterableIterator } from "./static-helpers/pagingHelpers.js"; export { PageableClientOptionalParams } from "./api/pageableContext.js"; diff --git a/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/static-helpers/pagingHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/static-helpers/pagingHelpers.ts new file mode 100644 index 0000000000..5855ada5a8 --- /dev/null +++ b/packages/typespec-ts/test/modularIntegration/generated/payload/pageable/src/static-helpers/pagingHelpers.ts @@ -0,0 +1,277 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { + Client, + createRestError, + PathUncheckedResponse, +} from "@azure-rest/core-client"; +import { RestError } from "@azure/core-rest-pipeline"; + +/** + * Options for the byPage method + */ +export interface PageSettings { + /** + * A reference to a specific page to start iterating from. + */ + continuationToken?: string; +} + +/** + * An interface that describes a page of results. + */ +export type ContinuablePage = TPage & { + /** + * 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 extends PageSettings = 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< + TElement, + TPage, + TPageSettings + >; + /** + * Return an AsyncIterableIterator that works a page at a time + */ + byPage: ( + settings?: TPageSettings, + ) => AsyncIterableIterator>; +} + +/** + * An interface that describes how to communicate with the service. + */ +export interface PagedResult< + TElement, + TPage = TElement[], + TPageSettings extends PageSettings = PageSettings, +> { + /** + * Link to the first page of results. + */ + firstPageLink?: string; + /** + * A method that returns a page of results. + */ + getPage: ( + pageLink?: string, + ) => Promise<{ page: TPage; nextPageLink?: string } | 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) => TElement[]; +} + +/** + * Options for the paging helper + */ +export interface BuildPagedAsyncIteratorOptions { + itemName?: string; + nextLinkName?: string; +} + +/** + * Helper to paginate results in a generic way and return a PagedAsyncIterableIterator + */ +export function buildPagedAsyncIterator< + TElement, + TPage = TElement[], + TPageSettings extends PageSettings = PageSettings, + TResponse extends PathUncheckedResponse = PathUncheckedResponse, +>( + client: Client, + getInitialResponse: () => PromiseLike, + processResponseBody: (result: TResponse) => PromiseLike, + expectedStatuses: string[], + options: BuildPagedAsyncIteratorOptions = {}, +): PagedAsyncIterableIterator { + const itemName = options.itemName ?? "value"; + const nextLinkName = options.nextLinkName ?? "nextLink"; + const pagedResult: PagedResult = { + getPage: async (pageLink?: string) => { + const result = + pageLink === undefined + ? await getInitialResponse() + : await client.pathUnchecked(pageLink).get(); + checkPagingRequest(result, expectedStatuses); + const results = await processResponseBody(result as TResponse); + const nextLink = getNextLink(results, nextLinkName); + const values = getElements(results, itemName) as TPage; + return { + page: values, + nextPageLink: nextLink, + }; + }, + byPage: (settings?: TPageSettings) => { + const { continuationToken } = settings ?? {}; + return getPageAsyncIterator(pagedResult, { + pageLink: continuationToken, + }); + }, + }; + return getPagedAsyncIterator(pagedResult); +} + +/** + * 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 extends PageSettings = PageSettings, +>( + pagedResult: PagedResult, +): PagedAsyncIterableIterator { + const iter = getItemAsyncIterator( + pagedResult, + ); + return { + next() { + return iter.next(); + }, + [Symbol.asyncIterator]() { + return this; + }, + byPage: + pagedResult?.byPage ?? + ((settings?: TPageSettings) => { + const { continuationToken } = settings ?? {}; + return getPageAsyncIterator(pagedResult, { + pageLink: continuationToken, + }); + }), + }; +} + +async function* getItemAsyncIterator< + TElement, + TPage, + TPageSettings extends PageSettings, +>( + pagedResult: PagedResult, +): AsyncIterableIterator { + const pages = getPageAsyncIterator(pagedResult); + for await (const page of pages) { + yield* page as unknown as TElement[]; + } +} + +async function* getPageAsyncIterator< + TElement, + TPage, + TPageSettings extends PageSettings, +>( + pagedResult: PagedResult, + options: { + pageLink?: string; + } = {}, +): AsyncIterableIterator> { + const { pageLink } = options; + let response = await pagedResult.getPage( + pageLink ?? pagedResult.firstPageLink, + ); + if (!response) { + return; + } + let result = response.page as ContinuablePage; + result.continuationToken = response.nextPageLink; + yield result; + while (response.nextPageLink) { + response = await pagedResult.getPage(response.nextPageLink); + if (!response) { + return; + } + result = response.page as ContinuablePage; + result.continuationToken = response.nextPageLink; + yield result; + } +} + +/** + * 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)[nextLinkName]; + + if ( + typeof nextLink !== "string" && + typeof nextLink !== "undefined" && + nextLink !== null + ) { + throw new RestError( + `Body Property ${nextLinkName} should be a string or undefined or null but got ${typeof nextLink}`, + ); + } + + if (nextLink === null) { + return undefined; + } + + return nextLink; +} + +/** + * Gets the elements of the current request in the body. + */ +function getElements(body: unknown, itemName: string): T[] { + const value = (body as Record)[itemName] as T[]; + if (!Array.isArray(value)) { + throw new RestError( + `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, + expectedStatuses: string[], +): void { + if (!expectedStatuses.includes(response.status)) { + throw createRestError( + `Pagination failed with unexpected statusCode ${response.status}`, + response, + ); + } +} diff --git a/packages/typespec-ts/test/modularIntegration/generated/resiliency/srv-driven-main/generated/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/resiliency/srv-driven-main/generated/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/resiliency/srv-driven-main/generated/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/resiliency/srv-driven-main/generated/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/resiliency/srv-driven-old/generated/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/resiliency/srv-driven-old/generated/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/resiliency/srv-driven-old/generated/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/resiliency/srv-driven-old/generated/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/serialization/encoded-name/json/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/serialization/encoded-name/json/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/serialization/encoded-name/json/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/serialization/encoded-name/json/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/server/endpoint/not-defined/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/server/endpoint/not-defined/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/server/endpoint/not-defined/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/server/endpoint/not-defined/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/server/path/multiple/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/server/path/multiple/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/server/path/multiple/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/server/path/multiple/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/server/path/single/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/server/path/single/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/server/path/single/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/server/path/single/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/server/versions/not-versioned/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/server/versions/not-versioned/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/server/versions/not-versioned/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/server/versions/not-versioned/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/server/versions/versioned/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/server/versions/versioned/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/server/versions/versioned/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/server/versions/versioned/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/special-headers/repeatability/generated/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/special-headers/repeatability/generated/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/special-headers/repeatability/generated/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/special-headers/repeatability/generated/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/special-words/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/special-words/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/special-words/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/special-words/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/type/array/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/type/array/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/type/array/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/type/array/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/type/dictionary/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/type/dictionary/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/type/dictionary/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/type/dictionary/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/type/enum/extensible/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/type/enum/extensible/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/type/enum/extensible/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/type/enum/extensible/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/type/enum/fixed/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/type/enum/fixed/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/type/enum/fixed/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/type/enum/fixed/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/type/model/empty/generated/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/type/model/empty/generated/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/type/model/empty/generated/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/type/model/empty/generated/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/type/model/flatten/generated/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/type/model/flatten/generated/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/type/model/flatten/generated/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/type/model/flatten/generated/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/type/model/inheritance/enum-discriminator/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/type/model/inheritance/enum-discriminator/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/type/model/inheritance/enum-discriminator/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/type/model/inheritance/enum-discriminator/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/type/model/inheritance/nested-discriminator/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/type/model/inheritance/nested-discriminator/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/type/model/inheritance/nested-discriminator/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/type/model/inheritance/nested-discriminator/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/type/model/inheritance/not-discriminated/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/type/model/inheritance/not-discriminated/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/type/model/inheritance/not-discriminated/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/type/model/inheritance/not-discriminated/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/type/model/inheritance/recursive/generated/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/type/model/inheritance/recursive/generated/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/type/model/inheritance/recursive/generated/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/type/model/inheritance/recursive/generated/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/type/model/inheritance/single-discriminator/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/type/model/inheritance/single-discriminator/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/type/model/inheritance/single-discriminator/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/type/model/inheritance/single-discriminator/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/type/model/usage/generated/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/type/model/usage/generated/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/type/model/usage/generated/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/type/model/usage/generated/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/type/property/additional-properties/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/type/property/additional-properties/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/type/property/additional-properties/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/type/property/additional-properties/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/type/property/nullable/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/type/property/nullable/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/type/property/nullable/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/type/property/nullable/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/type/property/optionality/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/type/property/optionality/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/type/property/optionality/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/type/property/optionality/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/type/property/value-types/generated/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/type/property/value-types/generated/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/type/property/value-types/generated/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/type/property/value-types/generated/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/type/scalar/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/type/scalar/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/type/scalar/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/type/scalar/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/type/union/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/type/union/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/type/union/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/type/union/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/versioning/added/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/versioning/added/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/versioning/added/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/versioning/added/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/versioning/madeOptional/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/versioning/madeOptional/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/versioning/madeOptional/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/versioning/madeOptional/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/versioning/removed/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/versioning/removed/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/versioning/removed/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/versioning/removed/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/versioning/renamedFrom/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/versioning/renamedFrom/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/versioning/renamedFrom/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/versioning/renamedFrom/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/versioning/returnTypeChangedFrom/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/versioning/returnTypeChangedFrom/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/versioning/returnTypeChangedFrom/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/versioning/returnTypeChangedFrom/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/generated/versioning/typeChangedFrom/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/modularIntegration/generated/versioning/typeChangedFrom/src/helpers/serializerHelpers.ts index b2f2334fea..332381cdb6 100644 --- a/packages/typespec-ts/test/modularIntegration/generated/versioning/typeChangedFrom/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/modularIntegration/generated/versioning/typeChangedFrom/src/helpers/serializerHelpers.ts @@ -38,33 +38,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/modularIntegration/lroStardard.spec.ts b/packages/typespec-ts/test/modularIntegration/lroStardard.spec.ts index 5563e2204a..bc8bdaa0ed 100644 --- a/packages/typespec-ts/test/modularIntegration/lroStardard.spec.ts +++ b/packages/typespec-ts/test/modularIntegration/lroStardard.spec.ts @@ -2,7 +2,7 @@ import { StandardClient, User } from "./generated/azure/core/lro/standard/generated/src/index.js"; -import { assert } from "chai"; +import { assert, expect } from "chai"; import { restorePoller } from "./generated/azure/core/lro/standard/generated/src/restorePollerHelpers.js"; import { OperationState } from "@azure/core-lro"; @@ -148,9 +148,9 @@ describe("LROStandardClient Classical Client", () => { await poller.submitted(); assert.fail("Expected an exception"); } catch (err: any) { - assert.strictEqual( - err.message, - "Body provided doesn't match expected body" + expect(err.message).to.match( + /Body provided doesn't match expected body/i, + `Expected ${err.message} to match /Body provided doesn't match expected body/i` ); } }); @@ -164,9 +164,9 @@ describe("LROStandardClient Classical Client", () => { await poller.poll(); assert.fail("Expected an exception"); } catch (err: any) { - assert.strictEqual( - err.message, - "Body provided doesn't match expected body" + expect(err.message).to.match( + /Body provided doesn't match expected body/i, + `Expected ${err.message} to match /Body provided doesn't match expected body/i` ); } }); @@ -180,9 +180,9 @@ describe("LROStandardClient Classical Client", () => { await poller.pollUntilDone(); assert.fail("Expected an exception"); } catch (err: any) { - assert.strictEqual( - err.message, - "Body provided doesn't match expected body" + expect(err.message).to.match( + /Body provided doesn't match expected body/i, + `Expected ${err.message} to match /Body provided doesn't match expected body/i` ); } }); @@ -194,9 +194,9 @@ describe("LROStandardClient Classical Client", () => { } as any); assert.fail("Expected an exception"); } catch (err: any) { - assert.strictEqual( - err.message, - "Body provided doesn't match expected body" + expect(err.message).to.match( + /Body provided doesn't match expected body/i, + `Expected ${err.message} to match /Body provided doesn't match expected body/i` ); } }); diff --git a/packages/typespec-ts/test/modularUnit/anonymousModel.spec.ts b/packages/typespec-ts/test/modularUnit/anonymousModel.spec.ts index 12bcea59f4..346fe0a845 100644 --- a/packages/typespec-ts/test/modularUnit/anonymousModel.spec.ts +++ b/packages/typespec-ts/test/modularUnit/anonymousModel.spec.ts @@ -993,7 +993,7 @@ describe("anonymous model", () => { emptyAnomyousArray: result.body["emptyAnomyousArray"], emptyAnomyousDict: result.body["emptyAnomyousDict"], emptyModel: {}, - emptyModelArray: result.body["emptyModelArray"].map(() => { + emptyModelArray: result.body["emptyModelArray"].map((p: any) => { return {}; }), emptyModelDict: result.body["emptyModelDict"], diff --git a/packages/typespec-ts/test/modularUnit/apiOperations.spec.ts b/packages/typespec-ts/test/modularUnit/apiOperations.spec.ts index 33b9049796..d851248c46 100644 --- a/packages/typespec-ts/test/modularUnit/apiOperations.spec.ts +++ b/packages/typespec-ts/test/modularUnit/apiOperations.spec.ts @@ -583,6 +583,7 @@ describe("api operations in Modular", () => { await assertEqualContent( classicClient?.getFullText()!, ` + import { TokenCredential, KeyCredential } from "@azure/core-auth"; import { Pipeline } from "@azure/core-rest-pipeline"; export { TestingClientOptionalParams } from "./api/testingContext.js"; @@ -741,6 +742,7 @@ describe("api operations in Modular", () => { await assertEqualContent( classicClient?.getFullText()!, ` + import { TokenCredential, KeyCredential } from "@azure/core-auth"; import { Pipeline } from "@azure/core-rest-pipeline"; export { TestingClientOptionalParams } from "./api/testingContext.js"; @@ -895,6 +897,7 @@ describe("api operations in Modular", () => { await assertEqualContent( classicClient?.getFullText()!, ` + import { TokenCredential, KeyCredential } from "@azure/core-auth"; import { Pipeline } from "@azure/core-rest-pipeline"; export { TestingClientOptionalParams } from "./api/testingContext.js"; diff --git a/packages/typespec-ts/test/modularUnit/operations.spec.ts b/packages/typespec-ts/test/modularUnit/operations.spec.ts index cf4828408c..eb42af55a0 100644 --- a/packages/typespec-ts/test/modularUnit/operations.spec.ts +++ b/packages/typespec-ts/test/modularUnit/operations.spec.ts @@ -131,7 +131,7 @@ describe("operations", () => { import { TestingContext as Client } from "./index.js"; import { StreamableMethod, operationOptionsToRequestParameters, PathUncheckedResponse, createRestError } from "@azure-rest/core-client"; import { uint8ArrayToString } from "@azure/core-util"; - + import { buildCsvCollection } from "../static-helpers/serialization/build-csv-collection.js"; export function _readSend( context: Client, requiredHeader: string, @@ -626,6 +626,7 @@ describe("operations", () => { true, true ); + assert.ok(operationFiles); assert.equal(operationFiles?.length, 1); @@ -634,6 +635,10 @@ describe("operations", () => { ` import { TestingContext as Client } from "./index.js"; import { StreamableMethod, operationOptionsToRequestParameters, PathUncheckedResponse, createRestError } from "@azure-rest/core-client"; + import { + PagedAsyncIterableIterator, + buildPagedAsyncIterator, + } from "../static-helpers/pagingHelpers.js"; export function _testSend(context: Client, options: TestOptionalParams = { requestOptions: {} }): StreamableMethod { return context.path("/", ).post({...operationOptionsToRequestParameters(options), }) ; diff --git a/packages/typespec-ts/test/nonBrandedIntegration/modular/generated/models/usage/src/helpers/serializerHelpers.ts b/packages/typespec-ts/test/nonBrandedIntegration/modular/generated/models/usage/src/helpers/serializerHelpers.ts index 4608af90fd..9c73b7fc55 100644 --- a/packages/typespec-ts/test/nonBrandedIntegration/modular/generated/models/usage/src/helpers/serializerHelpers.ts +++ b/packages/typespec-ts/test/nonBrandedIntegration/modular/generated/models/usage/src/helpers/serializerHelpers.ts @@ -37,33 +37,3 @@ function isSupportedRecordType(t: any) { t instanceof Date ); } - -export function buildMultiCollection( - items: string[], - parameterName: string, -): string { - return items - .map((item, index) => { - if (index === 0) { - return item; - } - return `${parameterName}=${item}`; - }) - .join("&"); -} - -export function buildPipeCollection(items: string[] | number[]): string { - return items.join("|"); -} - -export function buildSsvCollection(items: string[] | number[]): string { - return items.join(" "); -} - -export function buildTsvCollection(items: string[] | number[]): string { - return items.join("\t"); -} - -export function buildCsvCollection(items: string[] | number[]): string { - return items.join(","); -} diff --git a/packages/typespec-ts/test/util/emitUtil.ts b/packages/typespec-ts/test/util/emitUtil.ts index 14b716c866..8dc5b3caf5 100644 --- a/packages/typespec-ts/test/util/emitUtil.ts +++ b/packages/typespec-ts/test/util/emitUtil.ts @@ -33,6 +33,8 @@ import { buildSerializeUtils } from "../../src/modular/buildSerializeUtils.js"; import { buildClientContext } from "../../src/modular/buildClientContext.js"; import { buildClassicalClient } from "../../src/modular/buildClassicalClient.js"; import { Project } from "ts-morph"; +import { useBinder } from "../../src/framework/hooks/binder.js"; +import { useContext } from "../../src/contextManager.js"; export async function emitPageHelperFromTypeSpec( tspContent: string, @@ -418,7 +420,8 @@ export async function emitModularOperationsFromTypeSpec( string, RLCModel >(); - const project = new Project(); + const project = useContext("outputProject"); + const binder = useBinder(); const clients = getRLCClients(dpgContext); if (clients && clients[0]) { dpgContext.rlcOptions!.isModularLibrary = true; @@ -447,6 +450,7 @@ export async function emitModularOperationsFromTypeSpec( if (mustEmptyDiagnostic && dpgContext.program.diagnostics.length > 0) { throw dpgContext.program.diagnostics; } + binder.resolveAllReferences(); return res; } } diff --git a/packages/typespec-ts/test/util/testUtil.ts b/packages/typespec-ts/test/util/testUtil.ts index f00be395cf..1a2e912618 100644 --- a/packages/typespec-ts/test/util/testUtil.ts +++ b/packages/typespec-ts/test/util/testUtil.ts @@ -17,6 +17,21 @@ import { createContextWithDefaultOptions } from "../../src/index.js"; import { provideContext } from "../../src/contextManager.js"; import { Project } from "ts-morph"; import { provideSdkTypes } from "../../src/framework/hooks/sdkTypes.js"; +import { provideBinder } from "../../src/framework/hooks/binder.js"; +import { loadStaticHelpers } from "../../src/framework/load-static-helpers.js"; +import path from "path"; +import { getDirname } from "../../src/utils/dirname.js"; +import { + PagingHelpers, + PollingHelpers, + SerializationHelpers +} from "../../src/modular/static-helpers-metadata.js"; +import { + AzureCoreDependencies, + AzurePollingDependencies +} from "../../src/modular/external-dependencies.js"; + +const { __dirname } = getDirname(import.meta.url); export async function createRLCEmitterTestHost() { return createTestHost({ @@ -92,10 +107,11 @@ export async function createDpgContextTestHelper( program: Program, enableModelNamespace = false ): Promise { + const outputProject = new Project({ useInMemoryFileSystem: true }); provideContext("rlcMetaTree", new Map()); provideContext("modularMetaTree", new Map()); provideContext("symbolMap", new Map()); - provideContext("outputProject", new Project()); + provideContext("outputProject", outputProject); const context = await createContextWithDefaultOptions({ program @@ -116,6 +132,7 @@ export async function createDpgContextTestHelper( }); provideSdkTypes(context.sdkPackage); + await provideBinderWithAzureDependencies(outputProject); return sdkContext; } @@ -147,3 +164,32 @@ export type VerifyPropertyConfig = { additionalInputContent?: string; additionalOutputContent?: string; }; + +export async function provideBinderWithAzureDependencies(project: Project) { + const helpersDirectory = path.resolve( + __dirname, + "../../static/static-helpers" + ); + + const extraDependencies = { + ...AzurePollingDependencies, + ...AzureCoreDependencies + }; + + const staticHelpers = { + ...SerializationHelpers, + ...PagingHelpers, + ...PollingHelpers + }; + + const staticHelperMap = await loadStaticHelpers(project, staticHelpers, { + helpersAssetDirectory: helpersDirectory + }); + + const binder = provideBinder(project, { + staticHelpers: staticHelperMap, + dependencies: extraDependencies + }); + + return binder; +} diff --git a/packages/typespec-ts/vitest.config.ts b/packages/typespec-ts/vitest.config.ts index a2b268c9e6..026c5222a9 100644 --- a/packages/typespec-ts/vitest.config.ts +++ b/packages/typespec-ts/vitest.config.ts @@ -6,7 +6,11 @@ export default defineConfig({ provider: "istanbul", reporter: ["text", "json", "html"], all: true, - include: ["src/modular/serialization/**/*.ts", "src/framework/**/*.ts"], + include: [ + "src/modular/serialization/**/*.ts", + "src/framework/**/*.ts", + "static/static-helpers/**/*.ts" + ], exclude: ["**/*.spec.ts", "**/*.spec.tsx", ".next/*"] } }