diff --git a/src/openapiToTsJsonSchema.ts b/src/openapiToTsJsonSchema.ts index 3891622..c98b565 100644 --- a/src/openapiToTsJsonSchema.ts +++ b/src/openapiToTsJsonSchema.ts @@ -6,7 +6,7 @@ import { clearFolder, makeTsJsonSchemaFiles, SCHEMA_ID_SYMBOL, - convertOpenApiToJsonSchema, + convertOpenApiDocumentToJsonSchema, convertOpenApiPathsParameters, addSchemaToMetaData, makeId, @@ -18,6 +18,7 @@ import { import type { SchemaMetaDataMap, OpenApiObject, + OpenApiDocument, JSONSchema, ReturnPayload, Options, @@ -78,9 +79,13 @@ export async function openapiToTsJsonSchema( const jsonSchemaParser = new $RefParser(); // Resolve and inline external $ref definitions - const bundledOpenApiSchema = await openApiParser.bundle(openApiSchemaPath); - // Convert oas definitions to JSON schema - const initialJsonSchema = convertOpenApiToJsonSchema(bundledOpenApiSchema); + // @ts-expect-error @apidevtools/json-schema-ref-parser types supports JSON schemas only + const bundledOpenApiSchema: OpenApiDocument = + await openApiParser.bundle(openApiSchemaPath); + + // Convert oas definitions to JSON schema (excluding paths and parameter objects) + const initialJsonSchema = + convertOpenApiDocumentToJsonSchema(bundledOpenApiSchema); const inlinedRefs: Map< string, @@ -88,9 +93,9 @@ export async function openapiToTsJsonSchema( > = new Map(); // Inline and collect internal $ref definitions - const dereferencedJsonSchema = await jsonSchemaParser.dereference( - initialJsonSchema, - { + // @ts-expect-error @apidevtools/json-schema-ref-parser types supports JSON schemas only + const dereferencedJsonSchema: OpenApiDocument = + await jsonSchemaParser.dereference(initialJsonSchema, { dereference: { // @ts-expect-error onDereference seems not to be properly typed onDereference: (ref, inlinedSchema) => { @@ -130,8 +135,7 @@ export async function openapiToTsJsonSchema( } }, }, - }, - ); + }); const jsonSchema = convertOpenApiPathsParameters(dereferencedJsonSchema); const schemaMetaDataMap: SchemaMetaDataMap = new Map(); @@ -163,7 +167,6 @@ export async function openapiToTsJsonSchema( const openApiDefinitions = get(bundledOpenApiSchema, definitionPath); for (const schemaName in jsonSchemaDefinitions) { - // Create expected OpenAPI ref const id = makeId({ schemaRelativeDirName: definitionPath, schemaName, diff --git a/src/types.ts b/src/types.ts index e2c15c0..9551578 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,15 +4,21 @@ import type { SchemaObject as SchemaObject_v3_0, ParameterObject as ParameterObject_v3_0, ReferenceObject as ReferenceObject_v3_0, + OpenAPIObject as OpenAPIObject_v3_0, } from 'openapi3-ts/oas30'; import type { PathItemObject as PathItemObject_v3_1, SchemaObject as SchemaObject_v3_1, ParameterObject as ParameterObject_v3_1, ReferenceObject as ReferenceObject_v3_1, + OpenAPIObject as OpenAPIObject_v3_1, } from 'openapi3-ts/oas31'; -export type OpenApiDocument = Record; +export type OpenApiDocument = Omit< + OpenAPIObject_v3_0 | OpenAPIObject_v3_1, + 'openapi' | 'info' +>; + // This type should represent any generated OpenAPI type OpenApiObject_v3_0 = | PathItemObject_v3_0 diff --git a/src/utils/convertOpenApiToJsonSchema.ts b/src/utils/convertOpenApiDocumentToJsonSchema.ts similarity index 96% rename from src/utils/convertOpenApiToJsonSchema.ts rename to src/utils/convertOpenApiDocumentToJsonSchema.ts index 20a5656..47d750e 100644 --- a/src/utils/convertOpenApiToJsonSchema.ts +++ b/src/utils/convertOpenApiDocumentToJsonSchema.ts @@ -44,9 +44,9 @@ function convertToJsonSchema( * * @TODO Find a nicer way to convert convert all the expected OpenAPI schemas */ -export function convertOpenApiToJsonSchema( +export function convertOpenApiDocumentToJsonSchema( schema: OpenApiDocument, -): Record { +): OpenApiDocument { return mapObject( schema, (key, value) => { diff --git a/src/utils/index.ts b/src/utils/index.ts index 23f29e3..94bbdd5 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,7 +1,7 @@ export { patchJsonSchema } from './makeTsJsonSchema/patchJsonSchema'; export { makeTsJsonSchema } from './makeTsJsonSchema'; export { convertOpenApiPathsParameters } from './convertOpenApiPathsParameters'; -export { convertOpenApiToJsonSchema } from './convertOpenApiToJsonSchema'; +export { convertOpenApiDocumentToJsonSchema } from './convertOpenApiDocumentToJsonSchema'; export { convertOpenApiParameterToJsonSchema } from './convertOpenApiParameterToJsonSchema'; export { makeTsJsonSchemaFiles } from './makeTsJsonSchemaFiles'; export { parseId } from './parseId'; diff --git a/test/unit-tests/convertOpenApiToJsonSchema.test.ts b/test/unit-tests/convertOpenApiToJsonSchema.test.ts index e0349db..7942e08 100644 --- a/test/unit-tests/convertOpenApiToJsonSchema.test.ts +++ b/test/unit-tests/convertOpenApiToJsonSchema.test.ts @@ -1,9 +1,9 @@ import { describe, it, expect, vi } from 'vitest'; -import { convertOpenApiToJsonSchema } from '../../src/utils'; +import { convertOpenApiDocumentToJsonSchema } from '../../src/utils'; import * as openapiSchemaToJsonSchema from '@openapi-contrib/openapi-schema-to-json-schema'; const openApiDefinition = { - type: 'string', + type: 'string' as const, nullable: true, enum: ['yes', 'no'], }; @@ -13,14 +13,14 @@ const jsonSchemaDefinition = { enum: ['yes', 'no', null], }; -describe('convertOpenApiToJsonSchema', () => { +describe('convertOpenApiDocumentToJsonSchema', () => { describe('Nested definitions', () => { it('convert nested definitions', () => { - const actual = convertOpenApiToJsonSchema({ - foo: { bar: openApiDefinition }, + const actual = convertOpenApiDocumentToJsonSchema({ + components: { schemas: { bar: openApiDefinition } }, }); const expected = { - foo: { bar: jsonSchemaDefinition }, + components: { schemas: { bar: jsonSchemaDefinition } }, }; expect(actual).toEqual(expected); }); @@ -28,16 +28,18 @@ describe('convertOpenApiToJsonSchema', () => { describe('array of definitions', () => { it('convert nested definitions', () => { - const actual = convertOpenApiToJsonSchema({ - foo: { schema: { oneOf: [openApiDefinition, openApiDefinition] } }, + const actual = convertOpenApiDocumentToJsonSchema({ + components: { + schemas: { bar: { oneOf: [openApiDefinition, openApiDefinition] } }, + }, }); - const expected = { - foo: { - schema: { oneOf: [jsonSchemaDefinition, jsonSchemaDefinition] }, + components: { + schemas: { + bar: { oneOf: [jsonSchemaDefinition, jsonSchemaDefinition] }, + }, }, }; - expect(actual).toEqual(expected); }); }); @@ -50,7 +52,7 @@ describe('convertOpenApiToJsonSchema', () => { type: ['string'], }, }; - const actual = convertOpenApiToJsonSchema(definition); + const actual = convertOpenApiDocumentToJsonSchema(definition); expect(actual).toEqual(definition); }); }); @@ -61,7 +63,7 @@ describe('convertOpenApiToJsonSchema', () => { in: 'path', name: 'userId', }; - const actual = convertOpenApiToJsonSchema(definition); + const actual = convertOpenApiDocumentToJsonSchema(definition); expect(actual).toEqual(definition); }); }); @@ -80,7 +82,7 @@ describe('convertOpenApiToJsonSchema', () => { }, ], }; - const actual = convertOpenApiToJsonSchema(definition); + const actual = convertOpenApiDocumentToJsonSchema(definition); expect(actual).toEqual(definition); }); }); @@ -91,23 +93,27 @@ describe('convertOpenApiToJsonSchema', () => { type: 'http', scheme: 'bearer', }; - const actual = convertOpenApiToJsonSchema(definition); + const actual = convertOpenApiDocumentToJsonSchema(definition); expect(actual).toEqual(definition); }); }); describe('OpenAPI definition as object prop (entities converted multiple times)', () => { it('convert nested definitions', () => { - const actual = convertOpenApiToJsonSchema({ - schemaName: { - type: 'object', - properties: { - two: { + const actual = convertOpenApiDocumentToJsonSchema({ + components: { + schemas: { + schemaName: { type: 'object', properties: { - three: { - type: 'string', - nullable: true, + two: { + type: 'object', + properties: { + three: { + type: 'string', + nullable: true, + }, + }, }, }, }, @@ -116,18 +122,22 @@ describe('convertOpenApiToJsonSchema', () => { }); const expected = { - schemaName: { - properties: { - two: { + components: { + schemas: { + schemaName: { properties: { - three: { - type: ['string', 'null'], + two: { + properties: { + three: { + type: ['string', 'null'], + }, + }, + type: 'object', }, }, type: 'object', }, }, - type: 'object', }, }; expect(actual).toEqual(expected); @@ -136,24 +146,35 @@ describe('convertOpenApiToJsonSchema', () => { describe('Object with "type" prop (#211)', () => { it('convert object definitions', () => { - const actual = convertOpenApiToJsonSchema({ - type: 'object', - properties: { - type: { type: 'string', nullable: true }, - bar: { type: 'string' }, + const actual = convertOpenApiDocumentToJsonSchema({ + components: { + schemas: { + foo: { + type: 'object', + properties: { + type: { type: 'string', nullable: true }, + bar: { type: 'string' }, + }, + required: ['type', 'bar'], + }, + }, }, - required: ['type', 'bar'], }); const expected = { - type: 'object', - properties: { - type: { type: ['string', 'null'] }, - bar: { type: 'string' }, + components: { + schemas: { + foo: { + type: 'object', + properties: { + type: { type: ['string', 'null'] }, + bar: { type: 'string' }, + }, + required: ['type', 'bar'], + }, + }, }, - required: ['type', 'bar'], }; - expect(actual).toEqual(expected); }); }); @@ -166,10 +187,17 @@ describe('convertOpenApiToJsonSchema', () => { }); expect(() => { - convertOpenApiToJsonSchema({ - type: 'object', - properties: { - bar: { type: 'invalid-type' }, + convertOpenApiDocumentToJsonSchema({ + components: { + schemas: { + foo: { + type: 'object', + properties: { + // @ts-expect-error Deliberately testing invalid definition types + bar: { type: 'invalid-type' }, + }, + }, + }, }, }); }).toThrowError(