Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import org.slf4j.LoggerFactory;

import java.io.File;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.file.Path;
import java.util.*;
Expand Down Expand Up @@ -1024,6 +1025,75 @@ public CodegenParameter fromRequestBody(RequestBody body, Set<String> imports, S
return codegenParameter;
}

private String getIntegerDataType(String format,
BigInteger minimum,
boolean exclusiveMinimum,
BigInteger maximum,
boolean exclusiveMaximum,
boolean explicitUnsigned) {
boolean unsigned = explicitUnsigned || canFitIntoUnsigned(minimum, exclusiveMinimum);
Comment thread
LSX-s-Software marked this conversation as resolved.
Outdated

if (explicitUnsigned && !canFitIntoUnsigned(minimum, exclusiveMinimum)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder what happen if:
explicitUnsigned == true, minimum == -2, maximum == -1

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated rust-axum to align with RustClient and RustServer behavior: explicit unsigned intent is no longer used to force an unsigned type when the numeric bounds are negative. So for explicitUnsigned = true, minimum = -2, maximum = -1:

  1. canFitIntoUnsigned(minimum, exclusiveMinimum) is false
  2. we fall back to signed mapping
  3. result is signed (for example, int32 -> i32, int64 -> i64; and for no format/unknown format we use best-fit signed type)

This avoids generating an invalid unsigned type for a negative-only range and keeps behavior consistent across Rust generators.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the explanation

// Preserve explicit unsigned intent (e.g. x-unsigned) even when no lower bound is provided.
minimum = BigInteger.ZERO;
exclusiveMinimum = false;
}

if (StringUtils.isEmpty(format)) {
return bestFittingIntegerType(
minimum,
exclusiveMinimum,
maximum,
exclusiveMaximum,
unsigned);
}

switch (format) {
// custom integer formats (legacy)
case "uint32":
return "u32";
case "uint64":
return "u64";
case "int32":
return unsigned ? "u32" : "i32";
case "int64":
return unsigned ? "u64" : "i64";
default:
LOGGER.warn("The integer format '{}' is not recognized and will be ignored.", format);
return bestFittingIntegerType(
minimum,
exclusiveMinimum,
maximum,
exclusiveMaximum,
unsigned);
}
}

private boolean hasExplicitUnsignedExtension(Map<String, Object> extensions) {
Comment thread
LSX-s-Software marked this conversation as resolved.
Outdated
return extensions != null && Boolean.TRUE.equals(extensions.get("x-unsigned"));
}

@Override
public String getSchemaType(Schema p) {
if (Objects.equals(p.getType(), "integer")) {
BigInteger minimum = Optional.ofNullable(p.getMinimum()).map(BigDecimal::toBigInteger).orElse(null);
Comment thread
LSX-s-Software marked this conversation as resolved.
Outdated
BigInteger maximum = Optional.ofNullable(p.getMaximum()).map(BigDecimal::toBigInteger).orElse(null);
Comment thread
LSX-s-Software marked this conversation as resolved.
Outdated
boolean explicitUnsigned = ModelUtils.isUnsignedIntegerSchema(p)
Comment thread
LSX-s-Software marked this conversation as resolved.
Outdated
|| ModelUtils.isUnsignedLongSchema(p)
|| hasExplicitUnsignedExtension(p.getExtensions());

return getIntegerDataType(
p.getFormat(),
minimum,
Optional.ofNullable(p.getExclusiveMinimum()).orElse(false),
maximum,
Optional.ofNullable(p.getExclusiveMaximum()).orElse(false),
explicitUnsigned);
}

return super.getSchemaType(p);
}

