diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java index 39a45900a91a..e54439b40fa0 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java @@ -610,6 +610,10 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation processParam(param, op); } + for (CodegenParameter param : op.pathParams) { + processParam(param, op); + } + // We keep track of the 'default' model type for this API. If there are // *any* XML responses, then we set the default to XML, otherwise we // let the default be JSON. It would be odd for an API to want to use @@ -1459,6 +1463,45 @@ public String toAllOfName(List 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); @@ -1492,41 +1535,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); @@ -1580,6 +1594,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); diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/rust/RustServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/rust/RustServerCodegenTest.java new file mode 100644 index 000000000000..6b730cce6f18 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/rust/RustServerCodegenTest.java @@ -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 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(); + } +} diff --git a/modules/openapi-generator/src/test/resources/3_0/rust-server/integer-params.yaml b/modules/openapi-generator/src/test/resources/3_0/rust-server/integer-params.yaml new file mode 100644 index 000000000000..5869a005e910 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/rust-server/integer-params.yaml @@ -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 diff --git a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/bin/cli.rs b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/bin/cli.rs index 5cc7d996d9a2..4bb45def50f0 100644 --- a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/bin/cli.rs +++ b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/bin/cli.rs @@ -154,9 +154,9 @@ enum Operation { #[clap(value_parser = parse_json::)] byte: swagger::ByteArray, /// None - integer: Option, + integer: Option, /// None - int32: Option, + int32: Option, /// None int64: Option, /// None @@ -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 { diff --git a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/docs/fake_api.md b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/docs/fake_api.md index 27c59de54b24..9b690e7b1ff3 100644 --- a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/docs/fake_api.md +++ b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/docs/fake_api.md @@ -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 | diff --git a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/docs/store_api.md b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/docs/store_api.md index f8e633abd83e..2e92b2ee355a 100644 --- a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/docs/store_api.md +++ b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/docs/store_api.md @@ -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 diff --git a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/examples/server/server.rs b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/examples/server/server.rs index 87df5af2b446..98ed4ce9894a 100644 --- a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/examples/server/server.rs +++ b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/examples/server/server.rs @@ -269,8 +269,8 @@ impl Api for Server where C: Has + Send + Sync double: f64, pattern_without_delimiter: String, byte: swagger::ByteArray, - integer: Option, - int32: Option, + integer: Option, + int32: Option, int64: Option, float: Option, string: Option, @@ -458,7 +458,7 @@ impl Api for Server where C: Has + Send + Sync /// Find purchase order by ID async fn get_order_by_id( &self, - order_id: i64, + order_id: u64, context: &C) -> Result { info!("get_order_by_id({}) - X-Span-ID: {:?}", order_id, context.get().0.clone()); diff --git a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/src/client/mod.rs b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/src/client/mod.rs index 93a264aefb97..e2d780db6582 100644 --- a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/src/client/mod.rs +++ b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/src/client/mod.rs @@ -1208,8 +1208,8 @@ impl Api for Client where param_double: f64, param_pattern_without_delimiter: String, param_byte: swagger::ByteArray, - param_integer: Option, - param_int32: Option, + param_integer: Option, + param_int32: Option, param_int64: Option, param_float: Option, param_string: Option, @@ -3032,7 +3032,7 @@ impl Api for Client 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 { let mut client_service = self.client_service.clone(); diff --git a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/src/lib.rs b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/src/lib.rs index aa34695336e7..3c409b899326 100644 --- a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/src/lib.rs +++ b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/src/lib.rs @@ -380,8 +380,8 @@ pub trait Api { double: f64, pattern_without_delimiter: String, byte: swagger::ByteArray, - integer: Option, - int32: Option, + integer: Option, + int32: Option, int64: Option, float: Option, string: Option, @@ -501,7 +501,7 @@ pub trait Api { /// Find purchase order by ID async fn get_order_by_id( &self, - order_id: i64, + order_id: u64, context: &C) -> Result; /// Create user @@ -620,8 +620,8 @@ pub trait ApiNoContext { double: f64, pattern_without_delimiter: String, byte: swagger::ByteArray, - integer: Option, - int32: Option, + integer: Option, + int32: Option, int64: Option, float: Option, string: Option, @@ -741,7 +741,7 @@ pub trait ApiNoContext { /// Find purchase order by ID async fn get_order_by_id( &self, - order_id: i64, + order_id: u64, ) -> Result; /// Create user @@ -903,8 +903,8 @@ impl + Send + Sync, C: Clone + Send + Sync> ApiNoContext for Contex double: f64, pattern_without_delimiter: String, byte: swagger::ByteArray, - integer: Option, - int32: Option, + integer: Option, + int32: Option, int64: Option, float: Option, string: Option, @@ -1092,7 +1092,7 @@ impl + Send + Sync, C: Clone + Send + Sync> ApiNoContext for Contex /// Find purchase order by ID async fn get_order_by_id( &self, - order_id: i64, + order_id: u64, ) -> Result { let context = self.context().clone(); diff --git a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/src/server/mod.rs b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/src/server/mod.rs index 57e02efadc1f..83e6ca4f40a6 100644 --- a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/src/server/mod.rs +++ b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/src/server/mod.rs @@ -2606,7 +2606,7 @@ impl hyper::service::Service<(Request, C)> for Service match param_order_id.parse::() { + Ok(param_order_id) => match param_order_id.parse::() { Ok(param_order_id) => param_order_id, Err(e) => return Ok(Response::builder() .status(StatusCode::BAD_REQUEST)