Skip to content

Commit c59d9ed

Browse files
authored
Add Either support (#3)
* Remove codecov token * Add Either support * Update snapshots * Add types for ApiError, fix error for babel * Update snapshots * Apply changes to v3 parser too, fix formatting, reject with error results (not successes)
1 parent 65aa0cc commit c59d9ed

File tree

12 files changed

+3061
-59
lines changed

12 files changed

+3061
-59
lines changed

src/client/interfaces/Operation.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ export interface Operation extends OperationParameters {
1212
path: string;
1313
errors: OperationError[];
1414
results: OperationResponse[];
15+
errorResults: OperationResponse[];
1516
responseHeader: string | null;
1617
}

src/openApi/v2/parser/getOperation.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { getOperationName } from './getOperationName';
77
import { getOperationParameters } from './getOperationParameters';
88
import { getOperationResponseHeader } from './getOperationResponseHeader';
99
import { getOperationResponses } from './getOperationResponses';
10-
import { getOperationResults } from './getOperationResults';
10+
import { getOperationErrorResults, getOperationResults } from './getOperationResults';
1111
import { getServiceName } from './getServiceName';
1212
import { sortByRequired } from './sortByRequired';
1313

@@ -41,6 +41,7 @@ export const getOperation = (
4141
imports: [],
4242
errors: [],
4343
results: [],
44+
errorResults: [],
4445
responseHeader: null,
4546
};
4647

@@ -61,13 +62,19 @@ export const getOperation = (
6162
if (op.responses) {
6263
const operationResponses = getOperationResponses(openApi, op.responses);
6364
const operationResults = getOperationResults(operationResponses);
65+
const operationErrorResults = getOperationErrorResults(operationResponses);
6466
operation.errors = getOperationErrors(operationResponses);
6567
operation.responseHeader = getOperationResponseHeader(operationResults);
6668

6769
operationResults.forEach(operationResult => {
6870
operation.results.push(operationResult);
6971
operation.imports.push(...operationResult.imports);
7072
});
73+
74+
operationErrorResults.forEach(operationResult => {
75+
operation.errorResults.push(operationResult);
76+
operation.imports.push(...operationResult.imports);
77+
});
7178
}
7279

7380
operation.parameters = operation.parameters.sort(sortByRequired);

src/openApi/v2/parser/getOperationResults.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,41 @@ const areEqual = (a: Model, b: Model): boolean => {
99
return equal;
1010
};
1111

12+
export const getOperationErrorResults = (operationResponses: OperationResponse[]): OperationResponse[] => {
13+
const operationResults: OperationResponse[] = [];
14+
15+
operationResponses.forEach(operationResponse => {
16+
const { code } = operationResponse;
17+
if (code && (code === 204 || code < 200 || code >= 300)) {
18+
operationResults.push(operationResponse);
19+
}
20+
});
21+
22+
if (!operationResults.length) {
23+
operationResults.push({
24+
in: 'response',
25+
name: '',
26+
code: 0,
27+
description: '',
28+
export: 'generic',
29+
type: 'any',
30+
base: 'any',
31+
template: null,
32+
link: null,
33+
isDefinition: false,
34+
isReadOnly: false,
35+
isRequired: false,
36+
isNullable: false,
37+
imports: [],
38+
enum: [],
39+
enums: [],
40+
properties: [],
41+
});
42+
}
43+
44+
return operationResults;
45+
};
46+
1247
export const getOperationResults = (operationResponses: OperationResponse[]): OperationResponse[] => {
1348
const operationResults: OperationResponse[] = [];
1449

src/openApi/v3/parser/getOperation.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { getOperationParameters } from './getOperationParameters';
99
import { getOperationRequestBody } from './getOperationRequestBody';
1010
import { getOperationResponseHeader } from './getOperationResponseHeader';
1111
import { getOperationResponses } from './getOperationResponses';
12-
import { getOperationResults } from './getOperationResults';
12+
import { getOperationErrorResults, getOperationResults } from './getOperationResults';
1313
import { getRef } from './getRef';
1414
import { getServiceName } from './getServiceName';
1515
import { sortByRequired } from './sortByRequired';
@@ -44,6 +44,7 @@ export const getOperation = (
4444
imports: [],
4545
errors: [],
4646
results: [],
47+
errorResults: [],
4748
responseHeader: null,
4849
};
4950

@@ -72,13 +73,19 @@ export const getOperation = (
7273
if (op.responses) {
7374
const operationResponses = getOperationResponses(openApi, op.responses);
7475
const operationResults = getOperationResults(operationResponses);
76+
const operationErrorResults = getOperationErrorResults(operationResponses);
7577
operation.errors = getOperationErrors(operationResponses);
7678
operation.responseHeader = getOperationResponseHeader(operationResults);
7779

7880
operationResults.forEach(operationResult => {
7981
operation.results.push(operationResult);
8082
operation.imports.push(...operationResult.imports);
8183
});
84+
85+
operationErrorResults.forEach(operationResult => {
86+
operation.errorResults.push(operationResult);
87+
operation.imports.push(...operationResult.imports);
88+
});
8289
}
8390

8491
operation.parameters = operation.parameters.sort(sortByRequired);

src/openApi/v3/parser/getOperationResults.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,41 @@ const areEqual = (a: Model, b: Model): boolean => {
99
return equal;
1010
};
1111

12+
export const getOperationErrorResults = (operationResponses: OperationResponse[]): OperationResponse[] => {
13+
const operationResults: OperationResponse[] = [];
14+
15+
operationResponses.forEach(operationResponse => {
16+
const { code } = operationResponse;
17+
if (code && (code === 204 || code < 200 || code >= 300)) {
18+
operationResults.push(operationResponse);
19+
}
20+
});
21+
22+
if (!operationResults.length) {
23+
operationResults.push({
24+
in: 'response',
25+
name: '',
26+
code: 0,
27+
description: '',
28+
export: 'generic',
29+
type: 'any',
30+
base: 'any',
31+
template: null,
32+
link: null,
33+
isDefinition: false,
34+
isReadOnly: false,
35+
isRequired: false,
36+
isNullable: false,
37+
imports: [],
38+
enum: [],
39+
enums: [],
40+
properties: [],
41+
});
42+
}
43+
44+
return operationResults;
45+
};
46+
1247
export const getOperationResults = (operationResponses: OperationResponse[]): OperationResponse[] => {
1348
const operationResults: OperationResponse[] = [];
1449

src/templates/core/ApiError.hbs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33
import type { ApiRequestOptions } from './ApiRequestOptions';
44
import type { ApiResult } from './ApiResult';
55

6-
export class ApiError extends Error {
6+
export class ApiError<
7+
StatusCode extends number = number,
8+
ApiErrorBody = any,
9+
> extends Error {
710
public readonly url: string;
8-
public readonly status: number;
11+
public readonly status: StatusCode;
912
public readonly statusText: string;
10-
public readonly body: any;
13+
public readonly body: ApiErrorBody;
1114
public readonly request: ApiRequestOptions;
1215

13-
constructor(request: ApiRequestOptions, response: ApiResult, message: string) {
16+
constructor(request: ApiRequestOptions, response: ApiResult<StatusCode, ApiErrorBody>, message: string) {
1417
super(message);
1518

1619
this.name = 'ApiError';

src/templates/core/ApiResult.hbs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,24 @@
11
{{>header}}
22

3-
export type ApiResult = {
3+
export type ApiResult<
4+
StatusCode extends number = number,
5+
ApiErrorBody = any,
6+
> = {
47
readonly url: string;
58
readonly ok: boolean;
6-
readonly status: number;
9+
readonly status: StatusCode;
710
readonly statusText: string;
8-
readonly body: any;
11+
readonly body: ApiErrorBody;
912
};
13+
14+
export interface Left<E> {
15+
readonly _tag: 'Left';
16+
readonly left: E;
17+
}
18+
19+
export interface Right<A> {
20+
readonly _tag: 'Right';
21+
readonly right: A;
22+
}
23+
24+
export type Either<E, A> = Left<E> | Right<A>;

src/templates/exportService.hbs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,14 @@ import type { CancelablePromise } from '../core/CancelablePromise';
2525
import { BaseHttpRequest } from '../core/BaseHttpRequest';
2626
{{else}}
2727
import type { BaseHttpRequest } from '../core/BaseHttpRequest';
28+
import type { Left, Right, Either } from '../core/ApiResult';
29+
import type { ApiError } from '../core/ApiError';
2830
{{/equals}}
2931
{{else}}
3032
import { OpenAPI } from '../core/OpenAPI';
3133
import { request as __request } from '../core/request';
34+
import type { Left, Right, Either } from '../core/ApiResult';
35+
import type { ApiError } from '../core/ApiError';
3236
{{/if}}
3337

3438
{{#equals @root.httpClient 'angular'}}
@@ -146,5 +150,128 @@ export class {{{name}}}{{{@root.postfix}}} {
146150
});
147151
}
148152

153+
{{#equals @root.httpClient 'angular'}}
154+
{{else}}
155+
/**
156+
{{#if deprecated}}
157+
* @deprecated
158+
{{/if}}
159+
{{#if summary}}
160+
* {{{escapeComment summary}}}
161+
{{/if}}
162+
{{#if description}}
163+
* {{{escapeComment description}}}
164+
{{/if}}
165+
{{#unless @root.useOptions}}
166+
{{#if parameters}}
167+
{{#each parameters}}
168+
* @param {{{name}}} {{#if description}}{{{escapeComment description}}}{{/if}}
169+
{{/each}}
170+
{{/if}}
171+
{{/unless}}
172+
{{#each results}}
173+
* @returns {{{type}}} {{#if description}}{{{escapeComment description}}}{{/if}}
174+
{{/each}}
175+
* @throws ApiError
176+
*/
177+
{{#if @root.exportClient}}
178+
{{#equals @root.httpClient 'angular'}}
179+
public async {{{name}}}Either({{>parameters}}): Observable<{{>result}}> {
180+
try {
181+
const result: ({{>result}}) = await this.httpRequest.request({
182+
{{else}}
183+
public async {{{name}}}Either({{>parameters}}): Promise<Either<{{>errorResult}}, {{>result}}>> {
184+
try {
185+
const result: ({{>result}}) = await this.httpRequest.request({
186+
{{/equals}}
187+
{{else}}
188+
{{#equals @root.httpClient 'angular'}}
189+
public async {{{name}}}Either({{>parameters}}): Observable<{{>result}}> {
190+
try {
191+
const result: ({{>result}}) = await __request(OpenAPI, this.http, {
192+
{{else}}
193+
public static async {{{name}}}Either({{>parameters}}): Promise<Either<{{>errorResult}}, {{>result}}>> {
194+
try {
195+
const result: ({{>result}}) = await __request(OpenAPI, {
196+
{{/equals}}
197+
{{/if}}
198+
method: '{{{method}}}',
199+
url: '{{{path}}}',
200+
{{#if parametersPath}}
201+
path: {
202+
{{#each parametersPath}}
203+
'{{{prop}}}': {{{name}}},
204+
{{/each}}
205+
},
206+
{{/if}}
207+
{{#if parametersCookie}}
208+
cookies: {
209+
{{#each parametersCookie}}
210+
'{{{prop}}}': {{{name}}},
211+
{{/each}}
212+
},
213+
{{/if}}
214+
{{#if parametersHeader}}
215+
headers: {
216+
{{#each parametersHeader}}
217+
'{{{prop}}}': {{{name}}},
218+
{{/each}}
219+
},
220+
{{/if}}
221+
{{#if parametersQuery}}
222+
query: {
223+
{{#each parametersQuery}}
224+
'{{{prop}}}': {{{name}}},
225+
{{/each}}
226+
},
227+
{{/if}}
228+
{{#if parametersForm}}
229+
formData: {
230+
{{#each parametersForm}}
231+
'{{{prop}}}': {{{name}}},
232+
{{/each}}
233+
},
234+
{{/if}}
235+
{{#if parametersBody}}
236+
{{#equals parametersBody.in 'formData'}}
237+
formData: {{{parametersBody.name}}},
238+
{{/equals}}
239+
{{#equals parametersBody.in 'body'}}
240+
body: {{{parametersBody.name}}},
241+
{{/equals}}
242+
{{#if parametersBody.mediaType}}
243+
mediaType: '{{{parametersBody.mediaType}}}',
244+
{{/if}}
245+
{{/if}}
246+
{{#if responseHeader}}
247+
responseHeader: '{{{responseHeader}}}',
248+
{{/if}}
249+
{{#if errors}}
250+
errors: {
251+
{{#each errors}}
252+
{{{code}}}: `{{{escapeDescription description}}}`,
253+
{{/each}}
254+
},
255+
{{/if}}
256+
});
257+
258+
const right: Right<{{>result}}> = {
259+
_tag: 'Right',
260+
right: result,
261+
};
262+
263+
return right;
264+
} catch (e) {
265+
const left: Left<{{>errorResult}}> = {
266+
_tag: 'Left',
267+
left: e as ({{>errorResult}}),
268+
};
269+
270+
return left;
271+
}
272+
}
273+
{{/equals}}
274+
275+
149276
{{/each}}
150277
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{{~#if errorResults~}}
2+
{{#each errorResults}}ApiError<{{#equals code 0}}number{{else}}{{{code}}}{{/equals}}, {{type}}>{{#unless @last}} | {{/unless}}{{/each}}
3+
{{~else~}}
4+
ApiError<number, any>
5+
{{~/if~}}

src/utils/registerHandlebarTemplates.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import templateExportSchema from '../templates/exportSchema.hbs';
5757
import templateExportService from '../templates/exportService.hbs';
5858
import templateIndex from '../templates/index.hbs';
5959
import partialBase from '../templates/partials/base.hbs';
60+
import partialErrorResult from '../templates/partials/errorResult.hbs';
6061
import partialExportComposition from '../templates/partials/exportComposition.hbs';
6162
import partialExportEnum from '../templates/partials/exportEnum.hbs';
6263
import partialExportInterface from '../templates/partials/exportInterface.hbs';
@@ -148,6 +149,7 @@ export const registerHandlebarTemplates = (root: {
148149
Handlebars.registerPartial('isRequired', Handlebars.template(partialIsRequired));
149150
Handlebars.registerPartial('parameters', Handlebars.template(partialParameters));
150151
Handlebars.registerPartial('result', Handlebars.template(partialResult));
152+
Handlebars.registerPartial('errorResult', Handlebars.template(partialErrorResult));
151153
Handlebars.registerPartial('schema', Handlebars.template(partialSchema));
152154
Handlebars.registerPartial('schemaArray', Handlebars.template(partialSchemaArray));
153155
Handlebars.registerPartial('schemaDictionary', Handlebars.template(partialSchemaDictionary));

0 commit comments

Comments
 (0)