@Override
public String toInstantiationType(final Schema p) {
if (ModelUtils.isArraySchema(p)) {
Expand Down Expand Up @@ -1113,13 +1183,19 @@ public void postProcessModelProperty(CodegenModel model, CodegenProperty propert
}

// Integer type fitting
if (Objects.equals(property.baseType, "integer")) {
if (Boolean.TRUE.equals(property.isInteger) || Boolean.TRUE.equals(property.isLong) || Objects.equals(property.baseType, "UnsignedInteger") || Objects.equals(property.baseType, "UnsignedLong")) {
BigInteger minimum = Optional.ofNullable(property.getMinimum()).map(BigInteger::new).orElse(null);
BigInteger maximum = Optional.ofNullable(property.getMaximum()).map(BigInteger::new).orElse(null);
property.dataType = bestFittingIntegerType(
minimum, property.getExclusiveMinimum(),
maximum, property.getExclusiveMaximum(),
true);
boolean explicitUnsigned = Objects.equals(property.baseType, "UnsignedInteger")
Comment thread
LSX-s-Software marked this conversation as resolved.
Outdated
|| Objects.equals(property.baseType, "UnsignedLong")
|| hasExplicitUnsignedExtension(property.vendorExtensions);
property.dataType = getIntegerDataType(
property.dataFormat,
minimum,
property.getExclusiveMinimum(),
maximum,
property.getExclusiveMaximum(),
explicitUnsigned);
}

property.name = underscore(property.name);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package org.openapitools.codegen.rust;

import io.swagger.v3.oas.models.media.IntegerSchema;
import org.openapitools.codegen.DefaultGenerator;
import org.openapitools.codegen.TestUtils;
import org.openapitools.codegen.config.CodegenConfigurator;
import org.openapitools.codegen.languages.RustAxumServerCodegen;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;

import static org.openapitools.codegen.TestUtils.linearize;
Expand Down Expand Up @@ -49,4 +54,66 @@ public void testPreventDuplicateOperationDeclaration() throws IOException {
TestUtils.assertFileExists(outputPath);
TestUtils.assertFileContains(outputPath, routerSpec);
}
}

@Test
public void testIntegerSchemaTypeMapping() {
RustAxumServerCodegen codegen = new RustAxumServerCodegen();
IntegerSchema schema = new IntegerSchema();

schema.setFormat("uint32");
Assert.assertEquals(codegen.getSchemaType(schema), "u32");

schema = new IntegerSchema();
schema.setFormat("uint64");
Assert.assertEquals(codegen.getSchemaType(schema), "u64");

schema = new IntegerSchema();
schema.setFormat("int32");
schema.setMinimum(BigDecimal.ZERO);
Assert.assertEquals(codegen.getSchemaType(schema), "u32");

schema = new IntegerSchema();
schema.setFormat("int64");
schema.setMinimum(BigDecimal.ZERO);
Assert.assertEquals(codegen.getSchemaType(schema), "u64");

schema = new IntegerSchema();
schema.setFormat(null);
schema.setMinimum(BigDecimal.ZERO);
schema.setMaximum(BigDecimal.valueOf(255));
Assert.assertEquals(codegen.getSchemaType(schema), "u8");

schema = new IntegerSchema();
schema.setExtensions(new HashMap<>());
schema.getExtensions().put("x-unsigned", true);
Assert.assertEquals(codegen.getSchemaType(schema), "u32");

schema = new IntegerSchema();
schema.setFormat("custom");
schema.setExtensions(new HashMap<>());
schema.getExtensions().put("x-unsigned", true);
Assert.assertEquals(codegen.getSchemaType(schema), "u32");
}

@Test
public void testGeneratedIntegerTypes() throws IOException {
Path target = Files.createTempDirectory("test");
final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("rust-axum")
.setInputSpec("src/test/resources/3_0/rust-axum/integer-types.yaml")
.setSkipOverwrite(false)
.setOutputDir(target.toAbsolutePath().toString().replace("\\", "/"));
List<File> files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate();
files.forEach(File::deleteOnExit);

Path modelsPath = Path.of(target.toString(), "/src/models.rs");
TestUtils.assertFileExists(modelsPath);
TestUtils.assertFileContains(modelsPath, "pub legacy_uint32: u32");
TestUtils.assertFileContains(modelsPath, "pub legacy_uint64: u64");
TestUtils.assertFileContains(modelsPath, "pub positive_int32: u32");
TestUtils.assertFileContains(modelsPath, "pub positive_int64: u64");
TestUtils.assertFileContains(modelsPath, "pub small_positive: u8");
TestUtils.assertFileContains(modelsPath, "pub explicit_unsigned: u32");
TestUtils.assertFileContains(modelsPath, "pub struct GetIntegersQueryParams");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
openapi: 3.0.3
info:
title: Rust Axum Integer Type Mapping Test
version: 1.0.0
paths:
/integers:
get:
operationId: getIntegers
parameters:
- name: legacy_uint32
in: query
required: true
schema:
type: integer
format: uint32
- name: legacy_uint64
in: query
required: true
schema:
type: integer
format: uint64
- name: positive_int32
in: query
required: true
schema:
type: integer
format: int32
minimum: 0
- name: positive_int64
in: query
required: true
schema:
type: integer
format: int64
minimum: 0
- name: small_positive
in: query
required: true
schema:
type: integer
minimum: 0
maximum: 255
- name: explicit_unsigned
in: query
required: true
schema:
type: integer
x-unsigned: true
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/IntegerTypes'
components:
schemas:
IntegerTypes:
type: object
required:
- legacy_uint32
- legacy_uint64
- positive_int32
- positive_int64
- small_positive
- explicit_unsigned
properties:
legacy_uint32:
type: integer
format: uint32
legacy_uint64:
type: integer
format: uint64
positive_int32:
type: integer
format: int32
minimum: 0
positive_int64:
type: integer
format: int64
minimum: 0
small_positive:
type: integer
minimum: 0
maximum: 255
explicit_unsigned:
type: integer
x-unsigned: true
Loading