Skip to content

Commit

Permalink
feat: push openapi v2 source parser
Browse files Browse the repository at this point in the history
  • Loading branch information
hairyf committed Feb 24, 2023
1 parent c9975d1 commit a6cff98
Show file tree
Hide file tree
Showing 20 changed files with 589 additions and 25 deletions.
15 changes: 10 additions & 5 deletions packages/apipgen/typings/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StatementFunction, StatementImported, StatementInterface, StatementTypeAlias, StatementVariable } from './statement'
import type { StatementFunction, StatementImported, StatementInterface, StatementTypeAlias, StatementVariable } from './statement'

namespace ApiPipeline {
export interface Output {
Expand Down Expand Up @@ -33,7 +33,7 @@ namespace ApiPipeline {
/** @default 'src/api/index.ts' */
main?: string
/** @default 'src/api/index.type.ts' */
type?: string
type?: string | false
}
}

Expand Down Expand Up @@ -66,6 +66,12 @@ namespace ApiPipeline {
}

export interface Graphs {

/**
* all comments
*/
comments?: string[]

/**
* all api options
*/
Expand All @@ -79,12 +85,10 @@ namespace ApiPipeline {
* all request variables
*/
variables?: StatementVariable[]

/**
* all request typings
*/
typings?: StatementTypeAlias[]

/**
* all request interfaces
*/
Expand Down Expand Up @@ -121,6 +125,7 @@ namespace ApiPipeline {
export type Pipeline = (config: ApiPipeline.Config) => Promise<void>
}

export { StatementFunction, StatementImported, StatementInterface, StatementTypeAlias, StatementVariable, ApiPipeline }
export type { ApiPipeline }
export type { LiteralField, StatementField, StatementFunction, StatementImported, StatementInterface, StatementTypeAlias, StatementVariable } from './statement'

export default ApiPipeline
26 changes: 18 additions & 8 deletions packages/apipgen/typings/statement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export interface StatementFunction {
/**
* function params
*/
parameters?: StatementFiled[]
parameters?: StatementField[]
/**
* function block
*/
Expand All @@ -29,48 +29,58 @@ export interface StatementInterface {
/**
* all properties
*/
properties?: StatementFiled[]
properties?: StatementField[]
/**
* is export
*/
export?: boolean
}

export interface StatementFiled {
export interface StatementField {
/**
* filed name
* field name
*/
name: string
/**
* filed type
* field type
*/
type?: string
/**
* is required
*/
required?: boolean
/**
* filed description
* field description
*/
description?: string | string[]
}

/**
* @example import [name], {[names]} from [value]
* @example import http, { AxiosConfig } from 'axios'
*/
export interface StatementImported {
name?: string
names?: string[]
namespace?: boolean
value: string
}

/**
* @example [export] [flag] [name] = [value]
*/
export interface StatementVariable {
export?: boolean
flag: 'let' | 'const' | 'var'
name: string
value?: string
}

/**
* @example [export] type [name] = [value]
*/
export interface StatementTypeAlias {
export?: boolean
name: string
value: string
}

export type LiteralFiled = string | [string | '...', string]
7 changes: 5 additions & 2 deletions packages/apipgen/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
/* eslint-disable @typescript-eslint/no-var-requires */
import path from 'path'
import { cwd } from 'process'
import type ApiPipeline from '../typings'

export function inPipeline(pipe: string): ApiPipeline.Pipeline | undefined {
const inputs = [`apipgen-${pipe}`, absolutePath(pipe)]
for (const input of inputs) {
if (require(input))
return require(input)
const inputModule = require(input)
const pipeline = inputModule.default || inputModule
if (pipeline)
return pipeline
}
}

Expand Down
10 changes: 8 additions & 2 deletions packages/swag-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,16 @@
},
"dependencies": {
"got": "^12.5.3",
"p-pipe": "^3"
"lodash": "^4.17.21",
"p-pipe": "^3",
"pascal-case": "^3.1.2",
"transliteration": "^2.3.5",
"ts-factory-extra": "^0.0.1"
},
"devDependencies": {
"apipgen": "workspace:*"
"@types/lodash": "^4.14.191",
"apipgen": "workspace:*",
"openapi-specification-types": "^0.0.1"
},
"keywords": [],
"author": "",
Expand Down
5 changes: 5 additions & 0 deletions packages/swag-ts/src/compiler/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { ApiPipeline } from 'apipgen'

export function compiler(configRead: ApiPipeline.ConfigRead) {
return configRead
}
12 changes: 8 additions & 4 deletions packages/swag-ts/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export function readConfig(config: ApiPipeline.Config): ApiPipeline.ConfigRead {
config.output = config.output || {}
config.import.http = config.import.http || 'axios'
config.output.main = config.output.main || 'src/api/index.ts'
config.output.type = config.output.type || config.output.main.replace(/\.ts|\.js/g, '.type.ts')
if (config.output.type !== false)
config.output.type = config.output.type || config.output.main.replace(/\.ts|\.js/g, '.type.ts')
config.responseType = config.responseType || 'T'

const imports: (StatementImported | false)[] = [
Expand All @@ -37,13 +38,16 @@ export function readConfig(config: ApiPipeline.Config): ApiPipeline.ConfigRead {
import: config.output.main.replace(/\.ts$/, ''),
path: path.join(USER_ROOT, config.output.main),
},
{
]

if (config.output.type !== false) {
outputs.push({
type: 'typings',
root: path.join(USER_ROOT, path.dirname(config.output.type)),
import: prefix(path.relative(path.dirname(config.output.main), config.output.type)),
path: path.join(USER_ROOT, config.output.type),
},
]
})
}

const inputs: ApiPipeline.Inputs = {}

Expand Down
4 changes: 4 additions & 0 deletions packages/swag-ts/src/dest/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { ApiPipeline } from 'apipgen'

export function dest(configRead: ApiPipeline.ConfigRead) {
}
5 changes: 5 additions & 0 deletions packages/swag-ts/src/generate/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { ApiPipeline } from 'apipgen'

export function generate(configRead: ApiPipeline.ConfigRead) {
return configRead
}
7 changes: 7 additions & 0 deletions packages/swag-ts/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import type { ApiPipeline } from 'apipgen'
import { pipeline } from 'apipgen'
import { compiler } from './compiler'
import { readConfig } from './config'
import { dest } from './dest'
import { generate } from './generate'
import { original } from './original'
import { parser } from './parser'

function OpenAPI2Typescript(config: ApiPipeline.Config) {
const process = pipeline(
config => readConfig(config),
configRead => original(configRead),
configRead => parser(configRead),
// TODO
configRead => compiler(configRead),
// TODO
configRead => generate(configRead),
// TODO
configRead => dest(configRead),
)
return process(config)
Expand Down
4 changes: 4 additions & 0 deletions packages/swag-ts/src/original/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,9 @@ export async function original(configRead: ApiPipeline.ConfigRead) {
configRead.source = await got.get(configRead.inputs.uri).json<any>()
if (configRead.inputs.json)
configRead.source = configRead.inputs.json

if (!configRead.source)
throw new Error('Please enter source')

return configRead
}
120 changes: 120 additions & 0 deletions packages/swag-ts/src/parser/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import camelCase from 'lodash/camelCase'
import type { ApiPipeline, StatementFunction, StatementInterface, StatementTypeAlias } from 'apipgen'
import type { OpenAPISpecificationV2, Schema } from 'openapi-specification-types'
import { getFunctionOptions, getPropertieType, traversePaths } from './utils'
import { varName } from './utils/format'
import { literalFieldsToString } from './utils/other'

export function parser(configRead: ApiPipeline.ConfigRead) {
const source = configRead.source as OpenAPISpecificationV2

const comments = [
`@title ${source.info.title}`,
`@description ${source.info.description}`,
source.swagger && `@swagger ${source.swagger}`,
`@version ${source.info.version}`,
]
const typings: StatementTypeAlias[] = [
{ export: true, name: 'Response<T>', value: 'T' },
]
const interfaces: StatementInterface[] = []
const functions: StatementFunction[] = []

function transformPaths() {
traversePaths(source.paths, (config) => {
const { method, path, options: meta } = config
/**
* function params/function options/function use interfaces
*/
const { parameters, interfaces: interfaceUses, options } = getFunctionOptions(config)

interfaces.push(...interfaceUses)

/**
* function comments
*/
const comments = [
meta.summary && `@summary ${meta.summary}`,
meta.description && `@description ${meta.description}`,
`@method ${method}`,
meta.tags && `@tags ${meta.tags.join(' | ') || '-'}`,
meta.consumes && `@consumes ${meta.consumes.join('; ') || '-'}`,
]
/**
* function name
*/
const name = camelCase(`${method}/${path}`)
const url = `${path.replace(/({)/g, '${paths?.')}`

/**
* response type
*/
const responseType = meta.responses['200'] ? getPropertieType(meta.responses['200']) : 'void'
const prefixType = configRead.config.output?.type === false ? '' : 'OpenAPITypes.'
const genericType = `${prefixType}Response<${spliceTypeSpace(responseType)}>`

functions.push({
export: true,
name,
description: comments.filter(Boolean),
parameters,
body: [
url.includes('$') ? `const url = \`${url}\`;` : `const url = "${url}"`,
`http.request<${genericType}>>({ ${literalFieldsToString(options)} })`,
],
})
})
}

function transformDefinitions() {
for (const [name, definition] of Object.entries(source.definitions)) {
const { properties = {} } = definition

interfaces.push({
export: true,
name: varName(name),
properties: Object.keys(properties).map(name => defToFields(name, properties[name])),
})

function defToFields(name: string, propertie: Schema) {
propertie.required = definition?.required?.some(v => v === name)
if (propertie.description)
propertie.description = `@description ${propertie.description}`
return {
name,
type: getPropertieType(propertie),
description: propertie.description,
required: propertie.required,
}
}
}
}

function transformNameSpace() {
for (const iterator of functions) {
for (const parameter of iterator.parameters || []) {
if (parameter.type)
parameter.type = spliceTypeSpace(parameter.type)
}
}
}

function spliceTypeSpace(name: string) {
const isRenderType = configRead.config.output?.type !== false
const isSomeType = interfaces.map(v => v.name).includes(name.replace('[]', ''))
if (isRenderType && isSomeType)
return `OpenAPITypes.${name}`
return name
}

transformPaths()
transformDefinitions()
transformNameSpace()

configRead.graphs.comments = comments
configRead.graphs.functions = functions
configRead.graphs.typings = typings
configRead.graphs.interfaces = interfaces

return configRead
}
Loading

0 comments on commit a6cff98

Please sign in to comment.