Skip to content

Commit caf22ad

Browse files
authored
[rust] Fixed nullable byte arrays (#20720)
* [rust] fixed nullable byte arrays * Updated tests and samples * updated type test * Added double option support * updated samples
1 parent e767496 commit caf22ad

File tree

35 files changed

+475
-2
lines changed

35 files changed

+475
-2
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,9 +605,18 @@ public void postProcessModelProperty(CodegenModel model, CodegenProperty propert
605605
additionalProperties.put("serdeWith", true);
606606
}
607607

608+
// Add a field for checking if a field is with optional or required in templates.
609+
// This is useful in Mustache templates as it's not possible to do OR logic between variables.
610+
property.vendorExtensions.put("isMandatory", !property.isNullable && property.required);
611+
608612
// If a property is a base64-encoded byte array, use `serde_with` for deserialization.
609613
if (property.isByteArray) {
610614
additionalProperties.put("serdeWith", true);
615+
// If a byte array is both nullable and not required we need to include our own
616+
// custom double option as serde_as does not work with serde_with's double_option.
617+
if (property.isNullable && !property.required) {
618+
additionalProperties.put("serdeAsDoubleOption", true);
619+
}
611620
}
612621
}
613622

modules/openapi-generator/src/main/resources/rust/model.mustache

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,9 @@ pub struct {{{classname}}} {
129129
/// {{{.}}}
130130
{{/description}}
131131
{{#isByteArray}}
132-
{{#required}}#[serde_as(as = "serde_with::base64::Base64")]{{/required}}{{^required}}#[serde_as(as = "Option<serde_with::base64::Base64>")]{{/required}}
132+
{{#vendorExtensions.isMandatory}}#[serde_as(as = "serde_with::base64::Base64")]{{/vendorExtensions.isMandatory}}{{^vendorExtensions.isMandatory}}#[serde_as(as = "{{^serdeAsDoubleOption}}Option{{/serdeAsDoubleOption}}{{#serdeAsDoubleOption}}super::DoubleOption{{/serdeAsDoubleOption}}<serde_with::base64::Base64>")]{{/vendorExtensions.isMandatory}}
133133
{{/isByteArray}}
134-
#[serde(rename = "{{{baseName}}}"{{^required}}{{#isNullable}}, default, with = "::serde_with::rust::double_option"{{/isNullable}}{{/required}}{{^required}}, skip_serializing_if = "Option::is_none"{{/required}}{{#required}}{{#isNullable}}, deserialize_with = "Option::deserialize"{{/isNullable}}{{/required}})]
134+
#[serde(rename = "{{{baseName}}}"{{^required}}{{#isNullable}}, default{{^isByteArray}}, with = "::serde_with::rust::double_option"{{/isByteArray}}{{/isNullable}}{{/required}}{{^required}}, skip_serializing_if = "Option::is_none"{{/required}}{{#required}}{{#isNullable}}, deserialize_with = "Option::deserialize"{{/isNullable}}{{/required}})]
135135
pub {{{name}}}: {{!
136136
### Option Start
137137
}}{{#isNullable}}Option<{{/isNullable}}{{^required}}Option<{{/required}}{{!

modules/openapi-generator/src/main/resources/rust/model_mod.mustache

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,42 @@ pub mod {{{classFilename}}};
44
pub use self::{{{classFilename}}}::{{{classname}}};
55
{{/model}}
66
{{/models}}
7+
{{#serdeAsDoubleOption}}
8+
use serde::{Deserialize, Deserializer, Serializer};
9+
use serde_with::{de::DeserializeAsWrap, ser::SerializeAsWrap, DeserializeAs, SerializeAs};
10+
use std::marker::PhantomData;
11+
12+
pub(crate) struct DoubleOption<T>(PhantomData<T>);
13+
14+
impl<T, TAs> SerializeAs<Option<Option<T>>> for DoubleOption<TAs>
15+
where
16+
TAs: SerializeAs<T>,
17+
{
18+
fn serialize_as<S>(values: &Option<Option<T>>, serializer: S) -> Result<S::Ok, S::Error>
19+
where
20+
S: Serializer,
21+
{
22+
match values {
23+
None => serializer.serialize_unit(),
24+
Some(None) => serializer.serialize_none(),
25+
Some(Some(v)) => serializer.serialize_some(&SerializeAsWrap::<T, TAs>::new(v)),
26+
}
27+
}
28+
}
29+
30+
impl<'de, T, TAs> DeserializeAs<'de, Option<Option<T>>> for DoubleOption<TAs>
31+
where
32+
TAs: DeserializeAs<'de, T>,
33+
T: std::fmt::Debug,
34+
{
35+
fn deserialize_as<D>(deserializer: D) -> Result<Option<Option<T>>, D::Error>
36+
where
37+
D: Deserializer<'de>,
38+
{
39+
Ok(Some(
40+
DeserializeAsWrap::<Option<T>, Option<TAs>>::deserialize(deserializer)?
41+
.into_inner(),
42+
))
43+
}
44+
}
45+
{{/serdeAsDoubleOption}}

modules/openapi-generator/src/test/resources/3_0/rust/petstore.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -935,6 +935,10 @@ components:
935935
bytes:
936936
type: string
937937
format: byte
938+
nullableBytes: # Regression test for bug report in #20500
939+
type: string
940+
format: byte
941+
nullable: true
938942
decimal:
939943
type: string
940944
format: number

samples/client/petstore/rust/hyper/petstore/docs/TypeTesting.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Name | Type | Description | Notes
1212
**boolean** | **bool** | |
1313
**uuid** | [**uuid::Uuid**](uuid::Uuid.md) | |
1414
**bytes** | **String** | |
15+
**nullable_bytes** | Option<**String**> | | [optional]
1516
**decimal** | **String** | |
1617

1718
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

samples/client/petstore/rust/hyper/petstore/src/models/mod.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,40 @@ pub mod user;
4242
pub use self::user::User;
4343
pub mod vehicle;
4444
pub use self::vehicle::Vehicle;
45+
use serde::{Deserialize, Deserializer, Serializer};
46+
use serde_with::{de::DeserializeAsWrap, ser::SerializeAsWrap, DeserializeAs, SerializeAs};
47+
use std::marker::PhantomData;
48+
49+
pub(crate) struct DoubleOption<T>(PhantomData<T>);
50+
51+
impl<T, TAs> SerializeAs<Option<Option<T>>> for DoubleOption<TAs>
52+
where
53+
TAs: SerializeAs<T>,
54+
{
55+
fn serialize_as<S>(values: &Option<Option<T>>, serializer: S) -> Result<S::Ok, S::Error>
56+
where
57+
S: Serializer,
58+
{
59+
match values {
60+
None => serializer.serialize_unit(),
61+
Some(None) => serializer.serialize_none(),
62+
Some(Some(v)) => serializer.serialize_some(&SerializeAsWrap::<T, TAs>::new(v)),
63+
}
64+
}
65+
}
66+
67+
impl<'de, T, TAs> DeserializeAs<'de, Option<Option<T>>> for DoubleOption<TAs>
68+
where
69+
TAs: DeserializeAs<'de, T>,
70+
T: std::fmt::Debug,
71+
{
72+
fn deserialize_as<D>(deserializer: D) -> Result<Option<Option<T>>, D::Error>
73+
where
74+
D: Deserializer<'de>,
75+
{
76+
Ok(Some(
77+
DeserializeAsWrap::<Option<T>, Option<TAs>>::deserialize(deserializer)?
78+
.into_inner(),
79+
))
80+
}
81+
}

samples/client/petstore/rust/hyper/petstore/src/models/type_testing.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ pub struct TypeTesting {
3434
#[serde_as(as = "serde_with::base64::Base64")]
3535
#[serde(rename = "bytes")]
3636
pub bytes: Vec<u8>,
37+
#[serde_as(as = "super::DoubleOption<serde_with::base64::Base64>")]
38+
#[serde(rename = "nullableBytes", default, skip_serializing_if = "Option::is_none")]
39+
pub nullable_bytes: Option<Option<Vec<u8>>>,
3740
#[serde(rename = "decimal")]
3841
pub decimal: String,
3942
}
@@ -50,6 +53,7 @@ impl TypeTesting {
5053
boolean,
5154
uuid,
5255
bytes,
56+
nullable_bytes: None,
5357
decimal,
5458
}
5559
}

samples/client/petstore/rust/hyper0x/petstore/docs/TypeTesting.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Name | Type | Description | Notes
1212
**boolean** | **bool** | |
1313
**uuid** | [**uuid::Uuid**](uuid::Uuid.md) | |
1414
**bytes** | **String** | |
15+
**nullable_bytes** | Option<**String**> | | [optional]
1516
**decimal** | **String** | |
1617

1718
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

samples/client/petstore/rust/hyper0x/petstore/src/models/mod.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,40 @@ pub mod user;
4242
pub use self::user::User;
4343
pub mod vehicle;
4444
pub use self::vehicle::Vehicle;
45+
use serde::{Deserialize, Deserializer, Serializer};
46+
use serde_with::{de::DeserializeAsWrap, ser::SerializeAsWrap, DeserializeAs, SerializeAs};
47+
use std::marker::PhantomData;
48+
49+
pub(crate) struct DoubleOption<T>(PhantomData<T>);
50+
51+
impl<T, TAs> SerializeAs<Option<Option<T>>> for DoubleOption<TAs>
52+
where
53+
TAs: SerializeAs<T>,
54+
{
55+
fn serialize_as<S>(values: &Option<Option<T>>, serializer: S) -> Result<S::Ok, S::Error>
56+
where
57+
S: Serializer,
58+
{
59+
match values {
60+
None => serializer.serialize_unit(),
61+
Some(None) => serializer.serialize_none(),
62+
Some(Some(v)) => serializer.serialize_some(&SerializeAsWrap::<T, TAs>::new(v)),
63+
}
64+
}
65+
}
66+
67+
impl<'de, T, TAs> DeserializeAs<'de, Option<Option<T>>> for DoubleOption<TAs>
68+
where
69+
TAs: DeserializeAs<'de, T>,
70+
T: std::fmt::Debug,
71+
{
72+
fn deserialize_as<D>(deserializer: D) -> Result<Option<Option<T>>, D::Error>
73+
where
74+
D: Deserializer<'de>,
75+
{
76+
Ok(Some(
77+
DeserializeAsWrap::<Option<T>, Option<TAs>>::deserialize(deserializer)?
78+
.into_inner(),
79+
))
80+
}
81+
}

samples/client/petstore/rust/hyper0x/petstore/src/models/type_testing.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ pub struct TypeTesting {
3434
#[serde_as(as = "serde_with::base64::Base64")]
3535
#[serde(rename = "bytes")]
3636
pub bytes: Vec<u8>,
37+
#[serde_as(as = "super::DoubleOption<serde_with::base64::Base64>")]
38+
#[serde(rename = "nullableBytes", default, skip_serializing_if = "Option::is_none")]
39+
pub nullable_bytes: Option<Option<Vec<u8>>>,
3740
#[serde(rename = "decimal")]
3841
pub decimal: String,
3942
}
@@ -50,6 +53,7 @@ impl TypeTesting {
5053
boolean,
5154
uuid,
5255
bytes,
56+
nullable_bytes: None,
5357
decimal,
5458
}
5559
}

0 commit comments

Comments
 (0)