[rust] [rust-server] More abstract functions including integer fitting (#13503)

* [rust] [rust-server] Abstract Rust Integer fitting

* Add docstrings
This commit is contained in:
Jacob Halsey
2022-10-06 17:40:24 +01:00
committed by GitHub
parent 8b10dc3830
commit 32936ad71b
13 changed files with 373 additions and 278 deletions

View File

@@ -1,13 +1,17 @@
package org.openapitools.codegen.languages;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import org.openapitools.codegen.CodegenConfig;
import org.openapitools.codegen.CodegenProperty;
import org.openapitools.codegen.DefaultCodegen;
import org.openapitools.codegen.GeneratorLanguage;
import org.openapitools.codegen.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.math.BigInteger;
import java.util.*;
import java.util.function.Function;
@@ -37,12 +41,106 @@ public abstract class AbstractRustCodegen extends DefaultCodegen implements Code
);
}
@Override
public GeneratorLanguage generatorLanguage() { return GeneratorLanguage.RUST; }
@Override
public String escapeQuotationMark(String input) {
// remove " to avoid code injection
return input.replace("\"", "");
}
@Override
public String escapeUnsafeCharacters(String input) {
return input.replace("*/", "*_/").replace("/*", "/_*");
}
@Override
public boolean isReservedWord(String word) {
// This is overridden to take account of Rust reserved words being case-sensitive.
return word != null && reservedWords.contains(word);
}
/**
* Determine the best fitting Rust type for an integer property. This is intended for use when a specific format
* has not been defined in the specification. Where the minimum or maximum is not known then the returned type
* will default to having at least 32 bits.
* @param minimum The minimum value as set in the specification.
* @param exclusiveMinimum If the minimum value itself is excluded by the specification.
* @param maximum The maximum value as set in the specification.
* @param exclusiveMaximum If the maximum value itself is excluded by the specification.
* @param preferUnsigned Use unsigned types where the effective minimum is greater than or equal to zero.
* @return The Rust data type name.
*/
@VisibleForTesting
public String bestFittingIntegerType(@Nullable BigInteger minimum,
boolean exclusiveMinimum,
@Nullable BigInteger maximum,
boolean exclusiveMaximum,
boolean preferUnsigned) {
if (exclusiveMinimum) {
minimum = Optional.ofNullable(minimum).map(min -> min.add(BigInteger.ONE)).orElse(null);
}
if (exclusiveMaximum) {
maximum = Optional.ofNullable(maximum).map(max -> max.subtract(BigInteger.ONE)).orElse(null);
}
// If the minimum value is greater than or equal to zero, then it is safe to use an unsigned type
boolean guaranteedPositive = Optional.ofNullable(minimum).map(min -> min.signum() >= 0).orElse(false);
int requiredBits = Math.max(
Optional.ofNullable(minimum).map(BigInteger::bitLength).orElse(0),
Optional.ofNullable(maximum).map(BigInteger::bitLength).orElse(0)
);
// We will only enable the smaller types (less than 32 bits) if we know both the minimum and maximum
boolean knownRange = !(Objects.isNull(minimum) || Objects.isNull(maximum));
if (guaranteedPositive && preferUnsigned) {
if (requiredBits <= 8 && knownRange) {
return "u8";
} else if (requiredBits <= 16 && knownRange) {
return "u16";
} else if (requiredBits <= 32) {
return "u32";
} else if (requiredBits <= 64) {
return "u64";
} else if (requiredBits <= 128) {
return "u128";
}
} else {
if (requiredBits <= 7 && knownRange) {
return "i8";
} else if (requiredBits <= 15 && knownRange) {
return "i16";
} else if (requiredBits <= 31) {
return "i32";
} else if (requiredBits <= 63) {
return "i64";
} else if (requiredBits <= 127) {
return "i128";
}
}
throw new RuntimeException("Number is too large to fit into i128");
}
/**
* Determine if an integer property can be guaranteed to fit into an unsigned data type.
* @param minimum The minimum value as set in the specification.
* @param exclusiveMinimum If boundary values are excluded by the specification.
* @return True if the effective minimum is greater than or equal to zero.
*/
@VisibleForTesting
public boolean canFitIntoUnsigned(@Nullable BigInteger minimum, boolean exclusiveMinimum) {
return Optional.ofNullable(minimum).map(min -> {
if (exclusiveMinimum) {
min = min.add(BigInteger.ONE);
}
return min.signum() >= 0;
}).orElse(false);
}
public enum CasingType {CAMEL_CASE, SNAKE_CASE};
/**

View File

@@ -23,6 +23,7 @@ import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.parser.util.SchemaTypeUtil;
import joptsimple.internal.Strings;
import org.openapitools.codegen.*;
import org.openapitools.codegen.meta.features.*;
import org.openapitools.codegen.model.ModelMap;
@@ -38,6 +39,7 @@ import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import static org.openapitools.codegen.utils.StringUtils.camelize;
@@ -66,7 +68,6 @@ public class RustClientCodegen extends AbstractRustCodegen implements CodegenCon
protected String modelDocPath = "docs/";
protected String apiFolder = "src/apis";
protected String modelFolder = "src/models";
protected Map<String, String> unsignedMapping;
public CodegenType getTag() {
return CodegenType.CLIENT;
@@ -160,12 +161,6 @@ public class RustClientCodegen extends AbstractRustCodegen implements CodegenCon
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<String, String>();
@@ -455,38 +450,33 @@ public class RustClientCodegen extends AbstractRustCodegen implements CodegenCon
String schemaType = super.getSchemaType(p);
String type = typeMapping.getOrDefault(schemaType, schemaType);
boolean bestFit = convertPropertyToBoolean(BEST_FIT_INT);
boolean unsigned = convertPropertyToBoolean(PREFER_UNSIGNED_INT);
if (Objects.equals(p.getType(), "integer")) {
boolean bestFit = convertPropertyToBoolean(BEST_FIT_INT);
boolean preferUnsigned = convertPropertyToBoolean(PREFER_UNSIGNED_INT);
if (bestFit || unsigned) {
BigDecimal maximum = p.getMaximum();
BigDecimal minimum = p.getMinimum();
BigInteger minimum = Optional.ofNullable(p.getMinimum()).map(BigDecimal::toBigInteger).orElse(null);
boolean exclusiveMinimum = Optional.ofNullable(p.getExclusiveMinimum()).orElse(false);
try {
if (maximum != null && minimum != null) {
long max = maximum.longValueExact();
long min = minimum.longValueExact();
boolean unsigned = preferUnsigned && canFitIntoUnsigned(minimum, exclusiveMinimum);
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);
if (Strings.isNullOrEmpty(p.getFormat())) {
if (bestFit) {
return bestFittingIntegerType(
minimum,
exclusiveMinimum,
Optional.ofNullable(p.getMaximum()).map(BigDecimal::toBigInteger).orElse(null),
Optional.ofNullable(p.getExclusiveMaximum()).orElse(false),
preferUnsigned);
} else {
return unsigned ? "u32" : "i32";
}
} else {
switch (p.getFormat()) {
case "int32":
return unsigned ? "u32" : "i32";
case "int64":
return unsigned ? "u64" : "i64";
}
}
}
@@ -564,12 +554,6 @@ public class RustClientCodegen extends AbstractRustCodegen implements CodegenCon
return objs;
}
@Override
protected boolean needToImport(String type) {
return !defaultIncludes.contains(type)
&& !languageSpecificPrimitives.contains(type);
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
@@ -578,17 +562,6 @@ public class RustClientCodegen extends AbstractRustCodegen implements CodegenCon
this.packageVersion = packageVersion;
}
@Override
public String escapeQuotationMark(String input) {
// remove " to avoid code injection
return input.replace("\"", "");
}
@Override
public String escapeUnsafeCharacters(String input) {
return input.replace("*/", "*_/").replace("/*", "/_*");
}
@Override
public String toDefaultValue(Schema p) {
if (p.getDefault() != null) {
@@ -598,6 +571,4 @@ public class RustClientCodegen extends AbstractRustCodegen implements CodegenCon
}
}
@Override
public GeneratorLanguage generatorLanguage() { return GeneratorLanguage.RUST; }
}

View File

@@ -30,6 +30,7 @@ import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.servers.Server;
import joptsimple.internal.Strings;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.*;
@@ -46,6 +47,7 @@ import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.net.URL;
import java.util.*;
import java.util.Map.Entry;
@@ -61,8 +63,6 @@ public class RustServerCodegen extends AbstractRustCodegen implements CodegenCon
private Map<String, String> modelXmlNames = new HashMap<String, String>();
private static final String NO_FORMAT = "%%NO_FORMAT";
protected String apiVersion = "1.0.0";
protected String serverHost = "localhost";
protected int serverPort = 8080;
@@ -391,17 +391,6 @@ public class RustServerCodegen extends AbstractRustCodegen implements CodegenCon
return toApiName(name) + "_api";
}
@Override
public String escapeQuotationMark(String input) {
// remove " to avoid code injection
return input.replace("\"", "");
}
@Override
public String escapeUnsafeCharacters(String input) {
return input.replace("*/", "*_/").replace("/*", "/_*");
}
private boolean isMimetypeXml(String mimetype) {
return mimetype.toLowerCase(Locale.ROOT).startsWith(xmlMimeType) ||
mimetype.toLowerCase(Locale.ROOT).startsWith(problemXmlMimeType) ||
@@ -1293,46 +1282,42 @@ public class RustServerCodegen extends AbstractRustCodegen implements CodegenCon
property.isPrimitiveType = true;
}
if ("integer".equals(property.baseType)) {
// custom integer formats (legacy)
if ("uint32".equals(property.dataFormat)) {
property.dataType = "u32";
} else if ("uint64".equals(property.dataFormat)) {
property.dataType = "u64";
// Integer type fitting
if (Objects.equals(property.baseType, "integer")) {
BigInteger minimum = Optional.ofNullable(property.getMinimum()).map(BigInteger::new).orElse(null);
BigInteger maximum = Optional.ofNullable(property.getMaximum()).map(BigInteger::new).orElse(null);
boolean unsigned = canFitIntoUnsigned(minimum, property.getExclusiveMinimum());
if (Strings.isNullOrEmpty(property.dataFormat)) {
property.dataType = bestFittingIntegerType(minimum,
property.getExclusiveMinimum(),
maximum,
property.getExclusiveMaximum(),
true);
} else {
// match int type to schema constraints
Long inclusiveMinimum = property.minimum != null ? Long.parseLong(property.minimum) : null;
if (inclusiveMinimum != null && property.exclusiveMinimum) {
inclusiveMinimum++;
}
// a signed int is required unless a minimum greater than zero is set
boolean unsigned = inclusiveMinimum != null && inclusiveMinimum >= 0;
Long inclusiveMaximum = property.maximum != null ? Long.parseLong(property.maximum) : null;
if (inclusiveMaximum != null && property.exclusiveMaximum) {
inclusiveMaximum--;
}
switch (property.dataFormat == null ? NO_FORMAT : property.dataFormat) {
// standard swagger formats
switch (property.dataFormat) {
// custom integer formats (legacy)
case "uint32":
property.dataType = "u32";
break;
case "uint64":
property.dataType = "u64";
break;
case "int32":
property.dataType = unsigned ? "u32" : "i32";
break;
case "int64":
property.dataType = unsigned ? "u64" : "i64";
break;
case NO_FORMAT:
property.dataType = matchingIntType(unsigned, inclusiveMinimum, inclusiveMaximum);
break;
default:
// unknown format
LOGGER.warn("The integer format '{}' is not recognized and will be ignored.", property.dataFormat);
property.dataType = matchingIntType(unsigned, inclusiveMinimum, inclusiveMaximum);
property.dataType = bestFittingIntegerType(minimum,
property.getExclusiveMinimum(),
maximum,
property.getExclusiveMaximum(),
true);
}
}
}
@@ -1355,46 +1340,6 @@ public class RustServerCodegen extends AbstractRustCodegen implements CodegenCon
}
}
private long requiredBits(Long bound, boolean unsigned) {
if (bound == null) return 0;
if (unsigned) {
if (bound < 0) {
throw new RuntimeException("Unsigned bound is negative: " + bound);
}
return 65L - Long.numberOfLeadingZeros(bound >> 1);
}
return 65L - Long.numberOfLeadingZeros(
// signed bounds go from (-n) to (n - 1), i.e. i8 goes from -128 to 127
bound < 0 ? Math.abs(bound) - 1 : bound);
}
private String matchingIntType(boolean unsigned, Long inclusiveMin, Long inclusiveMax) {
long requiredMinBits = requiredBits(inclusiveMin, unsigned);
long requiredMaxBits = requiredBits(inclusiveMax, unsigned);
long requiredBits = Math.max(requiredMinBits, requiredMaxBits);
if (requiredMaxBits == 0 && requiredMinBits <= 16) {
// rust 'size' types are arch-specific and thus somewhat loose
// so they are used when no format or maximum are specified
// and as long as minimum stays within plausible smallest ptr size (16 bits)
// this way all rust types are obtainable without defining custom formats
// this behavior (default int size) could also follow a generator flag
return unsigned ? "usize" : "isize";
} else if (requiredBits <= 8) {
return unsigned ? "u8" : "i8";
} else if (requiredBits <= 16) {
return unsigned ? "u16" : "i16";
} else if (requiredBits <= 32) {
return unsigned ? "u32" : "i32";
}
return unsigned ? "u64" : "i64";
}
@Override
public ModelsMap postProcessModels(ModelsMap objs) {
for (ModelMap mo : objs.getModels()) {
@@ -1578,9 +1523,6 @@ public class RustServerCodegen extends AbstractRustCodegen implements CodegenCon
}
}
@Override
public GeneratorLanguage generatorLanguage() { return GeneratorLanguage.RUST; }
@Override
protected String getParameterDataType(Parameter parameter, Schema schema) {
if (parameter.get$ref() != null) {

View File

@@ -5,29 +5,30 @@ import org.openapitools.codegen.languages.AbstractRustCodegen;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.math.BigInteger;
import java.util.function.Function;
public class AbstractRustCodegenTest {
private final AbstractRustCodegen fakeRustCodegen = new P_AbstractRustCodegen();
private final AbstractRustCodegen codegen = new P_AbstractRustCodegen();
@Test
public void testIsReservedWord() {
Assert.assertTrue(fakeRustCodegen.isReservedWord("return"));
Assert.assertTrue(fakeRustCodegen.isReservedWord("self"));
Assert.assertTrue(fakeRustCodegen.isReservedWord("Self"));
Assert.assertTrue(codegen.isReservedWord("return"));
Assert.assertTrue(codegen.isReservedWord("self"));
Assert.assertTrue(codegen.isReservedWord("Self"));
Assert.assertFalse(fakeRustCodegen.isReservedWord("Return"));
Assert.assertFalse(codegen.isReservedWord("Return"));
}
@Test
public void testSanitizeIdentifier() {
// Functions to make this less verbose
Function<String, String> sanitizeSnakeCase = (String name) ->
fakeRustCodegen.sanitizeIdentifier(name, AbstractRustCodegen.CasingType.SNAKE_CASE, "p", "Rust", true);
codegen.sanitizeIdentifier(name, AbstractRustCodegen.CasingType.SNAKE_CASE, "p", "Rust", true);
Function<String, String> sanitizeCamelCase = (String name) ->
fakeRustCodegen.sanitizeIdentifier(name, AbstractRustCodegen.CasingType.CAMEL_CASE, "p", "Rust", true);
codegen.sanitizeIdentifier(name, AbstractRustCodegen.CasingType.CAMEL_CASE, "p", "Rust", true);
// Underscores should be allowed through
Assert.assertEquals(sanitizeSnakeCase.apply("pet_name"), "pet_name");
@@ -72,54 +73,54 @@ public class AbstractRustCodegenTest {
@Test
public void testToVarName() {
// Should be converted to snake case
Assert.assertEquals(fakeRustCodegen.toVarName("PetName"), "pet_name");
Assert.assertEquals(codegen.toVarName("PetName"), "pet_name");
// Prefix is added when starting with a number
Assert.assertEquals(fakeRustCodegen.toVarName("1PetName"), "param_1_pet_name");
Assert.assertEquals(codegen.toVarName("1PetName"), "param_1_pet_name");
}
@Test
public void testToParamName() {
// Should be converted to snake case
Assert.assertEquals(fakeRustCodegen.toParamName("PetName"), "pet_name");
Assert.assertEquals(codegen.toParamName("PetName"), "pet_name");
// Prefix is added when starting with a number
Assert.assertEquals(fakeRustCodegen.toParamName("1PetName"), "param_1_pet_name");
Assert.assertEquals(codegen.toParamName("1PetName"), "param_1_pet_name");
}
@Test
public void testToOperationId() {
// Should be converted to camel case
Assert.assertEquals(fakeRustCodegen.toOperationId("createPet"), "create_pet");
Assert.assertEquals(codegen.toOperationId("createPet"), "create_pet");
// Prefix is added when starting with a number
Assert.assertEquals(fakeRustCodegen.toOperationId("1CreatePet"), "call_1_create_pet");
Assert.assertEquals(codegen.toOperationId("1CreatePet"), "call_1_create_pet");
}
@Test
public void testToModelName() {
// Should be converted to camel case
Assert.assertEquals(fakeRustCodegen.toModelName("pet_summary"), "PetSummary");
Assert.assertEquals(codegen.toModelName("pet_summary"), "PetSummary");
// Prefix is added when starting with a number
Assert.assertEquals(fakeRustCodegen.toModelName("1_pet_summary"), "Model1PetSummary");
Assert.assertEquals(codegen.toModelName("1_pet_summary"), "Model1PetSummary");
}
@Test
public void testToModelFileName() {
// Should be converted to snake case
Assert.assertEquals(fakeRustCodegen.toModelFilename("PetSummary"), "pet_summary");
Assert.assertEquals(codegen.toModelFilename("PetSummary"), "pet_summary");
// Prefix is added when starting with a number
Assert.assertEquals(fakeRustCodegen.toModelFilename("1PetSummary"), "model_1_pet_summary");
Assert.assertEquals(codegen.toModelFilename("1PetSummary"), "model_1_pet_summary");
}
@Test
public void testToEnumVarName() {
// Should be converted to camel case
Assert.assertEquals(fakeRustCodegen.toEnumVarName("pending", null), "Pending");
Assert.assertEquals(codegen.toEnumVarName("pending", null), "Pending");
// Enums are often represented in SCREAMING_SNAKE_CASE, check these are also converted to Rust enum camel case
Assert.assertEquals(fakeRustCodegen.toEnumVarName("SCREAMING_SNAKE_CASE", null), "ScreamingSnakeCase");
Assert.assertEquals(codegen.toEnumVarName("SCREAMING_SNAKE_CASE", null), "ScreamingSnakeCase");
// Prefix is added when starting with a number
Assert.assertEquals(fakeRustCodegen.toEnumVarName("1_pending", null), "Variant1Pending");
Assert.assertEquals(codegen.toEnumVarName("1_pending", null), "Variant1Pending");
// Empty strings need to be mapped to "Empty"
// https://github.com/OpenAPITools/openapi-generator/issues/13453
Assert.assertEquals(fakeRustCodegen.toEnumVarName("", null), "Empty");
Assert.assertEquals(codegen.toEnumVarName("", null), "Empty");
}
@Test
@@ -127,7 +128,7 @@ public class AbstractRustCodegenTest {
Function<String, String> toEnumName = (String name) -> {
CodegenProperty property = new CodegenProperty();
property.name = name;
return fakeRustCodegen.toEnumName(property);
return codegen.toEnumName(property);
};
// Should be converted to camel case
Assert.assertEquals(toEnumName.apply("pet_status"), "PetStatusWithSuffix");
@@ -138,31 +139,100 @@ public class AbstractRustCodegenTest {
@Test
public void testToEnumValue() {
// Value should match spec
Assert.assertEquals(fakeRustCodegen.toEnumValue("12345valueAbc#!", null), "12345valueAbc#!");
Assert.assertEquals(codegen.toEnumValue("12345valueAbc#!", null), "12345valueAbc#!");
// Quotes should be escaped so that the Rust string is valid
Assert.assertEquals(fakeRustCodegen.toEnumValue("\"quote\"", null), "\\\"quote\\\"");
Assert.assertEquals(codegen.toEnumValue("\"quote\"", null), "\\\"quote\\\"");
}
@Test
public void testToApiName() {
// Unnamed
Assert.assertEquals(fakeRustCodegen.toApiName(""), "DefaultWithSuffix");
Assert.assertEquals(codegen.toApiName(""), "DefaultWithSuffix");
// Should be camel case
Assert.assertEquals(fakeRustCodegen.toApiName("pet"), "PetWithSuffix");
Assert.assertEquals(codegen.toApiName("pet"), "PetWithSuffix");
// Prefix is added when starting with a number
Assert.assertEquals(fakeRustCodegen.toApiName("1_pet"), "Api1PetWithSuffix");
Assert.assertEquals(codegen.toApiName("1_pet"), "Api1PetWithSuffix");
}
@Test
public void testToApiFilename() {
// Unnamed
Assert.assertEquals(fakeRustCodegen.toApiFilename(""), "default_with_suffix");
Assert.assertEquals(codegen.toApiFilename(""), "default_with_suffix");
// Should be snake case
Assert.assertEquals(fakeRustCodegen.toApiFilename("Pet"), "pet_with_suffix");
Assert.assertEquals(codegen.toApiFilename("Pet"), "pet_with_suffix");
// Prefix is added when starting with a number
Assert.assertEquals(fakeRustCodegen.toApiFilename("1Pet"), "api_1_pet_with_suffix");
Assert.assertEquals(codegen.toApiFilename("1Pet"), "api_1_pet_with_suffix");
}
@Test
public void testBestFittingIntegerType() {
final BigInteger u8_MAX = BigInteger.valueOf(255L);
final BigInteger u16_MAX = BigInteger.valueOf(65_535L);
final BigInteger u32_MAX = BigInteger.valueOf(4_294_967_295L);
final BigInteger i8_MIN = BigInteger.valueOf(Byte.MIN_VALUE);
final BigInteger i16_MIN = BigInteger.valueOf(Short.MIN_VALUE);
final BigInteger i32_MIN = BigInteger.valueOf(Integer.MIN_VALUE);
final BigInteger i8_MAX = BigInteger.valueOf(Byte.MAX_VALUE);
final BigInteger i16_MAX = BigInteger.valueOf(Short.MAX_VALUE);
final BigInteger i32_MAX = BigInteger.valueOf(Integer.MAX_VALUE);
// No range specified
Assert.assertEquals(codegen.bestFittingIntegerType(null, false, null, false, true), "i32");
// Test when only minimum specified (prefer unsigned)
Assert.assertEquals(codegen.bestFittingIntegerType(i32_MIN.subtract(BigInteger.ONE), false, null, false, true), "i64");
Assert.assertEquals(codegen.bestFittingIntegerType(i32_MIN, false, null, false, true), "i32");
Assert.assertEquals(codegen.bestFittingIntegerType(BigInteger.valueOf(-1), false, null, false, true), "i32");
Assert.assertEquals(codegen.bestFittingIntegerType(BigInteger.ZERO, false, null, false, true), "u32");
Assert.assertEquals(codegen.bestFittingIntegerType(u32_MAX, false, null, false, true), "u32");
Assert.assertEquals(codegen.bestFittingIntegerType(u32_MAX.add(BigInteger.ONE), false, null, false, true), "u64");
// Test when only minimum specified (disable unsigned)
Assert.assertEquals(codegen.bestFittingIntegerType(BigInteger.ZERO, false, null, false, false), "i32");
Assert.assertEquals(codegen.bestFittingIntegerType(i32_MAX, false, null, false, false), "i32");
Assert.assertEquals(codegen.bestFittingIntegerType(i32_MAX.add(BigInteger.ONE), false, null, false, false), "i64");
// Test when only maximum specified
Assert.assertEquals(codegen.bestFittingIntegerType(null, false, i32_MIN.subtract(BigInteger.ONE), false, true), "i64");
Assert.assertEquals(codegen.bestFittingIntegerType(null, false, i32_MIN, false, true), "i32");
Assert.assertEquals(codegen.bestFittingIntegerType(null, false, BigInteger.ZERO, false, true), "i32");
// Test when maximum bits biggest (prefer unsigned)
Assert.assertEquals(codegen.bestFittingIntegerType(BigInteger.ZERO, false, u8_MAX, false, true), "u8");
Assert.assertEquals(codegen.bestFittingIntegerType(BigInteger.ZERO, false, u8_MAX.add(BigInteger.ONE), false, true), "u16");
Assert.assertEquals(codegen.bestFittingIntegerType(BigInteger.ZERO, false, u16_MAX, false, true), "u16");
Assert.assertEquals(codegen.bestFittingIntegerType(BigInteger.ZERO, false, u16_MAX.add(BigInteger.ONE), false, true), "u32");
// Test when maximum bits biggest (disable unsigned)
Assert.assertEquals(codegen.bestFittingIntegerType(BigInteger.ZERO, false, i8_MAX, false, false), "i8");
Assert.assertEquals(codegen.bestFittingIntegerType(BigInteger.ZERO, false, i8_MAX.add(BigInteger.ONE), false, false), "i16");
Assert.assertEquals(codegen.bestFittingIntegerType(BigInteger.ZERO, false, i16_MAX, false, false), "i16");
Assert.assertEquals(codegen.bestFittingIntegerType(BigInteger.ZERO, false, i16_MAX.add(BigInteger.ONE), false, false), "i32");
// Test when minimum bits biggest
Assert.assertEquals(codegen.bestFittingIntegerType(i16_MIN.subtract(BigInteger.ONE), false, BigInteger.ZERO, false, true), "i32");
Assert.assertEquals(codegen.bestFittingIntegerType(i16_MIN, false, BigInteger.ZERO, false, true), "i16");
Assert.assertEquals(codegen.bestFittingIntegerType(i8_MIN.subtract(BigInteger.ONE), false, BigInteger.ZERO, false, true), "i16");
Assert.assertEquals(codegen.bestFittingIntegerType(i8_MIN, false, BigInteger.ZERO, false, true), "i8");
// Test when exclusive bounds
Assert.assertEquals(codegen.bestFittingIntegerType(i8_MIN.subtract(BigInteger.ONE), true, BigInteger.ZERO, false, false), "i8");
Assert.assertEquals(codegen.bestFittingIntegerType(BigInteger.ZERO, true, i8_MAX.add(BigInteger.ONE), true, false), "i8");
}
@Test
public void testCanFitIntoUnsigned() {
Assert.assertFalse(codegen.canFitIntoUnsigned(BigInteger.valueOf(-1), false));
Assert.assertTrue(codegen.canFitIntoUnsigned(BigInteger.valueOf(0), false));
Assert.assertTrue(codegen.canFitIntoUnsigned(BigInteger.valueOf(1), false));
Assert.assertTrue(codegen.canFitIntoUnsigned(BigInteger.valueOf(-1), true));
}
private static class P_AbstractRustCodegen extends AbstractRustCodegen {
P_AbstractRustCodegen() {

View File

@@ -78,151 +78,165 @@ public class RustClientCodegenTest {
}
@Test
public void testGetSchemaTypeIntegerNoBounds() {
public void testWithIntegerDefaults() {
final IntegerSchema s = new IntegerSchema();
final RustClientCodegen codegen = new RustClientCodegen();
codegen.setBestFitInt(true);
codegen.setBestFitInt(false);
codegen.setPreferUnsignedInt(false);
codegen.processOpts();
s.setType("i32");
s.setMinimum(BigDecimal.valueOf(0));
s.setMaximum(BigDecimal.valueOf(1));
// min and max are null
s.setFormat("int32");
Assert.assertEquals(codegen.getSchemaType(s), "i32");
s.setMinimum(BigDecimal.valueOf(Short.MIN_VALUE));
s.setFormat("int64");
Assert.assertEquals(codegen.getSchemaType(s), "i64");
// max is null
// Clear format - should use default of i32
s.setFormat(null);
s.setMaximum(BigDecimal.valueOf(Byte.MAX_VALUE));
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");
s.setMaximum(BigDecimal.valueOf(Long.MAX_VALUE));
Assert.assertEquals(codegen.getSchemaType(s), "i32");
}
@Test
public void testGetSchemaTypeI16() {
public void testWithIntegerFitting() {
final IntegerSchema s = new IntegerSchema();
final RustClientCodegen codegen = new RustClientCodegen();
codegen.setBestFitInt(true);
codegen.setPreferUnsignedInt(false);
codegen.processOpts();
s.setType("i32");
s.setMinimum(BigDecimal.valueOf(Short.MIN_VALUE));
s.setMaximum(BigDecimal.valueOf(Short.MAX_VALUE));
// No bounds
Assert.assertEquals(codegen.getSchemaType(s), "i32");
Assert.assertEquals(codegen.getSchemaType(s), "i16");
}
// Set Bounds
s.setMinimum(BigDecimal.valueOf(0));
s.setMaximum(BigDecimal.valueOf(1));
@Test
public void testGetSchemaTypeI8() {
final IntegerSchema s = new IntegerSchema();
final RustClientCodegen codegen = new RustClientCodegen();
codegen.setBestFitInt(true);
codegen.processOpts();
// Should respect hardcoded format
s.setFormat("int32");
Assert.assertEquals(codegen.getSchemaType(s), "i32");
s.setType("i32");
s.setMinimum(BigDecimal.valueOf(-128));
s.setMaximum(BigDecimal.valueOf(127));
// Should respect hardcoded format
s.setFormat("int64");
Assert.assertEquals(codegen.getSchemaType(s), "i64");
// No format - use best fitting
s.setFormat(null);
s.setMaximum(BigDecimal.valueOf(Byte.MAX_VALUE));
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.setMaximum(BigDecimal.valueOf(Short.MAX_VALUE));
Assert.assertEquals(codegen.getSchemaType(s), "i16");
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);
s.setMaximum(BigDecimal.valueOf(Integer.MAX_VALUE));
Assert.assertEquals(codegen.getSchemaType(s), "i32");
s.setMaximum(BigDecimal.valueOf(Long.MAX_VALUE));
Assert.assertEquals(codegen.getSchemaType(s), "i64");
}
@Test
public void testGetSchemaTypeU32() {
public void testWithPreferUnsigned() {
final IntegerSchema s = new IntegerSchema();
final RustClientCodegen codegen = new RustClientCodegen();
codegen.setBestFitInt(false);
codegen.setPreferUnsignedInt(true);
codegen.processOpts();
s.setType("i32");
s.setMinimum(BigDecimal.ZERO);
// Minimum of zero, should fit in unsigned
s.setMinimum(BigDecimal.valueOf(0));
// No integer fitting, but prefer unsigned
s.setMaximum(BigDecimal.valueOf(Byte.MAX_VALUE));
Assert.assertEquals(codegen.getSchemaType(s), "u32");
s.setMaximum(BigDecimal.valueOf(65535));
s.setMaximum(BigDecimal.valueOf(Long.MAX_VALUE));
Assert.assertEquals(codegen.getSchemaType(s), "u32");
// Should respect hardcoded 32-bits, but prefer unsigned
s.setFormat("int32");
Assert.assertEquals(codegen.getSchemaType(s), "u32");
// Should respect hardcoded 64-bits, but prefer unsigned
s.setFormat("int64");
Assert.assertEquals(codegen.getSchemaType(s), "u64");
// Unknown minimum - should not use unsigned
s.setMinimum(null);
s.setFormat(null);
Assert.assertEquals(codegen.getSchemaType(s), "i32");
s.setFormat("int32");
Assert.assertEquals(codegen.getSchemaType(s), "i32");
s.setFormat("int64");
Assert.assertEquals(codegen.getSchemaType(s), "i64");
}
@Test
public void testGetSchemaTypeU16() {
public void testWithIntegerFittingAndPreferUnsigned() {
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));
// Minimum of zero, should fit in unsigned
s.setMinimum(BigDecimal.valueOf(0));
s.setMaximum(BigDecimal.valueOf(1));
Assert.assertEquals(codegen.getSchemaType(s), "u16");
}
// Should respect hardcoded 32-bits, but prefer unsigned
s.setFormat("int32");
Assert.assertEquals(codegen.getSchemaType(s), "u32");
@Test
public void testGetSchemaTypeU8() {
final IntegerSchema s = new IntegerSchema();
final RustClientCodegen codegen = new RustClientCodegen();
codegen.setBestFitInt(true);
codegen.setPreferUnsignedInt(true);
codegen.processOpts();
// Should respect hardcoded 64-bits, but prefer unsigned
s.setFormat("int64");
Assert.assertEquals(codegen.getSchemaType(s), "u64");
s.setType("i32");
s.setMinimum(BigDecimal.ZERO);
s.setMaximum(BigDecimal.valueOf(255));
// No format - use best fitting
s.setFormat(null);
s.setMaximum(BigDecimal.valueOf(Byte.MAX_VALUE));
Assert.assertEquals(codegen.getSchemaType(s), "u8");
s.setMaximum(BigDecimal.valueOf(Short.MAX_VALUE));
Assert.assertEquals(codegen.getSchemaType(s), "u16");
s.setMaximum(BigDecimal.valueOf(Integer.MAX_VALUE));
Assert.assertEquals(codegen.getSchemaType(s), "u32");
s.setMaximum(BigDecimal.valueOf(Long.MAX_VALUE));
Assert.assertEquals(codegen.getSchemaType(s), "u64");
// Unknown minimum - unable to use unsigned
s.setMinimum(null);
s.setMaximum(BigDecimal.valueOf(Integer.MAX_VALUE));
Assert.assertEquals(codegen.getSchemaType(s), "i32");
s.setMaximum(BigDecimal.valueOf(Long.MAX_VALUE));
Assert.assertEquals(codegen.getSchemaType(s), "i64");
s.setFormat("int32");
Assert.assertEquals(codegen.getSchemaType(s), "i32");
s.setFormat("int64");
Assert.assertEquals(codegen.getSchemaType(s), "i64");
}
}

View File

@@ -4,7 +4,7 @@
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**required_object_header** | **bool** | |
**optional_object_header** | **isize** | | [optional] [default to None]
**optional_object_header** | **i32** | | [optional] [default to None]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -4,7 +4,7 @@
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**required_param** | **bool** | |
**optional_param** | **isize** | | [optional] [default to None]
**optional_param** | **i32** | | [optional] [default to None]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -4,7 +4,7 @@
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**inner_string** | **String** | | [optional] [default to None]
**other_inner_rename** | **isize** | | [optional] [default to None]
**other_inner_rename** | **i32** | | [optional] [default to None]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -1805,7 +1805,7 @@ pub struct ObjectHeader {
#[serde(rename = "optionalObjectHeader")]
#[serde(skip_serializing_if="Option::is_none")]
pub optional_object_header: Option<isize>,
pub optional_object_header: Option<i32>,
}
@@ -1855,7 +1855,7 @@ impl std::str::FromStr for ObjectHeader {
#[allow(dead_code)]
struct IntermediateRep {
pub required_object_header: Vec<bool>,
pub optional_object_header: Vec<isize>,
pub optional_object_header: Vec<i32>,
}
let mut intermediate_rep = IntermediateRep::default();
@@ -1876,7 +1876,7 @@ impl std::str::FromStr for ObjectHeader {
#[allow(clippy::redundant_clone)]
"requiredObjectHeader" => intermediate_rep.required_object_header.push(<bool as std::str::FromStr>::from_str(val).map_err(|x| x.to_string())?),
#[allow(clippy::redundant_clone)]
"optionalObjectHeader" => intermediate_rep.optional_object_header.push(<isize as std::str::FromStr>::from_str(val).map_err(|x| x.to_string())?),
"optionalObjectHeader" => intermediate_rep.optional_object_header.push(<i32 as std::str::FromStr>::from_str(val).map_err(|x| x.to_string())?),
_ => return std::result::Result::Err("Unexpected key while parsing ObjectHeader".to_string())
}
}
@@ -1949,7 +1949,7 @@ pub struct ObjectParam {
#[serde(rename = "optionalParam")]
#[serde(skip_serializing_if="Option::is_none")]
pub optional_param: Option<isize>,
pub optional_param: Option<i32>,
}
@@ -1999,7 +1999,7 @@ impl std::str::FromStr for ObjectParam {
#[allow(dead_code)]
struct IntermediateRep {
pub required_param: Vec<bool>,
pub optional_param: Vec<isize>,
pub optional_param: Vec<i32>,
}
let mut intermediate_rep = IntermediateRep::default();
@@ -2020,7 +2020,7 @@ impl std::str::FromStr for ObjectParam {
#[allow(clippy::redundant_clone)]
"requiredParam" => intermediate_rep.required_param.push(<bool as std::str::FromStr>::from_str(val).map_err(|x| x.to_string())?),
#[allow(clippy::redundant_clone)]
"optionalParam" => intermediate_rep.optional_param.push(<isize as std::str::FromStr>::from_str(val).map_err(|x| x.to_string())?),
"optionalParam" => intermediate_rep.optional_param.push(<i32 as std::str::FromStr>::from_str(val).map_err(|x| x.to_string())?),
_ => return std::result::Result::Err("Unexpected key while parsing ObjectParam".to_string())
}
}
@@ -3017,7 +3017,7 @@ pub struct XmlObject {
#[serde(rename = "other_inner_rename")]
#[serde(skip_serializing_if="Option::is_none")]
pub other_inner_rename: Option<isize>,
pub other_inner_rename: Option<i32>,
}
@@ -3071,7 +3071,7 @@ impl std::str::FromStr for XmlObject {
#[allow(dead_code)]
struct IntermediateRep {
pub inner_string: Vec<String>,
pub other_inner_rename: Vec<isize>,
pub other_inner_rename: Vec<i32>,
}
let mut intermediate_rep = IntermediateRep::default();
@@ -3092,7 +3092,7 @@ impl std::str::FromStr for XmlObject {
#[allow(clippy::redundant_clone)]
"innerString" => intermediate_rep.inner_string.push(<String as std::str::FromStr>::from_str(val).map_err(|x| x.to_string())?),
#[allow(clippy::redundant_clone)]
"other_inner_rename" => intermediate_rep.other_inner_rename.push(<isize as std::str::FromStr>::from_str(val).map_err(|x| x.to_string())?),
"other_inner_rename" => intermediate_rep.other_inner_rename.push(<i32 as std::str::FromStr>::from_str(val).map_err(|x| x.to_string())?),
_ => return std::result::Result::Err("Unexpected key while parsing XmlObject".to_string())
}
}

View File

@@ -6,7 +6,7 @@ Name | Type | Description | Notes
**name** | **i32** | |
**snake_case** | **i32** | | [optional] [readonly] [default to None]
**property** | **String** | | [optional] [default to None]
**param_123_number** | **isize** | | [optional] [readonly] [default to None]
**param_123_number** | **i32** | | [optional] [readonly] [default to None]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -3807,7 +3807,7 @@ pub struct Name {
#[serde(rename = "123Number")]
#[serde(skip_serializing_if="Option::is_none")]
pub param_123_number: Option<isize>,
pub param_123_number: Option<i32>,
}
@@ -3877,7 +3877,7 @@ impl std::str::FromStr for Name {
pub name: Vec<i32>,
pub snake_case: Vec<i32>,
pub property: Vec<String>,
pub param_123_number: Vec<isize>,
pub param_123_number: Vec<i32>,
}
let mut intermediate_rep = IntermediateRep::default();
@@ -3902,7 +3902,7 @@ impl std::str::FromStr for Name {
#[allow(clippy::redundant_clone)]
"property" => intermediate_rep.property.push(<String as std::str::FromStr>::from_str(val).map_err(|x| x.to_string())?),
#[allow(clippy::redundant_clone)]
"123Number" => intermediate_rep.param_123_number.push(<isize as std::str::FromStr>::from_str(val).map_err(|x| x.to_string())?),
"123Number" => intermediate_rep.param_123_number.push(<i32 as std::str::FromStr>::from_str(val).map_err(|x| x.to_string())?),
_ => return std::result::Result::Err("Unexpected key while parsing Name".to_string())
}
}

View File

@@ -4,7 +4,7 @@
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**required_thing** | **String** | |
**optional_thing** | **isize** | | [optional] [default to None]
**optional_thing** | **i32** | | [optional] [default to None]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -839,7 +839,7 @@ pub struct ObjectOfObjectsInner {
#[serde(rename = "optional_thing")]
#[serde(skip_serializing_if="Option::is_none")]
pub optional_thing: Option<isize>,
pub optional_thing: Option<i32>,
}
@@ -889,7 +889,7 @@ impl std::str::FromStr for ObjectOfObjectsInner {
#[allow(dead_code)]
struct IntermediateRep {
pub required_thing: Vec<String>,
pub optional_thing: Vec<isize>,
pub optional_thing: Vec<i32>,
}
let mut intermediate_rep = IntermediateRep::default();
@@ -910,7 +910,7 @@ impl std::str::FromStr for ObjectOfObjectsInner {
#[allow(clippy::redundant_clone)]
"required_thing" => intermediate_rep.required_thing.push(<String as std::str::FromStr>::from_str(val).map_err(|x| x.to_string())?),
#[allow(clippy::redundant_clone)]
"optional_thing" => intermediate_rep.optional_thing.push(<isize as std::str::FromStr>::from_str(val).map_err(|x| x.to_string())?),
"optional_thing" => intermediate_rep.optional_thing.push(<i32 as std::str::FromStr>::from_str(val).map_err(|x| x.to_string())?),
_ => return std::result::Result::Err("Unexpected key while parsing ObjectOfObjectsInner".to_string())
}
}