Skip to content

Commit 30096d6

Browse files
authored
Revert "fix(rust): Add anyOf support to Rust client generator (#21896)" (#22038)
This reverts commit ee40887.
1 parent 44a3be1 commit 30096d6

File tree

4 files changed

+17
-220
lines changed

4 files changed

+17
-220
lines changed

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

Lines changed: 0 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -307,69 +307,6 @@ public CodegenModel fromModel(String name, Schema model) {
307307
mdl.getComposedSchemas().setOneOf(newOneOfs);
308308
}
309309

310-
// Handle anyOf schemas similarly to oneOf
311-
// This is pragmatic since Rust's untagged enum will deserialize to the first matching variant
312-
if (mdl.getComposedSchemas() != null && mdl.getComposedSchemas().getAnyOf() != null
313-
&& !mdl.getComposedSchemas().getAnyOf().isEmpty()) {
314-
315-
List<CodegenProperty> newAnyOfs = mdl.getComposedSchemas().getAnyOf().stream()
316-
.map(CodegenProperty::clone)
317-
.collect(Collectors.toList());
318-
List<Schema> schemas = ModelUtils.getInterfaces(model);
319-
if (newAnyOfs.size() != schemas.size()) {
320-
// For safety reasons, this should never happen unless there is an error in the code
321-
throw new RuntimeException("anyOf size does not match the model");
322-
}
323-
324-
Map<String, String> refsMapping = Optional.ofNullable(model.getDiscriminator())
325-
.map(Discriminator::getMapping).orElse(Collections.emptyMap());
326-
327-
// Reverse mapped references to use as baseName for anyOf, but different keys may point to the same $ref.
328-
// Thus, we group them by the value
329-
Map<String, List<String>> mappedNamesByRef = refsMapping.entrySet().stream()
330-
.collect(Collectors.groupingBy(Map.Entry::getValue,
331-
Collectors.mapping(Map.Entry::getKey, Collectors.toList())
332-
));
333-
334-
for (int i = 0; i < newAnyOfs.size(); i++) {
335-
CodegenProperty anyOf = newAnyOfs.get(i);
336-
Schema schema = schemas.get(i);
337-
338-
if (mappedNamesByRef.containsKey(schema.get$ref())) {
339-
// prefer mapped names if present
340-
// remove mapping not in order not to reuse for the next occurrence of the ref
341-
List<String> names = mappedNamesByRef.get(schema.get$ref());
342-
String mappedName = names.remove(0);
343-
anyOf.setBaseName(mappedName);
344-
anyOf.setName(toModelName(mappedName));
345-
} else if (!org.apache.commons.lang3.StringUtils.isEmpty(schema.get$ref())) {
346-
// use $ref if it's reference
347-
String refName = ModelUtils.getSimpleRef(schema.get$ref());
348-
if (refName != null) {
349-
String modelName = toModelName(refName);
350-
anyOf.setName(modelName);
351-
anyOf.setBaseName(refName);
352-
}
353-
} else if (anyOf.isArray) {
354-
// If the type is an array, extend the name with the inner type to prevent name collisions
355-
// in case multiple arrays with different types are defined. If the user has manually specified
356-
// a name, use that name instead.
357-
String collectionWithTypeName = toModelName(schema.getType()) + anyOf.containerTypeMapped + anyOf.items.dataType;
358-
String anyOfName = Optional.ofNullable(schema.getTitle()).orElse(collectionWithTypeName);
359-
anyOf.setName(anyOfName);
360-
}
361-
else {
362-
// In-placed type (primitive), because there is no mapping or ref for it.
363-
// use camelized `title` if present, otherwise use `type`
364-
String anyOfName = Optional.ofNullable(schema.getTitle()).orElseGet(schema::getType);
365-
anyOf.setName(toModelName(anyOfName));
366-
}
367-
}
368-
369-
// Set anyOf as oneOf for template processing since we want the same output
370-
mdl.getComposedSchemas().setOneOf(newAnyOfs);
371-
}
372-
373310
return mdl;
374311
}
375312

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

