Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 @@ -1459,6 +1459,45 @@ public String toAllOfName(List<String> names, Schema composedSchema) {
return null;
}

/**
* Determine the appropriate Rust integer type based on format and min/max constraints.
* Returns the fitted data type, or null if the baseType is not an integer.
*
* @param dataFormat The data format (e.g., "int32", "int64", "uint32", "uint64")
* @param minimum The minimum value constraint
* @param maximum The maximum value constraint
* @param exclusiveMinimum Whether the minimum is exclusive
* @param exclusiveMaximum Whether the maximum is exclusive
* @return The fitted Rust integer type.
*/
private String applyIntegerTypeFitting(String dataFormat,
String minimum, String maximum,
boolean exclusiveMinimum, boolean exclusiveMaximum) {
BigInteger min = Optional.ofNullable(minimum).filter(s -> !s.isEmpty()).map(BigInteger::new).orElse(null);
BigInteger max = Optional.ofNullable(maximum).filter(s -> !s.isEmpty()).map(BigInteger::new).orElse(null);

boolean unsigned = canFitIntoUnsigned(min, exclusiveMinimum);

if (Strings.isNullOrEmpty(dataFormat)) {
return bestFittingIntegerType(min, exclusiveMinimum, max, exclusiveMaximum, true);
} else {
switch (dataFormat) {
// 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.", dataFormat);
return bestFittingIntegerType(min, exclusiveMinimum, max, exclusiveMaximum, true);
}
}
}

@Override
public void postProcessModelProperty(CodegenModel model, CodegenProperty property) {
super.postProcessModelProperty(model, property);
Expand Down Expand Up @@ -1492,41 +1531,12 @@ public void postProcessModelProperty(CodegenModel model, CodegenProperty propert
// 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 {
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;
default:
LOGGER.warn("The integer format '{}' is not recognized and will be ignored.", property.dataFormat);
property.dataType = bestFittingIntegerType(minimum,
property.getExclusiveMinimum(),
maximum,
property.getExclusiveMaximum(),
true);
}
}
property.dataType = applyIntegerTypeFitting(
property.dataFormat,
property.getMinimum(),
property.getMaximum(),
property.getExclusiveMinimum(),
property.getExclusiveMaximum());
}

