Skip to content

Commit 8a1d445

Browse files
jacob-proJayAtOC
authored andcommitted
[rust] [rust-server] More abstract functions including integer fitting (OpenAPITools#13503)
* [rust] [rust-server] Abstract Rust Integer fitting * Add docstrings
1 parent 95df2d7 commit 8a1d445

File tree

13 files changed

+367
-272
lines changed

13 files changed

+367
-272
lines changed

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

+98
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
package org.openapitools.codegen.languages;
22

3+
import com.google.common.annotations.VisibleForTesting;
34
import com.google.common.base.Strings;
45
import org.openapitools.codegen.CodegenConfig;
56
import org.openapitools.codegen.CodegenProperty;
67
import org.openapitools.codegen.DefaultCodegen;
8+
import org.openapitools.codegen.GeneratorLanguage;
79
import org.openapitools.codegen.utils.StringUtils;
810
import org.slf4j.Logger;
911
import org.slf4j.LoggerFactory;
1012

13+
import javax.annotation.Nullable;
14+
import java.math.BigInteger;
1115
import java.util.*;
1216
import java.util.function.Function;
1317

@@ -37,12 +41,106 @@ public AbstractRustCodegen() {
3741
);
3842
}
3943

44+
@Override
45+
public GeneratorLanguage generatorLanguage() { return GeneratorLanguage.RUST; }
46+
47+
@Override
48+
public String escapeQuotationMark(String input) {
49+
// remove " to avoid code injection
50+
return input.replace("\"", "");
51+
}
52+
53+
@Override
54+
public String escapeUnsafeCharacters(String input) {
55+
return input.replace("*/", "*_/").replace("/*", "/_*");
56+
}
57+
4058
@Override
4159
public boolean isReservedWord(String word) {
4260
// This is overridden to take account of Rust reserved words being case-sensitive.
4361
return word != null && reservedWords.contains(word);
4462
}
4563

64+
/**
65+
* Determine the best fitting Rust type for an integer property. This is intended for use when a specific format
66+
* has not been defined in the specification. Where the minimum or maximum is not known then the returned type
67+
* will default to having at least 32 bits.
68+
* @param minimum The minimum value as set in the specification.
69+
* @param exclusiveMinimum If the minimum value itself is excluded by the specification.
70+
* @param maximum The maximum value as set in the specification.
71+
* @param exclusiveMaximum If the maximum value itself is excluded by the specification.
72+
* @param preferUnsigned Use unsigned types where the effective minimum is greater than or equal to zero.
73+
* @return The Rust data type name.
74+
*/
75+
@VisibleForTesting
76+
public String bestFittingIntegerType(@Nullable BigInteger minimum,
77+
boolean exclusiveMinimum,
78+
@Nullable BigInteger maximum,
79+
boolean exclusiveMaximum,
80+
boolean preferUnsigned) {
81+
if (exclusiveMinimum) {
82+
minimum = Optional.ofNullable(minimum).map(min -> min.add(BigInteger.ONE)).orElse(null);
83+
}
84+
if (exclusiveMaximum) {
85+
maximum = Optional.ofNullable(maximum).map(max -> max.subtract(BigInteger.ONE)).orElse(null);
86+
}
87+
88+
// If the minimum value is greater than or equal to zero, then it is safe to use an unsigned type
89+
boolean guaranteedPositive = Optional.ofNullable(minimum).map(min -> min.signum() >= 0).orElse(false);
90+
91+
int requiredBits = Math.max(
92+
Optional.ofNullable(minimum).map(BigInteger::bitLength).orElse(0),
93+
Optional.ofNullable(maximum).map(BigInteger::bitLength).orElse(0)
94+
);
95+
96+
// We will only enable the smaller types (less than 32 bits) if we know both the minimum and maximum
97+
boolean knownRange = !(Objects.isNull(minimum) || Objects.isNull(maximum));
98+
99+
if (guaranteedPositive && preferUnsigned) {
100+
if (requiredBits <= 8 && knownRange) {
101+
return "u8";
102+
} else if (requiredBits <= 16 && knownRange) {
103+
return "u16";
104+
} else if (requiredBits <= 32) {
105+
return "u32";
106+
} else if (requiredBits <= 64) {
107+
return "u64";
108+
} else if (requiredBits <= 128) {
109+
return "u128";
110+
}
111+
} else {
112+
if (requiredBits <= 7 && knownRange) {
113+
return "i8";
114+
} else if (requiredBits <= 15 && knownRange) {
115+
return "i16";
116+
} else if (requiredBits <= 31) {
117+
return "i32";
118+
} else if (requiredBits <= 63) {
119+
return "i64";
120+
} else if (requiredBits <= 127) {
121+
return "i128";
122+
}
123+
}
124+
125+
throw new RuntimeException("Number is too large to fit into i128");
126+
}
127+
128+
/**
129+
* Determine if an integer property can be guaranteed to fit into an unsigned data type.
130+
* @param minimum The minimum value as set in the specification.
131+
* @param exclusiveMinimum If boundary values are excluded by the specification.
132+
* @return True if the effective minimum is greater than or equal to zero.
133+
*/
134+
@VisibleForTesting
135+
public boolean canFitIntoUnsigned(@Nullable BigInteger minimum, boolean exclusiveMinimum) {
136+
return Optional.ofNullable(minimum).map(min -> {
137+
if (exclusiveMinimum) {
138+
min = min.add(BigInteger.ONE);
139+
}
140+
return min.signum() >= 0;
141+
}).orElse(false);
142+
}
143+
46144
public enum CasingType {CAMEL_CASE, SNAKE_CASE};
47145

