From 2a039545711640207a20037f7ec072985433ffc7 Mon Sep 17 00:00:00 2001 From: Luke Hagar Date: Sat, 27 Sep 2025 01:52:32 +0000 Subject: [PATCH] Refactor vendor extensions by consolidating and enhancing type safety. Introduce a new common vendor extension and remove outdated examples, improving overall structure and IntelliSense support. --- src/extensions/example-usage.ts | 143 ++++++++++++++++++++- src/extensions/vendor-loader.ts | 6 +- src/extensions/vendor/common.ts | 21 ++++ src/extensions/vendor/example-enhanced.ts | 145 ---------------------- src/extensions/vendor/example-usage.ts | 25 ---- src/extensions/vendor/postman.ts | 37 ------ src/extensions/vendor/redoc.ts | 42 ------- src/extensions/vendor/speakeasy.ts | 65 +--------- test/vendor.test.ts | 9 +- 9 files changed, 165 insertions(+), 328 deletions(-) create mode 100644 src/extensions/vendor/common.ts delete mode 100644 src/extensions/vendor/example-enhanced.ts delete mode 100644 src/extensions/vendor/example-usage.ts delete mode 100644 src/extensions/vendor/postman.ts delete mode 100644 src/extensions/vendor/redoc.ts diff --git a/src/extensions/example-usage.ts b/src/extensions/example-usage.ts index 1a4aff9..ed66570 100644 --- a/src/extensions/example-usage.ts +++ b/src/extensions/example-usage.ts @@ -1,9 +1,4 @@ -/** - * Example Vendor Extensions - */ - -import { defineConfig } from './index.js'; - +import { defineConfig, createPositionHelpers } from './index.js'; // Complete vendor configuration with smart positioning export const config = defineConfig({ @@ -34,3 +29,139 @@ export const config = defineConfig({ } }); +// Example vendor with enhanced type safety +export const exampleEnhanced = defineConfig({ + info: { + name: 'Example Enhanced', + website: 'https://example.com', + support: 'support@example.com' + }, + extensions: { + // Type-safe context with IntelliSense for available keys + 'top-level': (before, after) => { + // IntelliSense will show: 'swagger', 'openapi', 'jsonSchemaDialect', 'info', 'externalDocs', etc. + return { + 'x-example-sdk': before('info'), // ✅ Type-safe: 'info' is a valid top-level key + 'x-example-config': after('paths'), // ✅ Type-safe: 'paths' is a valid top-level key + // 'x-example-invalid': before('invalidKey'), // ❌ TypeScript error: 'invalidKey' is not a valid top-level key + }; + }, + + 'info': (before, after) => { + // IntelliSense will show: 'title', 'version', 'summary', 'description', 'termsOfService', etc. + return { + 'x-example-info': after('version'), // ✅ Type-safe: 'version' is a valid info key + 'x-example-metadata': before('description'), // ✅ Type-safe: 'description' is a valid info key + }; + }, + + 'operation': (before, after) => { + // IntelliSense will show: 'summary', 'operationId', 'description', 'externalDocs', 'tags', etc. + return { + 'x-example-retries': after('parameters'), // ✅ Type-safe: 'parameters' is a valid operation key + 'x-example-timeout': before('responses'), // ✅ Type-safe: 'responses' is a valid operation key + 'x-example-cache': after('servers'), // ✅ Type-safe: 'servers' is a valid operation key + }; + }, + + 'schema': (before, after) => { + // IntelliSense will show: '$ref', 'title', 'description', 'type', 'format', 'enum', etc. + return { + 'x-example-validation': after('type'), // ✅ Type-safe: 'type' is a valid schema key + 'x-example-example': after('example'), // ✅ Type-safe: 'example' is a valid schema key + }; + }, + + 'parameter': (before, after) => { + // IntelliSense will show: 'name', 'description', 'in', 'required', 'deprecated', etc. + return { + 'x-example-param': after('schema'), // ✅ Type-safe: 'schema' is a valid parameter key + }; + }, + + 'response': (before, after) => { + // IntelliSense will show: 'description', 'headers', 'content', 'links' + return { + 'x-example-response': after('description'), // ✅ Type-safe: 'description' is a valid response key + }; + }, + + 'securityScheme': (before, after) => { + // IntelliSense will show: 'type', 'description', 'name', 'in', 'scheme', etc. + return { + 'x-example-auth': after('type'), // ✅ Type-safe: 'type' is a valid security scheme key + }; + }, + + 'server': (before, after) => { + // IntelliSense will show: 'url', 'description', 'variables' + return { + 'x-example-server': after('url'), // ✅ Type-safe: 'url' is a valid server key + }; + }, + + 'tag': (before, after) => { + // IntelliSense will show: 'name', 'description', 'externalDocs' + return { + 'x-example-tag': after('name'), // ✅ Type-safe: 'name' is a valid tag key + }; + }, + + 'externalDocs': (before, after) => { + // IntelliSense will show: 'description', 'url' + return { + 'x-example-docs': after('url'), // ✅ Type-safe: 'url' is a valid external docs key + }; + }, + + 'webhook': (before, after) => { + // IntelliSense will show: 'summary', 'operationId', 'description', 'deprecated', etc. + return { + 'x-example-webhook': after('operationId'), // ✅ Type-safe: 'operationId' is a valid webhook key + }; + }, + + 'definitions': (before, after) => { + // IntelliSense will show schema keys: '$ref', 'title', 'description', 'type', etc. + return { + 'x-example-definition': after('type'), // ✅ Type-safe: 'type' is a valid schema key + }; + }, + + 'securityDefinitions': (before, after) => { + // IntelliSense will show security scheme keys: 'type', 'description', 'name', etc. + return { + 'x-example-security': after('type'), // ✅ Type-safe: 'type' is a valid security scheme key + }; + } + } +}); + +// Alternative approach using the enhanced helper functions +export const exampleWithHelpers = defineConfig({ + info: { + name: 'Example With Helpers', + website: 'https://example.com', + support: 'support@example.com' + }, + extensions: { + 'top-level': (before, after) => { + // You can also use the enhanced helpers for additional functionality + const helpers = createPositionHelpers('top-level'); + + // Get all available keys for this context + const availableKeys = helpers.getAvailableKeys(); + console.log('Available top-level keys:', availableKeys); + + // Validate if a key exists + if (helpers.isValidKey('info')) { + console.log('info is a valid top-level key'); + } + + return { + 'x-example-enhanced': before('info'), + 'x-example-config': after('paths'), + }; + } + } +}); diff --git a/src/extensions/vendor-loader.ts b/src/extensions/vendor-loader.ts index 76b28ae..468a0bc 100644 --- a/src/extensions/vendor-loader.ts +++ b/src/extensions/vendor-loader.ts @@ -7,16 +7,14 @@ import { before, after, type KeyMap, type VendorExtensions } from './index.js'; // Import vendor extensions statically +import { common } from './vendor/common.js'; import { speakeasy } from './vendor/speakeasy.js'; -import { postman } from './vendor/postman.js'; -import { redoc } from './vendor/redoc.js'; // Import vendor extensions statically const vendorModules = [ // Update this list as new vendors are added speakeasy, - postman, - redoc +common ]; // Type for vendor module diff --git a/src/extensions/vendor/common.ts b/src/extensions/vendor/common.ts new file mode 100644 index 0000000..67cb03d --- /dev/null +++ b/src/extensions/vendor/common.ts @@ -0,0 +1,21 @@ +import { defineConfig } from "../index.js"; + +// Function-based extensions with before/after helpers +export const common = defineConfig({ + info: { + name: 'Common', + }, + extensions: { + 'top-level': (before, after) => { + return { + 'x-tagGroups': after('tags'), + }; + }, + 'operation': (before, after) => { + return { + 'x-badges': after('tags'), + 'x-codeSamples': after('schemes'), + }; + }, + } +}); diff --git a/src/extensions/vendor/example-enhanced.ts b/src/extensions/vendor/example-enhanced.ts deleted file mode 100644 index 2cf7c91..0000000 --- a/src/extensions/vendor/example-enhanced.ts +++ /dev/null @@ -1,145 +0,0 @@ -/** - * Enhanced Vendor Extensions Example - * - * This example demonstrates the improved IntelliSense and type safety - * for vendor extensions configuration. - */ - -import { defineConfig, createPositionHelpers } from "../index.js"; - -// Example vendor with enhanced type safety -export const exampleEnhanced = defineConfig({ - info: { - name: 'Example Enhanced', - website: 'https://example.com', - support: 'support@example.com' - }, - extensions: { - // Type-safe context with IntelliSense for available keys - 'top-level': (before, after) => { - // IntelliSense will show: 'swagger', 'openapi', 'jsonSchemaDialect', 'info', 'externalDocs', etc. - return { - 'x-example-sdk': before('info'), // ✅ Type-safe: 'info' is a valid top-level key - 'x-example-config': after('paths'), // ✅ Type-safe: 'paths' is a valid top-level key - // 'x-example-invalid': before('invalidKey'), // ❌ TypeScript error: 'invalidKey' is not a valid top-level key - }; - }, - - 'info': (before, after) => { - // IntelliSense will show: 'title', 'version', 'summary', 'description', 'termsOfService', etc. - return { - 'x-example-info': after('version'), // ✅ Type-safe: 'version' is a valid info key - 'x-example-metadata': before('description'), // ✅ Type-safe: 'description' is a valid info key - }; - }, - - 'operation': (before, after) => { - // IntelliSense will show: 'summary', 'operationId', 'description', 'externalDocs', 'tags', etc. - return { - 'x-example-retries': after('parameters'), // ✅ Type-safe: 'parameters' is a valid operation key - 'x-example-timeout': before('responses'), // ✅ Type-safe: 'responses' is a valid operation key - 'x-example-cache': after('servers'), // ✅ Type-safe: 'servers' is a valid operation key - }; - }, - - 'schema': (before, after) => { - // IntelliSense will show: '$ref', 'title', 'description', 'type', 'format', 'enum', etc. - return { - 'x-example-validation': after('type'), // ✅ Type-safe: 'type' is a valid schema key - 'x-example-example': after('example'), // ✅ Type-safe: 'example' is a valid schema key - }; - }, - - 'parameter': (before, after) => { - // IntelliSense will show: 'name', 'description', 'in', 'required', 'deprecated', etc. - return { - 'x-example-param': after('schema'), // ✅ Type-safe: 'schema' is a valid parameter key - }; - }, - - 'response': (before, after) => { - // IntelliSense will show: 'description', 'headers', 'content', 'links' - return { - 'x-example-response': after('description'), // ✅ Type-safe: 'description' is a valid response key - }; - }, - - 'securityScheme': (before, after) => { - // IntelliSense will show: 'type', 'description', 'name', 'in', 'scheme', etc. - return { - 'x-example-auth': after('type'), // ✅ Type-safe: 'type' is a valid security scheme key - }; - }, - - 'server': (before, after) => { - // IntelliSense will show: 'url', 'description', 'variables' - return { - 'x-example-server': after('url'), // ✅ Type-safe: 'url' is a valid server key - }; - }, - - 'tag': (before, after) => { - // IntelliSense will show: 'name', 'description', 'externalDocs' - return { - 'x-example-tag': after('name'), // ✅ Type-safe: 'name' is a valid tag key - }; - }, - - 'externalDocs': (before, after) => { - // IntelliSense will show: 'description', 'url' - return { - 'x-example-docs': after('url'), // ✅ Type-safe: 'url' is a valid external docs key - }; - }, - - 'webhook': (before, after) => { - // IntelliSense will show: 'summary', 'operationId', 'description', 'deprecated', etc. - return { - 'x-example-webhook': after('operationId'), // ✅ Type-safe: 'operationId' is a valid webhook key - }; - }, - - 'definitions': (before, after) => { - // IntelliSense will show schema keys: '$ref', 'title', 'description', 'type', etc. - return { - 'x-example-definition': after('type'), // ✅ Type-safe: 'type' is a valid schema key - }; - }, - - 'securityDefinitions': (before, after) => { - // IntelliSense will show security scheme keys: 'type', 'description', 'name', etc. - return { - 'x-example-security': after('type'), // ✅ Type-safe: 'type' is a valid security scheme key - }; - } - } -}); - -// Alternative approach using the enhanced helper functions -export const exampleWithHelpers = defineConfig({ - info: { - name: 'Example With Helpers', - website: 'https://example.com', - support: 'support@example.com' - }, - extensions: { - 'top-level': (before, after) => { - // You can also use the enhanced helpers for additional functionality - const helpers = createPositionHelpers('top-level'); - - // Get all available keys for this context - const availableKeys = helpers.getAvailableKeys(); - console.log('Available top-level keys:', availableKeys); - - // Validate if a key exists - if (helpers.isValidKey('info')) { - console.log('info is a valid top-level key'); - } - - return { - 'x-example-enhanced': before('info'), - 'x-example-config': after('paths'), - }; - } - } -}); diff --git a/src/extensions/vendor/example-usage.ts b/src/extensions/vendor/example-usage.ts deleted file mode 100644 index 8af52aa..0000000 --- a/src/extensions/vendor/example-usage.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Example Vendor Extensions - */ - -// Function-based extensions with before/after helpers -export const extensions = { - 'top-level': (before: (key: string) => number, after: (key: string) => number) => { - return { - 'x-example-before-info': before('info'), // Before 'info' - 'x-example-after-paths': after('paths'), // After 'paths' - }; - }, - 'operation': (before: (key: string) => number, after: (key: string) => number) => { - return { - 'x-example-before-parameters': before('parameters'), // Before 'parameters' - 'x-example-after-responses': after('responses'), // After 'responses' - }; - }, - 'schema': (before: (key: string) => number, after: (key: string) => number) => { - return { - 'x-example-validation': after('type'), // After 'type' - 'x-example-example': after('example'), // After 'example' - }; - } -}; diff --git a/src/extensions/vendor/postman.ts b/src/extensions/vendor/postman.ts deleted file mode 100644 index 0b382a2..0000000 --- a/src/extensions/vendor/postman.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Postman Extensions - * - * Postman collection extensions for OpenAPI formatting. - * Website: https://postman.com - */ - -import { defineConfig } from "../index.js"; - -// Function-based extensions with before/after helpers -export const postman = defineConfig({ - info: { - name: 'Postman', - website: 'https://postman.com', - support: 'support@postman.com' - }, - extensions: { - 'top-level': (before, after) => { - return { - 'x-postman-collection': before('info'), // Before 'info' - 'x-postman-version': after('paths'), // After 'paths' - }; - }, - 'operation': (before, after) => { - return { - 'x-postman-test': after('responses'), // After 'responses' - 'x-postman-pre-request': before('parameters'), // Before 'parameters' - }; - }, - 'schema': (before, after) => { - return { - 'x-postman-example': after('example'), // After 'example' - 'x-postman-mock': after('deprecated'), // After 'deprecated' - }; - } - } -}); diff --git a/src/extensions/vendor/redoc.ts b/src/extensions/vendor/redoc.ts deleted file mode 100644 index d24a98f..0000000 --- a/src/extensions/vendor/redoc.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Redoc Extensions - * - * Redoc documentation extensions for OpenAPI formatting. - * Website: https://redocly.com - */ - -import { defineConfig } from "../index.js"; - -// Function-based extensions with before/after helpers -export const redoc = defineConfig({ - info: { - name: 'Redocly', - website: 'https://redocly.com', - support: 'team@redocly.com' - }, - extensions: { - 'top-level': (before, after) => { - return { - 'x-redoc-version': before('info'), // Before 'info' - 'x-redoc-theme': after('paths'), // After 'paths' - }; - }, - 'info': (before, after) => { - return { - 'x-redoc-info': after('version'), // After 'version' - }; - }, - 'operation': (before, after) => { - return { - 'x-redoc-group': after('tags'), // After 'tags' - 'x-redoc-hide': before('responses'), // Before 'responses' - }; - }, - 'schema': (before, after) => { - return { - 'x-redoc-example': after('example'), // After 'example' - 'x-redoc-readonly': after('deprecated'), // After 'deprecated' - }; - } - } -}); diff --git a/src/extensions/vendor/speakeasy.ts b/src/extensions/vendor/speakeasy.ts index 1b36638..f84e750 100644 --- a/src/extensions/vendor/speakeasy.ts +++ b/src/extensions/vendor/speakeasy.ts @@ -17,72 +17,15 @@ export const speakeasy = defineConfig({ extensions: { 'top-level': (before, after) => { return { - 'x-speakeasy-sdk': before('info'), // Before 'info' - 'x-speakeasy-auth': after('paths'), // After 'paths' - }; - }, - 'info': (before, after) => { - return { - 'x-speakeasy-info': after('version'), // After 'version' + 'x-speakeasy-globals': after('security'), + 'x-speakeasy-globals-hidden': after('security'), }; }, 'operation': (before, after) => { return { - 'x-speakeasy-retries': after('parameters'), // After 'parameters' - 'x-speakeasy-timeout': before('responses'), // Before 'responses' - 'x-speakeasy-cache': after('servers'), // After 'servers' + 'x-speakeasy-pagination': after('parameters'), + 'x-speakeasy-usage-example': after('deprecated'), }; }, - 'schema': (before, after) => { - return { - 'x-speakeasy-validation': after('type'), // After 'type' - 'x-speakeasy-example': after('example'), // After 'example' - }; - }, - 'parameter': (before, after) => { - return { - 'x-speakeasy-param': after('schema'), // After 'schema' - }; - }, - 'response': (before, after) => { - return { - 'x-speakeasy-response': after('description'), // After 'description' - }; - }, - 'securityScheme': (before, after) => { - return { - 'x-speakeasy-auth': after('type'), // After 'type' - }; - }, - 'server': (before, after) => { - return { - 'x-speakeasy-server': after('url'), // After 'url' - }; - }, - 'tag': (before, after) => { - return { - 'x-speakeasy-tag': after('name'), // After 'name' - }; - }, - 'externalDocs': (before, after) => { - return { - 'x-speakeasy-docs': after('url'), // After 'url' - }; - }, - 'webhook': (before, after) => { - return { - 'x-speakeasy-webhook': after('operationId'), // After 'operationId' - }; - }, - 'definitions': (before, after) => { - return { - 'x-speakeasy-definition': after('type'), // After 'type' - }; - }, - 'securityDefinitions': (before, after) => { - return { - 'x-speakeasy-security': after('type'), // After 'type' - }; - } } }); \ No newline at end of file diff --git a/test/vendor.test.ts b/test/vendor.test.ts index 767bc70..3609ea8 100644 --- a/test/vendor.test.ts +++ b/test/vendor.test.ts @@ -11,8 +11,7 @@ describe('Vendor Extension System', () => { // Check if extensions were loaded expect(vendorExtensions['top-level']).toBeDefined(); - expect(vendorExtensions['top-level']['x-speakeasy-sdk']).toBe(3); // before('info') = position 3 - expect(vendorExtensions['top-level']['x-speakeasy-auth']).toBe(14); // after('paths') = position 14 + expect(vendorExtensions['top-level']['x-tagGroups']).toBe(13); }); @@ -28,12 +27,6 @@ describe('Vendor Extension System', () => { // Check that extensions have the right structure expect(vendorExtensions['top-level']).toBeDefined(); - expect(vendorExtensions['info']).toBeDefined(); expect(vendorExtensions['operation']).toBeDefined(); - expect(vendorExtensions['schema']).toBeDefined(); - - // Check specific extensions - expect(vendorExtensions['top-level']['x-speakeasy-sdk']).toBe(3); // before('info') = position 3 - expect(vendorExtensions['top-level']['x-speakeasy-auth']).toBe(14); // after('paths') = position 14 }); });