diff --git a/lib/build.ts b/lib/build.ts index 7f4ee3a..2cbadfb 100644 --- a/lib/build.ts +++ b/lib/build.ts @@ -1,6 +1,6 @@ import { writeFile } from 'node:fs/promises'; import prettierConfig from '@block65/eslint-config/prettier'; -import type { OpenAPIV3_1 } from 'openapi-types'; +import type { oas31 } from 'openapi3-ts'; import { format as prettier } from 'prettier'; import { processOpenApiDocument } from './process-document.js'; @@ -11,7 +11,7 @@ export async function build( ) { const apischema = (await import(inputFile, { with: { type: 'json' }, - })) as { default: OpenAPIV3_1.Document }; + })) as { default: oas31.OpenAPIObject }; const banner = ` /** diff --git a/lib/process-document.ts b/lib/process-document.ts index 4d171ed..7421fc0 100644 --- a/lib/process-document.ts +++ b/lib/process-document.ts @@ -1,7 +1,7 @@ /* eslint-disable no-restricted-syntax */ import { join, relative } from 'node:path'; import { $RefParser } from '@apidevtools/json-schema-ref-parser'; -import type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types'; +import type { oas30, oas31 } from 'openapi3-ts'; import toposort from 'toposort'; import { InterfaceDeclaration, @@ -14,6 +14,7 @@ import { VariableDeclarationKind, Writers, } from 'ts-morph'; +import type { Simplify } from 'type-fest'; import { registerTypesFromSchema, schemaToType } from './process-schema.js'; import { castToValidJsIdentifier, @@ -60,7 +61,7 @@ function createUnion(...types: (string | undefined)[]) { export async function processOpenApiDocument( outputDir: string, - schema: OpenAPIV3_1.Document, + schema: Simplify, tags?: string[] | undefined, ) { const project = new Project(); @@ -191,22 +192,22 @@ export async function processOpenApiDocument( ); } - for (const [path, pathItemObject] of Object.entries(schema.paths || {})) { + for (const [path, pathItemObject] of Object.entries( + schema.paths || {}, + )) { if (pathItemObject) { - for (const [method, operationObject] of Object.entries( - pathItemObject, - ).filter( - ([, o]) => - !tags || - (typeof o === 'object' && 'tags' in o - ? o.tags.some((t) => tags.includes(t)) - : false), - )) { + for (const [method, operationObject] of Object.entries(pathItemObject) + // ensure op is an object + .filter( + (e): e is [string, oas31.OperationObject] => typeof e[1] === 'object', + ) + // tags + .filter(([, o]) => !tags || o.tags?.some((t) => tags?.includes(t)))) { if ( typeof operationObject === 'object' && 'operationId' in operationObject ) { - const pathParameters: OpenAPIV3.ParameterObject[] = []; + const pathParameters: oas30.ParameterObject[] = []; const commandName = pascalCase( operationObject.operationId.replace(/command$/i, ''), @@ -258,13 +259,13 @@ export async function processOpenApiDocument( ? operationObject.requestBody : undefined; - const queryParameters: OpenAPIV3.ParameterObject[] = []; + const queryParameters: oas30.ParameterObject[] = []; for (const parameter of [ ...(operationObject.parameters || []), ...(pathItemObject.parameters || []), ]) { - const resolvedParameter: OpenAPIV3.ParameterObject = + const resolvedParameter: oas30.ParameterObject = '$ref' in parameter ? refs.get(parameter.$ref) : parameter; if (resolvedParameter.in === 'path') { @@ -481,7 +482,7 @@ export async function processOpenApiDocument( } for (const [statusCode, response] of Object.entries( - operationObject.responses, + operationObject.responses || {}, ).filter(([s]) => s.startsWith('2'))) { // early out if response is 204 if (statusCode === '204') { diff --git a/lib/process-schema.ts b/lib/process-schema.ts index f46f15f..f909334 100644 --- a/lib/process-schema.ts +++ b/lib/process-schema.ts @@ -1,5 +1,5 @@ /* eslint-disable no-console */ -import type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types'; +import type { oas31, oas30 } from 'openapi3-ts'; import { Writers, type CodeBlockWriter, @@ -24,9 +24,7 @@ function maybeWithNullUnion(type: string | WriterFunction, withNull = false) { return withNull && type !== 'null' ? Writers.unionType(type, 'null') : type; } -function schemaTypeIsNull( - schema: OpenAPIV3.SchemaObject | OpenAPIV3_1.SchemaObject, -) { +function schemaTypeIsNull(schema: oas30.SchemaObject | oas31.SchemaObject) { return ( schema.type === 'null' || ('nullable' in schema && schema.nullable) || @@ -63,12 +61,9 @@ export function schemaToType( string, InterfaceDeclaration | TypeAliasDeclaration | EnumDeclaration >, - parentSchema: OpenAPIV3_1.SchemaObject | OpenAPIV3.SchemaObject, + parentSchema: oas31.SchemaObject | oas30.SchemaObject, propertyName: string, - schemaObject: - | OpenAPIV3_1.SchemaObject - | OpenAPIV3.SchemaObject - | OpenAPIV3_1.ReferenceObject, + schemaObject: oas31.SchemaObject | oas30.SchemaObject | oas31.ReferenceObject, options: { exactOptionalPropertyTypes?: boolean; booleanAsStringish?: boolean; @@ -127,9 +122,19 @@ export function schemaToType( }, ] : []), - ...(schemaObject.example + + // 3 + ...('example' in schemaObject ? [{ tagName: 'example', text: String(schemaObject.example) }] : []), + + // 3.1 + ...('examples' in schemaObject + ? schemaObject.examples.map((example) => ({ + tagName: 'example', + text: JSON.stringify(example), + })) + : []), ...(schemaObject.deprecated ? [{ tagName: 'deprecated' }] : []), ]; @@ -168,13 +173,11 @@ export function schemaToType( items: {}, ...schemaObject, type: 'array', - } satisfies - | OpenAPIV3.ArraySchemaObject - | OpenAPIV3_1.ArraySchemaObject) - : { + } satisfies typeof schemaObject) + : ({ ...schemaObject, type, - }; + } satisfies typeof schemaObject); return ( schemaToType(typesAndInterfaces, schemaObject, name, schema).type || @@ -420,10 +423,10 @@ export function registerTypesFromSchema( typesFile: SourceFile, schemaName: string, schemaObject: - | OpenAPIV3.SchemaObject - | OpenAPIV3.ReferenceObject - | OpenAPIV3_1.SchemaObject - | OpenAPIV3_1.ReferenceObject, + | oas30.SchemaObject + | oas30.ReferenceObject + | oas31.SchemaObject + | oas31.ReferenceObject, ) { // deal with refs if ('$ref' in schemaObject) { diff --git a/lib/utils.ts b/lib/utils.ts index 9b6afaa..17ceadd 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,5 +1,5 @@ import camelcase from 'camelcase'; -import type { OpenAPIV3_1 } from 'openapi-types'; +import type { oas31 } from 'openapi3-ts'; import wrap from 'word-wrap'; export function maybeJsDocDescription( @@ -8,9 +8,7 @@ export function maybeJsDocDescription( return str.length > 0 ? ['', ...str].filter(Boolean).join(' - ').trim() : ''; } -export function isReferenceObject( - obj: unknown, -): obj is OpenAPIV3_1.ReferenceObject { +export function isReferenceObject(obj: unknown): obj is oas31.ReferenceObject { return typeof obj === 'object' && obj !== null && '$ref' in obj; } @@ -18,9 +16,9 @@ export function getDependency(obj: unknown): string | undefined { return isReferenceObject(obj) ? obj.$ref : undefined; } -export function isNotReferenceObject< - T extends OpenAPIV3_1.ReferenceObject | unknown, ->(obj: T): obj is Exclude { +export function isNotReferenceObject( + obj: T, +): obj is Exclude { return !isReferenceObject(obj); } @@ -29,7 +27,7 @@ export function isNotNullOrUndefined(obj: T | null | undefined): obj is T { } export function getDependents( - obj: OpenAPIV3_1.ReferenceObject | OpenAPIV3_1.SchemaObject, + obj: oas31.ReferenceObject | oas31.SchemaObject, ): string[] { const strOnly = (x: string | undefined): x is string => typeof x === 'string'; diff --git a/package.json b/package.json index e5a75e6..5ba000c 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "eslint-plugin-react": "^7.37.1", "eslint-plugin-react-hooks": "^4.6.2", "js-yaml": "^4.1.0", - "openapi-types": "^12.1.3", + "openapi3-ts": "^4.4.0", "prettier": "^2.8.8", "typescript": "^5.6.2", "undici": "^6.19.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 128b91c..f0cbb11 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -93,9 +93,9 @@ importers: js-yaml: specifier: ^4.1.0 version: 4.1.0 - openapi-types: - specifier: ^12.1.3 - version: 12.1.3 + openapi3-ts: + specifier: ^4.4.0 + version: 4.4.0 typescript: specifier: ^5.6.2 version: 5.6.2 @@ -2076,8 +2076,8 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - openapi-types@12.1.3: - resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} + openapi3-ts@4.4.0: + resolution: {integrity: sha512-9asTNB9IkKEzWMcHmVZE7Ts3kC9G7AFHfs8i7caD8HbI76gEjdkId4z/AkP83xdZsH7PLAnnbl47qZkXuxpArw==} optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} @@ -2580,6 +2580,11 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yaml@2.5.1: + resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==} + engines: {node: '>= 14'} + hasBin: true + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -4897,7 +4902,9 @@ snapshots: dependencies: wrappy: 1.0.2 - openapi-types@12.1.3: {} + openapi3-ts@4.4.0: + dependencies: + yaml: 2.5.1 optionator@0.9.4: dependencies: @@ -5442,6 +5449,8 @@ snapshots: yallist@3.1.1: {} + yaml@2.5.1: {} + yargs-parser@21.1.1: {} yargs@17.7.2: