diff --git a/src/Converter/v3/Generator.ts b/src/Converter/v3/Generator.ts index 208ecaaf..68887b49 100644 --- a/src/Converter/v3/Generator.ts +++ b/src/Converter/v3/Generator.ts @@ -14,7 +14,7 @@ const extractPickedParameter = (parameter: OpenApi.Parameter): PickedParameter = }; }; -const extractSuccessStatusCode = (responses: { [statusCode: string]: OpenApi.Response }): string[] => { +const extractResponseNamesByStatusCode = (type: "success" | "error", responses: { [statusCode: string]: OpenApi.Response }): string[] => { const statusCodeList: string[] = []; Object.entries(responses || {}).forEach(([statusCodeLike, response]) => { // ContentTypeの定義が存在しない場合はstatusCodeを読み取らない @@ -23,8 +23,14 @@ const extractSuccessStatusCode = (responses: { [statusCode: string]: OpenApi.Res } if (typeof statusCodeLike === "string") { const statusCodeNumberValue = parseInt(statusCodeLike, 10); - if (200 <= statusCodeNumberValue && statusCodeNumberValue < 300) { - statusCodeList.push(statusCodeNumberValue.toString()); + if (type === "success") { + if (200 <= statusCodeNumberValue && statusCodeNumberValue < 300) { + statusCodeList.push(statusCodeNumberValue.toString()); + } + } else if (type === "error") { + if (400 <= statusCodeNumberValue && statusCodeNumberValue < 600) { + statusCodeList.push(statusCodeNumberValue.toString()); + } } } }); @@ -37,7 +43,7 @@ const getRequestContentTypeList = (requestBody: OpenApi.RequestBody): string[] = const getSuccessResponseContentTypeList = (responses: { [statusCode: string]: OpenApi.Response }): string[] => { let contentTypeList: string[] = []; - extractSuccessStatusCode(responses).forEach(statusCode => { + extractResponseNamesByStatusCode("success", responses).forEach(statusCode => { const response = responses[statusCode]; contentTypeList = contentTypeList.concat(Object.keys(response.content || {})); }); @@ -51,11 +57,21 @@ const hasQueryParameters = (parameters?: OpenApi.Parameter[]): boolean => { return parameters.filter(parameter => parameter.in === "query").length > 0; }; -const generateCodeGeneratorParamsList = (store: Store.Type, converterContext: ConverterContext.Types): CodeGeneratorParams[] => { +const generateCodeGeneratorParamsList = ( + store: Store.Type, + converterContext: ConverterContext.Types, + allowOperationIds: string[] | undefined, +): CodeGeneratorParams[] => { const operationState = store.getNoReferenceOperationState(); const params: CodeGeneratorParams[] = []; Object.entries(operationState).forEach(([operationId, item]) => { - const responseSuccessNames = extractSuccessStatusCode(item.responses).map(statusCode => + if (allowOperationIds && !allowOperationIds.includes(operationId)) { + return; + } + const responseSuccessNames = extractResponseNamesByStatusCode("success", item.responses).map(statusCode => + converterContext.generateResponseName(operationId, statusCode), + ); + const responseErrorNames = extractResponseNamesByStatusCode("error", item.responses).map(statusCode => converterContext.generateResponseName(operationId, statusCode), ); const requestContentTypeList = item.requestBody ? getRequestContentTypeList(item.requestBody) : []; @@ -65,6 +81,7 @@ const generateCodeGeneratorParamsList = (store: Store.Type, converterContext: Co const formatParams: CodeGeneratorParams = { operationId: operationId, + escapedOperationId: converterContext.escapeOperationIdText(operationId), rawRequestUri: item.requestUri, httpMethod: item.httpMethod, argumentParamsTypeDeclaration: converterContext.generateArgumentParamsTypeDeclaration(operationId), @@ -88,6 +105,7 @@ const generateCodeGeneratorParamsList = (store: Store.Type, converterContext: Co responseSuccessNames: responseSuccessNames, responseFirstSuccessName: responseSuccessNames.length === 1 ? responseSuccessNames[0] : undefined, has2OrMoreSuccessNames: hasOver2SuccessNames, + responseErrorNames: responseErrorNames, // Response Success Content Type successResponseContentTypes: responseSuccessContentTypes, successResponseFirstContentType: responseSuccessContentTypes.length === 1 ? responseSuccessContentTypes[0] : undefined, @@ -113,7 +131,8 @@ export const generateApiClientCode = ( context: ts.TransformationContext, converterContext: ConverterContext.Types, rewriteCodeAfterTypeDeclaration: RewriteCodeAfterTypeDeclaration, + allowOperationIds: string[] | undefined, ): void => { - const codeGeneratorParamsList = generateCodeGeneratorParamsList(store, converterContext); + const codeGeneratorParamsList = generateCodeGeneratorParamsList(store, converterContext, allowOperationIds); store.addAdditionalStatement(rewriteCodeAfterTypeDeclaration(context, codeGeneratorParamsList)); }; diff --git a/src/Converter/v3/index.ts b/src/Converter/v3/index.ts index be807b55..c3a2e3c0 100644 --- a/src/Converter/v3/index.ts +++ b/src/Converter/v3/index.ts @@ -28,6 +28,8 @@ export interface Option { * It is possible to rewrite the implementation after the type declaration. */ rewriteCodeAfterTypeDeclaration: Generator.RewriteCodeAfterTypeDeclaration; + + allowOperationIds?: string[]; } export const create = (entryPoint: string, rootSchema: OpenApi.Document, noReferenceOpenApiSchema: OpenApi.Document, option: Option): Type => { @@ -97,7 +99,7 @@ export const create = (entryPoint: string, rootSchema: OpenApi.Document, noRefer } if (rootSchema.paths) { Paths.generateStatements(entryPoint, currentPoint, store, factory, rootSchema.paths, toTypeNodeContext, converterContext); - Generator.generateApiClientCode(store, context, converterContext, option.rewriteCodeAfterTypeDeclaration); + Generator.generateApiClientCode(store, context, converterContext, option.rewriteCodeAfterTypeDeclaration, option.allowOperationIds); } return store.getRootStatements(); }; diff --git a/src/Converter/v3/types/CodeGeneratorParams.ts b/src/Converter/v3/types/CodeGeneratorParams.ts index 9f87147a..09b68dba 100644 --- a/src/Converter/v3/types/CodeGeneratorParams.ts +++ b/src/Converter/v3/types/CodeGeneratorParams.ts @@ -4,6 +4,7 @@ export type PickedParameter = Pick 1 + // Response Error Response Name + responseErrorNames: string[]; // Response Success Name responseSuccessNames: string[]; // `Response$${operationId}$Status$${statusCode}`[] responseFirstSuccessName: string | undefined; // responseSuccessNames.length === 1 only diff --git a/src/DefaultCodeTemplate/ApiClientClass/ApiClientInterface.ts b/src/DefaultCodeTemplate/ApiClientClass/ApiClientInterface.ts index 9ff92d7e..c6db3751 100644 --- a/src/DefaultCodeTemplate/ApiClientClass/ApiClientInterface.ts +++ b/src/DefaultCodeTemplate/ApiClientClass/ApiClientInterface.ts @@ -5,6 +5,27 @@ import { CodeGeneratorParams } from "../../Converter/v3"; const httpMethodList: string[] = ["GET", "PUT", "POST", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"]; +const createErrorResponsesTypeAlias = (typeName: string, factory: Factory.Type, errorResponseNames: string[]) => { + if (errorResponseNames.length === 0) { + return factory.TypeAliasDeclaration.create({ + export: true, + name: typeName, + type: ts.factory.createToken(ts.SyntaxKind.VoidKeyword), + }); + } + return factory.TypeAliasDeclaration.create({ + export: true, + name: typeName, + type: factory.UnionTypeNode.create({ + typeNodes: errorResponseNames.map(name => { + return factory.TypeReferenceNode.create({ + name, + }); + }), + }), + }); +}; + const createSuccessResponseTypeAlias = (typeName: string, factory: Factory.Type, successResponseNames: string[]) => { if (successResponseNames.length === 0) { return factory.TypeAliasDeclaration.create({ @@ -134,6 +155,14 @@ export const create = (factory: Factory.Type, list: CodeGeneratorParams[]): ts.S const successResponseNames = list.map(item => item.responseSuccessNames).flat(); + const errorResponseNamespace = factory.Namespace.create({ + export: true, + name: "ErrorResponse", + statements: list.map(item => { + return createErrorResponsesTypeAlias(`${item.escapedOperationId}`, factory, item.responseErrorNames); + }), + }); + const functionType = factory.FunctionTypeNode.create({ typeParameters: [ factory.TypeParameterDeclaration.create({ @@ -165,6 +194,7 @@ export const create = (factory: Factory.Type, list: CodeGeneratorParams[]): ts.S createObjectLikeInterface(factory), ...createQueryParamsDeclarations(factory), createSuccessResponseTypeAlias("SuccessResponses", factory, successResponseNames), + errorResponseNamespace, factory.InterfaceDeclaration.create({ export: true, name: "ApiClient", diff --git a/src/index.ts b/src/index.ts index dcf2a8da..49d8f7b7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,11 +17,20 @@ export interface Params { /** default: true */ enableValidate?: boolean; log?: { - validator?: Validator.v3.LogOption; + validator?: { + /** + * default: undefined (all logs) + * Number of lines displayed in the latest log + */ + displayLogLines?: number; + }; + }; + filter?: { + allowOperationIds?: string[]; }; } -export const generateTypeScriptCode = ({ entryPoint, option, enableValidate = true, log }: Params): string => { +export const generateTypeScriptCode = ({ entryPoint, option, enableValidate = true, log, filter = {} }: Params): string => { const schema = fileSystem.loadJsonOrYaml(entryPoint); const resolvedReferenceDocument = ResolveReference.resolve(entryPoint, entryPoint, JSON.parse(JSON.stringify(schema))); @@ -30,8 +39,14 @@ export const generateTypeScriptCode = ({ entryPoint, option, enableValidate = tr } const convertOption: Converter.v3.Option = option - ? { rewriteCodeAfterTypeDeclaration: option.rewriteCodeAfterTypeDeclaration || DefaultCodeTemplate.rewriteCodeAfterTypeDeclaration } - : { rewriteCodeAfterTypeDeclaration: DefaultCodeTemplate.rewriteCodeAfterTypeDeclaration }; + ? { + rewriteCodeAfterTypeDeclaration: option.rewriteCodeAfterTypeDeclaration || DefaultCodeTemplate.rewriteCodeAfterTypeDeclaration, + allowOperationIds: filter.allowOperationIds, + } + : { + rewriteCodeAfterTypeDeclaration: DefaultCodeTemplate.rewriteCodeAfterTypeDeclaration, + allowOperationIds: filter.allowOperationIds, + }; const { createFunction, generateLeadingComment } = Converter.v3.create(entryPoint, schema, resolvedReferenceDocument, convertOption); return [generateLeadingComment(), TypeScriptCodeGenerator.generate(createFunction)].join(EOL + EOL + EOL); }; diff --git a/test/__tests__/__snapshots__/snapshot-test.ts.snap b/test/__tests__/__snapshots__/snapshot-test.ts.snap index ac18dcef..255dfbc4 100644 --- a/test/__tests__/__snapshots__/snapshot-test.ts.snap +++ b/test/__tests__/__snapshots__/snapshot-test.ts.snap @@ -345,6 +345,12 @@ export interface QueryParameters { [key: string]: QueryParameter; } export type SuccessResponses = Response$getIncludeLocalReference$Status$200 | Response$getFullRemoteReference$Status$200 | Response$getReferenceItems$Status$200; +export namespace ErrorResponse { + export type getIncludeLocalReference = void; + export type getIncludeRemoteReference = void; + export type getFullRemoteReference = void; + export type getReferenceItems = void; +} export interface ApiClient { request: (httpMethod: HttpMethod, url: string, headers: ObjectLike | any, requestBody: ObjectLike | any, queryParameters: QueryParameters | undefined, options?: RequestOption) => Promise; }