implement $ref support for responses, parameters, requestBody
This commit is contained in:
parent
ff1c909dc7
commit
771c34b8c8
7 changed files with 123 additions and 52 deletions
|
@ -519,6 +519,8 @@ This tool only supports `application/json` type for request and response body. A
|
|||
Other $ref like requestBody, responseBody are not supported currently.
|
||||
|
||||
## Versions
|
||||
#### 2.0.5
|
||||
- implement \$ref support for responses, parameters, requestBody
|
||||
#### 2.0.4
|
||||
- fix FullDate stringify in Axios params
|
||||
- use local timezone instead of UTC in FullDate
|
||||
|
|
31
dist/OpenAPI.d.ts
vendored
31
dist/OpenAPI.d.ts
vendored
|
@ -1,3 +1,6 @@
|
|||
declare type Dict<T> = {
|
||||
[_: string]: T;
|
||||
};
|
||||
export interface OpenAPI {
|
||||
paths: Paths;
|
||||
components?: Components;
|
||||
|
@ -14,14 +17,11 @@ interface PathItem {
|
|||
[_: string]: any;
|
||||
}
|
||||
interface Operation {
|
||||
responses: Responses;
|
||||
parameters?: Parameter[];
|
||||
requestBody?: RequestBody;
|
||||
responses: Dict<Response | Reference>;
|
||||
parameters?: Array<Parameter | Reference>;
|
||||
requestBody?: RequestBody | Reference;
|
||||
operationId?: string;
|
||||
}
|
||||
interface Responses {
|
||||
[status: string]: Response;
|
||||
}
|
||||
interface Response {
|
||||
content?: TMediaTypes;
|
||||
}
|
||||
|
@ -48,20 +48,16 @@ declare type EParameterIn = 'query' | 'header' | 'path' | 'cookie';
|
|||
export declare const ELParameterIn: Array<EParameterIn>;
|
||||
interface RequestBody {
|
||||
description: string;
|
||||
content: {
|
||||
[contentType: string]: MediaType;
|
||||
};
|
||||
content: Dict<MediaType>;
|
||||
required?: boolean;
|
||||
}
|
||||
interface Components {
|
||||
schemas: {
|
||||
[_: string]: Schema | Reference;
|
||||
};
|
||||
schemas: Dict<Schema | Reference>;
|
||||
responses: Dict<Response | Reference>;
|
||||
parameters: Dict<Parameter | Reference>;
|
||||
requestBodies: Dict<RequestBody | Reference>;
|
||||
}
|
||||
export declare type Schemas = {
|
||||
[_: string]: Schema | Reference;
|
||||
};
|
||||
interface Schema {
|
||||
export interface Schema {
|
||||
type: string;
|
||||
format?: string;
|
||||
nullable?: boolean;
|
||||
|
@ -79,7 +75,7 @@ interface ObjectSchema extends Schema {
|
|||
};
|
||||
}
|
||||
export declare function isObjectSchema(x: any): x is ObjectSchema;
|
||||
interface Reference {
|
||||
export interface Reference {
|
||||
$ref: string;
|
||||
maxSize?: string | number;
|
||||
}
|
||||
|
@ -108,6 +104,7 @@ declare type TReqTypes = {
|
|||
declare type TResTypes = {
|
||||
[status: string]: SchemaType;
|
||||
};
|
||||
export declare function resolveRef<T>(obj: T | Reference, dict: Dict<T | Reference> | undefined, prefix: string): T | undefined;
|
||||
export declare class SchemaType {
|
||||
private _required;
|
||||
private _typeName?;
|
||||
|
|
56
dist/OpenAPI.js
vendored
56
dist/OpenAPI.js
vendored
|
@ -28,6 +28,28 @@ var APIFunction = /** @class */ (function () {
|
|||
return APIFunction;
|
||||
}());
|
||||
/* ==== ==== */
|
||||
// Reference
|
||||
function resolveRef(obj, dict, prefix) {
|
||||
do {
|
||||
if (!isReference(obj))
|
||||
return obj;
|
||||
var ref = obj.$ref;
|
||||
if (ref.startsWith(prefix)) {
|
||||
var name_1 = ref.substring(prefix.length + 1); // $prefix/
|
||||
var obj0 = dict === null || dict === void 0 ? void 0 : dict[name_1];
|
||||
if (obj0 === undefined) {
|
||||
console.error("ref not found: " + ref);
|
||||
return;
|
||||
}
|
||||
obj = obj0;
|
||||
}
|
||||
else {
|
||||
console.error("Invalid ref: " + ref + ", expect prefix " + prefix);
|
||||
return;
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
exports.resolveRef = resolveRef;
|
||||
function mediaTypes2type(content, required) {
|
||||
var media = content === null || content === void 0 ? void 0 : content['application/json']; // TODO
|
||||
if (media == null) {
|
||||
|
@ -94,8 +116,8 @@ var SchemaType = /** @class */ (function () {
|
|||
else if (isObjectSchema(schema)) {
|
||||
sType = '{';
|
||||
for (var _i = 0, _b = Object.entries(schema.properties); _i < _b.length; _i++) {
|
||||
var _c = _b[_i], name_1 = _c[0], sub = _c[1];
|
||||
sType += name_1 + ": " + SchemaType.typeNameOf(sub) + ", ";
|
||||
var _c = _b[_i], name_2 = _c[0], sub = _c[1];
|
||||
sType += name_2 + ": " + SchemaType.typeNameOf(sub) + ", ";
|
||||
}
|
||||
sType += '}';
|
||||
}
|
||||
|
@ -137,8 +159,8 @@ var SchemaType = /** @class */ (function () {
|
|||
else if (isObjectSchema(schema)) {
|
||||
sStp = '()=>({';
|
||||
for (var _i = 0, _a = Object.entries(schema.properties); _i < _a.length; _i++) {
|
||||
var _b = _a[_i], name_2 = _b[0], sub = _b[1];
|
||||
sStp += name_2 + ": " + SchemaType.gcStp(para + '.' + name_2, sub, label + '.' + name_2, false) + ", ";
|
||||
var _b = _a[_i], name_3 = _b[0], sub = _b[1];
|
||||
sStp += name_3 + ": " + SchemaType.gcStp(para + '.' + name_3, sub, label + '.' + name_3, false) + ", ";
|
||||
}
|
||||
sStp += '})';
|
||||
}
|
||||
|
@ -188,7 +210,8 @@ var SchemaType = /** @class */ (function () {
|
|||
}());
|
||||
exports.SchemaType = SchemaType;
|
||||
function apiFunctionsOf(openAPI) {
|
||||
var paths = openAPI.paths;
|
||||
var paths = openAPI.paths, comps = openAPI.components;
|
||||
var compPrefix = '#/components/';
|
||||
var functions = {};
|
||||
for (var _i = 0, _a = Object.entries(paths); _i < _a.length; _i++) {
|
||||
var _b = _a[_i], url = _b[0], pathItem = _b[1];
|
||||
|
@ -204,32 +227,41 @@ function apiFunctionsOf(openAPI) {
|
|||
'operationId should be given');
|
||||
continue;
|
||||
}
|
||||
var name_3 = operationId;
|
||||
var name_4 = operationId;
|
||||
var reqTypes = {};
|
||||
var resTypes = {};
|
||||
// reqParas
|
||||
if (parameters != null) {
|
||||
for (var _d = 0, parameters_1 = parameters; _d < parameters_1.length; _d++) {
|
||||
var para = parameters_1[_d];
|
||||
var name_4 = para.name, _in = para.in, required = para.required, schema = para.schema;
|
||||
var rPara = parameters_1[_d];
|
||||
var para = resolveRef(rPara, comps === null || comps === void 0 ? void 0 : comps.parameters, compPrefix + 'parameters');
|
||||
if (para == null)
|
||||
continue;
|
||||
var name_5 = para.name, _in = para.in, required = para.required, schema = para.schema;
|
||||
// add
|
||||
if (reqTypes[_in] == null)
|
||||
reqTypes[_in] = {};
|
||||
reqTypes[_in][name_4] = new SchemaType(schema !== null && schema !== void 0 ? schema : 'any', required !== null && required !== void 0 ? required : false);
|
||||
reqTypes[_in][name_5] = new SchemaType(schema !== null && schema !== void 0 ? schema : 'any', required !== null && required !== void 0 ? required : false);
|
||||
}
|
||||
}
|
||||
// requestBody
|
||||
if (requestBody != null) {
|
||||
reqTypes.body = mediaTypes2type(requestBody.content, requestBody.required);
|
||||
var requestBodyO = resolveRef(requestBody, comps === null || comps === void 0 ? void 0 : comps.requestBodies, compPrefix + 'requestBodies');
|
||||
if (requestBodyO == null)
|
||||
continue;
|
||||
reqTypes.body = mediaTypes2type(requestBodyO.content, requestBodyO.required);
|
||||
}
|
||||
// responses
|
||||
for (var _e = 0, _f = Object.entries(responses); _e < _f.length; _e++) {
|
||||
var _g = _f[_e], status_1 = _g[0], res = _g[1];
|
||||
var _g = _f[_e], status_1 = _g[0], rRes = _g[1];
|
||||
var res = resolveRef(rRes, comps === null || comps === void 0 ? void 0 : comps.responses, compPrefix + 'responses');
|
||||
if (res == null)
|
||||
continue;
|
||||
resTypes[status_1] = mediaTypes2type(res.content, true);
|
||||
}
|
||||
// add to group
|
||||
var saf = new APIFunction(method, url, reqTypes, resTypes);
|
||||
functions[name_3] = saf;
|
||||
functions[name_4] = saf;
|
||||
}
|
||||
}
|
||||
return functions;
|
||||
|
|
5
dist/codegen.js
vendored
5
dist/codegen.js
vendored
|
@ -255,7 +255,10 @@ function codegenSchemas(schemas, config, cp) {
|
|||
cp.writeln("import {FullDate, StrictTypeParser as STP} from '" + utilsTSPath + "'");
|
||||
// schema
|
||||
for (var _i = 0, _b = Object.entries(schemas); _i < _b.length; _i++) {
|
||||
var _c = _b[_i], typeName = _c[0], schema = _c[1];
|
||||
var _c = _b[_i], typeName = _c[0], rSchema = _c[1];
|
||||
var schema = OpenAPI_1.resolveRef(rSchema, schemas, '#/components/schemas');
|
||||
if (schema == null)
|
||||
continue;
|
||||
cp.writeln();
|
||||
if (OpenAPI_1.isObjectSchema(schema)) {
|
||||
// interface
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {StrictTypeParser as STP} from './utils/StrictTypeParser';
|
||||
const warn = (x: any) => console.warn('\x1b[1;33mWarning: '+x+'\x1b[0m');
|
||||
type Dict<T> = {[_: string]: T};
|
||||
|
||||
/* ==== type declaration ==== */
|
||||
export interface OpenAPI {
|
||||
|
@ -22,16 +23,13 @@ interface PathItem {
|
|||
type EMethod = 'get' | 'put' | 'post' | 'delete' | 'patch';
|
||||
const ELMethod: Array<EMethod> = ['get', 'put', 'post', 'delete', 'patch'];
|
||||
interface Operation {
|
||||
responses: Responses;
|
||||
parameters?: Parameter[];
|
||||
requestBody?: RequestBody;
|
||||
responses: Dict<Response | Reference>;
|
||||
parameters?: Array<Parameter | Reference>;
|
||||
requestBody?: RequestBody | Reference;
|
||||
operationId?: string;
|
||||
}
|
||||
|
||||
// response
|
||||
interface Responses {
|
||||
[status: string]: Response // | Reference;
|
||||
}
|
||||
interface Response {
|
||||
// headers?: Header;
|
||||
content?: TMediaTypes;
|
||||
|
@ -60,18 +58,20 @@ export const ELParameterIn: Array<EParameterIn> = [
|
|||
// request body
|
||||
interface RequestBody {
|
||||
description: string;
|
||||
content: {[contentType: string]: MediaType};
|
||||
content: Dict<MediaType>;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
// components
|
||||
interface Components {
|
||||
schemas: {[_: string]: Schema | Reference};
|
||||
schemas: Dict<Schema | Reference>;
|
||||
responses: Dict<Response | Reference>;
|
||||
parameters: Dict<Parameter | Reference>;
|
||||
requestBodies: Dict<RequestBody | Reference>;
|
||||
}
|
||||
|
||||
// schemeType
|
||||
export type Schemas = {[_: string]: Schema | Reference};
|
||||
interface Schema {
|
||||
export interface Schema {
|
||||
type: string;
|
||||
format?: string;
|
||||
nullable?: boolean;
|
||||
|
@ -91,7 +91,7 @@ interface ObjectSchema extends Schema {
|
|||
export function isObjectSchema(x: any): x is ObjectSchema {
|
||||
return x.type === 'object';
|
||||
}
|
||||
interface Reference {
|
||||
export interface Reference {
|
||||
$ref: string;
|
||||
maxSize?: string | number;
|
||||
}
|
||||
|
@ -118,8 +118,31 @@ type TReqTypes = {
|
|||
type TResTypes = {[status: string]: SchemaType};
|
||||
/* ==== ==== */
|
||||
|
||||
function mediaTypes2type(content?: TMediaTypes, required?: boolean):
|
||||
SchemaType {
|
||||
// Reference
|
||||
export function resolveRef<T>(
|
||||
obj: T|Reference, dict: Dict<T|Reference>|undefined, prefix: string,
|
||||
): T | undefined {
|
||||
do {
|
||||
if (!isReference(obj)) return obj;
|
||||
const ref = obj.$ref;
|
||||
if (ref.startsWith(prefix)) {
|
||||
const name = ref.substring(prefix.length+1); // $prefix/
|
||||
const obj0 = dict?.[name];
|
||||
if (obj0 === undefined) {
|
||||
console.error(`ref not found: ${ref}`);
|
||||
return;
|
||||
}
|
||||
obj = obj0;
|
||||
} else {
|
||||
console.error(`Invalid ref: ${ref}, expect prefix ${prefix}`);
|
||||
return;
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
function mediaTypes2type(
|
||||
content?: TMediaTypes, required?: boolean,
|
||||
): SchemaType {
|
||||
const media = content?.['application/json']; // TODO
|
||||
if (media == null) {
|
||||
if (Object.keys(content ?? {}).length > 0) {
|
||||
|
@ -250,7 +273,8 @@ export class SchemaType {
|
|||
|
||||
export type APIFunctions = {[_: string]: APIFunction};
|
||||
export function apiFunctionsOf(openAPI: OpenAPI): APIFunctions {
|
||||
const {paths} = openAPI;
|
||||
const {paths, components: comps} = openAPI;
|
||||
const compPrefix = '#/components/';
|
||||
const functions: APIFunctions = {};
|
||||
for (const [url, pathItem] of Object.entries(paths)) {
|
||||
for (const method of ELMethod) {
|
||||
|
@ -270,7 +294,10 @@ export function apiFunctionsOf(openAPI: OpenAPI): APIFunctions {
|
|||
const resTypes: TResTypes = {};
|
||||
// reqParas
|
||||
if (parameters != null) {
|
||||
for (const para of parameters) {
|
||||
for (const rPara of parameters) {
|
||||
const para = resolveRef(
|
||||
rPara, comps?.parameters, compPrefix+'parameters');
|
||||
if (para == null) continue;
|
||||
const {
|
||||
name, in: _in, required, schema,
|
||||
} = para;
|
||||
|
@ -282,13 +309,18 @@ export function apiFunctionsOf(openAPI: OpenAPI): APIFunctions {
|
|||
}
|
||||
// requestBody
|
||||
if (requestBody != null) {
|
||||
const requestBodyO = resolveRef(
|
||||
requestBody, comps?.requestBodies, compPrefix+'requestBodies');
|
||||
if (requestBodyO == null) continue;
|
||||
reqTypes.body = mediaTypes2type(
|
||||
requestBody.content,
|
||||
requestBody.required,
|
||||
requestBodyO.content,
|
||||
requestBodyO.required,
|
||||
);
|
||||
}
|
||||
// responses
|
||||
for (const [status, res] of Object.entries(responses)) {
|
||||
for (const [status, rRes] of Object.entries(responses)) {
|
||||
const res = resolveRef(rRes, comps?.responses, compPrefix+'responses');
|
||||
if (res == null) continue;
|
||||
resTypes[status] = mediaTypes2type(res.content, true);
|
||||
}
|
||||
// add to group
|
||||
|
|
|
@ -3,9 +3,10 @@ import * as path from 'path';
|
|||
import {Config, ConfigUser, configDefault} from './Config';
|
||||
import {
|
||||
apiFunctionsOf, OpenAPI, APIFunctions as APIFuncs,
|
||||
ELParameterIn, SchemaType, Schemas, isObjectSchema,
|
||||
ELParameterIn, SchemaType, Schema, isObjectSchema, Reference, resolveRef,
|
||||
} from './OpenAPI';
|
||||
import {CodePrinter} from './CodePrinter';
|
||||
type Dict<T> = {[_: string]: T};
|
||||
|
||||
function codegenIHandler(funcs: APIFuncs, config: Config, cp: CodePrinter) {
|
||||
const {
|
||||
|
@ -239,13 +240,17 @@ function codegenClientAPI(funcs: APIFuncs, config: Config, cp: CodePrinter) {
|
|||
return cp.end();
|
||||
}
|
||||
|
||||
function codegenSchemas(schemas: Schemas, config: Config, cp: CodePrinter) {
|
||||
function codegenSchemas(
|
||||
schemas: Dict<Schema|Reference>, config: Config, cp: CodePrinter,
|
||||
) {
|
||||
const {utilsTSPath} = config;
|
||||
// import
|
||||
cp.writeln(
|
||||
`import {FullDate, StrictTypeParser as STP} from '${utilsTSPath}'`);
|
||||
// schema
|
||||
for (const [typeName, schema] of Object.entries(schemas)) {
|
||||
for (const [typeName, rSchema] of Object.entries(schemas)) {
|
||||
const schema = resolveRef(rSchema, schemas, '#/components/schemas');
|
||||
if (schema == null) continue;
|
||||
cp.writeln();
|
||||
if (isObjectSchema(schema)) {
|
||||
// interface
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@sup39/api-ts-gen",
|
||||
"version": "2.0.4-a",
|
||||
"version": "2.0.5",
|
||||
"description": "OpenAPI code generator for TypeScript",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
|
Reference in a new issue