property.name = underscore(property.name);
Expand Down Expand Up @@ -1580,6 +1590,17 @@ public ModelsMap postProcessModels(ModelsMap objs) {
private void processParam(CodegenParameter param, CodegenOperation op) {
String example = null;

// If a parameter is an integer, fit it into the right type.
// Note: For CodegenParameter, baseType may be null, so we check isInteger/isLong/isShort flags instead.
if (param.isInteger || param.isLong || param.isShort) {
param.dataType = applyIntegerTypeFitting(
param.dataFormat,
param.minimum,
param.maximum,
param.exclusiveMinimum,
param.exclusiveMaximum);
}

// If a parameter uses UUIDs, we need to import the UUID package.
if (uuidType.equals(param.dataType)) {
additionalProperties.put("apiUsesUuid", true);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.openapitools.codegen.rust;

import org.openapitools.codegen.DefaultGenerator;
import org.openapitools.codegen.TestUtils;
import org.openapitools.codegen.config.CodegenConfigurator;
import org.testng.annotations.Test;

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

/**
* Tests for RustServerCodegen.
*/
public class RustServerCodegenTest {

/**
* Test that integer parameters with minimum/maximum constraints are assigned appropriate Rust types.
* This tests that integer parameter type fitting logic is applied to CodegenParameter objects.
*/
@Test
public void testIntegerParameterTypeFitting() throws IOException {
Path target = Files.createTempDirectory("test");
final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("rust-server")
.setInputSpec("src/test/resources/3_0/rust-server/integer-params.yaml")
.setSkipOverwrite(false)
.setOutputDir(target.toAbsolutePath().toString().replace("\\", "/"));
List<File> files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate();
files.forEach(File::deleteOnExit);

Path libPath = Path.of(target.toString(), "/src/lib.rs");
TestUtils.assertFileExists(libPath);

// Verify that parameters with known min/max ranges get appropriate types
// age: 0-150 should fit in u8
TestUtils.assertFileContains(libPath, "age: u8");

// temperature: -50 to 50 should fit in i8
TestUtils.assertFileContains(libPath, "temperature: i8");

// count: 0-65535 should fit in u16
TestUtils.assertFileContains(libPath, "count: u16");

// offset: -32768 to 32767 should fit in i16
TestUtils.assertFileContains(libPath, "offset: i16");

// large_unsigned: 0-4294967295 should be u32
TestUtils.assertFileContains(libPath, "large_unsigned: u32");

// Verify integer with int32 format and minimum >= 0 becomes u32
TestUtils.assertFileContains(libPath, "positive_int32: u32");

// Verify integer with int64 format and minimum >= 0 becomes u64
TestUtils.assertFileContains(libPath, "positive_int64: u64");

// Clean up
target.toFile().deleteOnExit();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@

# Test that integer parameters are generated into the right
# primitives in Rust code.
openapi: 3.1.1
info:
title: Integer Parameter Type Fitting Test
description: Test spec to verify that integer parameters with minimum/maximum constraints get appropriate Rust types
version: 1.0.0
servers:
- url: http://localhost:8080
paths:
/test/integers:
get:
operationId: testIntegerParameters
summary: Test integer parameter type fitting
parameters:
# age: 0-150 should fit in u8 (unsigned, 8-bit)
- name: age
in: query
required: true
schema:
type: integer
minimum: 0
maximum: 150
# temperature: -50 to 50 should fit in i8 (signed, 8-bit)
- name: temperature
in: query
required: true
schema:
type: integer
minimum: -50
maximum: 50
# count: 0-65535 should fit in u16 (unsigned, 16-bit)
- name: count
in: query
required: true
schema:
type: integer
minimum: 0
maximum: 65535
# offset: -32768 to 32767 should fit in i16 (signed, 16-bit)
- name: offset
in: query
required: true
schema:
type: integer
minimum: -32768
maximum: 32767
# large_unsigned: 0-4294967295 should be u32
- name: large_unsigned
in: query
required: true
schema:
type: integer
minimum: 0
maximum: 4294967295
# positive_int32: format int32 with min >= 0 should become u32
- name: positive_int32
in: query
required: true
schema:
type: integer
format: int32
minimum: 0
# positive_int64: format int64 with min >= 0 should become u64
- name: positive_int64
in: query
required: true
schema:
type: integer
format: int64
minimum: 0
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,9 @@ enum Operation {
#[clap(value_parser = parse_json::<swagger::ByteArray>)]
byte: swagger::ByteArray,
/// None
integer: Option<i32>,
integer: Option<u32>,
/// None
int32: Option<i32>,
int32: Option<u32>,
/// None
int64: Option<i64>,
/// None
Expand Down Expand Up @@ -292,7 +292,7 @@ enum Operation {
/// Find purchase order by ID
GetOrderById {
/// ID of pet that needs to be fetched
order_id: i64,
order_id: u64,
},
/// Create user
CreateUser {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,8 @@ Name | Type | Description | Notes
**double** | **f64**| None |
**pattern_without_delimiter** | **String**| None |
**byte** | **swagger::ByteArray**| None |
**integer** | **i32**| None |
**int32** | **i32**| None |
**integer** | **u32**| None |
**int32** | **u32**| None |
**int64** | **i64**| None |
**float** | **f32**| None |
**string** | **String**| None |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ For valid response try integer IDs with value <= 5 or > 10. Other values will ge

Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**order_id** | **i64**| ID of pet that needs to be fetched |
**order_id** | **u64**| ID of pet that needs to be fetched |

### Return type

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,8 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
double: f64,
pattern_without_delimiter: String,
byte: swagger::ByteArray,
integer: Option<i32>,
int32: Option<i32>,
integer: Option<u32>,
int32: Option<u32>,
int64: Option<i64>,
float: Option<f32>,
string: Option<String>,
Expand Down Expand Up @@ -458,7 +458,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
/// Find purchase order by ID
async fn get_order_by_id(
&self,
order_id: i64,
order_id: u64,
context: &C) -> Result<GetOrderByIdResponse, ApiError>
{
info!("get_order_by_id({}) - X-Span-ID: {:?}", order_id, context.get().0.clone());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1208,8 +1208,8 @@ impl<S, C, B> Api<C> for Client<S, C> where
param_double: f64,
param_pattern_without_delimiter: String,
param_byte: swagger::ByteArray,
param_integer: Option<i32>,
param_int32: Option<i32>,
param_integer: Option<u32>,
param_int32: Option<u32>,
param_int64: Option<i64>,
param_float: Option<f32>,
param_string: Option<String>,
Expand Down Expand Up @@ -3032,7 +3032,7 @@ impl<S, C, B> Api<C> for Client<S, C> where
#[allow(clippy::vec_init_then_push)]
async fn get_order_by_id(
&self,
param_order_id: i64,
param_order_id: u64,
context: &C) -> Result<GetOrderByIdResponse, ApiError>
{
let mut client_service = self.client_service.clone();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -380,8 +380,8 @@ pub trait Api<C: Send + Sync> {
double: f64,
pattern_without_delimiter: String,
byte: swagger::ByteArray,
integer: Option<i32>,
int32: Option<i32>,
integer: Option<u32>,
int32: Option<u32>,
int64: Option<i64>,
float: Option<f32>,
string: Option<String>,
Expand Down Expand Up @@ -501,7 +501,7 @@ pub trait Api<C: Send + Sync> {
/// Find purchase order by ID
async fn get_order_by_id(
&self,
order_id: i64,
order_id: u64,
context: &C) -> Result<GetOrderByIdResponse, ApiError>;

/// Create user
Expand Down Expand Up @@ -620,8 +620,8 @@ pub trait ApiNoContext<C: Send + Sync> {
double: f64,
pattern_without_delimiter: String,
byte: swagger::ByteArray,
integer: Option<i32>,
int32: Option<i32>,
integer: Option<u32>,
int32: Option<u32>,
int64: Option<i64>,
float: Option<f32>,
string: Option<String>,
Expand Down Expand Up @@ -741,7 +741,7 @@ pub trait ApiNoContext<C: Send + Sync> {
/// Find purchase order by ID
async fn get_order_by_id(
&self,
order_id: i64,
order_id: u64,
) -> Result<GetOrderByIdResponse, ApiError>;

/// Create user
Expand Down Expand Up @@ -903,8 +903,8 @@ impl<T: Api<C> + Send + Sync, C: Clone + Send + Sync> ApiNoContext<C> for Contex
double: f64,
pattern_without_delimiter: String,
byte: swagger::ByteArray,
integer: Option<i32>,
int32: Option<i32>,
integer: Option<u32>,
int32: Option<u32>,
int64: Option<i64>,
float: Option<f32>,
string: Option<String>,
Expand Down Expand Up @@ -1092,7 +1092,7 @@ impl<T: Api<C> + Send + Sync, C: Clone + Send + Sync> ApiNoContext<C> for Contex
/// Find purchase order by ID
async fn get_order_by_id(
&self,
order_id: i64,
order_id: u64,
) -> Result<GetOrderByIdResponse, ApiError>
{
let context = self.context().clone();
Expand Down
Loading