import JSON5 from 'json5' ; import Mock from 'mockjs' ; import path from 'path' ; import prettier from 'prettier' ; import toJsonSchema from 'to-json-schema' ; import { castArray , forOwn , isArray , isEmpty , isObject , mapKeys , memoize , omit , run , } from 'vtils' ; import { compile , Options } from 'json-schema-to-typescript' ; import { Defined , OneOrMore } from 'vtils/types' ; import { FileData } from './helpers' ; import { Interface , Method , PropDefinition , PropDefinitions , RequestBodyType , RequestFormItemType , Required , ResponseBodyType , } from './types' ; import { JSONSchema4 , JSONSchema4TypeName } from 'json-schema' ; param msg export function throwError ( ... msg ) { throw new Error ( msg . join ( '' ) ) ; } param path returns export function toUnixPath ( path ) { return path . replace ( /[/\\]+/g , '/' ) ; } param from param to returns export function getNormalizedRelativePath ( from , to ) { return toUnixPath ( path . relative ( path . dirname ( from ) , to ) ) . replace ( /^(?=[^.])/ , './' ) . replace ( /\.(ts|js)x?$/i , '' ) ; } param jsonSchema returns export function processJsonSchema < T extends JSONSchema4 > ( jsonSchema , customTypeMapping ) { if ( ! isObject ( jsonSchema ) ) return jsonSchema ; delete jsonSchema . title ; delete jsonSchema . id ; delete jsonSchema . minItems ; delete jsonSchema . maxItems ; if ( jsonSchema . type === 'object' ) { jsonSchema . additionalProperties = false ; } delete jsonSchema . $ref ; delete jsonSchema . $$ref ; delete jsonSchema . default ; if ( jsonSchema . type ) { const typeMapping = { byte : 'integer' , short : 'integer' , int : 'integer' , long : 'integer' , float : 'number' , double : 'number' , bigdecimal : 'number' , char : 'string' , void : 'null' , ... mapKeys ( customTypeMapping , ( _ , key ) => key . toLowerCase ( ) ) , } ; const isMultiple = Array . isArray ( jsonSchema . type ) ; const types = castArray ( jsonSchema . type ) . map ( type => { type = type . toLowerCase ( ) as any ; type = typeMapping [ type ] || type ; return type ; } ) ; jsonSchema . type = isMultiple ? types : types [ 0 ] ; } if ( isArray ( jsonSchema . properties ) ) { jsonSchema . properties = ( jsonSchema . properties as JSONSchema4 [ ] ) . reduce < Defined < JSONSchema4 [ 'properties' ] > > ( ( props , js ) => { props [ js . name ] = js ; return props ; } , { } ) ; } if ( jsonSchema . properties ) { forOwn ( jsonSchema . properties , ( _ , prop ) => { const propDef = jsonSchema . properties ! [ prop ] ; delete jsonSchema . properties ! [ prop ] ; jsonSchema . properties ! [ ( prop as string ) . trim ( ) ] = propDef ; } ) ; jsonSchema . required = jsonSchema . required && jsonSchema . required . map ( prop => prop . trim ( ) ) ; } if ( jsonSchema . properties ) { forOwn ( jsonSchema . properties , item => processJsonSchema ( item , customTypeMapping ) ) ; } if ( jsonSchema . items ) { castArray ( jsonSchema . items ) . forEach ( item => processJsonSchema ( item , customTypeMapping ) ) ; } if ( jsonSchema . oneOf ) { jsonSchema . oneOf . forEach ( item => processJsonSchema ( item , customTypeMapping ) ) ; } if ( jsonSchema . anyOf ) { jsonSchema . anyOf . forEach ( item => processJsonSchema ( item , customTypeMapping ) ) ; } if ( jsonSchema . allOf ) { jsonSchema . allOf . forEach ( item => processJsonSchema ( item , customTypeMapping ) ) ; } return jsonSchema ; } param str returns export function jsonSchemaStringToJsonSchema ( str , customTypeMapping ) { return processJsonSchema ( JSON . parse ( str ) , customTypeMapping ) ; } param json returns export function jsonToJsonSchema ( json , customTypeMapping ) { const schema = toJsonSchema ( json , { required : false , arrays : { mode : 'first' , } , objects : { additionalProperties : false , } , strings : { detectFormat : false , } , postProcessFnc : ( type , schema , value ) => { if ( ! schema . description && ! ! value && type !== 'object' ) { schema . description = JSON . stringify ( value ) ; } return schema ; } , } ) ; delete schema . description ; return processJsonSchema ( schema as any , customTypeMapping ) ; } param template returns export function mockjsTemplateToJsonSchema ( template , customTypeMapping ) { return processJsonSchema ( Mock . toJSONSchema ( template ) as any , customTypeMapping ) ; } param propDefinitions returns export function propDefinitionsToJsonSchema ( propDefinitions , customTypeMapping ) { return processJsonSchema ( { type : 'object' , required : propDefinitions . reduce < string [ ] > ( ( res , prop ) => { if ( prop . required ) { res . push ( prop . name ) ; } return res ; } , [ ] ) , properties : propDefinitions . reduce < Exclude < JSONSchema4 [ 'properties' ] , undefined > > ( ( res , prop ) => { res [ prop . name ] = { type : prop . type , description : prop . comment , ... ( prop . type === ( 'file' as any ) ? { tsType : FileData . name } : { } ) , } ; return res ; } , { } ) , } , customTypeMapping ) ; } const JSTTOptions = { bannerComment : '' , style : { bracketSpacing : false , printWidth : 120 , semi : true , singleQuote : true , tabWidth : 2 , trailingComma : 'none' , useTabs : false , } , } ; param jsonSchema param typeName returns export async function jsonSchemaToType ( jsonSchema , typeName ) { if ( isEmpty ( jsonSchema ) ) { return `export interface ${ typeName } {}` ; } if ( jsonSchema . __is_any__ ) { delete jsonSchema . __is_any__ ; return `export type ${ typeName } = any` ; } const fakeTypeName = 'THISISAFAKETYPENAME' ; const code = await compile ( omit ( jsonSchema , [ 'description' ] ) , fakeTypeName , JSTTOptions ) ; delete jsonSchema . id ; return code . replace ( fakeTypeName , typeName ) . trim ( ) ; } export function getRequestDataJsonSchema ( interfaceInfo , customTypeMapping ) { let jsonSchema ; if ( isPostLikeMethod ( interfaceInfo . method ) ) { switch ( interfaceInfo . req_body_type ) { case RequestBodyType . form : jsonSchema = propDefinitionsToJsonSchema ( interfaceInfo . req_body_form . map < PropDefinition > ( item => ( { name : item . name , required : item . required === Required . true , type : ( item . type === RequestFormItemType . file ? 'file' : 'string' ) as any , comment : item . desc , } ) ) , customTypeMapping ) ; break ; case RequestBodyType . json : if ( interfaceInfo . req_body_other ) { jsonSchema = interfaceInfo . req_body_is_json_schema ? jsonSchemaStringToJsonSchema ( interfaceInfo . req_body_other , customTypeMapping ) : jsonToJsonSchema ( JSON5 . parse ( interfaceInfo . req_body_other ) , customTypeMapping ) ; } break ; default : break ; } } if ( isArray ( interfaceInfo . req_query ) && interfaceInfo . req_query . length ) { const queryJsonSchema = propDefinitionsToJsonSchema ( interfaceInfo . req_query . map < PropDefinition > ( item => ( { name : item . name , required : item . required === Required . true , type : item . type || 'string' , comment : item . desc , } ) ) , customTypeMapping ) ; if ( jsonSchema ) { jsonSchema . properties = { ... jsonSchema . properties , ... queryJsonSchema . properties , } ; jsonSchema . required = [ ... ( jsonSchema . required || [ ] ) , ... ( queryJsonSchema . required || [ ] ) , ] ; } else { jsonSchema = queryJsonSchema ; } } if ( isArray ( interfaceInfo . req_params ) && interfaceInfo . req_params . length ) { const paramsJsonSchema = propDefinitionsToJsonSchema ( interfaceInfo . req_params . map < PropDefinition > ( item => ( { name : item . name , required : true , type : item . type || 'string' , comment : item . desc , } ) ) , customTypeMapping ) ; if ( jsonSchema ) { jsonSchema . properties = { ... jsonSchema . properties , ... paramsJsonSchema . properties , } ; jsonSchema . required = [ ... ( jsonSchema . required || [ ] ) , ... ( paramsJsonSchema . required || [ ] ) , ] ; } else { jsonSchema = paramsJsonSchema ; } } return jsonSchema || { } ; } export function getResponseDataJsonSchema ( interfaceInfo , customTypeMapping , dataKey ? ) { let jsonSchema = { } ; switch ( interfaceInfo . res_body_type ) { case ResponseBodyType . json : if ( interfaceInfo . res_body ) { jsonSchema = interfaceInfo . res_body_is_json_schema ? jsonSchemaStringToJsonSchema ( interfaceInfo . res_body , customTypeMapping ) : mockjsTemplateToJsonSchema ( JSON5 . parse ( interfaceInfo . res_body ) , customTypeMapping ) ; } break ; default : jsonSchema = { __is_any__ : true } ; break ; } if ( dataKey && jsonSchema ) { jsonSchema = reachJsonSchema ( jsonSchema , dataKey ) ; } return jsonSchema ; } export function reachJsonSchema ( jsonSchema , path ) { let last = jsonSchema ; for ( const segment of castArray ( path ) ) { const _last = last . properties ?. [ segment ] ; if ( ! _last ) { return jsonSchema ; } last = _last ; } return last ; } export function sortByWeights < T extends { weights : number [ ] ; } > ( list ) { list . sort ( ( a , b ) => { const x = a . weights . length > b . weights . length ? b : a ; const minLen = Math . min ( a . weights . length , b . weights . length ) ; const maxLen = Math . max ( a . weights . length , b . weights . length ) ; x . weights . push ( ... new Array ( maxLen - minLen ) . fill ( 0 ) ) ; const w = a . weights . reduce ( ( w , _ , i ) => { if ( w === 0 ) { w = a . weights [ i ] - b . weights [ i ] ; } return w ; } , 0 ) ; return w ; } ) ; return list ; } export function isGetLikeMethod ( method ) { return ( method === Method . GET || method === Method . OPTIONS || method === Method . HEAD ) ; } export function isPostLikeMethod ( method ) { return ! isGetLikeMethod ( method ) ; } export async function getPrettierOptions ( ) { const prettierOptions = { parser : 'typescript' , printWidth : 120 , tabWidth : 2 , singleQuote : true , semi : false , trailingComma : 'all' , bracketSpacing : false , endOfLine : 'lf' , } ; if ( process . env . JEST_WORKER_ID ) { return prettierOptions ; } const [ prettierConfigPathErr , prettierConfigPath ] = await run ( ( ) => prettier . resolveConfigFile ( ) ) ; if ( prettierConfigPathErr || ! prettierConfigPath ) { return prettierOptions ; } const [ prettierConfigErr , prettierConfig ] = await run ( ( ) => prettier . resolveConfig ( prettierConfigPath ) ) ; if ( prettierConfigErr || ! prettierConfig ) { return prettierOptions ; } return { ... prettierOptions , ... prettierConfig , parser : 'typescript' , } ; } export const getCachedPrettierOptions = memoize ( getPrettierOptions ) ;