Skip to content

Commit cd559d1

Browse files
committed
introducing OmitReadonly type
- it omits readonly properties from an object - it infers the original type in case of passed type is not an `object` - it is applied to parameters for "POST", "PUT" or "PATCH" operations and only if it is a "reference" type
1 parent 7cb5a91 commit cd559d1

15 files changed

+255
-14
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* The contents of this file are inspired from https://github.com/ts-essentials/ts-essentials#ReadonlyKeys
3+
*
4+
* The MIT License
5+
*
6+
* Copyright (c) 2018-2019 Chris Kaczor (github.com/krzkaczor)
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
* */
26+
27+
type Writable<T> = { -readonly [P in keyof T]: T[P] };
28+
29+
type IsEqualConsideringWritability<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
30+
? true
31+
: false;
32+
33+
type _Obj<T> = { [Q in keyof T]: T[Q] };
34+
35+
type IsFullyWritable<T extends object> = IsEqualConsideringWritability<_Obj<T>, Writable<_Obj<T>>>;
36+
37+
/** Gets keys of an object which are readonly */
38+
type ReadonlyKeys<T extends object> = {
39+
[P in keyof T]-?: IsFullyWritable<Pick<T, P>> extends true ? never : P;
40+
}[keyof T];
41+
42+
/**
43+
* Exclude keys of an object which are readonly
44+
* In case of union types containing types not extending object, type is inferred as-is
45+
* */
46+
export declare type OmitReadonly<T> = T extends object ? Omit<T, ReadonlyKeys<T>> : T;

src/templates/exportService.hbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import type { BaseHttpRequest } from '../core/BaseHttpRequest';
3030
import { OpenAPI } from '../core/OpenAPI';
3131
import { request as __request } from '../core/request';
3232
{{/if}}
33+
import { OmitReadonly } from '../core/utils/OmitReadonly';
3334

