Created
July 14, 2023 10:44
-
-
Save solesensei/7b3af164c9ef990a1166e9e4b2c00010 to your computer and use it in GitHub Desktop.
Swagger V2 to TypeScript Schemas
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const fetch = require('node-fetch'); | |
const fs = require('fs'); | |
const BASE_URL = 'http://localhost:8080'; | |
const SERVICES = ['api', 'api/dt/v1']; | |
function JSONstringifyOrder(obj, space) { | |
var allKeys = []; | |
JSON.stringify(obj, function (key, value) { | |
allKeys.push(key); | |
return value; | |
}); | |
allKeys.sort(); | |
return JSON.stringify(obj, allKeys, space); | |
} | |
function genQueryInterface(operationId) { | |
return 'T' + upperCaseFirst(operationId) + 'Query'; | |
} | |
function genBodyInterface(operationId) { | |
return 'T' + upperCaseFirst(operationId) + 'Body'; | |
} | |
function upperCaseFirst(str) { | |
return str.charAt(0).toUpperCase() + str.slice(1); | |
} | |
function genPathCall(method, path, params) { | |
const queryParams = params.filter((p) => p.in === 'query'); | |
const bodyParams = params.filter((p) => p.in === 'body'); | |
return ( | |
`\`${path.replace(/\{[^}]*\}/g, (e) => `\$${e}`)}\`` + | |
(queryParams.length === 0 ? '' : ' + formatParams(queryParams as any)') + | |
(bodyParams.length === 0 | |
? ', { signal }' | |
: ", { method: '" + | |
method.toUpperCase() + | |
"', body: JSON.stringify(data), signal, headers: { 'Content-Type': 'application/json' } }") | |
); | |
} | |
function allOf(ao) { | |
if (ao.length === 1) { | |
return ao[0]; | |
} else { | |
return `(${ao.map((i) => getInterfaceName(i)).join(' | ')})`; | |
} | |
} | |
function genParamTypeNonNullable(param) { | |
if (param.type === 'boolean') { | |
return 'boolean'; | |
} else if (param.type === 'string') { | |
if (param.enum) { | |
return param.enum.map((v) => `'${v}'`).join(' | '); | |
} else { | |
return 'string'; | |
} | |
} else if (param.type === 'enum') { | |
return param.enum.map((v) => `'${v}'`).join(' | '); | |
} else if (param.type === 'integer' || param.type === 'number') { | |
return 'number'; | |
} else if (param.type === 'array') { | |
if (param.items.enum && param.items.type === 'string') { | |
return `(${genParamType(param.items)})[]`; | |
} | |
if (param.items.type) { | |
return `${genParamType(param.items)}[]`; | |
} | |
if (param.items.$ref) { | |
return `${getInterfaceName(param.items.$ref)}[]`; | |
} | |
const types = param.items.allOf.map((d) => getInterfaceName(d.$ref)); | |
if (types.length > 1) { | |
return `(${types.join(' | ')})[]`; | |
} else { | |
return `${types[0]}[]`; | |
} | |
} else if (param.allOf) { | |
return param.allOf.map((i) => getInterfaceName(i.$ref)).join(' | '); | |
} else if (param.type === 'object' && param.additionalProperties) { | |
return `{[key: string]: ${genParamType(param.additionalProperties)}}`; | |
} else if (param.$ref) { | |
return getInterfaceName(param.$ref); | |
} else if (param.type === 'object' && param.additionalProperties) { | |
return `{[id: string]: ${allOf(param.additionalProperties.allOf)}}`; | |
} else if (param.type === 'object') { | |
return 'Object'; | |
} | |
throw new Error(`no type ${param.type} ${JSON.stringify(param)}`); | |
} | |
function genParamType(param) { | |
if (param['x-nullable']) { | |
return `${genParamTypeNonNullable(param)} | null`; | |
} else { | |
return genParamTypeNonNullable(param); | |
} | |
} | |
const formatParams = ` | |
function formatParams(queryParams: {[key: string]: (string | boolean | number) | (string | boolean | number)[]}) { | |
const params = Object.entries(queryParams) | |
if (!params.length) return ''; | |
return '?' + params.map(([name, value]) => { | |
if(Array.isArray(value)) | |
return \`\${name}=\${value.map(encodeURIComponent).join(',')}\` | |
return \`\${name}=\${encodeURIComponent(value)}\` | |
}).join('&') | |
} | |
`; | |
const apiFetch = ` | |
function apiFetch<T>(path: string, params: RequestInit = {}) { | |
return fetch(path, params) | |
.then(async response => { | |
const res = await response.json() | |
if(response.status >= 200 && response.status < 300) | |
return res as T | |
throw res | |
}) | |
} | |
`; | |
function getInterfaceName(ref) { | |
return ref.replace(/^.*\//g, ''); | |
} | |
function compareString(a, b) { | |
if (a < b) { | |
return -1; | |
} else if (a > b) { | |
return 1; | |
} else { | |
return 0; | |
} | |
} | |
function compareFirstString(a, b) { | |
return compareString(a[0], b[0]); | |
} | |
Promise.all( | |
SERVICES.map((service) => | |
fetch(`${BASE_URL}/${service}/docs/openapi.json`).then((r) => r.json()), | |
), | |
).then((responses) => { | |
response = { | |
definitions: Object.assign({}, ...responses.map((r) => r.definitions)), | |
paths: Object.assign({}, ...responses.map((r) => r.paths)), | |
}; | |
const paths = Object.keys(response.paths); | |
let entryPoints = []; | |
paths.forEach((path) => { | |
const pathObj = response.paths[path]; | |
if (pathObj.get) { | |
entryPoints.push({}); | |
} | |
}); | |
const sortedDefinitions = Object.entries(response.definitions).sort(compareFirstString); | |
const data = | |
'// DO NOT EDIT: Generated by npm run gen_api\n' + | |
formatParams + | |
apiFetch + | |
'export class SwaggerApi {\n' + | |
Object.entries(response.paths) | |
.map(([path, methods]) => { | |
return Object.entries(methods) | |
.map(([method, entryPoint]) => { | |
let params = entryPoint.parameters | |
.filter((p) => p.in === 'path') | |
.map((p) => `${p.name}: ${genParamType(p)}`); | |
const queryParams = entryPoint.parameters.filter((p) => p.in === 'query'); | |
if (queryParams.length > 0) { | |
params.push( | |
`queryParams: ${genQueryInterface(entryPoint.operationId)}`, | |
); | |
} | |
const bodyParams = entryPoint.parameters.filter((p) => p.in === 'body'); | |
if (bodyParams.length > 0) { | |
params.push(`data: ${genBodyInterface(entryPoint.operationId)}`); | |
} | |
params.push(`signal: AbortSignal | null = null`); | |
const schema = entryPoint.responses['200'].schema; | |
return ( | |
` /**\n` + | |
` * ${entryPoint.summary}\n` + | |
` * ${entryPoint.description}\n` + | |
` */\n` + | |
` ${entryPoint.operationId} (${params.join(', ')}) {\n` + | |
` return apiFetch<${ | |
schema ? schema.$ref.replace(/^.*\//g, '') : 'unknown' | |
}>(${genPathCall(method, path, entryPoint.parameters)})\n` + | |
` }\n` | |
); | |
}) | |
.join(''); | |
}) | |
.join('') + | |
'}\n\n' + | |
Object.entries(response.paths) | |
.map(([path, methods]) => | |
Object.entries(methods) | |
.filter( | |
([_, entry]) => | |
entry.parameters.filter((p) => p.in === 'query').length !== 0, | |
) | |
.map(([_method, entryPoint]) => { | |
return ( | |
`interface ${genQueryInterface(entryPoint.operationId)} {\n` + | |
entryPoint.parameters | |
.filter((p) => p.in === 'query') | |
.map((p) => { | |
return ( | |
(p.description | |
? ` /**\n` + ` * ${p.description}\n */\n` | |
: ``) + ` ${p.name}?: ${genParamType(p)}\n` | |
); | |
}) | |
.join('') + | |
`}\n\n` | |
); | |
}) | |
.join(''), | |
) | |
.join('') + | |
`\n` + | |
Object.entries(response.paths) | |
.map(([path, methods]) => | |
Object.entries(methods) | |
.filter( | |
([_, entry]) => | |
entry.parameters.filter((p) => p.in === 'body').length !== 0, | |
) | |
.map(([_method, entryPoint]) => { | |
return ( | |
`interface ${genBodyInterface(entryPoint.operationId)} {\n` + | |
entryPoint.parameters | |
.filter((p) => p.in === 'body') | |
.map((p) => { | |
return Object.entries(p.schema.properties) | |
.map(([fieldName, meta]) => { | |
return ( | |
(meta.description | |
? ` /**\n` + | |
` * ${meta.description}\n */\n` | |
: ``) + | |
` ${fieldName}${ | |
meta.required ? '' : '?' | |
}: ${genParamType(meta)}\n` | |
); | |
}) | |
.join(''); | |
}) | |
.join('') + | |
`}\n\n` | |
); | |
}) | |
.join(''), | |
) | |
.join('') + | |
sortedDefinitions | |
.map(([name, definition]) => { | |
if (definition.type === 'object') { | |
return ( | |
`export interface ${name} {\n` + | |
Object.entries(definition.properties) | |
.sort(compareFirstString) | |
.map(([fieldName, meta]) => { | |
return ( | |
(meta.description | |
? ` /**\n` + ` * ${meta.description}\n */\n` | |
: ``) + | |
` ${fieldName}${ | |
(definition.required || []).indexOf(fieldName) !== -1 | |
? '' | |
: '?' | |
}: ${genParamType(meta)}\n` | |
); | |
}) | |
.join('') + | |
`}\n` | |
); | |
} | |
}) | |
.join('\n'); | |
fs.writeFileSync('./src/services/swagger.json', JSONstringifyOrder(response, 3), 'utf-8'); | |
fs.writeFile('./src/services/SwaggerApi.ts', data, (e) => { | |
console.log('finished'); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
OpenApi V3