Lines changed: 17 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -121,40 +121,8 @@ impl Default for {{classname}} {
121121
{{!-- for non-enum schemas --}}
122122
{{^isEnum}}
123123
{{^discriminator}}
124-
{{#composedSchemas}}
125-
{{#oneOf}}
126-
{{#-first}}
127-
{{! Model with composedSchemas.oneOf - generate enum}}
128-
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
129-
#[serde(untagged)]
130-
pub enum {{classname}} {
131-
{{/-first}}
132-
{{/oneOf}}
133-
{{/composedSchemas}}
134-
{{#composedSchemas}}
135-
{{#oneOf}}
136-
{{#description}}
137-
/// {{{.}}}
138-
{{/description}}
139-
{{{name}}}({{#isModel}}{{^avoidBoxedModels}}Box<{{/avoidBoxedModels}}{{/isModel}}{{{dataType}}}{{#isModel}}{{^avoidBoxedModels}}>{{/avoidBoxedModels}}{{/isModel}}),
140-
{{/oneOf}}
141-
{{/composedSchemas}}
142-
{{#composedSchemas}}
143-
{{#oneOf}}
144-
{{#-last}}
145-
}
146-
147-
impl Default for {{classname}} {
148-
fn default() -> Self {
149-
{{#oneOf}}{{#-first}}Self::{{{name}}}(Default::default()){{/-first}}{{/oneOf}}
150-
}
151-
}
152-
{{/-last}}
153-
{{/oneOf}}
154-
{{^oneOf}}
155-
{{! composedSchemas exists but no oneOf - generate normal struct}}
156124
{{#vendorExtensions.x-rust-has-byte-array}}#[serde_as]
157-
{{/vendorExtensions.x-rust-has-byte-array}}#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
125+
{{/vendorExtensions.x-rust-has-byte-array}}{{#oneOf.isEmpty}}#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
158126
pub struct {{{classname}}} {
159127
{{#vars}}
160128
{{#description}}
@@ -204,62 +172,29 @@ impl {{{classname}}} {
204172
}
205173
}
206174
}
207-
{{/oneOf}}
208-
{{/composedSchemas}}
209-
{{^composedSchemas}}
210-
{{! Normal struct without composedSchemas}}
211-
{{#vendorExtensions.x-rust-has-byte-array}}#[serde_as]
212-
{{/vendorExtensions.x-rust-has-byte-array}}#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
213-
pub struct {{{classname}}} {
214-
{{#vars}}
175+
{{/oneOf.isEmpty}}
176+
{{^oneOf.isEmpty}}
177+
{{! TODO: add other vars that are not part of the oneOf}}
178+
{{#description}}
179+
/// {{{.}}}
180+
{{/description}}
181+
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
182+
#[serde(untagged)]
183+
pub enum {{classname}} {
184+
{{#composedSchemas.oneOf}}
215185
{{#description}}
216186
/// {{{.}}}
217187
{{/description}}
218-
{{#isByteArray}}
219-
{{#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}}
220-
{{/isByteArray}}
221-
#[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}})]
222-
pub {{{name}}}: {{!
223-
### Option Start
224-
}}{{#isNullable}}Option<{{/isNullable}}{{^required}}Option<{{/required}}{{!
225-
### Enums
226-
}}{{#isEnum}}{{#isArray}}{{#uniqueItems}}std::collections::HashSet<{{/uniqueItems}}{{^uniqueItems}}Vec<{{/uniqueItems}}{{/isArray}}{{{enumName}}}{{#isArray}}>{{/isArray}}{{/isEnum}}{{!
227-
### Non-Enums Start
228-
}}{{^isEnum}}{{!
229-
### Models
230-
}}{{#isModel}}{{^avoidBoxedModels}}Box<{{/avoidBoxedModels}}{{{dataType}}}{{^avoidBoxedModels}}>{{/avoidBoxedModels}}{{/isModel}}{{!
231-
### Primative datatypes
232-
}}{{^isModel}}{{#isByteArray}}Vec<u8>{{/isByteArray}}{{^isByteArray}}{{{dataType}}}{{/isByteArray}}{{/isModel}}{{!
233-
### Non-Enums End
234-
}}{{/isEnum}}{{!
235-
### Option End (and trailing comma)
236-
}}{{#isNullable}}>{{/isNullable}}{{^required}}>{{/required}},
237-
{{/vars}}
188+
{{{name}}}({{#isModel}}{{^avoidBoxedModels}}Box<{{/avoidBoxedModels}}{{/isModel}}{{{dataType}}}{{#isModel}}{{^avoidBoxedModels}}>{{/avoidBoxedModels}}{{/isModel}}),
189+
{{/composedSchemas.oneOf}}
238190
}
239191
240-
impl {{{classname}}} {
241-
{{#description}}
242-
/// {{{.}}}
243-
{{/description}}
244-
pub fn new({{#requiredVars}}{{{name}}}: {{!
245-
### Option Start
246-
}}{{#isNullable}}Option<{{/isNullable}}{{!
247-
### Enums
248-
}}{{#isEnum}}{{#isArray}}{{#uniqueItems}}std::collections::HashSet<{{/uniqueItems}}{{^uniqueItems}}Vec<{{/uniqueItems}}{{/isArray}}{{{enumName}}}{{#isArray}}>{{/isArray}}{{/isEnum}}{{!
249-
### Non-Enums
250-
}}{{^isEnum}}{{#isByteArray}}Vec<u8>{{/isByteArray}}{{^isByteArray}}{{{dataType}}}{{/isByteArray}}{{/isEnum}}{{!
251-
### Option End
252-
}}{{#isNullable}}>{{/isNullable}}{{!
253-
### Comma for next arguement
254-
}}{{^-last}}, {{/-last}}{{/requiredVars}}) -> {{{classname}}} {
255-
{{{classname}}} {
256-
{{#vars}}
257-
{{{name}}}{{^required}}: None{{/required}}{{#required}}{{#isModel}}{{^avoidBoxedModels}}: {{^isNullable}}Box::new({{{name}}}){{/isNullable}}{{#isNullable}}if let Some(x) = {{{name}}} {Some(Box::new(x))} else {None}{{/isNullable}}{{/avoidBoxedModels}}{{/isModel}}{{/required}},
258-
{{/vars}}
259-
}
192+
impl Default for {{classname}} {
193+
fn default() -> Self {
194+
{{#composedSchemas.oneOf}}{{#-first}}Self::{{{name}}}(Default::default()){{/-first}}{{/composedSchemas.oneOf}}
260195
}
261196
}
262-
{{/composedSchemas}}
197+
{{/oneOf.isEmpty}}
263198
{{/discriminator}}
264199
{{/isEnum}}
265200
{{!-- for properties that are of enum type --}}

modules/openapi-generator/src/test/java/org/openapitools/codegen/rust/RustClientCodegenTest.java

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -271,37 +271,4 @@ public void testMultipleArrayTypesEnum() throws IOException {
271271
TestUtils.assertFileExists(outputPath);
272272
TestUtils.assertFileContains(outputPath, enumSpec);
273273
}
274-
275-
@Test
276-
public void testAnyOfSupport() throws IOException {
277-
Path target = Files.createTempDirectory("test-anyof");
278-
final CodegenConfigurator configurator = new CodegenConfigurator()
279-
.setGeneratorName("rust")
280-
.setInputSpec("src/test/resources/3_0/rust/rust-anyof-test.yaml")
281-
.setSkipOverwrite(false)
282-
.setOutputDir(target.toAbsolutePath().toString().replace("\\", "/"));
283-
List<File> files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate();
284-
files.forEach(File::deleteOnExit);
285-
286-
// Test that ModelIdentifier generates an untagged enum, not an empty struct
287-
Path modelIdentifierPath = Path.of(target.toString(), "/src/models/model_identifier.rs");
288-
TestUtils.assertFileExists(modelIdentifierPath);
289-
290-
// Should generate an untagged enum
291-
TestUtils.assertFileContains(modelIdentifierPath, "#[serde(untagged)]");
292-
TestUtils.assertFileContains(modelIdentifierPath, "pub enum ModelIdentifier");
293-
294-
// Should have String variant (for anyOf with string types)
295-
TestUtils.assertFileContains(modelIdentifierPath, "String(String)");
296-
297-
// Should NOT generate an empty struct
298-
TestUtils.assertFileNotContains(modelIdentifierPath, "pub struct ModelIdentifier {");
299-
TestUtils.assertFileNotContains(modelIdentifierPath, "pub fn new()");
300-
301-
// Test AnotherAnyOfTest with mixed types
302-
Path anotherTestPath = Path.of(target.toString(), "/src/models/another_any_of_test.rs");
303-
TestUtils.assertFileExists(anotherTestPath);
304-
TestUtils.assertFileContains(anotherTestPath, "#[serde(untagged)]");
305-
TestUtils.assertFileContains(anotherTestPath, "pub enum AnotherAnyOfTest");
306-
}
307274
}

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

Lines changed: 0 additions & 42 deletions
This file was deleted.

0 commit comments

Comments
 (0)