import { createGenerator } from "ts-json-schema-generator" ; import { Config } from "./types" ; import { getConfig } from "./config" ; import { extractDefinedProperties , overwriteObject } from "./utils" ; import { toYAML } from "./yaml" ; import { convertNullToNullable } from "./json-schema" ; type SchemaType = ReturnType < ReturnType < typeof createGenerator > [ "createSchema" ] > ; type Definitions = SchemaType [ "definitions" ] ; function isExpandable ( key , expandableTypes ) { return expandableTypes . some ( type => key . startsWith ( `${ type }<` ) ) ; } function modifySchema ( schema , config ) { const { expandTypes , removeProps , detectGraphQL , graphQLExpandedTypes } = config ; if ( ! schema . definitions ) return ; const { definitions } = schema ; const expandableTypes = Object . keys ( definitions ) . filter ( name => isExpandable ( name , expandTypes ) ) ; const expandName = ( name ) => `#/definitions/${ encodeURIComponent ( name ) }` ; const renamedRef = ( ref ) => ref . replace ( "#/definitions/" , "#/components/schemas/" ) ; const expandableTypeMap = expandableTypes . reduce ( ( prev , cur ) => Object . assign ( prev , { [ expandName ( cur ) ] : definitions [ cur ] } ) , { } ) ; if ( detectGraphQL ) { Object . keys ( definitions ) . filter ( key => isExpandable ( key , graphQLExpandedTypes ) ) . forEach ( key => { const type = definitions [ key ] as Record < string , any > ; convertNullToNullable ( type ) ; } ) ; } const recurseSplitTypes = ( obj ) => { if ( ! obj ?. type && ! obj . anyOf && ! obj . allOf && ! obj . oneOf ) return ; const { type } = obj ; const isMaybeObject = Array . isArray ( type ) ? type . includes ( "object" ) : type === "object" ; const isMaybeArray = Array . isArray ( type ) ? type . includes ( "array" ) : type === "array" ; if ( isMaybeObject ) { if ( typeof obj . properties === "object" ) Object . values ( obj . properties ) . forEach ( prop => recurseSplitTypes ( prop ) ) ; if ( typeof obj . additionalProperties === "object" ) Object . values ( obj . additionalProperties ) . forEach ( prop => recurseSplitTypes ( prop ) ) ; if ( typeof obj . patternProperties === "object" ) Object . values ( obj . patternProperties ) . forEach ( prop => recurseSplitTypes ( prop ) ) ; } if ( isMaybeArray ) { if ( typeof obj . items === "object" ) { if ( Array . isArray ( obj . items ) ) ( obj . items as Array < any > ) . forEach ( item => recurseSplitTypes ( obj . items ) ) ; else recurseSplitTypes ( obj . items ) ; } if ( typeof obj . contains === "object" ) recurseSplitTypes ( obj . contains ) ; if ( typeof obj . additionalItems === "object" ) recurseSplitTypes ( obj . additionalItems ) ; } if ( Array . isArray ( obj . anyOf ) ) obj . anyOf . forEach ( ( item ) => recurseSplitTypes ( item ) ) ; if ( Array . isArray ( obj . allOf ) ) obj . allOf . forEach ( ( item ) => recurseSplitTypes ( item ) ) ; if ( Array . isArray ( obj . oneOf ) ) obj . oneOf . forEach ( ( item ) => recurseSplitTypes ( item ) ) ; if ( ! Array . isArray ( type ) ) return ; if ( type . length === 1 ) { obj . type = type [ 0 ] ; } else { const objectProperties = extractDefinedProperties ( obj , [ 'properties' , 'additionalProperties' , 'patternProperties' , 'required' , ] ) ; const arrayProperties = extractDefinedProperties ( obj , [ 'items' , 'contains' , 'additionalItems' , ] ) ; obj . anyOf = ( type as Array < string > ) . map ( type => ( { type , ... ( type === "object" ? objectProperties : { } ) , ... ( type === "array" ? arrayProperties : { } ) , } ) ) ; delete obj . type ; } } ; Object . values ( definitions ) . forEach ( def => recurseSplitTypes ( def ) ) ; const recurseExpandTypes = ( obj ) => { if ( typeof obj !== "object" ) return ; const toRemove = [ ] ; for ( const [ key , value ] of Object . entries ( obj ) ) { if ( value && typeof value === "object" ) { if ( Array . isArray ( value ) ) value . forEach ( ( value ) => { recurseExpandTypes ( value ) ; } ) ; else recurseExpandTypes ( value ) ; } if ( key === "$ref" && typeof value === "string" ) { if ( expandableTypeMap [ value ] ) { overwriteObject ( obj , expandableTypeMap [ value ] ) ; return ; } else obj [ key ] = renamedRef ( value ) ; } if ( removeProps . includes ( key ) ) toRemove . push ( key ) ; } toRemove . forEach ( key => { delete obj [ key ] ; } ) ; } ; recurseExpandTypes ( definitions ) ; expandableTypes . forEach ( type => { delete definitions [ type ] ; } ) ; } export function convert ( opts ) { const { config , schemaGeneratorConfig } = getConfig ( opts ) ; const generator = createGenerator ( schemaGeneratorConfig ) ; const definitions = [ ] ; config . types . forEach ( type => { const schema = generator . createSchema ( type ) ; modifySchema ( schema , config ) ; definitions . push ( schema . definitions ) ; } ) ; let full = { components : { schemas : Object . assign ( { } , ... definitions ) } } ; if ( config . jsonFormat ) { full = Object . assign ( { openapi : "3.0.0" , info : { version : config . modelVersion , title : config . types [ 0 ] , } , paths : { } , } , full ) ; } return config . jsonFormat ? JSON . stringify ( full , null , 2 ) : toYAML ( full , config ) ; }