Skip to content

Commit 349353d

Browse files
committed
[OCaml] Fix bugs around enum parsing
Problem: The OCaml client generator threw IllegalArgumentException: Unreferenced enum when encountering enums inside composed schemas (anyOf/allOf/oneOf). Root Causes: 1. The enum collection logic didn't traverse into composed schemas 2. The enum hashing used order-dependent string concatenation, causing lookups to fail when enum values appeared in different orders 3. Enums directly within composed schema branches (not in properties) weren't collected Solution: 1. Added composed schema support: - New `collectEnumSchemasFromComposed()` method handles `anyOf/allOf/oneOf` - New `collectEnumSchemasFromList()` method recursively processes composed schema branches - Enums directly in composed schemas (not just in properties) are now collected 2. Refactored enum hashing to use Set: - Changed from comma-joined strings to `TreeSet<String>` for order-independent, collision-free hashing - Handles edge cases like empty string enums `""` 3. Added test case: - Tests enums in nested composed schemas - Tests enum with empty string value in anyOf
1 parent 3b61324 commit 349353d

1 file changed

Lines changed: 58 additions & 13 deletions

File tree

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

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ public class OCamlClientCodegen extends DefaultCodegen implements CodegenConfig
5858
protected String apiFolder = "src/apis";
5959
protected String modelFolder = "src/models";
6060

61-
private Map<String, List<String>> enumNames = new HashMap<>();
62-
private Map<String, Schema> enumHash = new HashMap<>();
63-
private Map<String, String> enumUniqNames;
61+
private Map<Set<String>, List<String>> enumNames = new HashMap<>();
62+
private Map<Set<String>, Schema> enumHash = new HashMap<>();
63+
private Map<Set<String>, String> enumUniqNames;
6464

6565
@Override
6666
public CodegenType getTag() {
@@ -276,8 +276,10 @@ protected void updateDataTypeWithEnumForArray(CodegenProperty property) {
276276
}
277277

278278
@SuppressWarnings("unchecked")
279-
private String hashEnum(Schema schema) {
280-
return ((List<Object>) schema.getEnum()).stream().map(String::valueOf).collect(Collectors.joining(","));
279+
private Set<String> hashEnum(Schema schema) {
280+
return ((List<Object>) schema.getEnum()).stream()
281+
.map(String::valueOf)
282+
.collect(Collectors.toCollection(TreeSet::new));
281283
}
282284

283285
private boolean isEnumSchema(Schema schema) {
@@ -290,7 +292,7 @@ private void collectEnumSchemas(String parentName, String sName, Schema schema)
290292
} else if (ModelUtils.isMapSchema(schema) && schema.getAdditionalProperties() instanceof Schema) {
291293
collectEnumSchemas(parentName, sName, (Schema) schema.getAdditionalProperties());
292294
} else if (isEnumSchema(schema)) {
293-
String h = hashEnum(schema);
295+
Set<String> h = hashEnum(schema);
294296
if (!enumHash.containsKey(h)) {
295297
enumHash.put(h, schema);
296298
enumNames.computeIfAbsent(h, k -> new ArrayList<>()).add(sName.toLowerCase(Locale.ROOT));
@@ -299,6 +301,8 @@ private void collectEnumSchemas(String parentName, String sName, Schema schema)
299301
}
300302
}
301303
}
304+
// Note: Composed schemas (anyOf, allOf, oneOf) are handled in the Map-based method
305+
// via collectEnumSchemasFromComposed() which properly processes their structure
302306
}
303307

