Skip to content

Commit 924c4a1

Browse files
authored
feat(codegen): add error response type name list and add allowOperationIds (#20)
* feat(comment): Use the defined comment * chore(codegen): add error response names * test: update snapshot * feat: add error response namespace * feat: add allow operationId list
1 parent 7d50829 commit 924c4a1

File tree

6 files changed

+87
-12
lines changed

6 files changed

+87
-12
lines changed

src/Converter/v3/Generator.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const extractPickedParameter = (parameter: OpenApi.Parameter): PickedParameter =
1414
};
1515
};
1616

17-
const extractSuccessStatusCode = (responses: { [statusCode: string]: OpenApi.Response }): string[] => {
17+
const extractResponseNamesByStatusCode = (type: "success" | "error", responses: { [statusCode: string]: OpenApi.Response }): string[] => {
1818
const statusCodeList: string[] = [];
1919
Object.entries(responses || {}).forEach(([statusCodeLike, response]) => {
2020
// ContentTypeの定義が存在しない場合はstatusCodeを読み取らない
@@ -23,8 +23,14 @@ const extractSuccessStatusCode = (responses: { [statusCode: string]: OpenApi.Res
2323
}
2424
if (typeof statusCodeLike === "string") {
2525
const statusCodeNumberValue = parseInt(statusCodeLike, 10);
26-
if (200 <= statusCodeNumberValue && statusCodeNumberValue < 300) {
27-
statusCodeList.push(statusCodeNumberValue.toString());
26+
if (type === "success") {
27+
if (200 <= statusCodeNumberValue && statusCodeNumberValue < 300) {
28+
statusCodeList.push(statusCodeNumberValue.toString());
29+
}
30+
} else if (type === "error") {
31+
if (400 <= statusCodeNumberValue && statusCodeNumberValue < 600) {
32+
statusCodeList.push(statusCodeNumberValue.toString());
33+
}
2834
}
2935
}
3036
});
@@ -37,7 +43,7 @@ const getRequestContentTypeList = (requestBody: OpenApi.RequestBody): string[] =
3743

3844
const getSuccessResponseContentTypeList = (responses: { [statusCode: string]: OpenApi.Response }): string[] => {
3945
let contentTypeList: string[] = [];
40-
extractSuccessStatusCode(responses).forEach(statusCode => {
46+
extractResponseNamesByStatusCode("success", responses).forEach(statusCode => {
4147
const response = responses[statusCode];
4248
contentTypeList = contentTypeList.concat(Object.keys(response.content || {}));
4349
});
@@ -51,11 +57,21 @@ const hasQueryParameters = (parameters?: OpenApi.Parameter[]): boolean => {
5157
return parameters.filter(parameter => parameter.in === "query").length > 0;
5258
};
5359

54-
const generateCodeGeneratorParamsList = (store: Store.Type, converterContext: ConverterContext.Types): CodeGeneratorParams[] => {
60+
const generateCodeGeneratorParamsList = (
61+
store: Store.Type,
62+
converterContext: ConverterContext.Types,
63+
allowOperationIds: string[] | undefined,
64+
): CodeGeneratorParams[] => {
5565
const operationState = store.getNoReferenceOperationState();
5666
const params: CodeGeneratorParams[] = [];
5767
Object.entries(operationState).forEach(([operationId, item]) => {
58-
const responseSuccessNames = extractSuccessStatusCode(item.responses).map(statusCode =>
68+
if (allowOperationIds && !allowOperationIds.includes(operationId)) {
69+
return;
70+
}
71+
const responseSuccessNames = extractResponseNamesByStatusCode("success", item.responses).map(statusCode =>
72+
converterContext.generateResponseName(operationId, statusCode),
73+
);
74+
const responseErrorNames = extractResponseNamesByStatusCode("error", item.responses).map(statusCode =>
5975
converterContext.generateResponseName(operationId, statusCode),
6076
);
6177
const requestContentTypeList = item.requestBody ? getRequestContentTypeList(item.requestBody) : [];
@@ -65,6 +81,7 @@ const generateCodeGeneratorParamsList = (store: Store.Type, converterContext: Co
6581

6682
const formatParams: CodeGeneratorParams = {
6783
operationId: operationId,
84+
escapedOperationId: converterContext.escapeOperationIdText(operationId),
6885
rawRequestUri: item.requestUri,
6986
httpMethod: item.httpMethod,
7087
argumentParamsTypeDeclaration: converterContext.generateArgumentParamsTypeDeclaration(operationId),
@@ -88,6 +105,7 @@ const generateCodeGeneratorParamsList = (store: Store.Type, converterContext: Co
88105
responseSuccessNames: responseSuccessNames,
89106
responseFirstSuccessName: responseSuccessNames.length === 1 ? responseSuccessNames[0] : undefined,
90107
has2OrMoreSuccessNames: hasOver2SuccessNames,
108+
responseErrorNames: responseErrorNames,
91109
// Response Success Content Type
92110
successResponseContentTypes: responseSuccessContentTypes,
93111
successResponseFirstContentType: responseSuccessContentTypes.length === 1 ? responseSuccessContentTypes[0] : undefined,
@@ -113,7 +131,8 @@ export const generateApiClientCode = (
113131
context: ts.TransformationContext,
114132
converterContext: ConverterContext.Types,
115133
rewriteCodeAfterTypeDeclaration: RewriteCodeAfterTypeDeclaration,
134+
allowOperationIds: string[] | undefined,
116135
): void => {
117-
const codeGeneratorParamsList = generateCodeGeneratorParamsList(store, converterContext);
136+
const codeGeneratorParamsList = generateCodeGeneratorParamsList(store, converterContext, allowOperationIds);
118137
store.addAdditionalStatement(rewriteCodeAfterTypeDeclaration(context, codeGeneratorParamsList));
119138
};

src/Converter/v3/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export interface Option {
2828
* It is possible to rewrite the implementation after the type declaration.
2929
*/
3030
rewriteCodeAfterTypeDeclaration: Generator.RewriteCodeAfterTypeDeclaration;
31+
32+
allowOperationIds?: string[];
3133
}
3234

3335
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
9799
}
98100
if (rootSchema.paths) {
99101
Paths.generateStatements(entryPoint, currentPoint, store, factory, rootSchema.paths, toTypeNodeContext, converterContext);
100-
Generator.generateApiClientCode(store, context, converterContext, option.rewriteCodeAfterTypeDeclaration);
102+
Generator.generateApiClientCode(store, context, converterContext, option.rewriteCodeAfterTypeDeclaration, option.allowOperationIds);
101103
}
102104
return store.getRootStatements();
103105
};

src/Converter/v3/types/CodeGeneratorParams.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export type PickedParameter = Pick<OpenApi.Parameter, "name" | "in" | "required"
44

55
export interface CodeGeneratorParams {
66
operationId: string;
7+
escapedOperationId: string;
78
httpMethod: string; // get, post, put, delete ...etc
89
rawRequestUri: string;
910
functionName: string;
@@ -19,6 +20,8 @@ export interface CodeGeneratorParams {
1920
requestContentTypes: string[];
2021
requestFirstContentType: string | undefined; // requestContentTypes.length === 1 only
2122
has2OrMoreRequestContentTypes: boolean; // requestContentTypes.length > 1
23+
// Response Error Response Name
24+
responseErrorNames: string[];
2225
// Response Success Name
2326
responseSuccessNames: string[]; // `Response$${operationId}$Status$${statusCode}`[]
2427
responseFirstSuccessName: string | undefined; // responseSuccessNames.length === 1 only

src/DefaultCodeTemplate/ApiClientClass/ApiClientInterface.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,27 @@ import { CodeGeneratorParams } from "../../Converter/v3";
55

66
const httpMethodList: string[] = ["GET", "PUT", "POST", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"];
77

8+
const createErrorResponsesTypeAlias = (typeName: string, factory: Factory.Type, errorResponseNames: string[]) => {
9+
if (errorResponseNames.length === 0) {
10+
return factory.TypeAliasDeclaration.create({
11+
export: true,
12+
name: typeName,
13+
type: ts.factory.createToken(ts.SyntaxKind.VoidKeyword),
14+
});
15+
}
16+
return factory.TypeAliasDeclaration.create({
17+
export: true,
18+
name: typeName,
19+
type: factory.UnionTypeNode.create({
20+
typeNodes: errorResponseNames.map(name => {
21+
return factory.TypeReferenceNode.create({
22+
name,
23+
});
24+
}),
25+
}),
26+
});
27+
};
28+
829
const createSuccessResponseTypeAlias = (typeName: string, factory: Factory.Type, successResponseNames: string[]) => {
930
if (successResponseNames.length === 0) {
1031
return factory.TypeAliasDeclaration.create({
@@ -134,6 +155,14 @@ export const create = (factory: Factory.Type, list: CodeGeneratorParams[]): ts.S
134155

135156
const successResponseNames = list.map(item => item.responseSuccessNames).flat();
136157

158+
const errorResponseNamespace = factory.Namespace.create({
159+
export: true,
160+
name: "ErrorResponse",
161+
statements: list.map(item => {
162+
return createErrorResponsesTypeAlias(`${item.escapedOperationId}`, factory, item.responseErrorNames);
163+
}),
164+
});
165+
137166
const functionType = factory.FunctionTypeNode.create({
138167
typeParameters: [
139168
factory.TypeParameterDeclaration.create({
@@ -165,6 +194,7 @@ export const create = (factory: Factory.Type, list: CodeGeneratorParams[]): ts.S
165194
createObjectLikeInterface(factory),
166195
...createQueryParamsDeclarations(factory),
167196
createSuccessResponseTypeAlias("SuccessResponses", factory, successResponseNames),
197+
errorResponseNamespace,
168198
factory.InterfaceDeclaration.create({
169199
export: true,
170200
name: "ApiClient",

src/index.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,20 @@ export interface Params {
1717
/** default: true */
1818
enableValidate?: boolean;
1919
log?: {
20-
validator?: Validator.v3.LogOption;
20+
validator?: {
21+
/**
22+
* default: undefined (all logs)
23+
* Number of lines displayed in the latest log
24+
*/
25+
displayLogLines?: number;
26+
};
27+
};
28+
filter?: {
29+
allowOperationIds?: string[];
2130
};
2231
}
2332

24-
export const generateTypeScriptCode = ({ entryPoint, option, enableValidate = true, log }: Params): string => {
33+
export const generateTypeScriptCode = ({ entryPoint, option, enableValidate = true, log, filter = {} }: Params): string => {
2534
const schema = fileSystem.loadJsonOrYaml(entryPoint);
2635
const resolvedReferenceDocument = ResolveReference.resolve(entryPoint, entryPoint, JSON.parse(JSON.stringify(schema)));
2736

@@ -30,8 +39,14 @@ export const generateTypeScriptCode = ({ entryPoint, option, enableValidate = tr
3039
}
3140

3241
const convertOption: Converter.v3.Option = option
33-
? { rewriteCodeAfterTypeDeclaration: option.rewriteCodeAfterTypeDeclaration || DefaultCodeTemplate.rewriteCodeAfterTypeDeclaration }
34-
: { rewriteCodeAfterTypeDeclaration: DefaultCodeTemplate.rewriteCodeAfterTypeDeclaration };
42+
? {
43+
rewriteCodeAfterTypeDeclaration: option.rewriteCodeAfterTypeDeclaration || DefaultCodeTemplate.rewriteCodeAfterTypeDeclaration,
44+
allowOperationIds: filter.allowOperationIds,
45+
}
46+
: {
47+
rewriteCodeAfterTypeDeclaration: DefaultCodeTemplate.rewriteCodeAfterTypeDeclaration,
48+
allowOperationIds: filter.allowOperationIds,
49+
};
3550
const { createFunction, generateLeadingComment } = Converter.v3.create(entryPoint, schema, resolvedReferenceDocument, convertOption);
3651
return [generateLeadingComment(), TypeScriptCodeGenerator.generate(createFunction)].join(EOL + EOL + EOL);
3752
};

test/__tests__/__snapshots__/snapshot-test.ts.snap

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,12 @@ export interface QueryParameters {
345345
[key: string]: QueryParameter;
346346
}
347347
export type SuccessResponses = Response$getIncludeLocalReference$Status$200 | Response$getFullRemoteReference$Status$200 | Response$getReferenceItems$Status$200;
348+
export namespace ErrorResponse {
349+
export type getIncludeLocalReference = void;
350+
export type getIncludeRemoteReference = void;
351+
export type getFullRemoteReference = void;
352+
export type getReferenceItems = void;
353+
}
348354
export interface ApiClient<RequestOption> {
349355
request: <T = SuccessResponses>(httpMethod: HttpMethod, url: string, headers: ObjectLike | any, requestBody: ObjectLike | any, queryParameters: QueryParameters | undefined, options?: RequestOption) => Promise<T>;
350356
}

0 commit comments

Comments
 (0)