48146
/**

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

+28-57
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.swagger.v3.oas.models.media.Schema;
2424
import io.swagger.v3.oas.models.media.StringSchema;
2525
import io.swagger.v3.parser.util.SchemaTypeUtil;
26+
import joptsimple.internal.Strings;
2627
import org.openapitools.codegen.*;
2728
import org.openapitools.codegen.meta.features.*;
2829
import org.openapitools.codegen.model.ModelMap;
@@ -38,6 +39,7 @@
3839
import java.io.IOException;
3940
import java.io.Writer;
4041
import java.math.BigDecimal;
42+
import java.math.BigInteger;
4143
import java.util.*;
4244

4345
import static org.openapitools.codegen.utils.StringUtils.camelize;
@@ -66,7 +68,6 @@ public class RustClientCodegen extends AbstractRustCodegen implements CodegenCon
6668
protected String modelDocPath = "docs/";
6769
protected String apiFolder = "src/apis";
6870
protected String modelFolder = "src/models";
69-
protected Map<String, String> unsignedMapping;
7071

7172
public CodegenType getTag() {
7273
return CodegenType.CLIENT;
@@ -160,12 +161,6 @@ public RustClientCodegen() {
160161
typeMapping.put("object", "serde_json::Value");
161162
typeMapping.put("AnyType", "serde_json::Value");
162163

163-
unsignedMapping = new HashMap<>();
164-
unsignedMapping.put("i8", "u8");
165-
unsignedMapping.put("i16", "u16");
166-
unsignedMapping.put("i32", "u32");
167-
unsignedMapping.put("i64", "u64");
168-
169164
// no need for rust
170165
//importMapping = new HashMap<String, String>();
171166

@@ -455,38 +450,33 @@ public String getSchemaType(Schema p) {
455450
String schemaType = super.getSchemaType(p);
456451
String type = typeMapping.getOrDefault(schemaType, schemaType);
457452

458-
boolean bestFit = convertPropertyToBoolean(BEST_FIT_INT);
459-
boolean unsigned = convertPropertyToBoolean(PREFER_UNSIGNED_INT);
460-
461-
if (bestFit || unsigned) {
462-
BigDecimal maximum = p.getMaximum();
463-
BigDecimal minimum = p.getMinimum();
464-
465-
try {
466-
if (maximum != null && minimum != null) {
467-
long max = maximum.longValueExact();
468-
long min = minimum.longValueExact();
469-
470-
if (unsigned && bestFit && max <= (Byte.MAX_VALUE * 2) + 1 && min >= 0) {
471-
type = "u8";
472-
} else if (bestFit && max <= Byte.MAX_VALUE && min >= Byte.MIN_VALUE) {
473-
type = "i8";
474-
} else if (unsigned && bestFit && max <= (Short.MAX_VALUE * 2) + 1 && min >= 0) {
475-
type = "u16";
476-
} else if (bestFit && max <= Short.MAX_VALUE && min >= Short.MIN_VALUE) {
477-
type = "i16";
478-
} else if (unsigned && bestFit && max <= (Integer.MAX_VALUE * 2L) + 1 && min >= 0) {
479-
type = "u32";
480-
} else if (bestFit && max <= Integer.MAX_VALUE && min >= Integer.MIN_VALUE) {
481-
type = "i32";
482-
}
483-
}
484-
} catch (ArithmeticException a) {
485-
// no-op; case will be handled in the next if block
486-
}
453+
if (Objects.equals(p.getType(), "integer")) {
454+
boolean bestFit = convertPropertyToBoolean(BEST_FIT_INT);
455+
boolean preferUnsigned = convertPropertyToBoolean(PREFER_UNSIGNED_INT);
456+
457+
BigInteger minimum = Optional.ofNullable(p.getMinimum()).map(BigDecimal::toBigInteger).orElse(null);
458+
boolean exclusiveMinimum = Optional.ofNullable(p.getExclusiveMinimum()).orElse(false);
487459

488-
if (unsigned && minimum != null && minimum.compareTo(BigDecimal.ZERO) >= 0 && unsignedMapping.containsKey(type)) {
489-
type = unsignedMapping.get(type);
460+
boolean unsigned = preferUnsigned && canFitIntoUnsigned(minimum, exclusiveMinimum);
461+
462+
if (Strings.isNullOrEmpty(p.getFormat())) {
463+
if (bestFit) {
464+
return bestFittingIntegerType(
465+
minimum,
466+
exclusiveMinimum,
467+
Optional.ofNullable(p.getMaximum()).map(BigDecimal::toBigInteger).orElse(null),
468+
Optional.ofNullable(p.getExclusiveMaximum()).orElse(false),
469+
preferUnsigned);
470+
} else {
471+
return unsigned ? "u32" : "i32";
472+
}
473+
} else {
474+
switch (p.getFormat()) {
475+
case "int32":
476+
return unsigned ? "u32" : "i32";
477+
case "int64":
478+
return unsigned ? "u64" : "i64";
479+
}
490480
}
491481
}
492482

@@ -564,12 +554,6 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<Mo
564554
return objs;
565555
}
566556

567-
@Override
568-
protected boolean needToImport(String type) {
569-
return !defaultIncludes.contains(type)
570-
&& !languageSpecificPrimitives.contains(type);
571-
}
572-
573557
public void setPackageName(String packageName) {
574558
this.packageName = packageName;
575559
}
@@ -578,17 +562,6 @@ public void setPackageVersion(String packageVersion) {
578562
this.packageVersion = packageVersion;
579563
}
580564

581-
@Override
582-
public String escapeQuotationMark(String input) {
583-
// remove " to avoid code injection
584-
return input.replace("\"", "");
585-
}
586-
587-
@Override
588-
public String escapeUnsafeCharacters(String input) {
589-
return input.replace("*/", "*_/").replace("/*", "/_*");
590-
}
591-
592565
@Override
593566
public String toDefaultValue(Schema p) {
594567
if (p.getDefault() != null) {
@@ -598,6 +571,4 @@ public String toDefaultValue(Schema p) {
598571
}
599572
}
600573

601-
@Override
602-
public GeneratorLanguage generatorLanguage() { return GeneratorLanguage.RUST; }
603574
}

0 commit comments

Comments
 (0)