304308
private void collectEnumSchemas(String sName, Schema schema) {
@@ -327,6 +331,47 @@ private void collectEnumSchemas(String parentName, Map<String, Schema> schemas)
327331
collectEnumSchemas(pName, ModelUtils.getSchemaItems(schema));
328332
}
329333
}
334+
335+
// Handle composed schemas (anyOf, allOf, oneOf) - recursively process their structure
336+
collectEnumSchemasFromComposed(pName, schema);
337+
}
338+
}
339+
340+
private void collectEnumSchemasFromComposed(String parentName, Schema schema) {
341+
if (schema.getAnyOf() != null) {
342+
collectEnumSchemasFromList(parentName, schema.getAnyOf());
343+
}
344+
345+
if (schema.getAllOf() != null) {
346+
collectEnumSchemasFromList(parentName, schema.getAllOf());
347+
}
348+
349+
if (schema.getOneOf() != null) {
350+
collectEnumSchemasFromList(parentName, schema.getOneOf());
351+
}
352+
}
353+
354+
private void collectEnumSchemasFromList(String parentName, List<Schema> schemas) {
355+
int index = 0;
356+
for (Schema composedSchema : schemas) {
357+
// Check if the composed schema itself is an enum
358+
if (isEnumSchema(composedSchema)) {
359+
String enumName = composedSchema.getName() != null ? composedSchema.getName() : "any_of_" + index;
360+
collectEnumSchemas(parentName, enumName, composedSchema);
361+
}
362+
363+
if (composedSchema.getProperties() != null) {
364+
collectEnumSchemas(parentName, composedSchema.getProperties());
365+
}
366+
if (composedSchema.getAdditionalProperties() != null && composedSchema.getAdditionalProperties() instanceof Schema) {
367+
collectEnumSchemas(parentName, composedSchema.getName(), (Schema) composedSchema.getAdditionalProperties());
368+
}
369+
if (ModelUtils.isArraySchema(composedSchema) && ModelUtils.getSchemaItems(composedSchema) != null) {
370+
collectEnumSchemas(parentName, composedSchema.getName(), ModelUtils.getSchemaItems(composedSchema));
371+
}
372+
// Recursively handle nested composed schemas
373+
collectEnumSchemasFromComposed(parentName, composedSchema);
374+
index++;
330375
}
331376
}
332377

@@ -378,8 +423,8 @@ private String sanitizeOCamlTypeName(String name) {
378423
}
379424

380425
private void computeEnumUniqNames() {
381-
Map<String, String> definitiveNames = new HashMap<>();
382-
for (String h : enumNames.keySet()) {
426+
Map<String, Set<String>> definitiveNames = new HashMap<>();
427+
for (Set<String> h : enumNames.keySet()) {
383428
boolean hasDefName = false;
384429
List<String> nameCandidates = enumNames.get(h);
385430
for (String name : nameCandidates) {
@@ -600,13 +645,13 @@ public String getTypeDeclaration(Schema p) {
600645
String prefix = inner.getEnum() != null ? "Enums." : "";
601646
return "(string * " + prefix + getTypeDeclaration(inner) + ") list";
602647
} else if (p.getEnum() != null) {
603-
String h = hashEnum(p);
648+
Set<String> h = hashEnum(p);
604649
return enumUniqNames.get(h);
605650
}
606651

607652
Schema referencedSchema = ModelUtils.getReferencedSchema(openAPI, p);
608653
if (referencedSchema != null && referencedSchema.getEnum() != null) {
609-
String h = hashEnum(referencedSchema);
654+
Set<String> h = hashEnum(referencedSchema);
610655
return "Enums." + enumUniqNames.get(h);
611656
}
612657

@@ -739,8 +784,8 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<Mo
739784
}
740785
}
741786

742-
for (Map.Entry<String, String> e : enumUniqNames.entrySet()) {
743-
allModels.add(buildEnumModelWrapper(e.getValue(), e.getKey()));
787+
for (Map.Entry<Set<String>, String> e : enumUniqNames.entrySet()) {
788+
allModels.add(buildEnumModelWrapper(e.getValue(), String.join(",", e.getKey())));
744789
}
745790

746791
enumUniqNames.clear();
@@ -770,7 +815,7 @@ public String escapeUnsafeCharacters(String input) {
770815

771816
@Override
772817
public String toEnumName(CodegenProperty property) {
773-
String hash = String.join(",", property.get_enum());
818+
Set<String> hash = new TreeSet<>(property.get_enum());
774819

775820
if (enumUniqNames.containsKey(hash)) {
776821
return enumUniqNames.get(hash);

0 commit comments

Comments
 (0)