Skip to content

Commit a89ffe5

Browse files
committed
Merge branch 'feat/typescript-fetch-date-library' of https://github.com/LeComptoirDesPharmacies/openapi-generator into LeComptoirDesPharmacies-feat/typescript-fetch-date-library
2 parents dbf38dc + dddd45d commit a89ffe5

94 files changed

Lines changed: 3832 additions & 134 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
generatorName: typescript-fetch
2+
outputDir: samples/client/petstore/typescript-fetch/builds/date-library-date
3+
inputSpec: modules/openapi-generator/src/test/resources/3_0/typescript-fetch/date-handling.yaml
4+
templateDir: modules/openapi-generator/src/main/resources/typescript-fetch
5+
additionalProperties:
6+
dateLibrary: date
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
generatorName: typescript-fetch
2+
outputDir: samples/client/petstore/typescript-fetch/builds/date-library-string
3+
inputSpec: modules/openapi-generator/src/test/resources/3_0/typescript-fetch/date-handling.yaml
4+
templateDir: modules/openapi-generator/src/main/resources/typescript-fetch
5+
additionalProperties:
6+
dateLibrary: string

docs/generators/typescript-fetch.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
1919
| Option | Description | Values | Default |
2020
| ------ | ----------- | ------ | ------- |
2121
|allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
22+
|dateLibrary|Option. Date library to use.|<dl><dt>**date**</dt><dd>Native Date. `format: date` and `format: date-time` are both mapped to Date and (de)serialized by the runtime.</dd><dt>**string**</dt><dd>Plain string. Values are passed through untouched, leaving date handling to the consumer.</dd></dl>|date|
2223
|disallowAdditionalPropertiesIfNotPresent|If false, the 'additionalProperties' implementation (set to true by default) is compliant with the OAS and JSON schema specifications. If true (default), keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.|<dl><dt>**false**</dt><dd>The 'additionalProperties' implementation is compliant with the OAS and JSON schema specifications.</dd><dt>**true**</dt><dd>Keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.</dd></dl>|true|
2324
|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
2425
|enumNameSuffix|Suffix that will be appended to all enum names.| |Enum|

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodege
6363
public static final String USE_SINGLE_REQUEST_PARAMETER = "useSingleRequestParameter";
6464
public static final String PREFIX_PARAMETER_INTERFACES = "prefixParameterInterfaces";
6565
public static final String WITHOUT_RUNTIME_CHECKS = "withoutRuntimeChecks";
66+
public static final String DATE_LIBRARY = "dateLibrary";
67+
public static final String DATE_LIBRARY_DESC = "Option. Date library to use.";
68+
public static final String DATE_LIBRARY_DATE = "date";
69+
public static final String DATE_LIBRARY_STRING = "string";
6670
public static final String STRING_ENUMS = "stringEnums";
6771
public static final String STRING_ENUMS_DESC = "Generate string enums instead of objects for enum values.";
6872
public static final String IMPORT_FILE_EXTENSION_SWITCH = "importFileExtension";
@@ -85,6 +89,7 @@ public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodege
8589
protected boolean addedApiIndex = false;
8690
protected boolean addedModelIndex = false;
8791
protected boolean withoutRuntimeChecks = false;
92+
protected String dateLibrary = DATE_LIBRARY_DATE;
8893
protected boolean stringEnums = false;
8994
protected String fileNaming = PASCAL_CASE;
9095
protected String apiDocPath = "docs";
@@ -102,6 +107,7 @@ public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodege
102107
private static final String X_OPERATION_RETURN_PASSTHROUGH = "x-operationReturnPassthrough";
103108
private static final String X_KEEP_AS_JS_OBJECT = "x-keepAsJSObject";
104109
private static final String X_TYPESCRIPT_FETCH_API_EXAMPLE = "x-typescriptFetchApiExample";
110+
private static final String X_HAS_DATE_VARS = "x-hasDateVars";
105111
private static final String BLOB_API_EXAMPLE = "new Blob(['example file content'], { type: 'application/octet-stream' })";
106112

