From 13c233f8601cb150162d87b51556b65e83aad711 Mon Sep 17 00:00:00 2001 From: "K.Himeno" Date: Thu, 14 Jan 2021 18:01:05 +0900 Subject: [PATCH] chore: fix bugs and update docs (#6) * fix: example * docs: add playground * fix: additionalProperties bugs * fix: add typeAlias description --- README.md | 2 + docs/ja/README-ja.md | 2 + example/sample-axios.ts | 20 +------- example/sample-debug.ts | 21 +------- example/sample-fetch.ts | 22 ++------ example/sample-superagent.ts | 20 +------- example/utils.ts | 24 +++++++++ src/Converter/v3/components/Schema.ts | 1 + src/Converter/v3/components/Schemas.ts | 1 + src/Converter/v3/toTypeNode.ts | 50 ++++++++----------- .../__snapshots__/snapshot-test.ts.snap | 19 +++++++ test/api.test.domain/index.yml | 23 +++++++++ 12 files changed, 102 insertions(+), 103 deletions(-) create mode 100644 example/utils.ts diff --git a/README.md b/README.md index a722eabd..5ed295dd 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ The hierarchical structure of the directory is converted to the hierarchical str ## Usage +- [Playground](https://himenon.github.io/openapi-typescript-code-generator-playground/index.html) + ## Installation ```bash diff --git a/docs/ja/README-ja.md b/docs/ja/README-ja.md index 684b58f6..a234a15e 100644 --- a/docs/ja/README-ja.md +++ b/docs/ja/README-ja.md @@ -7,6 +7,8 @@ ## 使い方 +- [Playground](https://himenon.github.io/openapi-typescript-code-generator-playground/index.html) + ### インストール ```bash diff --git a/example/sample-axios.ts b/example/sample-axios.ts index ee211c64..89f73324 100644 --- a/example/sample-axios.ts +++ b/example/sample-axios.ts @@ -1,7 +1,7 @@ -import * as Formatter from "@himenon/openapi-parameter-formatter"; import * as axios from "axios"; import { ApiClient, Client, HttpMethod, ObjectLike, QueryParameters } from "./client"; +import { generateQueryString } from "./utils"; export interface RequestOption { retries?: number; @@ -9,22 +9,6 @@ export interface RequestOption { deadline?: number; } -const generateQueryString = (queryParameters: QueryParameters | undefined): string | undefined => { - if (!queryParameters) { - return undefined; - } - return Object.entries(queryParameters).reduce((queryString, [key, item]) => { - if (!item.style) { - return queryString + "&" + `${key}=${item}`; - } - const result = Formatter.QueryParameter.generate(key, item as Formatter.QueryParameter.Parameter); - if (result) { - return queryString + "&" + result; - } - return queryString; - }, ""); -}; - const convertHttpMethodToAxiosMethod = (httpMethod: HttpMethod): axios.Method => { const patterns: { [key in HttpMethod]: axios.Method } = { GET: "GET", @@ -49,7 +33,7 @@ const apiClientImpl: ApiClient = { options?: RequestOption, ): Promise => { const query = generateQueryString(queryParameters); - const requestUrl = query ? url + "&" + query : url; + const requestUrl = query ? url + "?" + encodeURI(query) : url; const response = await axios.default.request({ url: requestUrl, method: convertHttpMethodToAxiosMethod(httpMethod), diff --git a/example/sample-debug.ts b/example/sample-debug.ts index 81a08482..3fdbaa4b 100644 --- a/example/sample-debug.ts +++ b/example/sample-debug.ts @@ -1,27 +1,10 @@ -import * as Formatter from "@himenon/openapi-parameter-formatter"; - import { ApiClient, Client, HttpMethod, ObjectLike, QueryParameters } from "./client"; +import { generateQueryString } from "./utils"; export interface RequestOption { timeout?: number; } -const generateQueryString = (queryParameters: QueryParameters | undefined): string | undefined => { - if (!queryParameters) { - return undefined; - } - return Object.entries(queryParameters).reduce((queryString, [key, item]) => { - if (!item.style) { - return queryString + "&" + `${key}=${item}`; - } - const result = Formatter.QueryParameter.generate(key, item as Formatter.QueryParameter.Parameter); - if (result) { - return queryString + "&" + result; - } - return queryString; - }, ""); -}; - const apiClientImpl: ApiClient = { request: async ( httpMethod: HttpMethod, @@ -32,7 +15,7 @@ const apiClientImpl: ApiClient = { options?: RequestOption, ): Promise => { const query = generateQueryString(queryParameters); - const requestUrl = query ? url + "&" + query : url; + const requestUrl = query ? url + "?" + encodeURI(query) : url; console.log({ httpMethod, url, diff --git a/example/sample-fetch.ts b/example/sample-fetch.ts index be86c289..4080d9a2 100644 --- a/example/sample-fetch.ts +++ b/example/sample-fetch.ts @@ -1,28 +1,12 @@ -import * as Formatter from "@himenon/openapi-parameter-formatter"; import fetch from "node-fetch"; import { ApiClient, Client, HttpMethod, ObjectLike, QueryParameters } from "./client"; +import { generateQueryString } from "./utils"; export interface RequestOption { timeout?: number; } -const generateQueryString = (queryParameters: QueryParameters | undefined): string | undefined => { - if (!queryParameters) { - return undefined; - } - return Object.entries(queryParameters).reduce((queryString, [key, item]) => { - if (!item.style) { - return queryString + "&" + `${key}=${item}`; - } - const result = Formatter.QueryParameter.generate(key, item as Formatter.QueryParameter.Parameter); - if (result) { - return queryString + "&" + result; - } - return queryString; - }, ""); -}; - const apiClientImpl: ApiClient = { request: async ( httpMethod: HttpMethod, @@ -33,9 +17,9 @@ const apiClientImpl: ApiClient = { options?: RequestOption, ): Promise => { const query = generateQueryString(queryParameters); - const requestUrl = query ? url + "&" + query : url; + const requestUrl = query ? url + "?" + encodeURI(query) : url; const response = await fetch(requestUrl, { - body: requestBody, + body: JSON.stringify(requestBody), headers, method: httpMethod, timeout: options?.timeout, diff --git a/example/sample-superagent.ts b/example/sample-superagent.ts index 408c1f48..12d5a12f 100644 --- a/example/sample-superagent.ts +++ b/example/sample-superagent.ts @@ -1,7 +1,7 @@ -import * as Formatter from "@himenon/openapi-parameter-formatter"; import * as Superagent from "superagent"; import { ApiClient, Client, HttpMethod, ObjectLike, QueryParameters } from "./client"; +import { generateQueryString } from "./utils"; export interface RequestOption { retries?: number; @@ -9,22 +9,6 @@ export interface RequestOption { deadline?: number; } -const generateQueryString = (queryParameters: QueryParameters | undefined): string | undefined => { - if (!queryParameters) { - return undefined; - } - return Object.entries(queryParameters).reduce((queryString, [key, item]) => { - if (!item.style) { - return queryString + "&" + `${key}=${item}`; - } - const result = Formatter.QueryParameter.generate(key, item as Formatter.QueryParameter.Parameter); - if (result) { - return queryString + "&" + result; - } - return queryString; - }, ""); -}; - const apiClientImpl: ApiClient = { request: ( httpMethod: HttpMethod, @@ -35,7 +19,7 @@ const apiClientImpl: ApiClient = { options?: RequestOption, ): Promise => { const query = generateQueryString(queryParameters); - const requestUrl = query ? url + "&" + query : url; + const requestUrl = query ? url + "?" + encodeURI(query) : url; return new Promise((resolve, reject) => { const agent = Superagent; diff --git a/example/utils.ts b/example/utils.ts new file mode 100644 index 00000000..93d56ca7 --- /dev/null +++ b/example/utils.ts @@ -0,0 +1,24 @@ +import * as Formatter from "@himenon/openapi-parameter-formatter"; + +import { QueryParameters } from "./client"; + +export const generateQueryString = (queryParameters: QueryParameters | undefined): string | undefined => { + if (!queryParameters) { + return undefined; + } + const queries = Object.entries(queryParameters).reduce((queryStringList, [key, item]) => { + if (!item.value) { + return queryStringList; + } + if (!item.style) { + return queryStringList.concat(`${key}=${item.value}`); + } + const result = Formatter.QueryParameter.generate(key, item as Formatter.QueryParameter.Parameter); + if (result) { + return queryStringList.concat(result); + } + return queryStringList; + }, []); + + return queries.join("&"); +}; diff --git a/src/Converter/v3/components/Schema.ts b/src/Converter/v3/components/Schema.ts index c508dea2..da221013 100644 --- a/src/Converter/v3/components/Schema.ts +++ b/src/Converter/v3/components/Schema.ts @@ -109,6 +109,7 @@ export const generateTypeAlias = ( export: true, name, type, + comment: schema.description, }); }; diff --git a/src/Converter/v3/components/Schemas.ts b/src/Converter/v3/components/Schemas.ts index 431b961d..9c6b0ec3 100644 --- a/src/Converter/v3/components/Schemas.ts +++ b/src/Converter/v3/components/Schemas.ts @@ -56,6 +56,7 @@ export const generateNamespace = ( value: factory.TypeAliasDeclaration.create({ export: true, name: name, + comment: reference.data.description, type: factory.TypeReferenceNode.create({ name: context.getReferenceName(currentPoint, reference.path), }), diff --git a/src/Converter/v3/toTypeNode.ts b/src/Converter/v3/toTypeNode.ts index dabad4f7..1e015f8b 100644 --- a/src/Converter/v3/toTypeNode.ts +++ b/src/Converter/v3/toTypeNode.ts @@ -175,44 +175,36 @@ export const convert: Convert = ( return nullable(factory, typeNode, !!schema.nullable); } case "object": { - if (!schema.properties) { - return factory.TypeNode.create({ - type: "object", - value: [], - }); - } - let typeNode: ts.TypeNode; const required: string[] = schema.required || []; - // https://swagger.io/docs/specification/data-models/dictionaries/#free-form + // // https://swagger.io/docs/specification/data-models/dictionaries/#free-form if (schema.additionalProperties === true) { - typeNode = factory.TypeNode.create({ + return factory.TypeNode.create({ type: schema.type, value: [], }); - } else { - const value: ts.PropertySignature[] = Object.entries(schema.properties).map(([name, jsonSchema]) => { - return factory.PropertySignature.create({ - name, - type: convert(entryPoint, currentPoint, factory, jsonSchema, context, { parent: schema.properties }), - optional: !required.includes(name), - comment: typeof jsonSchema !== "boolean" ? jsonSchema.description : undefined, - }); + } + const value: ts.PropertySignature[] = Object.entries(schema.properties || {}).map(([name, jsonSchema]) => { + return factory.PropertySignature.create({ + name, + type: convert(entryPoint, currentPoint, factory, jsonSchema, context, { parent: schema.properties }), + optional: !required.includes(name), + comment: typeof jsonSchema !== "boolean" ? jsonSchema.description : undefined, }); - if (schema.additionalProperties) { - const additionalProperties = factory.IndexSignatureDeclaration.create({ - name: "key", - type: convert(entryPoint, currentPoint, factory, schema.additionalProperties, context, { parent: schema.properties }), - }); - return factory.TypeNode.create({ - type: schema.type, - value: [...value, additionalProperties], - }); - } - typeNode = factory.TypeNode.create({ + }); + if (schema.additionalProperties) { + const additionalProperties = factory.IndexSignatureDeclaration.create({ + name: "key", + type: convert(entryPoint, currentPoint, factory, schema.additionalProperties, context, { parent: schema.properties }), + }); + return factory.TypeNode.create({ type: schema.type, - value, + value: [...value, additionalProperties], }); } + const typeNode = factory.TypeNode.create({ + type: schema.type, + value, + }); return nullable(factory, typeNode, !!schema.nullable); } default: diff --git a/test/__tests__/__snapshots__/snapshot-test.ts.snap b/test/__tests__/__snapshots__/snapshot-test.ts.snap index 6f70cca9..8f4829ae 100644 --- a/test/__tests__/__snapshots__/snapshot-test.ts.snap +++ b/test/__tests__/__snapshots__/snapshot-test.ts.snap @@ -12,6 +12,7 @@ exports[`Generate Code Snapshot Test api.test.domain 1`] = ` /** @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#schemaObject */ export namespace Schemas { + /** String Literal */ export type StringType = string; export type StringHasEnumType = \\"a\\" | \\"A\\" | \\"b\\" | \\"B\\" | \\"c\\" | \\"C\\"; export type StringDateType = string; @@ -20,12 +21,14 @@ export namespace Schemas { export type StringByteType = string; export type StringBinaryType = string; export type StringWithPatternType = string; + /** Number Literal */ export type NumberType = number; export type NumberHasEnumType = 1 | 2 | 3 | 100 | 123 | 0.1 | -0.1 | 0; export type NumberInt32Type = number; export type NumberInt64Type = number; export type NumberFloat = number; export type NumberDouble = number; + /** Boolean Literal */ export type BooleanType = boolean; export type ArrayStringType = string[]; export type ArrayNumberType = number[]; @@ -64,8 +67,10 @@ export namespace Schemas { export type RemoteString = string; export type RemoteRefString = Schemas.RemoteString; export namespace Level1 { + /** Level 1 */ export type RemoteBoolean = boolean; export namespace Level2 { + /** Level 2 */ export type RemoteNumber = number; export namespace Level3 { /** Level 3 */ @@ -80,9 +85,13 @@ export namespace Schemas { } } } + /** Level 1 */ export type RemoteRefBoolean = Schemas.Level1.RemoteBoolean; + /** Level 2 */ export type RemoteRefNumber = Schemas.Level1.Level2.RemoteNumber; + /** Level 3 */ export type RemoteRefArray = Schemas.Level1.Level2.Level3.RemoteArray; + /** Level 4 */ export type RemoteRefObject = Schemas.Level1.Level2.Level3.Level4.RemoteObject; export namespace DirectRef { export type ForHeader = string; @@ -218,6 +227,16 @@ export namespace Parameters { export type RemoteReferenceB = Parameters.level1.B; /** parameters -> schemas */ export type ReferenceOfParameterToSchema = Schemas.DirectRef.ForParameters; + /** deepObject */ + export type DeepObjectParameter = { + [key: string]: { + gt?: string; + gte?: string; + lt?: string; + lte?: string; + any?: string | number | boolean; + }; + }; } /** @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#componentsObject */ export namespace RequestBodies { diff --git a/test/api.test.domain/index.yml b/test/api.test.domain/index.yml index 15f2b198..04638691 100644 --- a/test/api.test.domain/index.yml +++ b/test/api.test.domain/index.yml @@ -205,6 +205,29 @@ components: name: ForReference schema: $ref: "./components/schemas/DirectRef/ForParameters.yml" + DeepObjectParameter: + description: deepObject + required: true + in: query + name: filter + schema: + type: object + additionalProperties: + type: object + properties: + gt: + type: string + gte: + type: string + lt: + type: string + lte: + type: string + any: + oneOf: + - type: string + - type: number + - type: boolean responses: Continue: description: |