Skip to content

Commit

Permalink
Use the components object and reference objects for named interfaces …
Browse files Browse the repository at this point in the history
…and type aliases

This adds support for recursively defined types, which before this
change crashed typera-openapi.
  • Loading branch information
akheron committed Mar 18, 2022
1 parent 561ea77 commit 30cc2b4
Show file tree
Hide file tree
Showing 7 changed files with 445 additions and 188 deletions.
22 changes: 16 additions & 6 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,11 @@ const main = async () => {
}))

let success = true
for (const { outputFileName, paths } of results) {
let content = args.format === 'ts' ? tsString(paths) : jsonString(paths)
for (const { outputFileName, paths, components } of results) {
let content =
args.format === 'ts'
? tsString(paths, components)
: jsonString(paths, components)
if (args.prettify) {
content = await runPrettier(outputFileName, content)
}
Expand Down Expand Up @@ -133,15 +136,22 @@ const writeOutput = (fileName: string, content: string): void => {
fs.writeFileSync(fileName, content)
}

const tsString = (paths: OpenAPIV3.PathsObject): string => `\
const tsString = (
paths: OpenAPIV3.PathsObject,
components: OpenAPIV3.ComponentsObject
): string => `\
import { OpenAPIV3 } from 'openapi-types'
const spec: { paths: OpenAPIV3.PathsObject } = ${JSON.stringify({ paths })};
const spec: { paths: OpenAPIV3.PathsObject, components: OpenAPIV3.ComponentsObject } = ${JSON.stringify(
{ paths, components }
)};
export default spec;
`

const jsonString = (paths: OpenAPIV3.PathsObject): string =>
JSON.stringify({ paths })
const jsonString = (
paths: OpenAPIV3.PathsObject,
components: OpenAPIV3.ComponentsObject
): string => JSON.stringify({ paths, components })

main()
96 changes: 96 additions & 0 deletions src/components.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import * as ts from 'typescript'
import { OpenAPIV3 } from 'openapi-types'
import { isInterface, isTypeAlias } from './utils'

export class Components {
// undefined acts as a placeholder
#schemas: Map<string, OpenAPIV3.SchemaObject | undefined>
#symbolSchemas: Map<ts.Symbol, string>

constructor() {
this.#schemas = new Map()
this.#symbolSchemas = new Map()
}

withSymbol(
symbol: ts.Symbol | undefined,
run: (
addComponent: () => void
) => OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined
): OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined {
const ref = symbol && this.#getRefForSymbol(symbol)
if (ref) {
return { $ref: ref }
}

if (symbol && (isInterface(symbol) || isTypeAlias(symbol))) {
let added = false
const schema = run(() => {
this.#addSymbol(symbol)
added = true
})
if (added) {
if (schema === undefined || '$ref' in schema) {
this.#deleteSymbol(symbol)
return schema
} else {
return { $ref: this.#addSchema(symbol, schema) }
}
} else {
return schema
}
} else {
return run(() => {})
}
}

#getRefForSymbol(symbol: ts.Symbol): string | undefined {
const schemaName = this.#symbolSchemas.get(symbol)
return schemaName !== undefined
? `#/components/schemas/${schemaName}`
: undefined
}

#addSymbol(symbol: ts.Symbol) {
if (this.#symbolSchemas.has(symbol)) return

let name = symbol.name
for (let i = 2; ; i++) {
if (this.#schemas.has(name)) {
name = `${symbol.name}${i}`
} else {
break
}
}

this.#schemas.set(name, undefined)
this.#symbolSchemas.set(symbol, name)
}

#deleteSymbol(symbol: ts.Symbol) {
const name = this.#symbolSchemas.get(symbol)
if (name === undefined) return

this.#schemas.delete(name)
this.#symbolSchemas.delete(symbol)
}

#addSchema(symbol: ts.Symbol, schema: OpenAPIV3.SchemaObject): string {
const name = this.#symbolSchemas.get(symbol)
if (name === undefined)
throw new Error(`No schema has been added for symbol ${symbol.name}`)
this.#schemas.set(name, schema)
return `#/components/schemas/${name}`
}

build(): OpenAPIV3.ComponentsObject {
const schemas = Object.fromEntries(
[...this.#schemas.entries()].flatMap(([k, v]) =>
v !== undefined ? [[k, v]] : []
)
)
return {
...(this.#schemas.size > 0 ? { schemas } : undefined),
}
}
}
Loading

0 comments on commit 30cc2b4

Please sign in to comment.