3435
{{#equals @root.httpClient 'angular'}}
3536
@Injectable()

src/templates/partials/parameters.hbs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616
{{/if}}
1717
*/
1818
{{/ifdef}}
19-
{{{name}}}{{>isRequired}}: {{>type}},
19+
{{{name}}}{{>isRequired}}: {{>typeWithOmitReadOnly httpMethod=../method}},
2020
{{/each}}
2121
}
2222
{{~else}}
2323

2424
{{#each parameters}}
25-
{{{name}}}{{>isRequired}}: {{>type}}{{#if default}} = {{{default}}}{{/if}},
25+
{{{name}}}{{>isRequired}}: {{>typeWithOmitReadOnly httpMethod=../method}}{{#if default}} = {{{default}}}{{/if}},
2626
{{/each}}
2727
{{/if}}
2828
{{/if}}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{{#equals export "reference"}}
2+
{{#equals httpMethod "POST"~}}
3+
OmitReadonly<{{>type}}>
4+
{{~else equals httpMethod "PUT"~}}
5+
OmitReadonly<{{>type}}>
6+
{{~else equals httpMethod "PATCH"~}}
7+
OmitReadonly<{{>type}}>
8+
{{else}}
9+
{{~>type}}
10+
{{~/equals}}
11+
{{~else}}
12+
{{~>type}}
13+
{{~/equals}}

src/utils/registerHandlebarTemplates.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import nodeRequest from '../templates/core/node/request.hbs';
4646
import nodeSendRequest from '../templates/core/node/sendRequest.hbs';
4747
import templateCoreSettings from '../templates/core/OpenAPI.hbs';
4848
import templateCoreRequest from '../templates/core/request.hbs';
49+
import omitReadOnly from '../templates/core/utils/OmitReadOnly.hbs';
4950
import xhrGetHeaders from '../templates/core/xhr/getHeaders.hbs';
5051
import xhrGetRequestBody from '../templates/core/xhr/getRequestBody.hbs';
5152
import xhrGetResponseBody from '../templates/core/xhr/getResponseBody.hbs';
@@ -83,6 +84,7 @@ import partialTypeInterface from '../templates/partials/typeInterface.hbs';
8384
import partialTypeIntersection from '../templates/partials/typeIntersection.hbs';
8485
import partialTypeReference from '../templates/partials/typeReference.hbs';
8586
import partialTypeUnion from '../templates/partials/typeUnion.hbs';
87+
import typeWithOmitReadOnly from '../templates/partials/typeWithOmitReadOnly.hbs';
8688
import { registerHandlebarHelpers } from './registerHandlebarHelpers';
8789

8890
export interface Templates {
@@ -102,6 +104,9 @@ export interface Templates {
102104
request: Handlebars.TemplateDelegate;
103105
baseHttpRequest: Handlebars.TemplateDelegate;
104106
httpRequest: Handlebars.TemplateDelegate;
107+
utils: {
108+
omitReadOnly: Handlebars.TemplateDelegate;
109+
};
105110
};
106111
}
107112

@@ -134,6 +139,9 @@ export const registerHandlebarTemplates = (root: {
134139
request: Handlebars.template(templateCoreRequest),
135140
baseHttpRequest: Handlebars.template(templateCoreBaseHttpRequest),
136141
httpRequest: Handlebars.template(templateCoreHttpRequest),
142+
utils: {
143+
omitReadOnly: Handlebars.template(omitReadOnly),
144+
},
137145
},
138146
};
139147

@@ -163,6 +171,7 @@ export const registerHandlebarTemplates = (root: {
163171
Handlebars.registerPartial('typeInterface', Handlebars.template(partialTypeInterface));
164172
Handlebars.registerPartial('typeReference', Handlebars.template(partialTypeReference));
165173
Handlebars.registerPartial('typeUnion', Handlebars.template(partialTypeUnion));
174+
Handlebars.registerPartial('typeWithOmitReadOnly', Handlebars.template(typeWithOmitReadOnly));
166175
Handlebars.registerPartial('typeIntersection', Handlebars.template(partialTypeIntersection));
167176
Handlebars.registerPartial('base', Handlebars.template(partialBase));
168177

src/utils/writeClient.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ describe('writeClient', () => {
3333
request: () => 'request',
3434
baseHttpRequest: () => 'baseHttpRequest',
3535
httpRequest: () => 'httpRequest',
36+
utils: {
37+
omitReadOnly: () => 'omitReadOnly',
38+
},
3639
},
3740
};
3841

src/utils/writeClient.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export const writeClient = async (
5050
): Promise<void> => {
5151
const outputPath = resolve(process.cwd(), output);
5252
const outputPathCore = resolve(outputPath, 'core');
53+
const outputPathCoreUtils = resolve(outputPath, 'core', 'utils');
5354
const outputPathModels = resolve(outputPath, 'models');
5455
const outputPathSchemas = resolve(outputPath, 'schemas');
5556
const outputPathServices = resolve(outputPath, 'services');
@@ -60,8 +61,17 @@ export const writeClient = async (
6061

6162
if (exportCore) {
6263
await rmdir(outputPathCore);
63-
await mkdir(outputPathCore);
64-
await writeClientCore(client, templates, outputPathCore, httpClient, indent, clientName, request);
64+
await mkdir(outputPathCoreUtils);
65+
await writeClientCore(
66+
client,
67+
templates,
68+
outputPathCore,
69+
outputPathCoreUtils,
70+
httpClient,
71+
indent,
72+
clientName,
73+
request
74+
);
6575
}
6676

6777
if (exportServices) {

src/utils/writeClientClass.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ describe('writeClientClass', () => {
3333
request: () => 'request',
3434
baseHttpRequest: () => 'baseHttpRequest',
3535
httpRequest: () => 'httpRequest',
36+
utils: {
37+
omitReadOnly: () => 'omitReadOnly',
38+
},
3639
},
3740
};
3841

src/utils/writeClientCore.spec.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,20 @@ describe('writeClientCore', () => {
3535
request: () => 'request',
3636
baseHttpRequest: () => 'baseHttpRequest',
3737
httpRequest: () => 'httpRequest',
38+
utils: {
39+
omitReadOnly: () => 'omitReadOnly',
40+
},
3841
},
3942
};
4043

41-
await writeClientCore(client, templates, '/', HttpClient.FETCH, Indent.SPACE_4);
44+
await writeClientCore(client, templates, '/', '/utils', HttpClient.FETCH, Indent.SPACE_4);
4245

4346
expect(writeFile).toBeCalledWith('/OpenAPI.ts', `settings${EOL}`);
4447
expect(writeFile).toBeCalledWith('/ApiError.ts', `apiError${EOL}`);
4548
expect(writeFile).toBeCalledWith('/ApiRequestOptions.ts', `apiRequestOptions${EOL}`);
4649
expect(writeFile).toBeCalledWith('/ApiResult.ts', `apiResult${EOL}`);
4750
expect(writeFile).toBeCalledWith('/CancelablePromise.ts', `cancelablePromise${EOL}`);
4851
expect(writeFile).toBeCalledWith('/request.ts', `request${EOL}`);
52+
expect(writeFile).toBeCalledWith('/utils/OmitReadOnly.ts', `omitReadOnly${EOL}`);
4953
});
5054
});

src/utils/writeClientCore.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type { Templates } from './registerHandlebarTemplates';
1414
* @param client Client object, containing, models, schemas and services
1515
* @param templates The loaded handlebar templates
1616
* @param outputPath Directory to write the generated files to
17+
* @param outputUtilsPath Directory to write the generated util files to
1718
* @param httpClient The selected httpClient (fetch, xhr, node or axios)
1819
* @param indent Indentation options (4, 2 or tab)
1920
* @param clientName Custom client class name
@@ -23,6 +24,7 @@ export const writeClientCore = async (
2324
client: Client,
2425
templates: Templates,
2526
outputPath: string,
27+
outputUtilsPath: string,
2628
httpClient: HttpClient,
2729
indent: Indent,
2830
clientName?: string,
@@ -43,6 +45,7 @@ export const writeClientCore = async (
4345
await writeFile(resolve(outputPath, 'ApiResult.ts'), i(templates.core.apiResult(context), indent));
4446
await writeFile(resolve(outputPath, 'CancelablePromise.ts'), i(templates.core.cancelablePromise(context), indent));
4547
await writeFile(resolve(outputPath, 'request.ts'), i(templates.core.request(context), indent));
48+
await writeFile(resolve(outputUtilsPath, 'OmitReadOnly.ts'), i(templates.core.utils.omitReadOnly(context), indent));
4649

4750
if (isDefined(clientName)) {
4851
await writeFile(resolve(outputPath, 'BaseHttpRequest.ts'), i(templates.core.baseHttpRequest(context), indent));

src/utils/writeClientIndex.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ describe('writeClientIndex', () => {
3131
request: () => 'request',
3232
baseHttpRequest: () => 'baseHttpRequest',
3333
httpRequest: () => 'httpRequest',
34+
utils: {
35+
omitReadOnly: () => 'omitReadOnly',
36+
},
3437
},
3538
};
3639

src/utils/writeClientModels.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ describe('writeClientModels', () => {
4848
request: () => 'request',
4949
baseHttpRequest: () => 'baseHttpRequest',
5050
httpRequest: () => 'httpRequest',
51+
utils: {
52+
omitReadOnly: () => 'omitReadOnly',
53+
},
5154
},
5255
};
5356

src/utils/writeClientSchemas.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ describe('writeClientSchemas', () => {
4848
request: () => 'request',
4949
baseHttpRequest: () => 'baseHttpRequest',
5050
httpRequest: () => 'httpRequest',
51+
utils: {
52+
omitReadOnly: () => 'omitReadOnly',
53+
},
5154
},
5255
};
5356

src/utils/writeClientServices.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ describe('writeClientServices', () => {
3636
request: () => 'request',
3737
baseHttpRequest: () => 'baseHttpRequest',
3838
httpRequest: () => 'httpRequest',
39+
utils: {
40+
omitReadOnly: () => 'omitReadOnly',
41+
},
3942
},
4043
};
4144

0 commit comments

Comments
 (0)