Skip to content

Commit 599b93d

Browse files
authored
Feat: (Rust) Added chrono (date/date-time) support for Rust Generator [resolves #19319] (#23451)
* Functional Change * Updated tests to now use chrono * Added no chrono test case * Docs update * Review change from `chrono::NaiveDateTime` to `chrono::DateTime<chrono::FixedOffset> `` * Review change from `chrono::NaiveDateTime` to `chrono::DateTime<chrono::FixedOffset>` * Doc update
1 parent 72fe970 commit 599b93d

111 files changed

Lines changed: 4306 additions & 29 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: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
generatorName: rust
2+
outputDir: samples/client/petstore/rust/reqwest/petstore-no-chrono
3+
library: reqwest
4+
inputSpec: modules/openapi-generator/src/test/resources/3_0/rust/petstore.yaml
5+
templateDir: modules/openapi-generator/src/main/resources/rust
6+
additionalProperties:
7+
supportAsync: false
8+
useChrono: false
9+
packageName: petstore-reqwest-no-chrono
10+
enumNameMappings:
11+
delivered: shipped

docs/generators/rust.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
3434
|supportTokenSource|If set, add support for google-cloud-token. This option is for 'reqwest' and 'reqwest-trait' library only and requires the 'supportAsync' option| |false|
3535
|topLevelApiClient|Creates a top level `Api` trait and `ApiClient` struct that contain all Apis. This option is for 'reqwest-trait' library only| |false|
3636
|useBonBuilder|Use the bon crate for building parameter types. This option is for the 'reqwest-trait' library only| |false|
37+
|useChrono|If set, use chrono to represent date time objects (`chrono::NaiveDate` for `date` and `chrono::DateTime&lt;chrono::FixedOffset&gt;&gt;` for `date-time`)| |true|
3738
|useSerdePathToError|If set, use the serde_path_to_error library to enhance serde error messages. This option is for 'reqwest' and 'reqwest-trait' library only| |false|
3839
|useSingleRequestParameter|Setting this property to true will generate functions with a single argument containing all API endpoint parameters instead of one argument per parameter.| |false|
3940
|withAWSV4Signature|whether to include AWS v4 signature support| |false|

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

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import java.math.BigDecimal;
4646
import java.math.BigInteger;
4747
import java.util.*;
48+
import java.util.function.Function;
4849
import java.util.stream.Collectors;
4950

5051
public class RustClientCodegen extends AbstractRustCodegen implements CodegenConfig {
@@ -54,6 +55,7 @@ public class RustClientCodegen extends AbstractRustCodegen implements CodegenCon
5455
@Setter(AccessLevel.PRIVATE) private boolean supportMiddleware = false;
5556
@Setter(AccessLevel.PRIVATE) private boolean useSerdePathToError = false;
5657
@Setter(AccessLevel.PRIVATE) private boolean supportTokenSource = false;
58+
private boolean useChrono = true;
5759
private boolean supportMultipleResponses = false;
5860
private boolean withAWSV4Signature = false;
5961
@Setter private boolean preferUnsignedInt = false;
@@ -73,6 +75,7 @@ public class RustClientCodegen extends AbstractRustCodegen implements CodegenCon
7375
public static final String SUPPORT_MIDDLEWARE = "supportMiddleware";
7476
public static final String USE_SERDE_PATH_TO_ERROR = "useSerdePathToError";
7577
public static final String SUPPORT_TOKEN_SOURCE = "supportTokenSource";
78+
public static final String USE_CHRONO = "useChrono";
7679
public static final String SUPPORT_MULTIPLE_RESPONSES = "supportMultipleResponses";
7780
public static final String PREFER_UNSIGNED_INT = "preferUnsignedInt";
7881
public static final String BEST_FIT_INT = "bestFitInt";
@@ -91,6 +94,9 @@ public class RustClientCodegen extends AbstractRustCodegen implements CodegenCon
9194
// The API has at least one UUID type.
9295
// If the API does not contain any UUIDs we do not need depend on the `uuid` crate
9396
private boolean hasUUIDs = false;
97+
// The API has at least one Chrono type.
98+
// If the API does not contain any Dates or DateTimes we do not need to depend on the `chrono` crate
99+
private boolean usesChronoTypes = false;
94100

95101
@Override
96102
public CodegenType getTag() {
@@ -182,8 +188,10 @@ public RustClientCodegen() {
182188
typeMapping.put("map", "std::collections::HashMap");
183189
typeMapping.put("UUID", "uuid::Uuid");
184190
typeMapping.put("URI", "String");
185-
typeMapping.put("date", "string");
186-
typeMapping.put("DateTime", "String");
191+
// Temporarily set the default to chrono. Then when `processOpts` is called it will update to not use chrono if specified
192+
typeMapping.put("date", "chrono::NaiveDate");
193+
typeMapping.put("DateTime", "chrono::DateTime<chrono::FixedOffset>");
194+
187195
typeMapping.put("password", "String");
188196
typeMapping.put("decimal", "String");
189197

@@ -216,6 +224,8 @@ public RustClientCodegen() {
216224
.defaultValue(Boolean.FALSE.toString()));
217225
cliOptions.add(new CliOption(SUPPORT_TOKEN_SOURCE, "If set, add support for google-cloud-token. This option is for 'reqwest' and 'reqwest-trait' library only and requires the 'supportAsync' option", SchemaTypeUtil.BOOLEAN_TYPE)
218226
.defaultValue(Boolean.FALSE.toString()));
227+
cliOptions.add(new CliOption(USE_CHRONO, "If set, use chrono to represent date time objects (`chrono::NaiveDate` for `date` and `chrono::DateTime<chrono::FixedOffset>>` for `date-time`)", SchemaTypeUtil.BOOLEAN_TYPE)
228+
.defaultValue(Boolean.TRUE.toString()));
219229
cliOptions.add(new CliOption(SUPPORT_MULTIPLE_RESPONSES, "If set, return type wraps an enum of all possible 2xx schemas. This option is for 'reqwest' and 'reqwest-trait' library only", SchemaTypeUtil.BOOLEAN_TYPE)
220230
.defaultValue(Boolean.FALSE.toString()));
221231
cliOptions.add(new CliOption(CodegenConstants.ENUM_NAME_SUFFIX, CodegenConstants.ENUM_NAME_SUFFIX_DESC).defaultValue(this.enumSuffix));
@@ -463,6 +473,11 @@ public void processOpts() {
463473
}
464474
writePropertyBack(SUPPORT_TOKEN_SOURCE, getSupportTokenSource());
465475

476+
if (additionalProperties.containsKey(USE_CHRONO)) {
477+
this.setUseChrono(convertPropertyToBoolean(USE_CHRONO));
478+
}
479+
writePropertyBack(USE_CHRONO, isUseChrono());
480+
466481
if (additionalProperties.containsKey(SUPPORT_MULTIPLE_RESPONSES)) {
467482
this.setSupportMultipleReturns(convertPropertyToBoolean(SUPPORT_MULTIPLE_RESPONSES));
468483
}
@@ -772,12 +787,23 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<Mo
772787
}
773788
}
774789

790+
// Check for UUIDs
775791
for (var param : operation.allParams) {
776792
if (!hasUUIDs && param.isUuid) {
777793
hasUUIDs = true;
778794
break;
779795
}
780796
}
797+
// Check for Chrono Types
798+
if(isUseChrono()){
799+
for (CodegenParameter param : operation.allParams) {
800+
if (!usesChronoTypes && (param.isDate || param.isDateTime)) {
801+
LOGGER.debug("Found Chrono Type in operation Parameter: {}", param.paramName);
802+
usesChronoTypes = true;
803+
break;
804+
}
805+
}
806+
}
781807

782808
// If we use a file body parameter, we need to include the imports and crates for it
783809
// But they should be added only once per file
@@ -874,32 +900,61 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<Mo
874900
CodegenModel m = map.getModel();
875901
if (m.getIsUuid() || hasUuidInProperties(m.vars)) {
876902
hasUUIDs = true;
877-
LOGGER.debug("found UUID in model: " + m.name);
903+
LOGGER.debug("Found UUID in model: {}", m.name);
904+
break;
905+
}
906+
}
907+
}
908+
909+
if (isUseChrono() && !usesChronoTypes) {
910+
for (var map : allModels) {
911+
CodegenModel m = map.getModel();
912+
if (m.getIsDate() || hasChronoTypeInProperties(m.vars)) {
913+
usesChronoTypes = true;
914+
LOGGER.debug("Found Chrono Type in model: {}", m.name);
878915
break;
879916
}
880917
}
881918
}
882919

883920
this.additionalProperties.put("hasUUIDs", hasUUIDs);
921+
this.additionalProperties.put("usesChronoTypes", isUseChrono() && usesChronoTypes);
884922
return objs;
885923
}
886924

887925
/**
888926
* Recursively searches for a model's properties for a UUID type field.
889927
*/
890928
private boolean hasUuidInProperties(List<CodegenProperty> properties) {
929+
return checkForPropertiesRecursively(properties, (property) -> property.isUuid);
930+
}
931+
932+
/**
933+
* Recursively searches for a model's properties for a Date or DateTime type field.
934+
*/
935+
private boolean hasChronoTypeInProperties(List<CodegenProperty> properties) {
936+
return checkForPropertiesRecursively(properties, (property) -> property.isDate || property.isDateTime);
937+
}
938+
939+
/**
940+
* Recursively searches for a condition in a property
941+
* @param properties the {@link CodegenProperty} to recursively search for
942+
* @param propertyCheck the {@link Function} to be applied to check an individual {@link CodegenProperty} for a match
943+
* @return true if there is at least one match, false if there is no match
944+
*/
945+
private boolean checkForPropertiesRecursively(List<CodegenProperty> properties, Function<CodegenProperty, Boolean> propertyCheck){
891946
for (CodegenProperty property : properties) {
892-
if (property.isUuid) {
947+
if (propertyCheck.apply(property)) {
893948
return true;
894949
}
895950
// Check nested properties
896-
if (property.items != null && hasUuidInProperties(Collections.singletonList(property.items))) {
951+
if (property.items != null && checkForPropertiesRecursively(Collections.singletonList(property.items), propertyCheck)) {
897952
return true;
898953
}
899-
if (property.additionalProperties != null && hasUuidInProperties(Collections.singletonList(property.additionalProperties))) {
954+
if (property.additionalProperties != null && checkForPropertiesRecursively(Collections.singletonList(property.additionalProperties), propertyCheck)) {
900955
return true;
901956
}
902-
if (property.vars != null && hasUuidInProperties(property.vars)) {
957+
if (property.vars != null && checkForPropertiesRecursively(property.vars, propertyCheck)) {
903958
return true;
904959
}
905960
}
@@ -938,4 +993,20 @@ public static <K, V> boolean hasDuplicateValues(Map<K, V> map) {
938993
Set<V> uniqueValues = new HashSet<>(map.values());
939994
return uniqueValues.size() < map.size();
940995
}
996+
997+
998+
private boolean isUseChrono() {
999+
return useChrono;
1000+
}
1001+
1002+
private void setUseChrono(boolean useChrono) {
1003+
this.useChrono = useChrono;
1004+
if(isUseChrono()){
1005+
typeMapping.put("date", "chrono::NaiveDate");
1006+
typeMapping.put("DateTime", "chrono::DateTime<chrono::FixedOffset>");
1007+
}else{
1008+
typeMapping.put("date", "String");
1009+
typeMapping.put("DateTime", "String");
1010+
}
1011+
}
9411012
}

modules/openapi-generator/src/main/resources/rust/Cargo.mustache

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ url = "^2.5"
4545
{{#hasUUIDs}}
4646
uuid = { version = "^1.8", features = ["serde", "v4"] }
4747
{{/hasUUIDs}}
48+
{{#usesChronoTypes}}
49+
chrono = { version = "^0.4", features = ["serde"] }
50+
{{/usesChronoTypes}}
4851
{{#hyper}}
4952
{{#hyper0x}}
5053
hyper = { version = "~0.14", features = ["full"] }

samples/client/petstore/rust/hyper/petstore/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ serde_json = "^1.0"
1313
serde_repr = "^0.1"
1414
url = "^2.5"
1515
uuid = { version = "^1.8", features = ["serde", "v4"] }
16+
chrono = { version = "^0.4", features = ["serde"] }
1617
hyper = { version = "^1.3.1", features = ["full"] }
1718
hyper-util = { version = "0.1.5", features = ["client", "client-legacy", "http1", "http2"] }
1819
http-body-util = { version = "0.1.2" }

samples/client/petstore/rust/hyper/petstore/docs/Order.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Name | Type | Description | Notes
77
**id** | Option<**i64**> | | [optional]
88
**pet_id** | Option<**i64**> | | [optional]
99
**quantity** | Option<**i32**> | | [optional]
10-
**ship_date** | Option<**String**> | | [optional]
10+
**ship_date** | Option<**chrono::DateTime<chrono::FixedOffset>**> | | [optional]
1111
**status** | Option<**Status**> | Order Status (enum: placed, approved, delivered) | [optional]
1212
**complete** | Option<**bool**> | | [optional][default to false]
1313

samples/client/petstore/rust/hyper/petstore/src/models/order.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub struct Order {
2121
#[serde(rename = "quantity", skip_serializing_if = "Option::is_none")]
2222
pub quantity: Option<i32>,
2323
#[serde(rename = "shipDate", skip_serializing_if = "Option::is_none")]
24-
pub ship_date: Option<String>,
24+
pub ship_date: Option<chrono::DateTime<chrono::FixedOffset>>,
2525
/// Order Status
2626
#[serde(rename = "status", skip_serializing_if = "Option::is_none")]
2727
pub status: Option<Status>,

samples/client/petstore/rust/hyper0x/petstore/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ serde_json = "^1.0"
1313
serde_repr = "^0.1"
1414
url = "^2.5"
1515
uuid = { version = "^1.8", features = ["serde", "v4"] }
16+
chrono = { version = "^0.4", features = ["serde"] }
1617
hyper = { version = "~0.14", features = ["full"] }
1718
hyper-tls = "~0.5"
1819
http = "~0.2"

samples/client/petstore/rust/hyper0x/petstore/docs/Order.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Name | Type | Description | Notes
77
**id** | Option<**i64**> | | [optional]
88
**pet_id** | Option<**i64**> | | [optional]
99
**quantity** | Option<**i32**> | | [optional]
10-
**ship_date** | Option<**String**> | | [optional]
10+
**ship_date** | Option<**chrono::DateTime<chrono::FixedOffset>**> | | [optional]
1111
**status** | Option<**Status**> | Order Status (enum: placed, approved, delivered) | [optional]
1212
**complete** | Option<**bool**> | | [optional][default to false]
1313

samples/client/petstore/rust/hyper0x/petstore/src/models/order.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub struct Order {
2121
#[serde(rename = "quantity", skip_serializing_if = "Option::is_none")]
2222
pub quantity: Option<i32>,
2323
#[serde(rename = "shipDate", skip_serializing_if = "Option::is_none")]
24-
pub ship_date: Option<String>,
24+
pub ship_date: Option<chrono::DateTime<chrono::FixedOffset>>,
2525
/// Order Status
2626
#[serde(rename = "status", skip_serializing_if = "Option::is_none")]
2727
pub status: Option<Status>,

0 commit comments

Comments
 (0)