Skip to content

Commit acd97e9

Browse files
author
Thomas Ville
committed
[rust] support discriminators on existing fields
1 parent 4f9f14a commit acd97e9

File tree

110 files changed

+2553
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

110 files changed

+2553
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
generatorName: rust
2+
outputDir: samples/client/others/rust/reqwest/oneOf-discriminator
3+
library: reqwest
4+
inputSpec: modules/openapi-generator/src/test/resources/3_0/oneOfDiscriminator.yaml
5+
templateDir: modules/openapi-generator/src/main/resources/rust
6+
additionalProperties:
7+
supportAsync: false
8+
packageName: oneof-discriminator-reqwest

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

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,20 @@ public CodegenModel fromModel(String name, Schema model) {
318318
return mdl;
319319
}
320320

321+
@Override
322+
public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs) {
323+
// Collect all models into a single list
324+
List<ModelMap> allModels = new ArrayList<>();
325+
for (ModelsMap models : objs.values()) {
326+
allModels.addAll(models.getModels());
327+
}
328+
329+
// Process oneOf discriminators across all models
330+
postProcessOneOfModels(allModels);
331+
332+
return super.postProcessAllModels(objs);
333+
}
334+
321335
@Override
322336
public ModelsMap postProcessModels(ModelsMap objs) {
323337
for (ModelMap model : objs.getModels()) {
@@ -666,6 +680,102 @@ public void postProcessModelProperty(CodegenModel model, CodegenProperty propert
666680
}
667681
}
668682

683+
private void postProcessOneOfModels(List<ModelMap> allModels) {
684+
final HashMap<String, List<String>> oneOfMapDiscriminator = new HashMap<>();
685+
686+
for (ModelMap mo : allModels) {
687+
final CodegenModel cm = mo.getModel();
688+
689+
final CodegenComposedSchemas cs = cm.getComposedSchemas();
690+
691+
if (cs != null) {
692+
final List<CodegenProperty> csOneOf = cs.getOneOf();
693+
694+
if (csOneOf != null) {
695+
for (CodegenProperty model : csOneOf) {
696+
// Generate a valid name for the enum variant.
697+
// Mainly needed for primitive types.
698+
String[] modelParts = model.dataType.replace("<", "Of").replace(">", "").split("::");
699+
model.datatypeWithEnum = StringUtils.camelize(modelParts[modelParts.length - 1]);
700+
701+
// Primitive type is not properly set, this overrides it to guarantee adequate model generation.
702+
if (!model.getDataType().matches(String.format(Locale.ROOT, ".*::%s", model.getDatatypeWithEnum()))) {
703+
model.isPrimitiveType = true;
704+
}
705+
}
706+
707+
cs.setOneOf(csOneOf);
708+
cm.setComposedSchemas(cs);
709+
}
710+
}
711+
712+
if (cm.discriminator != null) {
713+
for (String model : cm.oneOf) {
714+
List<String> discriminators = oneOfMapDiscriminator.getOrDefault(model, new ArrayList<>());
715+
discriminators.add(cm.discriminator.getPropertyBaseName());
716+
oneOfMapDiscriminator.put(model, discriminators);
717+
}
718+
}
719+
}
720+
721+
for (ModelMap mo : allModels) {
722+
final CodegenModel cm = mo.getModel();
723+
724+
for (CodegenProperty var : cm.vars) {
725+
var.isDiscriminator = false;
726+
}
727+
728+
final List<String> discriminatorsForModel = oneOfMapDiscriminator.get(cm.getSchemaName());
729+
730+
if (discriminatorsForModel != null) {
731+
for (String discriminator : discriminatorsForModel) {
732+
boolean hasDiscriminatorDefined = false;
733+
734+
for (CodegenProperty var : cm.vars) {
735+
if (var.baseName.equals(discriminator)) {
736+
var.isDiscriminator = true;
737+
hasDiscriminatorDefined = true;
738+
break;
739+
}
740+
}
741+
742+
// If the discriminator field is not a defined attribute in the variant structure, create it.
743+
if (!hasDiscriminatorDefined) {
744+
CodegenProperty property = new CodegenProperty();
745+
746+
// Static attributes
747+
// Only strings are supported by serde for tag field types, so it's the only one we'll deal with
748+
property.openApiType = "string";
749+
property.complexType = "string";
750+
property.dataType = "String";
751+
property.datatypeWithEnum = "String";
752+
property.baseType = "string";
753+
property.required = true;
754+
property.isPrimitiveType = true;
755+
property.isString = true;
756+
property.isDiscriminator = true;
757+
758+
// Attributes based on the discriminator value
759+
property.baseName = discriminator;
760+
property.name = discriminator;
761+
property.nameInCamelCase = StringUtils.camelize(discriminator);
762+
property.nameInPascalCase = property.nameInCamelCase.substring(0, 1).toUpperCase(Locale.ROOT) + property.nameInCamelCase.substring(1);
763+
property.nameInSnakeCase = StringUtils.underscore(discriminator).toUpperCase(Locale.ROOT);
764+
property.getter = String.format(Locale.ROOT, "get%s", property.nameInPascalCase);
765+
property.setter = String.format(Locale.ROOT, "set%s", property.nameInPascalCase);
766+
property.defaultValueWithParam = String.format(Locale.ROOT, " = data.%s;", property.name);
767+
768+
// Attributes based on the model name
769+
property.defaultValue = String.format(Locale.ROOT, "r#\"%s\"#.to_string()", cm.getSchemaName());
770+
property.jsonSchema = String.format(Locale.ROOT, "{ \"default\":\"%s\"; \"type\":\"string\" }", cm.getSchemaName());
771+
772+
cm.vars.add(property);
773+
}
774+
}
775+
}
776+
}
777+
}
778+
669779
@Override
670780
public void postProcessParameter(CodegenParameter parameter) {
671781
super.postProcessParameter(parameter);

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ pub struct {{{classname}}} {
131131
{{#isByteArray}}
132132
{{#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+
{{#isDiscriminator}}
135+
#[serde(default = "{{{classname}}}::_name_for_{{#lambda.camelcase}}{{{name}}}{{/lambda.camelcase}}")]
136+
#[serde(serialize_with = "{{{classname}}}::_serialize_{{#lambda.camelcase}}{{{name}}}{{/lambda.camelcase}}")]
137+
{{/isDiscriminator}}
134138
#[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}})]
135139
pub {{{name}}}: {{!
136140
### Option Start
@@ -172,6 +176,22 @@ impl {{{classname}}} {
172176
}
173177
}
174178
}
179+
{{#vars}}
180+
{{#isDiscriminator}}
181+
182+
impl {{{classname}}} {
183+
fn _name_for_{{#lambda.camelcase}}{{{name}}}{{/lambda.camelcase}}() -> String {
184+
{{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}String::from("{{classname}}"){{/defaultValue}}
185+
}
186+
fn _serialize_{{#lambda.camelcase}}{{{name}}}{{/lambda.camelcase}}<S>(_: &String, s: S) -> Result<S::Ok, S::Error>
187+
where
188+
S: serde::Serializer,
189+
{
190+
s.serialize_str(&Self::_name_for_{{#lambda.camelcase}}{{{name}}}{{/lambda.camelcase}}())
191+
}
192+
}
193+
{{/isDiscriminator}}
194+
{{/vars}}
175195
{{/oneOf.isEmpty}}
176196
{{^oneOf.isEmpty}}
177197
{{! TODO: add other vars that are not part of the oneOf}}

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,3 +298,29 @@ components:
298298
- $ref: '#/components/schemas/FruitType'
299299
discriminator:
300300
propertyName: fruitType
301+
AppleReqDiscReservedKeyword:
302+
type: object
303+
required:
304+
- seeds
305+
- type
306+
properties:
307+
seeds:
308+
type: integer
309+
type:
310+
type: string
311+
BananaReqDiscReservedKeyword:
312+
type: object
313+
required:
314+
- length
315+
- type
316+
properties:
317+
length:
318+
type: integer
319+
type:
320+
type: string
321+
FruitOneOfDiscReservedKeyword:
322+
oneOf:
323+
- $ref: '#/components/schemas/AppleReqDiscReservedKeyword'
324+
- $ref: '#/components/schemas/BananaReqDiscReservedKeyword'
325+
discriminator:
326+
propertyName: type

samples/client/others/rust/hyper/composed-oneof/src/models/obj_a.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use serde::{Deserialize, Serialize};
1313

1414
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
1515
pub struct ObjA {
16+
#[serde(default = "ObjA::_name_for_realtype")]
17+
#[serde(serialize_with = "ObjA::_serialize_realtype")]
1618
#[serde(rename = "realtype", skip_serializing_if = "Option::is_none")]
1719
pub realtype: Option<String>,
1820
#[serde(rename = "message", skip_serializing_if = "Option::is_none")]
@@ -28,3 +30,15 @@ impl ObjA {
2830
}
2931
}
3032

33+
impl ObjA {
34+
fn _name_for_realtype() -> String {
35+
String::from("ObjA")
36+
}
37+
fn _serialize_realtype<S>(_: &String, s: S) -> Result<S::Ok, S::Error>
38+
where
39+
S: serde::Serializer,
40+
{
41+
s.serialize_str(&Self::_name_for_realtype())
42+
}
43+
}
44+

samples/client/others/rust/hyper/composed-oneof/src/models/obj_b.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use serde::{Deserialize, Serialize};
1313

1414
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
1515
pub struct ObjB {
16+
#[serde(default = "ObjB::_name_for_realtype")]
17+
#[serde(serialize_with = "ObjB::_serialize_realtype")]
1618
#[serde(rename = "realtype", skip_serializing_if = "Option::is_none")]
1719
pub realtype: Option<String>,
1820
#[serde(rename = "description", skip_serializing_if = "Option::is_none")]
@@ -31,3 +33,15 @@ impl ObjB {
3133
}
3234
}
3335

36+
impl ObjB {
37+
fn _name_for_realtype() -> String {
38+
String::from("ObjB")
39+
}
40+
fn _serialize_realtype<S>(_: &String, s: S) -> Result<S::Ok, S::Error>
41+
where
42+
S: serde::Serializer,
43+
{
44+
s.serialize_str(&Self::_name_for_realtype())
45+
}
46+
}
47+

samples/client/others/rust/hyper/composed-oneof/src/models/obj_c.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use serde::{Deserialize, Serialize};
1313

1414
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
1515
pub struct ObjC {
16+
#[serde(default = "ObjC::_name_for_realtype")]
17+
#[serde(serialize_with = "ObjC::_serialize_realtype")]
1618
#[serde(rename = "realtype", skip_serializing_if = "Option::is_none")]
1719
pub realtype: Option<String>,
1820
#[serde(rename = "state", skip_serializing_if = "Option::is_none")]
@@ -28,3 +30,15 @@ impl ObjC {
2830
}
2931
}
3032

33+
impl ObjC {
34+
fn _name_for_realtype() -> String {
35+
String::from("ObjC")
36+
}
37+
fn _serialize_realtype<S>(_: &String, s: S) -> Result<S::Ok, S::Error>
38+
where
39+
S: serde::Serializer,
40+
{
41+
s.serialize_str(&Self::_name_for_realtype())
42+
}
43+
}
44+

samples/client/others/rust/hyper/composed-oneof/src/models/obj_d.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use serde::{Deserialize, Serialize};
1313

1414
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
1515
pub struct ObjD {
16+
#[serde(default = "ObjD::_name_for_realtype")]
17+
#[serde(serialize_with = "ObjD::_serialize_realtype")]
1618
#[serde(rename = "realtype", skip_serializing_if = "Option::is_none")]
1719
pub realtype: Option<String>,
1820
#[serde(rename = "color", skip_serializing_if = "Option::is_none")]
@@ -28,3 +30,15 @@ impl ObjD {
2830
}
2931
}
3032

33+
impl ObjD {
34+
fn _name_for_realtype() -> String {
35+
String::from("ObjD")
36+
}
37+
fn _serialize_realtype<S>(_: &String, s: S) -> Result<S::Ok, S::Error>
38+
where
39+
S: serde::Serializer,
40+
{
41+
s.serialize_str(&Self::_name_for_realtype())
42+
}
43+
}
44+

samples/client/others/rust/hyper/oneOf-reuseRef/docs/Apple.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Name | Type | Description | Notes
66
------------ | ------------- | ------------- | -------------
77
**cultivar** | Option<**String**> | | [optional]
88
**origin** | Option<**String**> | | [optional]
9+
**fruitType** | **String** | | [default to r#"Apple"#.to_string()]
910

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

samples/client/others/rust/hyper/oneOf-reuseRef/docs/Banana.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
Name | Type | Description | Notes
66
------------ | ------------- | ------------- | -------------
77
**length_cm** | Option<**f64**> | | [optional]
8+
**fruitType** | **String** | | [default to r#"Banana"#.to_string()]
89

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

0 commit comments

Comments
 (0)