Archived
1
0
Fork 0

implement $ref support for responses, parameters, requestBody

This commit is contained in:
sup39 2020-05-24 15:15:53 +09:00
parent ff1c909dc7
commit 771c34b8c8
7 changed files with 123 additions and 52 deletions

View file

@ -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
View file

@ -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
View file

@ -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
View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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",