diff --git a/package-lock.json b/package-lock.json index de2a563d..eda3f4d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@apimatic/cli", - "version": "1.1.0-beta.11", + "version": "1.1.0-beta.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@apimatic/cli", - "version": "1.1.0-beta.11", + "version": "1.1.0-beta.14", "license": "MIT", "dependencies": { - "@apimatic/sdk": "0.2.0-alpha.8", + "@apimatic/sdk": "0.2.0-alpha.9", "@clack/prompts": "1.0.0-alpha.1", "@oclif/core": "^4.2.8", "@oclif/plugin-autocomplete": "^3.2.24", @@ -275,9 +275,9 @@ } }, "node_modules/@apimatic/sdk": { - "version": "0.2.0-alpha.8", - "resolved": "https://registry.npmjs.org/@apimatic/sdk/-/sdk-0.2.0-alpha.8.tgz", - "integrity": "sha512-W7ReyVMPq8+V40S+YeUQbw6J00ymkZYi88zrs1fj5RvynamMjalEabSUZ64VxpR1U1uIEABqVEFzzxG71UXnEA==", + "version": "0.2.0-alpha.9", + "resolved": "https://registry.npmjs.org/@apimatic/sdk/-/sdk-0.2.0-alpha.9.tgz", + "integrity": "sha512-lTZIToN43WuDGKJ8ny9IiusV7sJ+RJITQYxzhI1meD6cxCN7ryKEoS3tx2JOjCSRrRAxBmk3Wb2vDhXMYBAskA==", "license": "MIT", "dependencies": { "@apimatic/authentication-adapters": "^0.5.14", @@ -1271,7 +1271,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -3912,7 +3911,6 @@ "resolved": "https://registry.npmjs.org/@oclif/core/-/core-4.10.3.tgz", "integrity": "sha512-0mD8vcrrX5uRsxzvI8tbWmSVGngvZA/Qo6O0ZGvLPAWEauSf5GFniwgirhY0SkszuHwu0S1J1ivj/jHmqtIDuA==", "license": "MIT", - "peer": true, "dependencies": { "ansi-escapes": "^4.3.2", "ansis": "^3.17.0", @@ -4031,7 +4029,6 @@ "integrity": "sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.2.2", @@ -5845,7 +5842,6 @@ "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -6016,7 +6012,6 @@ "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", @@ -6246,7 +6241,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6839,7 +6833,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -7630,7 +7623,6 @@ "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -8143,7 +8135,6 @@ "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" @@ -8534,7 +8525,6 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -14525,7 +14515,6 @@ "dev": true, "inBundle": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -16613,7 +16602,6 @@ "integrity": "sha512-qqJDBhbtHsjUEMsojWKGuL5lQFCJuPtiXKEIlFKyTzDDGTAE/oyvznaP8GeOr5PvcqBJ6LQz4JCENWPLeehSpA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^12.0.0", "@semantic-release/error": "^4.0.0", @@ -17933,7 +17921,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -18280,7 +18267,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 66ee18f6..2283c739 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "test": "tsx node_modules/mocha/bin/_mocha --forbid-only \"test/**/*.test.ts\" --timeout 99999" }, "dependencies": { - "@apimatic/sdk": "0.2.0-alpha.8", + "@apimatic/sdk": "0.2.0-alpha.9", "@clack/prompts": "1.0.0-alpha.1", "@oclif/core": "^4.2.8", "@oclif/plugin-autocomplete": "^3.2.24", diff --git a/src/actions/portal/toc/new-toc.ts b/src/actions/portal/toc/new-toc.ts index dfe01f8d..d7532a75 100644 --- a/src/actions/portal/toc/new-toc.ts +++ b/src/actions/portal/toc/new-toc.ts @@ -8,12 +8,8 @@ import { TocContext } from '../../../types/toc-context.js'; import { FileService } from '../../../infrastructure/file-service.js'; import { BuildContext } from '../../../types/build-context.js'; import { - extractCallbacksForToc, - extractEndpointGroupsForToc, - extractModelsForToc, - extractWebhooksForToc, - SdlTocComponents -} from '../../../types/sdl/sdl.js'; + TocComponents +} from '../../../types/toc/toc-components.js'; import { withDirPath } from '../../../infrastructure/tmp-extensions.js'; import { TempContext } from '../../../types/temp-context.js'; import { PortalService } from '../../../infrastructure/services/portal-service.js'; @@ -67,27 +63,20 @@ export class PortalNewTocAction { return ActionResult.cancelled(); } - const sdlTocComponents: SdlTocComponents = await (async () => { - const defaultComponents = { - endpointGroups: new Map(), - models: [], - webhookGroups: new Map(), - callbackGroups: new Map() - }; - + const tocComponents: TocComponents = await (async () => { const specDirectory = buildDirectory.join('spec'); if (!(await this.fileService.directoryExists(specDirectory))) { this.prompts.fallingBackToDefault(); - return defaultComponents; + return TocComponents.empty(); } return await withDirPath(async (tempDirectory) => { const tempContext = new TempContext(tempDirectory); const specZipPath = await tempContext.zip(specDirectory); const specFileStream = await this.fileService.getStream(specZipPath); - const result = await this.prompts.extractComponents( - this.portalService.generateSdl(specFileStream, this.configDirectory, this.commandMetadata), + const result = await this.prompts.extractTocData( + this.portalService.generateTocData(specFileStream, this.configDirectory, this.commandMetadata), expandEndpoints, expandModels, expandWebhooks, @@ -96,15 +85,10 @@ export class PortalNewTocAction { specFileStream.close(); if (result.isErr()) { this.prompts.fallingBackToDefault(); - return defaultComponents; + return TocComponents.empty(); } - return { - endpointGroups: extractEndpointGroupsForToc(result.value), - models: extractModelsForToc(result.value), - webhookGroups: extractWebhooksForToc(result.value), - callbackGroups: extractCallbacksForToc(result.value) - }; + return TocComponents.fromTocData(result.value); }); })(); const contentContext = new ContentContext(contentDirectory); @@ -119,10 +103,11 @@ export class PortalNewTocAction { } const toc = this.tocGenerator.createTocStructure( - { data: sdlTocComponents.endpointGroups, expand: expandEndpoints }, - { data: sdlTocComponents.models, expand: expandModels }, - { data: sdlTocComponents.webhookGroups, expand: expandWebhooks }, - { data: sdlTocComponents.callbackGroups, expand: expandCallbacks }, + tocComponents, + expandEndpoints, + expandModels, + expandWebhooks, + expandCallbacks, contentGroups ); const yamlString = this.tocGenerator.transformToYaml(toc); diff --git a/src/application/portal/toc/toc-structure-generator.ts b/src/application/portal/toc/toc-structure-generator.ts index cc497eec..af6725c0 100644 --- a/src/application/portal/toc/toc-structure-generator.ts +++ b/src/application/portal/toc/toc-structure-generator.ts @@ -2,45 +2,31 @@ import { stringify } from 'yaml'; import { Toc, TocGroup, - TocEndpointGroupOverview, - TocModelPage, TocGenerated, - TocCallbackPage, - TocWebhookPage, - TocEndpoint + TocModelPage, + TocContainerModelPage, + TocInputModelPage, } from '../../../types/toc/toc.js'; - -// TODO: Refactor - -type Endpoints = { - data: Map; - expand: boolean; -}; - -type Webhooks = { - data: Map; - expand: boolean; -}; - -type Callbacks = { - data: Map; - expand: boolean; -}; - -type Models = { - data: TocModelPage[]; - expand: boolean; -}; +import { + TocComponents, + EndpointGroups, + WebhookGroups, + CallbackGroups +} from '../../../types/toc/toc-components.js'; export class TocStructureGenerator { createTocStructure( - endpoints: Endpoints, - models: Models, - webhooks: Webhooks, - callbacks: Callbacks, + tocComponents: TocComponents, + expandEndpoints: boolean, + expandModels: boolean, + expandWebhooks: boolean, + expandCallbacks: boolean, contentGroups: TocGroup[] = [] ): Toc { - const events = [...this.getCallbacksSection(callbacks), ...this.getWebhooksSection(webhooks)]; + const events = [ + ...this.getCallbacksSection(tocComponents.callbackGroups, expandCallbacks), + ...this.getWebhooksSection(tocComponents.webhookGroups, expandWebhooks) + ]; return { toc: [ @@ -54,7 +40,7 @@ export class TocStructureGenerator { ] }, ...contentGroups, - this.getEndpointsSection(endpoints), + this.getEndpointsSection(tocComponents.endpointGroups, expandEndpoints), ...(events.length > 0 ? [ { @@ -63,7 +49,14 @@ export class TocStructureGenerator { } ] : []), - ...this.getModelsSection(models), + ...this.getModelsSection( + tocComponents.models, + tocComponents.enums, + tocComponents.errors, + tocComponents.containerModels, + tocComponents.inputModels, + expandModels + ), { generate: 'SDK Infrastructure', from: 'sdk-infra' @@ -80,8 +73,8 @@ export class TocStructureGenerator { }); } - private getEndpointsSection(endpoints: Endpoints): TocGroup | TocGenerated { - if (!endpoints.expand || endpoints.data.size === 0) { + private getEndpointsSection(data: EndpointGroups, expand: boolean): TocGroup | TocGenerated { + if (!expand || data.size === 0) { return { generate: 'API Endpoints', from: 'endpoints' @@ -89,37 +82,30 @@ export class TocStructureGenerator { } return { group: 'API Endpoints', - items: Array.from(endpoints.data).map(([groupName, endpoints]) => ({ + items: Array.from(data).map(([groupName, pages]) => ({ group: groupName, - items: [ - { - generate: null, - from: 'endpoint-group-overview', - endpointGroup: groupName - } as TocEndpointGroupOverview, - ...endpoints - ] + items: pages })) }; } - private getCallbacksSection(callbacks: Callbacks): (TocGroup | TocGenerated)[] { - if (callbacks.data.size === 0) { + private getCallbacksSection(data: CallbackGroups, expand: boolean): (TocGroup | TocGenerated)[] { + if (data.size === 0) { return []; } - if (callbacks.expand === true) { - if (callbacks.data.size === 1) { + if (expand === true) { + if (data.size === 1) { return [ { - group: Array.from(callbacks.data.keys())[0], - items: Array.from(callbacks.data.values())[0] + group: Array.from(data.keys())[0], + items: Array.from(data.values())[0] } ]; } return [ { group: 'Callbacks', - items: Array.from(callbacks.data).map(([groupName, eventList]) => ({ + items: Array.from(data).map(([groupName, eventList]) => ({ group: groupName, items: eventList })) @@ -134,23 +120,23 @@ export class TocStructureGenerator { ]; } - private getWebhooksSection(webhooks: Webhooks): (TocGroup | TocGenerated)[] { - if (webhooks.data.size === 0) { + private getWebhooksSection(data: WebhookGroups, expand: boolean): (TocGroup | TocGenerated)[] { + if (data.size === 0) { return []; } - if (webhooks.expand === true) { - if (webhooks.data.size === 1) { + if (expand === true) { + if (data.size === 1) { return [ { - group: Array.from(webhooks.data.keys())[0], - items: Array.from(webhooks.data.values())[0] + group: Array.from(data.keys())[0], + items: Array.from(data.values())[0] } ]; } return [ { group: 'Webhooks', - items: Array.from(webhooks.data).map(([groupName, eventList]) => ({ + items: Array.from(data).map(([groupName, eventList]) => ({ group: groupName, items: eventList })) @@ -165,11 +151,24 @@ export class TocStructureGenerator { ]; } - private getModelsSection(models: Models): (TocGroup | TocGenerated)[] { - if (models.data.length === 0) { + private getModelsSection( + modelsData: TocModelPage[], + enumsData: TocModelPage[], + errorsData: TocModelPage[], + containerModelsData: TocContainerModelPage[], + inputModelsData: TocInputModelPage[], + expand: boolean + ): (TocGroup | TocGenerated)[] { + if ( + modelsData.length === 0 && + enumsData.length === 0 && + errorsData.length === 0 && + containerModelsData.length === 0 && + inputModelsData.length === 0 + ) { return []; } - if (!models.expand) { + if (!expand) { return [ { generate: 'Models', @@ -177,15 +176,25 @@ export class TocStructureGenerator { } ]; } + const subGroups: TocGroup[] = [ + ...(modelsData.length > 0 || inputModelsData.length > 0 + ? [{ group: 'Structures', items: [...modelsData, ...inputModelsData] }] + : []), + ...(enumsData.length > 0 ? [{ group: 'Enumerations', items: enumsData }] : []), + ...(errorsData.length > 0 ? [{ group: 'Exceptions', items: errorsData }] : []), + ...(containerModelsData.length > 0 + ? [{ group: 'OneOf/AnyOf Definitions', items: containerModelsData }] + : []) + ]; return [ { group: 'Models', - items: models.data + items: subGroups } ]; } - private transformKeys(obj: any): any { + private transformKeys(obj: unknown): unknown { if (Array.isArray(obj)) { return obj.map((item) => this.transformKeys(item)); } diff --git a/src/infrastructure/services/portal-service.ts b/src/infrastructure/services/portal-service.ts index a64533a5..363f12a8 100644 --- a/src/infrastructure/services/portal-service.ts +++ b/src/infrastructure/services/portal-service.ts @@ -13,6 +13,7 @@ import { ExportFormats, SdkLanguages, Status, + TableOfContentsController, } from "@apimatic/sdk"; import { AuthInfo, getAuthInfo } from "../../client-utils/auth-manager.js"; import { parseStreamBodyToJson } from "../../utils/utils.js"; @@ -28,6 +29,7 @@ import { Language } from "../../types/sdk/generate.js"; import { handleServiceError, ServiceError } from "../service-error.js"; import { ApiService } from "./api-service.js"; import { SemVersion } from "../../types/publish/version.js"; +import { TocData } from "../../types/toc/toc-components.js"; export interface GeneratedSdkResult { sdk: NodeJS.ReadableStream; @@ -258,6 +260,34 @@ export class PortalService { } } + public async generateTocData( + specFileStream: ReadStream, + configDir: DirectoryPath, + commandMetadata: CommandMetadata + ): Promise> { + const file = new FileWrapper(specFileStream); + const authInfo: AuthInfo | null = await getAuthInfo(configDir.toString()); + const authorizationHeader = this.createAuthorizationHeader(authInfo, null); + const client = apiClientFactory.createApiClient(authorizationHeader, commandMetadata.shell); + const tableOfContentsController = new TableOfContentsController(client); + + try { + const response = await tableOfContentsController.generateTocData( + ContentType.EnumMultipartformdata, + file, + this.createOriginQueryParameter(commandMetadata.commandName) + ); + + if ((response.result as NodeJS.ReadableStream).readable) { + return ok((await parseStreamBodyToJson(response.result as NodeJS.ReadableStream)) as TocData); + } else { + return err(ServiceError.InvalidResponse); + } + } catch (error) { + return err(handleServiceError(error)); + } + } + private createAuthorizationHeader = (authInfo: AuthInfo | null, overrideAuthKey: string | null): string => { const key = overrideAuthKey || authInfo?.authKey; return `X-Auth-Key ${key ?? ""}`; diff --git a/src/prompts/portal/toc/new-toc.ts b/src/prompts/portal/toc/new-toc.ts index 8f668429..e4e8ea93 100644 --- a/src/prompts/portal/toc/new-toc.ts +++ b/src/prompts/portal/toc/new-toc.ts @@ -4,8 +4,8 @@ import { Result } from "neverthrow"; import { format as f } from "../../format.js"; import { DirectoryPath } from "../../../types/file/directoryPath.js"; import { ServiceError } from "../../../infrastructure/service-error.js"; -import { Sdl } from "../../../types/sdl/sdl.js"; import { withSpinner } from "../../prompt.js"; +import { TocData } from "../../../types/toc/toc-components.js"; export class PortalNewTocPrompts { public async overwriteToc(tocPath: FilePath): Promise { @@ -42,13 +42,13 @@ export class PortalNewTocPrompts { log.error(message); } - public extractComponents( - fn: Promise>, + public extractTocData( + fn: Promise>, expandEndpoints: boolean, expandModels: boolean, expandWebhooks: boolean, expandCallbacks: boolean - ): Promise> { + ): Promise> { const components = [ expandEndpoints && "Endpoint groups", expandModels && "Models", @@ -56,9 +56,9 @@ export class PortalNewTocPrompts { expandCallbacks && "Callbacks" ] .filter(Boolean) - .join(" and "); + .join(" and ") || "TOC data"; - return withSpinner(`Extracting ${components}`, `${components} extracted`, `${components} extraction failed`, fn); + return withSpinner(`Extracting ${components}`, `${components} extracted`, `${components} extraction Failed`, fn); } public tocCreated(tocPath: FilePath) { diff --git a/src/types/sdl/sdl.ts b/src/types/sdl/sdl.ts index 528397d7..948a7bb0 100644 --- a/src/types/sdl/sdl.ts +++ b/src/types/sdl/sdl.ts @@ -1,42 +1,11 @@ -import { TocEndpoint, TocModelPage, TocCallback, TocWebhook, TocWebhookPage, TocCallbackPage } from '../toc/toc.js'; -import { toTitleCase } from '../../utils/string-utils.js'; - -export type EndpointGroup = Map; -export type WebhookGroup = Map; -export type CallbackGroup = Map; - -export type SdlTocComponents = { - endpointGroups: EndpointGroup; - models: TocModelPage[]; - webhookGroups: WebhookGroup; - callbackGroups: CallbackGroup; -}; - export interface Sdl { readonly Endpoints: SdlEndpoint[]; - readonly CustomTypes: SdlModel[]; - readonly Webhooks: SdlWebhook[]; } export interface SdlEndpoint { readonly Name: string; readonly Description: string; readonly Group: string; - readonly Callbacks: SdlCallback[]; -} - -export interface SdlModel { - readonly Name: string; -} - -export interface SdlCallback { - readonly Id: string; - readonly CallbackGroupName?: string; -} - -export interface SdlWebhook { - readonly Id: string; - readonly WebhookGroupName?: string; } export function getEndpointDescription( @@ -57,166 +26,8 @@ export function getEndpointGroupsFromSdl(sdl: Sdl): Map { endpointGroups.get(endpoint.Group)!.push({ Name: endpoint.Name, Description: endpoint.Description, - Group: endpoint.Group, - Callbacks: endpoint.Callbacks + Group: endpoint.Group }); } return endpointGroups; } - -export function extractEndpointGroupsForToc(sdl: Sdl): Map { - const endpointGroups = new Map(); - - const endpoints = sdl.Endpoints.map( - (e: SdlEndpoint): TocEndpoint => ({ - generate: null, - from: 'endpoint', - endpointName: e.Name, - endpointGroup: e.Group - }) - ); - - endpoints.forEach((endpoint: TocEndpoint) => { - const group = endpoint.endpointGroup; - if (!endpointGroups.has(group)) { - endpointGroups.set(group, []); - } - endpointGroups.get(group)!.push(endpoint); - }); - - return endpointGroups; -} - -export function extractModelsForToc(sdl: Sdl): TocModelPage[] { - return sdl.CustomTypes.map( - (e: SdlModel): TocModelPage => ({ - generate: null, - from: 'model', - modelName: e.Name - }) - ); -} - -export function extractWebhooksForToc(sdl: Sdl): Map { - if (sdl.Webhooks.length === 0) { - return new Map(); - } - - let groupedWebhooks = new Map(); - const ungrouped: TocWebhook[] = []; - - for (const webhook of sdl.Webhooks) { - const event: TocWebhook = { - generate: null, - from: 'webhook', - webhookName: webhook.Id, - webhookGroup: webhook.WebhookGroupName ?? null - }; - - if (webhook.WebhookGroupName) { - const groupTitle = toTitleCase(webhook.WebhookGroupName); - if (!groupedWebhooks.has(groupTitle)) { - groupedWebhooks.set(groupTitle, [ - { - generate: null, - from: 'webhook-group-overview', - webhookGroup: webhook.WebhookGroupName - } - ]); - } - groupedWebhooks.get(groupTitle)!.push(event); - } else { - ungrouped.push(event); - } - } - - let ungroupedWebhooks = new Map(); - - if (ungrouped.length > 0) { - const uniqueGroupName = getUniqueGroupName('Webhooks', new Set(groupedWebhooks.keys())); - const uniqueGroupTitle = toTitleCase(uniqueGroupName); - ungroupedWebhooks.set(uniqueGroupTitle, [ - { - generate: null, - from: 'webhook-group-overview', - webhookGroup: uniqueGroupName - }, - ...ungrouped.map( - (event): TocWebhook => ({ - ...event, - webhookGroup: uniqueGroupName - }) - ) - ]); - } - - return new Map([...[...groupedWebhooks].sort((a, b) => a[0].localeCompare(b[0])), ...ungroupedWebhooks]); -} - -export function extractCallbacksForToc(sdl: Sdl): Map { - if (sdl.Endpoints.length === 0) { - return new Map(); - } - - let groupedCallbacks = new Map(); - const ungrouped: TocCallback[] = []; - - for (const callback of sdl.Endpoints.flatMap((e) => e.Callbacks)) { - const event: TocCallback = { - generate: null, - from: 'callback', - callbackName: callback.Id, - callbackGroup: callback.CallbackGroupName ?? null - }; - - if (callback.CallbackGroupName) { - const groupTitle = toTitleCase(callback.CallbackGroupName); - if (!groupedCallbacks.has(groupTitle)) { - groupedCallbacks.set(groupTitle, [ - { - generate: null, - from: 'callback-group-overview', - callbackGroup: callback.CallbackGroupName - } - ]); - } - groupedCallbacks.get(groupTitle)!.push(event); - } else { - ungrouped.push(event); - } - } - - let ungroupedCallbacks = new Map(); - - if (ungrouped.length > 0) { - const uniqueGroupName = getUniqueGroupName('Callbacks', new Set(groupedCallbacks.keys())); - const uniqueGroupTitle = toTitleCase(uniqueGroupName); - ungroupedCallbacks.set(uniqueGroupTitle, [ - { - generate: null, - from: 'callback-group-overview', - callbackGroup: uniqueGroupName - }, - ...ungrouped.map( - (event): TocCallback => ({ - ...event, - callbackGroup: uniqueGroupName - }) - ) - ]); - } - - return new Map([...[...groupedCallbacks].sort((a, b) => a[0].localeCompare(b[0])), ...ungroupedCallbacks]); -} - -function getUniqueGroupName(baseName: string, existingGroups: Set): string { - let counter = 1; - let name = baseName; - - while (existingGroups.has(toTitleCase(name))) { - name = `${baseName}${counter}`; - counter++; - } - - return name; -} diff --git a/src/types/toc/toc-components.ts b/src/types/toc/toc-components.ts new file mode 100644 index 00000000..3b40ed5d --- /dev/null +++ b/src/types/toc/toc-components.ts @@ -0,0 +1,175 @@ +import { + TocEndpoint, + TocEndpointPage, + TocModelPage, + TocCallback, + TocWebhook, + TocWebhookPage, + TocCallbackPage, + TocContainerModelPage, + TocInputModelPage +} from './toc.js'; + +export interface InputModel { + readonly name: string; + readonly group: string; +} + +export interface TocData { + readonly endpoints: Record; + readonly models: string[]; + readonly enums: string[]; + readonly errors: string[]; + readonly webhooks: Record; + readonly callbacks: Record; + readonly containers: string[]; + readonly inputModels: InputModel[]; +} + +export type EndpointGroups = Map; +export type WebhookGroups = Map; +export type CallbackGroups = Map; + +export class TocComponents { + readonly endpointGroups: EndpointGroups; + readonly models: TocModelPage[]; + readonly enums: TocModelPage[]; + readonly errors: TocModelPage[]; + readonly containerModels: TocContainerModelPage[]; + readonly inputModels: TocInputModelPage[]; + readonly webhookGroups: WebhookGroups; + readonly callbackGroups: CallbackGroups; + + private constructor( + endpointGroups: EndpointGroups, + models: TocModelPage[], + enums: TocModelPage[], + errors: TocModelPage[], + containerModels: TocContainerModelPage[], + inputModels: TocInputModelPage[], + webhookGroups: WebhookGroups, + callbackGroups: CallbackGroups + ) { + this.endpointGroups = endpointGroups; + this.models = models; + this.enums = enums; + this.errors = errors; + this.containerModels = containerModels; + this.inputModels = inputModels; + this.webhookGroups = webhookGroups; + this.callbackGroups = callbackGroups; + } + + static empty(): TocComponents { + return new TocComponents(new Map(), [], [], [], [], [], new Map(), new Map()); + } + + static fromTocData(tocData: TocData): TocComponents { + return new TocComponents( + TocComponents.toEndpointGroups(tocData.endpoints), + TocComponents.toTocModelPages(tocData.models), + TocComponents.toTocModelPages(tocData.enums), + TocComponents.toTocModelPages(tocData.errors), + TocComponents.toTocContainerModelPages(tocData.containers), + TocComponents.toTocInputModelPages(tocData.inputModels), + TocComponents.toWebhookGroups(tocData.webhooks), + TocComponents.toCallbackGroups(tocData.callbacks) + ); + } + + private static toEndpointGroups(endpoints: Record): EndpointGroups { + return new Map( + Object.entries(endpoints).map(([group, names]) => [ + group, + [ + { + generate: null, + from: 'endpoint-group-overview', + endpointGroup: group + }, + ...names.map((name): TocEndpoint => ({ + generate: null, + from: 'endpoint', + endpointName: name, + endpointGroup: group + })) + ] + ]) + ); + } + + private static toTocModelPages(models: string[]): TocModelPage[] { + return models.map( + (name: string): TocModelPage => ({ + generate: null, + from: 'model', + modelName: name + }) + ); + } + + private static toTocContainerModelPages(containers: string[]): TocContainerModelPage[] { + return containers.map( + (name: string): TocContainerModelPage => ({ + generate: null, + from: 'container', + containerName: name + }) + ); + } + + private static toTocInputModelPages(inputModels: InputModel[]): TocInputModelPage[] { + return inputModels.map( + (m: InputModel): TocInputModelPage => ({ + generate: null, + from: 'input-model', + endpointName: m.name, + endpointGroup: m.group + }) + ); + } + + private static toWebhookGroups(webhooks: Record): WebhookGroups { + return new Map( + Object.entries(webhooks) + .map(([group, names]): [string, TocWebhookPage[]] => [ + group, + [ + { + generate: null, + from: 'webhook-group-overview', + webhookGroup: group + }, + ...names.map((name): TocWebhook => ({ + generate: null, + from: 'webhook', + webhookName: name, + webhookGroup: group + })) + ] + ]) + ); + } + + private static toCallbackGroups(callbacks: Record): CallbackGroups { + return new Map( + Object.entries(callbacks) + .map(([group, names]): [string, TocCallbackPage[]] => [ + group, + [ + { + generate: null, + from: 'callback-group-overview', + callbackGroup: group + }, + ...names.map((name): TocCallback => ({ + generate: null, + from: 'callback', + callbackName: name, + callbackGroup: group + })) + ] + ]) + ); + } +} diff --git a/src/types/toc/toc.ts b/src/types/toc/toc.ts index f20127d7..2d300ec6 100644 --- a/src/types/toc/toc.ts +++ b/src/types/toc/toc.ts @@ -37,6 +37,19 @@ export interface TocModelPage { readonly modelName: string; } +export interface TocContainerModelPage { + readonly generate: null; + readonly from: 'container'; + readonly containerName: string; +} + +export interface TocInputModelPage { + readonly generate: null; + readonly from: 'input-model'; + readonly endpointName: string; + readonly endpointGroup: string; +} + export interface TocWebhookOverview { readonly generate: null; readonly from: 'webhook-group-overview'; diff --git a/src/utils/string-utils.ts b/src/utils/string-utils.ts index 1fabb218..25aff368 100644 --- a/src/utils/string-utils.ts +++ b/src/utils/string-utils.ts @@ -35,50 +35,3 @@ export function stripAnsi(str: string) { } return result; } - -export function toTitleCase(str: string): string { - if (str === '') { - return ''; - } - - let result = ''; - let shouldCapitalizeNext = true; - - for (let i = 0; i < str.length; i++) { - const char = str[i]; - const prevChar = i > 0 ? str[i - 1] : ''; - - if (isLowercase(char)) { - result += shouldCapitalizeNext ? ' ' + char.toUpperCase() : char; - shouldCapitalizeNext = false; - } else if (isUppercase(char)) { - if (prevChar && !isUppercase(prevChar)) { - result += ' '; - } - result += char; - shouldCapitalizeNext = false; - } else if (isDigit(char)) { - if (prevChar && !isDigit(prevChar)) { - result += ' '; - } - result += char; - shouldCapitalizeNext = true; - } else { - shouldCapitalizeNext = true; - } - } - - return result.trim(); -} - -function isLowercase(char: string): boolean { - return char >= 'a' && char <= 'z'; -} - -function isUppercase(char: string): boolean { - return char >= 'A' && char <= 'Z'; -} - -function isDigit(char: string): boolean { - return char.length === 1 && char >= '0' && char <= '9'; -}