Skip to content

Commit ef3f19e

Browse files
committed
feat(rust): Add anyOf support to Rust client generator
This commit adds support for anyOf schemas in the Rust client generator by treating them similarly to oneOf schemas, generating untagged enums instead of empty structs. The implementation reuses the existing oneOf logic since Rust's serde untagged enum will deserialize to the first matching variant, which aligns well with anyOf semantics where one or more schemas must match. Fixes the issue where anyOf schemas would generate empty unusable structs.
1 parent 27ed27f commit ef3f19e

File tree

1 file changed

+63
-0
lines changed

1 file changed

+63
-0
lines changed

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

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,69 @@ 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+
310373
return mdl;
311374
}
312375

0 commit comments

Comments
 (0)