107113
protected boolean sagasAndRecords = false;
@@ -140,6 +146,13 @@ public TypeScriptFetchClientCodegen() {
140146
this.cliOptions.add(new CliOption(CodegenConstants.USE_SINGLE_REQUEST_PARAMETER, CodegenConstants.USE_SINGLE_REQUEST_PARAMETER_DESC, SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.TRUE.toString()));
141147
this.cliOptions.add(new CliOption(PREFIX_PARAMETER_INTERFACES, "Setting this property to true will generate parameter interface declarations prefixed with API class name to avoid name conflicts.", SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString()));
142148
this.cliOptions.add(new CliOption(WITHOUT_RUNTIME_CHECKS, "Setting this property to true will remove any runtime checks on the request and response payloads. Payloads will be casted to their expected types.", SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString()));
149+
150+
CliOption dateLibraryOption = new CliOption(DATE_LIBRARY, DATE_LIBRARY_DESC).defaultValue(this.getDateLibrary());
151+
Map<String, String> dateOptions = new HashMap<>();
152+
dateOptions.put(DATE_LIBRARY_DATE, "Native Date. `format: date` and `format: date-time` are both mapped to Date and (de)serialized by the runtime.");
153+
dateOptions.put(DATE_LIBRARY_STRING, "Plain string. Values are passed through untouched, leaving date handling to the consumer.");
154+
dateLibraryOption.setEnum(dateOptions);
155+
this.cliOptions.add(dateLibraryOption);
143156
this.cliOptions.add(new CliOption(SAGAS_AND_RECORDS, "Setting this property to true will generate additional files for use with redux-saga and immutablejs.", SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString()));
144157
this.cliOptions.add(new CliOption(STRING_ENUMS, STRING_ENUMS_DESC, SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString()));
145158
this.cliOptions.add(new CliOption(IMPORT_FILE_EXTENSION_SWITCH, IMPORT_FILE_EXTENSION_SWITCH_DESC).defaultValue(""));
@@ -198,6 +211,14 @@ public void setWithoutRuntimeChecks(Boolean withoutRuntimeChecks) {
198211
this.withoutRuntimeChecks = withoutRuntimeChecks;
199212
}
200213

214+
public String getDateLibrary() {
215+
return this.dateLibrary;
216+
}
217+
218+
public void setDateLibrary(String dateLibrary) {
219+
this.dateLibrary = dateLibrary;
220+
}
221+
201222
public Boolean getStringEnums() {
202223
return this.stringEnums;
203224
}
@@ -315,11 +336,34 @@ public void processOpts() {
315336
this.setFileNaming(additionalProperties.get(FILE_NAMING).toString());
316337
}
317338

339+
if (additionalProperties.containsKey(DATE_LIBRARY)) {
340+
this.setDateLibrary(additionalProperties.get(DATE_LIBRARY).toString());
341+
}
342+
318343
if (!withoutRuntimeChecks) {
319344
this.modelTemplateFiles.put("models.mustache", ".ts");
345+
}
346+
347+
// `date` needs the model (de)serialization to convert with, which
348+
// withoutRuntimeChecks removes: the raw string would just be cast to Date.
349+
if (withoutRuntimeChecks && DATE_LIBRARY_DATE.equals(this.dateLibrary)) {
350+
if (additionalProperties.containsKey(DATE_LIBRARY)) {
351+
LOGGER.warn("{}={} is not compatible with {}=true; falling back to {}={}.",
352+
DATE_LIBRARY, DATE_LIBRARY_DATE, WITHOUT_RUNTIME_CHECKS, DATE_LIBRARY, DATE_LIBRARY_STRING);
353+
}
354+
this.dateLibrary = DATE_LIBRARY_STRING;
355+
}
356+
357+
if (DATE_LIBRARY_DATE.equals(this.dateLibrary)) {
320358
typeMapping.put("date", "Date");
321359
typeMapping.put("DateTime", "Date");
360+
} else {
361+
typeMapping.put("date", "string");
362+
typeMapping.put("DateTime", "string");
322363
}
364+
additionalProperties.put(DATE_LIBRARY, this.dateLibrary);
365+
// Mustache cannot compare strings, so expose the selected library as a flag.
366+
additionalProperties.put("isDateLibraryDate", DATE_LIBRARY_DATE.equals(this.dateLibrary));
323367

324368
if (additionalProperties.containsKey(SAGAS_AND_RECORDS)) {
325369
this.setSagasAndRecords(convertPropertyToBoolean(SAGAS_AND_RECORDS));
@@ -419,6 +463,12 @@ public ModelsMap postProcessModels(ModelsMap objs) {
419463
ExtendedCodegenModel cm = (ExtendedCodegenModel) mo.getModel();
420464
cm.imports = new TreeSet<>(cm.imports);
421465
this.processCodeGenModel(cm);
466+
// Mirrors the branches in modelGeneric.mustache that call the date helpers, so a
467+
// model without dates does not import them.
468+
cm.vendorExtensions.put(X_HAS_DATE_VARS, cm.vars.stream()
469+
.filter(ExtendedCodegenProperty.class::isInstance)
470+
.map(ExtendedCodegenProperty.class::cast)
471+
.anyMatch(v -> v.isPrimitiveType && !v.isArray && (v.isDateType() || v.isDateTimeType())));
422472
}
423473

424474
// Add supporting file only if we plan to generate files in /models

modules/openapi-generator/src/main/resources/typescript-fetch/apis.mustache

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,15 +308,15 @@ export class {{classname}} extends runtime.BaseAPI {
308308
{{#pathParams}}
309309
{{#isDateTimeType}}
310310
if (requestParameters['{{paramName}}'] instanceof Date) {
311-
urlPath = urlPath.replace({{=<< >>=}}'{<<baseName>>}'<<={{ }}=>>, encodeURIComponent(requestParameters['{{paramName}}'].toISOString()));
311+
urlPath = urlPath.replace({{=<< >>=}}'{<<baseName>>}'<<={{ }}=>>, encodeURIComponent(runtime.serializeDateTime(requestParameters['{{paramName}}'])));
312312
} else {
313313
urlPath = urlPath.replace({{=<< >>=}}'{<<baseName>>}'<<={{ }}=>>, encodeURIComponent(String(requestParameters['{{paramName}}'])));
314314
}
315315
{{/isDateTimeType}}
316316
{{^isDateTimeType}}
317317
{{#isDateType}}
318318
if (requestParameters['{{paramName}}'] instanceof Date) {
319-
urlPath = urlPath.replace({{=<< >>=}}'{<<baseName>>}'<<={{ }}=>>, encodeURIComponent(requestParameters['{{paramName}}'].toISOString().substring(0,10)));
319+
urlPath = urlPath.replace({{=<< >>=}}'{<<baseName>>}'<<={{ }}=>>, encodeURIComponent(runtime.serializeDate(requestParameters['{{paramName}}'])));
320320
} else {
321321
urlPath = urlPath.replace({{=<< >>=}}'{<<baseName>>}'<<={{ }}=>>, encodeURIComponent(String(requestParameters['{{paramName}}'])));
322322
}

modules/openapi-generator/src/main/resources/typescript-fetch/apisAssignQueryParam.mustache

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{{! Assign query parameters based on their type }}
22
{{#isDateTimeType}}
3-
queryParameters['{{baseName}}'] = (requestParameters['{{paramName}}'] as any).toISOString();
3+
queryParameters['{{baseName}}'] = runtime.serializeDateTime(requestParameters['{{paramName}}'] as any);
44
{{/isDateTimeType}}
55
{{^isDateTimeType}}
66
{{#isDateType}}
7-
queryParameters['{{baseName}}'] = (requestParameters['{{paramName}}'] as any).toISOString().substring(0,10);
7+
queryParameters['{{baseName}}'] = runtime.serializeDate(requestParameters['{{paramName}}'] as any);
88
{{/isDateType}}
99
{{^isDateType}}
1010
queryParameters['{{baseName}}'] = requestParameters['{{paramName}}'];

modules/openapi-generator/src/main/resources/typescript-fetch/apisFormParams.mustache

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,13 @@
3737
{{^isArray}}
3838
if (requestParameters['{{paramName}}'] != null) {
3939
{{#isDateTimeType}}
40-
formParams.append('{{baseName}}', (requestParameters['{{paramName}}'] as any).toISOString());
40+
formParams.append('{{baseName}}', runtime.serializeDateTime(requestParameters['{{paramName}}'] as any));
4141
{{/isDateTimeType}}
4242
{{^isDateTimeType}}
43+
{{#isDateType}}
44+
formParams.append('{{baseName}}', runtime.serializeDate(requestParameters['{{paramName}}'] as any));
45+
{{/isDateType}}
46+
{{^isDateType}}
4347
{{#isPrimitiveType}}
4448
formParams.append('{{baseName}}', requestParameters['{{paramName}}'] as any);
4549
{{/isPrimitiveType}}
@@ -60,6 +64,7 @@
6064
{{/withoutRuntimeChecks}}
6165
{{/isEnumRef}}
6266
{{/isPrimitiveType}}
67+
{{/isDateType}}
6368
{{/isDateTimeType}}
6469
}
6570

modules/openapi-generator/src/main/resources/typescript-fetch/modelGeneric.mustache

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { mapValues } from '../runtime{{importFileExtension}}';
1+
import { mapValues{{#isDateLibraryDate}}{{#vendorExtensions.x-hasDateVars}}, parseDate, parseDateTime, serializeDate, serializeDateTime{{/vendorExtensions.x-hasDateVars}}{{/isDateLibraryDate}} } from '../runtime{{importFileExtension}}';
22
{{#hasImports}}
33
{{#tsImports}}
44
import type { {{{classname}}} } from './{{filename}}{{importFileExtension}}';
@@ -98,10 +98,10 @@ export function {{classname}}FromJSONTyped(json: any, ignoreDiscriminator: boole
9898
{{/isArray}}
9999
{{^isArray}}
100100
{{#isDateType}}
101-
'{{name}}': {{^required}}{{#isNullable}}json['{{baseName}}'] === undefined ? undefined : json['{{baseName}}'] === null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? undefined : {{/isNullable}}{{/required}}({{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? json['{{baseName}}'] : {{/isNullable}}{{/required}}new Date(json['{{baseName}}'])),
101+
'{{name}}': {{^required}}{{#isNullable}}json['{{baseName}}'] === undefined ? undefined : json['{{baseName}}'] === null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? undefined : {{/isNullable}}{{/required}}({{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? json['{{baseName}}'] : {{/isNullable}}{{/required}}parseDate(json['{{baseName}}'])),
102102
{{/isDateType}}
103103
{{#isDateTimeType}}
104-
'{{name}}': {{^required}}{{#isNullable}}json['{{baseName}}'] === undefined ? undefined : json['{{baseName}}'] === null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? undefined : {{/isNullable}}{{/required}}({{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? json['{{baseName}}'] : {{/isNullable}}{{/required}}new Date(json['{{baseName}}'])),
104+
'{{name}}': {{^required}}{{#isNullable}}json['{{baseName}}'] === undefined ? undefined : json['{{baseName}}'] === null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? undefined : {{/isNullable}}{{/required}}({{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? json['{{baseName}}'] : {{/isNullable}}{{/required}}parseDateTime(json['{{baseName}}'])),
105105
{{/isDateTimeType}}
106106
{{^isDateType}}
107107
{{^isDateTimeType}}
@@ -173,10 +173,10 @@ export function {{classname}}ToJSONTyped(value?: {{#hasReadOnly}}Omit<{{classnam
173173
{{^isReadOnly}}
174174
{{#isPrimitiveType}}
175175
{{#isDateType}}
176-
'{{baseName}}': value['{{name}}'] == null ? value['{{name}}'] : value['{{name}}'].toISOString().substring(0,10),
176+
'{{baseName}}': value['{{name}}'] == null ? value['{{name}}'] : serializeDate(value['{{name}}']),
177177
{{/isDateType}}
178178
{{#isDateTimeType}}
179-
'{{baseName}}': value['{{name}}'] == null ? value['{{name}}'] : value['{{name}}'].toISOString(),
179+
'{{baseName}}': value['{{name}}'] == null ? value['{{name}}'] : serializeDateTime(value['{{name}}']),
180180
{{/isDateTimeType}}
181181
{{#isArray}}
182182
'{{baseName}}': {{#uniqueItems}}{{^required}}value['{{name}}'] == null ? undefined : {{/required}}{{#required}}{{#isNullable}}value['{{name}}'] == null ? null : {{/isNullable}}{{/required}}Array.from(value['{{name}}'] as Set<any>){{/uniqueItems}}{{^uniqueItems}}value['{{name}}']{{/uniqueItems}},

modules/openapi-generator/src/main/resources/typescript-fetch/modelOneOf.mustache

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
{{#isDateLibraryDate}}
2+
import { parseDate, parseDateTime, serializeDate, serializeDateTime } from '../runtime{{importFileExtension}}';
3+
{{/isDateLibraryDate}}
14
{{#hasImports}}
25
{{#oneOfArrays}}
36
import type { {{{.}}} } from './{{.}}{{importFileExtension}}';
@@ -69,15 +72,15 @@ export function {{classname}}FromJSONTyped(json: any, ignoreDiscriminator: boole
6972
{{#items}}
7073
{{#isDateType}}
7174
if (Array.isArray(json)) {
72-
if (json.every(item => !(isNaN(new Date(item).getTime())))) {
73-
return json.map(value => new Date(value));
75+
if (json.every(item => !(isNaN(parseDate(item).getTime())))) {
76+
return json.map(value => parseDate(value));
7477
}
7578
}
7679
{{/isDateType}}
7780
{{#isDateTimeType}}
7881
if (Array.isArray(json)) {
79-
if (json.every(item => !(isNaN(new Date(item).getTime())))) {
80-
return json.map(value => new Date(value));
82+
if (json.every(item => !(isNaN(parseDateTime(item).getTime())))) {
83+
return json.map(value => parseDateTime(value));
8184
}
8285
}
8386
{{/isDateTimeType}}
@@ -115,14 +118,14 @@ export function {{classname}}FromJSONTyped(json: any, ignoreDiscriminator: boole
115118
{{#oneOfPrimitives}}
116119
{{^isArray}}
117120
{{#isDateType}}
118-
if (!(isNaN(new Date(json).getTime()))) {
119-
return {{^required}}json == null ? undefined : {{/required}}({{#required}}{{#isNullable}}json == null ? null : {{/isNullable}}{{/required}}new Date(json));
121+
if (!(isNaN(parseDate(json).getTime()))) {
122+
return {{^required}}json == null ? undefined : {{/required}}({{#required}}{{#isNullable}}json == null ? null : {{/isNullable}}{{/required}}parseDate(json));
120123
}
121124
{{/isDateType}}
122125
{{^isDateType}}
123126
{{#isDateTimeType}}
124-
if (!(isNaN(new Date(json).getTime()))) {
125-
return {{^required}}json == null ? undefined : {{/required}}({{#required}}{{#isNullable}}json == null ? null : {{/isNullable}}{{/required}}new Date(json));
127+
if (!(isNaN(parseDateTime(json).getTime()))) {
128+
return {{^required}}json == null ? undefined : {{/required}}({{#required}}{{#isNullable}}json == null ? null : {{/isNullable}}{{/required}}parseDateTime(json));
126129
}
127130
{{/isDateTimeType}}
128131
{{/isDateType}}
@@ -195,14 +198,14 @@ export function {{classname}}ToJSONTyped(value?: {{classname}} | null, ignoreDis
195198
{{#isDateType}}
196199
if (Array.isArray(value)) {
197200
if (value.every(item => item instanceof Date)) {
198-
return value.map(value => value.toISOString().substring(0,10));
201+
return value.map(value => serializeDate(value));
199202
}
200203
}
201204
{{/isDateType}}
202205
{{#isDateTimeType}}
203206
if (Array.isArray(value)) {
204207
if (value.every(item => item instanceof Date)) {
205-
return value.map(item => item.toISOString());
208+
return value.map(item => serializeDateTime(item));
206209
}
207210
}
208211
{{/isDateTimeType}}
@@ -241,12 +244,12 @@ export function {{classname}}ToJSONTyped(value?: {{classname}} | null, ignoreDis
241244
{{^isArray}}
242245
{{#isDateType}}
243246
if (value instanceof Date) {
244-
return ((value{{#isNullable}} as any{{/isNullable}}){{^required}}{{#isNullable}}?{{/isNullable}}{{/required}}.toISOString().substring(0,10));
247+
return (serializeDate(value{{#isNullable}} as any{{/isNullable}}));
245248
}
246249
{{/isDateType}}
247250
{{#isDateTimeType}}
248251
if (value instanceof Date) {
249-
return {{^required}}{{#isNullable}}value === null ? null : {{/isNullable}}{{^isNullable}}value == null ? undefined : {{/isNullable}}{{/required}}((value{{#isNullable}} as any{{/isNullable}}){{^required}}{{#isNullable}}?{{/isNullable}}{{/required}}.toISOString());
252+
return {{^required}}{{#isNullable}}value === null ? null : {{/isNullable}}{{^isNullable}}value == null ? undefined : {{/isNullable}}{{/required}}(serializeDateTime(value{{#isNullable}} as any{{/isNullable}}));
250253
}
251254
{{/isDateTimeType}}
252255
{{#isNumeric}}

0 commit comments

Comments
 (0)