From a1892b163688d205ee8a44051b59aba3928ec5fc Mon Sep 17 00:00:00 2001 From: thrykol Date: Sat, 9 Jul 2022 06:37:16 -0600 Subject: [PATCH] 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 --- docs/generators/rust.md | 2 + .../codegen/languages/RustClientCodegen.java | 86 ++++++++- .../src/main/resources/rust/request.rs | 3 + .../codegen/rust/RustClientCodegenTest.java | 178 +++++++++++++++++- 4 files changed, 261 insertions(+), 8 deletions(-) diff --git a/docs/generators/rust.md b/docs/generators/rust.md index 04601e32f85e..03303f46ec44 100644 --- a/docs/generators/rust.md +++ b/docs/generators/rust.md @@ -18,11 +18,13 @@ These options may be applied as additional-properties (cli) or configOptions (pl | Option | Description | Values | Default | | ------ | ----------- | ------ | ------- | +|bestFitInt|Use best fitting integer type where minimum or maximum is set| |false| |enumNameSuffix|Suffix that will be appended to all enum names.| || |hideGenerationTimestamp|Hides the generation timestamp when files are generated.| |true| |library|library template (sub-template) to use.|
**hyper**
HTTP client: Hyper.
**reqwest**
HTTP client: Reqwest.
|reqwest| |packageName|Rust package name (convention: lowercase).| |openapi| |packageVersion|Rust package version.| |1.0.0| +|preferUnsignedInt|Prefer unsigned integers where minimum value is >= 0| |false| |supportAsync|If set, generate async function call instead. This option is for 'reqwest' library only| |true| |supportMultipleResponses|If set, return type wraps an enum of all possible 2xx schemas. This option is for 'reqwest' library only| |false| |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| diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustClientCodegen.java index 7bcad6fbc571..6e54e1670f95 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustClientCodegen.java @@ -39,6 +39,7 @@ import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.io.Writer; +import java.math.BigDecimal; import java.util.*; import static org.openapitools.codegen.utils.StringUtils.camelize; @@ -50,6 +51,8 @@ public class RustClientCodegen extends DefaultCodegen implements CodegenConfig { private boolean supportAsync = true; private boolean supportMultipleResponses = false; private boolean withAWSV4Signature = false; + private boolean preferUnsignedInt = false; + private boolean bestFitInt = false; public static final String PACKAGE_NAME = "packageName"; public static final String PACKAGE_VERSION = "packageVersion"; @@ -57,6 +60,8 @@ public class RustClientCodegen extends DefaultCodegen implements CodegenConfig { public static final String REQWEST_LIBRARY = "reqwest"; public static final String SUPPORT_ASYNC = "supportAsync"; public static final String SUPPORT_MULTIPLE_RESPONSES = "supportMultipleResponses"; + public static final String PREFER_UNSIGNED_INT = "preferUnsignedInt"; + public static final String BEST_FIT_INT = "bestFitInt"; protected String packageName = "openapi"; protected String packageVersion = "1.0.0"; @@ -65,6 +70,7 @@ public class RustClientCodegen extends DefaultCodegen implements CodegenConfig { protected String apiFolder = "src/apis"; protected String modelFolder = "src/models"; protected String enumSuffix = ""; // default to empty string for backward compatibility + protected Map unsignedMapping; public CodegenType getTag() { return CodegenType.CLIENT; @@ -174,6 +180,12 @@ public class RustClientCodegen extends DefaultCodegen implements CodegenConfig { typeMapping.put("object", "serde_json::Value"); typeMapping.put("AnyType", "serde_json::Value"); + unsignedMapping = new HashMap<>(); + unsignedMapping.put("i8", "u8"); + unsignedMapping.put("i16", "u16"); + unsignedMapping.put("i32", "u32"); + unsignedMapping.put("i64", "u64"); + // no need for rust //importMapping = new HashMap(); @@ -193,6 +205,10 @@ public class RustClientCodegen extends DefaultCodegen implements CodegenConfig { cliOptions.add(new CliOption(CodegenConstants.ENUM_NAME_SUFFIX, CodegenConstants.ENUM_NAME_SUFFIX_DESC).defaultValue(this.enumSuffix)); cliOptions.add(new CliOption(CodegenConstants.WITH_AWSV4_SIGNATURE_COMMENT, CodegenConstants.WITH_AWSV4_SIGNATURE_COMMENT_DESC, SchemaTypeUtil.BOOLEAN_TYPE) .defaultValue(Boolean.FALSE.toString())); + cliOptions.add(new CliOption(PREFER_UNSIGNED_INT, "Prefer unsigned integers where minimum value is >= 0", SchemaTypeUtil.BOOLEAN_TYPE) + .defaultValue(Boolean.FALSE.toString())); + cliOptions.add(new CliOption(BEST_FIT_INT, "Use best fitting integer type where minimum or maximum is set", SchemaTypeUtil.BOOLEAN_TYPE) + .defaultValue(Boolean.FALSE.toString())); supportedLibraries.put(HYPER_LIBRARY, "HTTP client: Hyper."); supportedLibraries.put(REQWEST_LIBRARY, "HTTP client: Reqwest."); @@ -296,6 +312,16 @@ public class RustClientCodegen extends DefaultCodegen implements CodegenConfig { } writePropertyBack(SUPPORT_MULTIPLE_RESPONSES, getSupportMultipleReturns()); + if (additionalProperties.containsKey(PREFER_UNSIGNED_INT)) { + this.setPreferUnsignedInt(convertPropertyToBoolean(PREFER_UNSIGNED_INT)); + } + writePropertyBack(PREFER_UNSIGNED_INT, getPreferUnsignedInt()); + + if (additionalProperties.containsKey(BEST_FIT_INT)) { + this.setBestFitInt(convertPropertyToBoolean(BEST_FIT_INT)); + } + writePropertyBack(BEST_FIT_INT, getBestFitInt()); + additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName); additionalProperties.put(CodegenConstants.PACKAGE_VERSION, packageVersion); @@ -360,6 +386,22 @@ public class RustClientCodegen extends DefaultCodegen implements CodegenConfig { this.supportMultipleResponses = supportMultipleResponses; } + public boolean getPreferUnsignedInt() { + return preferUnsignedInt; + } + + public void setPreferUnsignedInt(boolean preferUnsignedInt) { + this.preferUnsignedInt = preferUnsignedInt; + } + + public boolean getBestFitInt() { + return bestFitInt; + } + + public void setBestFitInt(boolean bestFitInt) { + this.bestFitInt = bestFitInt; + } + private boolean getUseSingleRequestParameter() { return useSingleRequestParameter; } @@ -414,7 +456,8 @@ public class RustClientCodegen extends DefaultCodegen implements CodegenConfig { @Override public String toParamName(String name) { - return toVarName(name); + // $ref appears to be all uppercase which is contrary to rustfmt practice so lowercase parameters + return toVarName(name.toLowerCase(Locale.ROOT)); } @Override @@ -525,10 +568,44 @@ public class RustClientCodegen extends DefaultCodegen implements CodegenConfig { @Override public String getSchemaType(Schema p) { String schemaType = super.getSchemaType(p); - if (typeMapping.containsKey(schemaType)) { - return typeMapping.get(schemaType); + String type = typeMapping.getOrDefault(schemaType, schemaType); + + boolean bestFit = convertPropertyToBoolean(BEST_FIT_INT); + boolean unsigned = convertPropertyToBoolean(PREFER_UNSIGNED_INT); + + if (bestFit || unsigned) { + BigDecimal maximum = p.getMaximum(); + BigDecimal minimum = p.getMinimum(); + + try { + if (maximum != null && minimum != null) { + long max = maximum.longValueExact(); + long min = minimum.longValueExact(); + + if (unsigned && bestFit && max <= (Byte.MAX_VALUE * 2) + 1 && min >= 0) { + type = "u8"; + } else if (bestFit && max <= Byte.MAX_VALUE && min >= Byte.MIN_VALUE) { + type = "i8"; + } else if (unsigned && bestFit && max <= (Short.MAX_VALUE * 2) + 1 && min >= 0) { + type = "u16"; + } else if (bestFit && max <= Short.MAX_VALUE && min >= Short.MIN_VALUE) { + type = "i16"; + } else if (unsigned && bestFit && max <= (Integer.MAX_VALUE * 2L) + 1 && min >= 0) { + type = "u32"; + } else if (bestFit && max <= Integer.MAX_VALUE && min >= Integer.MIN_VALUE) { + type = "i32"; + } + } + } catch (ArithmeticException a) { + // no-op; case will be handled in the next if block + } + + if (unsigned && minimum != null && minimum.compareTo(BigDecimal.ZERO) >= 0 && unsignedMapping.containsKey(type)) { + type = unsignedMapping.get(type); + } } - return schemaType; + + return type; } @Override @@ -640,7 +717,6 @@ public class RustClientCodegen extends DefaultCodegen implements CodegenConfig { return input.replace("*/", "*_/").replace("/*", "/_*"); } - @Override public String toEnumValue(String value, String datatype) { if ("int".equals(datatype) || "double".equals(datatype) || "float".equals(datatype)) { diff --git a/modules/openapi-generator/src/main/resources/rust/request.rs b/modules/openapi-generator/src/main/resources/rust/request.rs index 0aa51fa0c592..d53037850212 100644 --- a/modules/openapi-generator/src/main/resources/rust/request.rs +++ b/modules/openapi-generator/src/main/resources/rust/request.rs @@ -75,16 +75,19 @@ impl Request { self } + #[allow(unused)] pub fn with_query_param(mut self, basename: String, param: String) -> Self { self.query_params.insert(basename, param); self } + #[allow(unused)] pub fn with_path_param(mut self, basename: String, param: String) -> Self { self.path_params.insert(basename, param); self } + #[allow(unused)] pub fn with_form_param(mut self, basename: String, param: String) -> Self { self.form_params.insert(basename, param); self diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/rust/RustClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/rust/RustClientCodegenTest.java index 76bdc94fdd67..906e55e51577 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/rust/RustClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/rust/RustClientCodegenTest.java @@ -17,11 +17,14 @@ package org.openapitools.codegen.rust; +import io.swagger.v3.oas.models.media.IntegerSchema; import org.openapitools.codegen.CodegenConstants; import org.openapitools.codegen.languages.RustClientCodegen; import org.testng.Assert; import org.testng.annotations.Test; +import java.math.BigDecimal; + public class RustClientCodegenTest { @Test @@ -30,17 +33,31 @@ public class RustClientCodegenTest { codegen.processOpts(); Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.HIDE_GENERATION_TIMESTAMP), Boolean.TRUE); - Assert.assertEquals(codegen.isHideGenerationTimestamp(), true); + Assert.assertTrue(codegen.isHideGenerationTimestamp()); + + Assert.assertEquals(codegen.additionalProperties().get(RustClientCodegen.PREFER_UNSIGNED_INT), Boolean.FALSE); + Assert.assertFalse(codegen.getPreferUnsignedInt()); + + Assert.assertEquals(codegen.additionalProperties().get(RustClientCodegen.BEST_FIT_INT), Boolean.FALSE); + Assert.assertFalse(codegen.getBestFitInt()); } @Test public void testSettersForConfigValues() throws Exception { final RustClientCodegen codegen = new RustClientCodegen(); codegen.setHideGenerationTimestamp(false); + codegen.setPreferUnsignedInt(true); + codegen.setBestFitInt(true); codegen.processOpts(); Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.HIDE_GENERATION_TIMESTAMP), Boolean.FALSE); - Assert.assertEquals(codegen.isHideGenerationTimestamp(), false); + Assert.assertFalse(codegen.isHideGenerationTimestamp()); + + Assert.assertEquals(codegen.additionalProperties().get(RustClientCodegen.PREFER_UNSIGNED_INT), Boolean.TRUE); + Assert.assertTrue(codegen.getPreferUnsignedInt()); + + Assert.assertEquals(codegen.additionalProperties().get(RustClientCodegen.BEST_FIT_INT), Boolean.TRUE); + Assert.assertTrue(codegen.getBestFitInt()); } @Test @@ -50,7 +67,162 @@ public class RustClientCodegenTest { codegen.processOpts(); Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.HIDE_GENERATION_TIMESTAMP), Boolean.FALSE); - Assert.assertEquals(codegen.isHideGenerationTimestamp(), false); + Assert.assertFalse(codegen.isHideGenerationTimestamp()); } + @Test + public void testLowercaseParameterName() throws Exception { + final RustClientCodegen codegen = new RustClientCodegen(); + + Assert.assertEquals(codegen.toParamName("TESTING"), "testing"); + } + + @Test + public void testGetSchemaTypeIntegerNoBounds() { + final IntegerSchema s = new IntegerSchema(); + final RustClientCodegen codegen = new RustClientCodegen(); + codegen.setBestFitInt(true); + codegen.processOpts(); + + s.setType("i32"); + + // min and max are null + Assert.assertEquals(codegen.getSchemaType(s), "i32"); + + s.setMinimum(BigDecimal.valueOf(Short.MIN_VALUE)); + + // max is null + Assert.assertEquals(codegen.getSchemaType(s), "i32"); + + s.setMaximum(BigDecimal.valueOf(Short.MAX_VALUE)); + s.setMinimum(null); + + // min is null + Assert.assertEquals(codegen.getSchemaType(s), "i32"); + } + + @Test + public void testGetSchemaTypeI64() { + final IntegerSchema s = new IntegerSchema(); + final RustClientCodegen codegen = new RustClientCodegen(); + codegen.setBestFitInt(true); + codegen.processOpts(); + + s.setType("i64"); + s.setMinimum(BigDecimal.valueOf(Long.MIN_VALUE)); + s.setMaximum(BigDecimal.valueOf(Long.MAX_VALUE)); + + Assert.assertEquals(codegen.getSchemaType(s), "i64"); + } + + @Test + public void testGetSchemaTypeI32() { + final IntegerSchema s = new IntegerSchema(); + final RustClientCodegen codegen = new RustClientCodegen(); + codegen.setBestFitInt(true); + codegen.processOpts(); + + s.setType("i32"); + s.setMinimum(BigDecimal.valueOf(Integer.MIN_VALUE)); + s.setMaximum(BigDecimal.valueOf(Integer.MAX_VALUE)); + + Assert.assertEquals(codegen.getSchemaType(s), "i32"); + } + + @Test + public void testGetSchemaTypeI16() { + final IntegerSchema s = new IntegerSchema(); + final RustClientCodegen codegen = new RustClientCodegen(); + codegen.setBestFitInt(true); + codegen.processOpts(); + + s.setType("i32"); + s.setMinimum(BigDecimal.valueOf(Short.MIN_VALUE)); + s.setMaximum(BigDecimal.valueOf(Short.MAX_VALUE)); + + Assert.assertEquals(codegen.getSchemaType(s), "i16"); + } + + @Test + public void testGetSchemaTypeI8() { + final IntegerSchema s = new IntegerSchema(); + final RustClientCodegen codegen = new RustClientCodegen(); + codegen.setBestFitInt(true); + codegen.processOpts(); + + s.setType("i32"); + s.setMinimum(BigDecimal.valueOf(-128)); + s.setMaximum(BigDecimal.valueOf(127)); + + Assert.assertEquals(codegen.getSchemaType(s), "i8"); + } + + @Test + public void testGetSchemaTypeU64() { + final IntegerSchema s = new IntegerSchema(); + final RustClientCodegen codegen = new RustClientCodegen(); + codegen.setPreferUnsignedInt(true); + codegen.processOpts(); + + s.setType("i64"); + s.setMinimum(BigDecimal.ZERO); + + Assert.assertEquals(codegen.getSchemaType(s), "u64"); + + s.setMaximum(BigDecimal.valueOf(Long.MAX_VALUE).add(BigDecimal.valueOf(Long.MAX_VALUE))); + + Assert.assertEquals(codegen.getSchemaType(s), "u64"); + + s.setMinimum(null); + s.setMaximum(null); + + Assert.assertEquals(codegen.getSchemaType(s), "i64"); + } + + @Test + public void testGetSchemaTypeU32() { + final IntegerSchema s = new IntegerSchema(); + final RustClientCodegen codegen = new RustClientCodegen(); + codegen.setPreferUnsignedInt(true); + codegen.processOpts(); + + s.setType("i32"); + s.setMinimum(BigDecimal.ZERO); + + Assert.assertEquals(codegen.getSchemaType(s), "u32"); + + s.setMaximum(BigDecimal.valueOf(65535)); + + Assert.assertEquals(codegen.getSchemaType(s), "u32"); + } + + @Test + public void testGetSchemaTypeU16() { + final IntegerSchema s = new IntegerSchema(); + final RustClientCodegen codegen = new RustClientCodegen(); + codegen.setBestFitInt(true); + codegen.setPreferUnsignedInt(true); + codegen.processOpts(); + + s.setType("i32"); + s.setMinimum(BigDecimal.ZERO); + s.setMaximum(BigDecimal.valueOf(65535)); + + Assert.assertEquals(codegen.getSchemaType(s), "u16"); + } + + @Test + public void testGetSchemaTypeU8() { + final IntegerSchema s = new IntegerSchema(); + final RustClientCodegen codegen = new RustClientCodegen(); + codegen.setBestFitInt(true); + codegen.setPreferUnsignedInt(true); + codegen.processOpts(); + + s.setType("i32"); + s.setMinimum(BigDecimal.ZERO); + s.setMaximum(BigDecimal.valueOf(255)); + + Assert.assertEquals(codegen.getSchemaType(s), "u8"); + } }