Skip to content

Commit d0b3492

Browse files
committed
Support integer enums in rust-server using serde-repr
1 parent 2ba5879 commit d0b3492

File tree

15 files changed

+558
-512
lines changed

15 files changed

+558
-512
lines changed

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

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1565,15 +1565,41 @@ public void postProcessModelProperty(CodegenModel model, CodegenProperty propert
15651565
public ModelsMap postProcessModels(ModelsMap objs) {
15661566
ModelsMap result = super.postProcessModelsEnum(objs);
15671567

1568-
// Fix for integer enums: add unquoted numeric values for serde serialization.
1569-
// Integer enums should serialize as JSON numbers, not strings.
1568+
// Detect integer enums and mark them for serde_repr usage
15701569
for (ModelMap modelMap : result.getModels()) {
15711570
CodegenModel model = modelMap.getModel();
15721571

15731572
if (Boolean.TRUE.equals(model.isEnum) &&
15741573
(model.isInteger || model.isLong || model.isNumber) &&
15751574
model.allowableValues != null) {
15761575

1576+
// Determine the correct Rust type for the enum's repr
1577+
String rustType;
1578+
if (model.isNumber && !model.isInteger && !model.isLong) {
1579+
// Floating point enum - use dataType or default to f64
1580+
rustType = "f32".equals(model.dataType) ? "f32" : "f64";
1581+
} else {
1582+
// Integer enum - apply the same type fitting logic as properties
1583+
rustType = applyIntegerTypeFitting(
1584+
model.getFormat(),
1585+
model.getMinimum(),
1586+
model.getMaximum(),
1587+
model.getExclusiveMinimum(),
1588+
model.getExclusiveMaximum());
1589+
// If applyIntegerTypeFitting returns null, default to i32
1590+
if (rustType == null) {
1591+
rustType = "i32";
1592+
}
1593+
}
1594+
1595+
// Mark this as an integer enum and store the Rust type
1596+
model.vendorExtensions.put("x-is-integer-enum", true);
1597+
model.vendorExtensions.put("x-rust-type", rustType);
1598+
1599+
// Set global flag to include serde_repr dependency
1600+
additionalProperties.put("apiUsesIntegerEnums", true);
1601+
1602+
// Add numeric discriminant values for enum variants
15771603
@SuppressWarnings("unchecked")
15781604
List<Map<String, Object>> enumVars =
15791605
(List<Map<String, Object>>) model.allowableValues.get("enumVars");
@@ -1582,8 +1608,9 @@ public ModelsMap postProcessModels(ModelsMap objs) {
15821608
for (Map<String, Object> enumVar : enumVars) {
15831609
String value = (String) enumVar.get("value");
15841610
if (value != null) {
1585-
// Strip quotes added by toEnumValue()
1586-
enumVar.put("numericValue", value.substring(1, value.length() - 1));
1611+
// Strip quotes to get raw numeric value
1612+
String numericValue = value.substring(1, value.length() - 1);
1613+
enumVar.put("numericDiscriminant", numericValue);
15871614
}
15881615
}
15891616
}

modules/openapi-generator/src/main/resources/rust-server-deprecated/models.mustache

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,7 @@ impl std::convert::TryFrom<hyper::header::HeaderValue> for header::IntoHeaderVal
579579
}
580580
}
581581

582-
#[cfg(feature = "server")]
582+
#[cfg(any(feature = "client", feature = "server"))]
583583
impl std::convert::TryFrom<header::IntoHeaderValue<Vec<{{{classname}}}>>> for hyper::header::HeaderValue {
584584
type Error = String;
585585
@@ -595,7 +595,7 @@ impl std::convert::TryFrom<header::IntoHeaderValue<Vec<{{{classname}}}>>> for hy
595595
}
596596
}
597597

598-
#[cfg(feature = "server")]
598+
#[cfg(any(feature = "client", feature = "server"))]
599599
impl std::convert::TryFrom<hyper::header::HeaderValue> for header::IntoHeaderValue<Vec<{{{classname}}}>> {
600600
type Error = String;
601601

modules/openapi-generator/src/main/resources/rust-server/Cargo.mustache

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ regex = "1.12"
105105

106106
serde = { version = "1.0", features = ["derive"] }
107107
serde_json = "1.0"
108+
{{#apiUsesIntegerEnums}}
109+
serde_repr = "0.1"
110+
{{/apiUsesIntegerEnums}}
108111
serde_valid = { version = "2.0", optional = true }
109112

110113
validator = { version = "0.20", features = ["derive"] }

modules/openapi-generator/src/main/resources/rust-server/models.mustache

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,41 +28,65 @@ use crate::header;
2828
/// Since this enum's variants do not hold data, we can easily define them as `#[repr(C)]`
2929
/// which helps with FFI.
3030
#[allow(non_camel_case_types)]
31+
{{#vendorExtensions.x-is-integer-enum}}
32+
#[repr({{{vendorExtensions.x-rust-type}}})]
33+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde_repr::Serialize_repr, serde_repr::Deserialize_repr, Hash)]
34+
{{/vendorExtensions.x-is-integer-enum}}
35+
{{^vendorExtensions.x-is-integer-enum}}
3136
#[repr(C)]
3237
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)]
38+
{{/vendorExtensions.x-is-integer-enum}}
3339
{{^hasConflictingModelNames}}{{^exts.x-skip-serde-valid}}#[cfg_attr(feature = "validate", derive(Validate))]{{/exts.x-skip-serde-valid}}{{/hasConflictingModelNames}}
3440
#[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))]{{#xmlName}}
3541
#[serde(rename = "{{{.}}}")]{{/xmlName}}
3642
pub enum {{{classname}}} {
3743
{{#allowableValues}}
3844
{{#enumVars}}
39-
{{#numericValue}}
40-
#[serde(rename = {{{numericValue}}})]
41-
{{/numericValue}}
42-
{{^numericValue}}
45+
{{^vendorExtensions.x-is-integer-enum}}
4346
#[serde(rename = {{{value}}})]
44-
{{/numericValue}}
47+
{{/vendorExtensions.x-is-integer-enum}}
48+
{{#numericDiscriminant}}
49+
{{{name}}} = {{{numericDiscriminant}}},
50+
{{/numericDiscriminant}}
51+
{{^numericDiscriminant}}
4552
{{{name}}},
53+
{{/numericDiscriminant}}
4654
{{/enumVars}}
4755
{{/allowableValues}}
4856
}
4957

5058
impl std::fmt::Display for {{{classname}}} {
5159
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60+
{{#vendorExtensions.x-is-integer-enum}}
61+
write!(f, "{}", *self as {{{vendorExtensions.x-rust-type}}})
62+
{{/vendorExtensions.x-is-integer-enum}}
63+
{{^vendorExtensions.x-is-integer-enum}}
5264
match *self {
5365
{{#allowableValues}}
5466
{{#enumVars}}
5567
{{{classname}}}::{{{name}}} => write!(f, {{{value}}}),
5668
{{/enumVars}}
5769
{{/allowableValues}}
5870
}
71+
{{/vendorExtensions.x-is-integer-enum}}
5972
}
6073
}
6174
6275
impl std::str::FromStr for {{{classname}}} {
6376
type Err = String;
6477
6578
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
79+
{{#vendorExtensions.x-is-integer-enum}}
80+
match s.parse::<{{{vendorExtensions.x-rust-type}}}>() {
81+
{{#allowableValues}}
82+
{{#enumVars}}
83+
std::result::Result::Ok({{{numericDiscriminant}}}) => std::result::Result::Ok({{{classname}}}::{{{name}}}),
84+
{{/enumVars}}
85+
{{/allowableValues}}
86+
_ => std::result::Result::Err(format!("Value not valid: {s}")),
87+
}
88+
{{/vendorExtensions.x-is-integer-enum}}
89+
{{^vendorExtensions.x-is-integer-enum}}
6690
match s {
6791
{{#allowableValues}}
6892
{{#enumVars}}
@@ -71,6 +95,7 @@ impl std::str::FromStr for {{{classname}}} {
7195
{{/allowableValues}}
7296
_ => std::result::Result::Err(format!("Value not valid: {s}")),
7397
}
98+
{{/vendorExtensions.x-is-integer-enum}}
7499
}
75100
}
76101
{{/isEnum}}
@@ -634,7 +659,7 @@ impl std::convert::TryFrom<hyper::header::HeaderValue> for header::IntoHeaderVal
634659
}
635660
}
636661

637-
#[cfg(feature = "server")]
662+
#[cfg(any(feature = "client", feature = "server"))]
638663
impl std::convert::TryFrom<header::IntoHeaderValue<Vec<{{{classname}}}>>> for hyper::header::HeaderValue {
639664
type Error = String;
640665
@@ -650,7 +675,7 @@ impl std::convert::TryFrom<header::IntoHeaderValue<Vec<{{{classname}}}>>> for hy
650675
}
651676
}
652677

653-
#[cfg(feature = "server")]
678+
#[cfg(any(feature = "client", feature = "server"))]
654679
impl std::convert::TryFrom<hyper::header::HeaderValue> for header::IntoHeaderValue<Vec<{{{classname}}}>> {
655680
type Error = String;
656681

samples/server/petstore/rust-server-deprecated/output/multipart-v3/src/models.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ impl std::convert::TryFrom<hyper::header::HeaderValue> for header::IntoHeaderVal
137137
}
138138
}
139139

140-
#[cfg(feature = "server")]
140+
#[cfg(any(feature = "client", feature = "server"))]
141141
impl std::convert::TryFrom<header::IntoHeaderValue<Vec<MultipartRelatedRequest>>> for hyper::header::HeaderValue {
142142
type Error = String;
143143

@@ -153,7 +153,7 @@ impl std::convert::TryFrom<header::IntoHeaderValue<Vec<MultipartRelatedRequest>>
153153
}
154154
}
155155

156-
#[cfg(feature = "server")]
156+
#[cfg(any(feature = "client", feature = "server"))]
157157
impl std::convert::TryFrom<hyper::header::HeaderValue> for header::IntoHeaderValue<Vec<MultipartRelatedRequest>> {
158158
type Error = String;
159159

@@ -307,7 +307,7 @@ impl std::convert::TryFrom<hyper::header::HeaderValue> for header::IntoHeaderVal
307307
}
308308
}
309309

310-
#[cfg(feature = "server")]
310+
#[cfg(any(feature = "client", feature = "server"))]
311311
impl std::convert::TryFrom<header::IntoHeaderValue<Vec<MultipartRequestObjectField>>> for hyper::header::HeaderValue {
312312
type Error = String;
313313

@@ -323,7 +323,7 @@ impl std::convert::TryFrom<header::IntoHeaderValue<Vec<MultipartRequestObjectFie
323323
}
324324
}
325325

326-
#[cfg(feature = "server")]
326+
#[cfg(any(feature = "client", feature = "server"))]
327327
impl std::convert::TryFrom<hyper::header::HeaderValue> for header::IntoHeaderValue<Vec<MultipartRequestObjectField>> {
328328
type Error = String;
329329

@@ -471,7 +471,7 @@ impl std::convert::TryFrom<hyper::header::HeaderValue> for header::IntoHeaderVal
471471
}
472472
}
473473

474-
#[cfg(feature = "server")]
474+
#[cfg(any(feature = "client", feature = "server"))]
475475
impl std::convert::TryFrom<header::IntoHeaderValue<Vec<MultipleIdenticalMimeTypesPostRequest>>> for hyper::header::HeaderValue {
476476
type Error = String;
477477

@@ -487,7 +487,7 @@ impl std::convert::TryFrom<header::IntoHeaderValue<Vec<MultipleIdenticalMimeType
487487
}
488488
}
489489

490-
#[cfg(feature = "server")]
490+
#[cfg(any(feature = "client", feature = "server"))]
491491
impl std::convert::TryFrom<hyper::header::HeaderValue> for header::IntoHeaderValue<Vec<MultipleIdenticalMimeTypesPostRequest>> {
492492
type Error = String;
493493

samples/server/petstore/rust-server-deprecated/output/no-example-v3/src/models.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ impl std::convert::TryFrom<hyper::header::HeaderValue> for header::IntoHeaderVal
120120
}
121121
}
122122

123-
#[cfg(feature = "server")]
123+
#[cfg(any(feature = "client", feature = "server"))]
124124
impl std::convert::TryFrom<header::IntoHeaderValue<Vec<OpGetRequest>>> for hyper::header::HeaderValue {
125125
type Error = String;
126126

@@ -136,7 +136,7 @@ impl std::convert::TryFrom<header::IntoHeaderValue<Vec<OpGetRequest>>> for hyper
136136
}
137137
}
138138

139-
#[cfg(feature = "server")]
139+
#[cfg(any(feature = "client", feature = "server"))]
140140
impl std::convert::TryFrom<hyper::header::HeaderValue> for header::IntoHeaderValue<Vec<OpGetRequest>> {
141141
type Error = String;
142142

0 commit comments

Comments
 (0)