Skip to content

Commit a1892b1

Browse files
authored
feat(rust): allow more granular Rust integer types and cleaned up clippy warnings (#12479)
* feat(rust): support various Rust integer types (#2) * fix: Use ROOT locale * fix: unsigned int bounds were incorrect * fix: deal with potential null value
1 parent d364daa commit a1892b1

File tree

4 files changed

+261
-8
lines changed

4 files changed

+261
-8
lines changed

docs/generators/rust.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ These options may be applied as additional-properties (cli) or configOptions (pl
1818

1919
| Option | Description | Values | Default |
2020
| ------ | ----------- | ------ | ------- |
21+
|bestFitInt|Use best fitting integer type where minimum or maximum is set| |false|
2122
|enumNameSuffix|Suffix that will be appended to all enum names.| ||
2223
|hideGenerationTimestamp|Hides the generation timestamp when files are generated.| |true|
2324
|library|library template (sub-template) to use.|<dl><dt>**hyper**</dt><dd>HTTP client: Hyper.</dd><dt>**reqwest**</dt><dd>HTTP client: Reqwest.</dd></dl>|reqwest|
2425
|packageName|Rust package name (convention: lowercase).| |openapi|
2526
|packageVersion|Rust package version.| |1.0.0|
27+
|preferUnsignedInt|Prefer unsigned integers where minimum value is &gt;= 0| |false|
2628
|supportAsync|If set, generate async function call instead. This option is for 'reqwest' library only| |true|
2729
|supportMultipleResponses|If set, return type wraps an enum of all possible 2xx schemas. This option is for 'reqwest' library only| |false|
2830
|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|

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

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import java.io.File;
4040
import java.io.IOException;
4141
import java.io.Writer;
42+
import java.math.BigDecimal;
4243
import java.util.*;
4344

4445
import static org.openapitools.codegen.utils.StringUtils.camelize;
@@ -50,13 +51,17 @@ public class RustClientCodegen extends DefaultCodegen implements CodegenConfig {
5051
private boolean supportAsync = true;
5152
private boolean supportMultipleResponses = false;
5253
private boolean withAWSV4Signature = false;
54+
private boolean preferUnsignedInt = false;
55+
private boolean bestFitInt = false;
5356

5457
public static final String PACKAGE_NAME = "packageName";
5558
public static final String PACKAGE_VERSION = "packageVersion";
5659
public static final String HYPER_LIBRARY = "hyper";
5760
public static final String REQWEST_LIBRARY = "reqwest";
5861
public static final String SUPPORT_ASYNC = "supportAsync";
5962
public static final String SUPPORT_MULTIPLE_RESPONSES = "supportMultipleResponses";
63+
public static final String PREFER_UNSIGNED_INT = "preferUnsignedInt";
64+
public static final String BEST_FIT_INT = "bestFitInt";
6065

6166
protected String packageName = "openapi";
6267
protected String packageVersion = "1.0.0";
@@ -65,6 +70,7 @@ public class RustClientCodegen extends DefaultCodegen implements CodegenConfig {
6570
protected String apiFolder = "src/apis";
6671
protected String modelFolder = "src/models";
6772
protected String enumSuffix = ""; // default to empty string for backward compatibility
73+
protected Map<String, String> unsignedMapping;
6874

6975
public CodegenType getTag() {
7076
return CodegenType.CLIENT;
@@ -174,6 +180,12 @@ public RustClientCodegen() {
174180
typeMapping.put("object", "serde_json::Value");
175181
typeMapping.put("AnyType", "serde_json::Value");
176182

183+
unsignedMapping = new HashMap<>();
184+
unsignedMapping.put("i8", "u8");
185+
unsignedMapping.put("i16", "u16");
186+
unsignedMapping.put("i32", "u32");
187+
unsignedMapping.put("i64", "u64");
188+
177189
// no need for rust
178190
//importMapping = new HashMap<String, String>();
179191

@@ -193,6 +205,10 @@ public RustClientCodegen() {
193205
cliOptions.add(new CliOption(CodegenConstants.ENUM_NAME_SUFFIX, CodegenConstants.ENUM_NAME_SUFFIX_DESC).defaultValue(this.enumSuffix));
194206
cliOptions.add(new CliOption(CodegenConstants.WITH_AWSV4_SIGNATURE_COMMENT, CodegenConstants.WITH_AWSV4_SIGNATURE_COMMENT_DESC, SchemaTypeUtil.BOOLEAN_TYPE)
195207
.defaultValue(Boolean.FALSE.toString()));
208+
cliOptions.add(new CliOption(PREFER_UNSIGNED_INT, "Prefer unsigned integers where minimum value is >= 0", SchemaTypeUtil.BOOLEAN_TYPE)
209+
.defaultValue(Boolean.FALSE.toString()));
210+
cliOptions.add(new CliOption(BEST_FIT_INT, "Use best fitting integer type where minimum or maximum is set", SchemaTypeUtil.BOOLEAN_TYPE)
211+
.defaultValue(Boolean.FALSE.toString()));
196212

197213
supportedLibraries.put(HYPER_LIBRARY, "HTTP client: Hyper.");
198214
supportedLibraries.put(REQWEST_LIBRARY, "HTTP client: Reqwest.");
@@ -296,6 +312,16 @@ public void processOpts() {
296312
}
297313
writePropertyBack(SUPPORT_MULTIPLE_RESPONSES, getSupportMultipleReturns());
298314

315+
if (additionalProperties.containsKey(PREFER_UNSIGNED_INT)) {
316+
this.setPreferUnsignedInt(convertPropertyToBoolean(PREFER_UNSIGNED_INT));
317+
}
318+
writePropertyBack(PREFER_UNSIGNED_INT, getPreferUnsignedInt());
319+
320+
if (additionalProperties.containsKey(BEST_FIT_INT)) {
321+
this.setBestFitInt(convertPropertyToBoolean(BEST_FIT_INT));
322+
}
323+
writePropertyBack(BEST_FIT_INT, getBestFitInt());
324+
299325
additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName);
300326
additionalProperties.put(CodegenConstants.PACKAGE_VERSION, packageVersion);
301327

@@ -360,6 +386,22 @@ public void setSupportMultipleReturns(boolean supportMultipleResponses) {
360386
this.supportMultipleResponses = supportMultipleResponses;
361387
}
362388

389+
public boolean getPreferUnsignedInt() {
390+
return preferUnsignedInt;
391+
}
392+
393+
public void setPreferUnsignedInt(boolean preferUnsignedInt) {
394+
this.preferUnsignedInt = preferUnsignedInt;
395+
}
396+
397+
public boolean getBestFitInt() {
398+
return bestFitInt;
399+
}
400+
401+
public void setBestFitInt(boolean bestFitInt) {
402+
this.bestFitInt = bestFitInt;
403+
}
404+
363405
private boolean getUseSingleRequestParameter() {
364406
return useSingleRequestParameter;
365407
}
@@ -414,7 +456,8 @@ public String toVarName(String name) {
414456

415457
@Override
416458
public String toParamName(String name) {
417-
return toVarName(name);
459+
// $ref appears to be all uppercase which is contrary to rustfmt practice so lowercase parameters
460+
return toVarName(name.toLowerCase(Locale.ROOT));
418461
}
419462

420463
@Override
@@ -525,10 +568,44 @@ public String getTypeDeclaration(Schema p) {
525568
@Override
526569
public String getSchemaType(Schema p) {
527570
String schemaType = super.getSchemaType(p);
528-
if (typeMapping.containsKey(schemaType)) {
529-
return typeMapping.get(schemaType);
571+
String type = typeMapping.getOrDefault(schemaType, schemaType);
572+
573+
boolean bestFit = convertPropertyToBoolean(BEST_FIT_INT);
574+
boolean unsigned = convertPropertyToBoolean(PREFER_UNSIGNED_INT);
575+
576+
if (bestFit || unsigned) {
577+
BigDecimal maximum = p.getMaximum();
578+
BigDecimal minimum = p.getMinimum();
579+
580+
try {
581+
if (maximum != null && minimum != null) {
582+
long max = maximum.longValueExact();
583+
long min = minimum.longValueExact();
584+
585+
if (unsigned && bestFit && max <= (Byte.MAX_VALUE * 2) + 1 && min >= 0) {
586+
type = "u8";
587+
} else if (bestFit && max <= Byte.MAX_VALUE && min >= Byte.MIN_VALUE) {
588+
type = "i8";
589+
} else if (unsigned && bestFit && max <= (Short.MAX_VALUE * 2) + 1 && min >= 0) {
590+
type = "u16";
591+
} else if (bestFit && max <= Short.MAX_VALUE && min >= Short.MIN_VALUE) {
592+
type = "i16";
593+
} else if (unsigned && bestFit && max <= (Integer.MAX_VALUE * 2L) + 1 && min >= 0) {
594+
type = "u32";
595+
} else if (bestFit && max <= Integer.MAX_VALUE && min >= Integer.MIN_VALUE) {
596+
type = "i32";
597+
}
598+
}
599+
} catch (ArithmeticException a) {
600+
// no-op; case will be handled in the next if block
601+
}
602+
603+
if (unsigned && minimum != null && minimum.compareTo(BigDecimal.ZERO) >= 0 && unsignedMapping.containsKey(type)) {
604+
type = unsignedMapping.get(type);
605+
}
530606
}
531-
return schemaType;
607+
608+
return type;
532609
}
533610

534611
@Override
@@ -640,7 +717,6 @@ public String escapeUnsafeCharacters(String input) {
640717
return input.replace("*/", "*_/").replace("/*", "/_*");
641718
}
642719

643-
644720
@Override
645721
public String toEnumValue(String value, String datatype) {
646722
if ("int".equals(datatype) || "double".equals(datatype) || "float".equals(datatype)) {

modules/openapi-generator/src/main/resources/rust/request.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,19 @@ impl Request {
7575
self
7676
}
7777

78+
#[allow(unused)]
7879
pub fn with_query_param(mut self, basename: String, param: String) -> Self {
7980
self.query_params.insert(basename, param);
8081
self
8182
}
8283

84+
#[allow(unused)]
8385
pub fn with_path_param(mut self, basename: String, param: String) -> Self {
8486
self.path_params.insert(basename, param);
8587
self
8688
}
8789

90+
#[allow(unused)]
8891
pub fn with_form_param(mut self, basename: String, param: String) -> Self {
8992
self.form_params.insert(basename, param);
9093
self

0 commit comments

Comments
 (0)