Skip to content

Commit ff5bdf8

Browse files
committed
feat: enhance array variant resolution in ParametersWithDataType
Rewrite array variant resolution for oneOf types: handle multiple array variants by scoring item models against sample data (required-field + property-counting heuristic). New methods: findBestArrayVariant(), scoreOneOfModelMatch(), scoreDirectModelMatch().
1 parent 5974f10 commit ff5bdf8

1 file changed

Lines changed: 100 additions & 4 deletions

File tree

generators/src/main/java/com/algolia/codegen/cts/tests/ParametersWithDataType.java

Lines changed: 100 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -719,12 +719,26 @@ private IJsonSchemaValidationProperties findMatchingOneOf(Object param, CodegenM
719719
return bestOneOf;
720720
}
721721

722-
for (CodegenProperty prop : model.getComposedSchemas().getOneOf()) {
723-
// find the correct list
724-
if (param instanceof List && prop.getIsArray()) {
725-
return prop;
722+
if (param instanceof List) {
723+
List<CodegenProperty> arrayVariants = model.getComposedSchemas().getOneOf().stream().filter(CodegenProperty::getIsArray).toList();
724+
725+
if (arrayVariants.size() == 1) {
726+
// Single array variant - return it directly (existing behavior)
727+
return arrayVariants.get(0);
726728
}
727729

730+
if (arrayVariants.size() > 1) {
731+
// Multiple array variants - inspect first element to disambiguate
732+
List<?> list = (List<?>) param;
733+
if (!list.isEmpty() && list.get(0) instanceof Map) {
734+
return findBestArrayVariant(arrayVariants, (Map<String, Object>) list.get(0));
735+
}
736+
// Empty list or non-Map elements - fall back to first
737+
return arrayVariants.get(0);
738+
}
739+
}
740+
741+
for (CodegenProperty prop : model.getComposedSchemas().getOneOf()) {
728742
// find the correct enum
729743
if (param instanceof String && prop.getIsEnumOrRef() && couldMatchEnum(param, prop)) {
730744
return prop;
@@ -765,6 +779,88 @@ private IJsonSchemaValidationProperties findMatchingOneOf(Object param, CodegenM
765779
return maybeMatch;
766780
}
767781

782+
/**
783+
* When a oneOf has multiple array variants, score each variant by checking how well its items
784+
* type matches a sample element from the data. Uses the same required-field and property-counting
785+
* heuristic as the Map-based oneOf resolution.
786+
*/
787+
@SuppressWarnings("unchecked")
788+
private CodegenProperty findBestArrayVariant(List<CodegenProperty> arrayVariants, Map<String, Object> sampleElement) {
789+
CodegenProperty bestVariant = arrayVariants.get(0);
790+
int bestScore = -1;
791+
792+
for (CodegenProperty variant : arrayVariants) {
793+
CodegenProperty items = variant.getItems();
794+
if (items == null) continue;
795+
796+
String itemTypeName = items.getDataType();
797+
if (itemTypeName == null) continue;
798+
799+
CodegenModel itemModel = models.get(itemTypeName);
800+
if (itemModel == null) {
801+
// Try lowercase first char
802+
String lower = itemTypeName.substring(0, 1).toLowerCase() + itemTypeName.substring(1);
803+
itemModel = models.get(lower);
804+
}
805+
if (itemModel == null) continue;
806+
807+
int score;
808+
if (!itemModel.oneOf.isEmpty() && itemModel.interfaceModels != null) {
809+
// Items type is itself a oneOf (e.g. MessageV4 = UserMessageV4 | AssistantMessageV4)
810+
// Find best sub-match score
811+
score = scoreOneOfModelMatch(sampleElement, itemModel);
812+
} else {
813+
// Items type is a direct model
814+
score = scoreDirectModelMatch(sampleElement, itemModel);
815+
}
816+
817+
if (score > bestScore) {
818+
bestScore = score;
819+
bestVariant = variant;
820+
}
821+
}
822+
823+
return bestVariant;
824+
}
825+
826+
/**
827+
* Score how well a Map matches against the best sub-variant of a oneOf model. Returns -1 if no
828+
* sub-variant matches (all have missing required fields).
829+
*/
830+
private int scoreOneOfModelMatch(Map<String, Object> data, CodegenModel oneOfModel) {
831+
int bestScore = -1;
832+
for (CodegenModel subVariant : oneOfModel.interfaceModels) {
833+
if (subVariant.vars.isEmpty()) continue;
834+
int score = scoreDirectModelMatch(data, subVariant);
835+
if (score > bestScore) {
836+
bestScore = score;
837+
}
838+
}
839+
return bestScore;
840+
}
841+
842+
/**
843+
* Score how well a Map matches a direct model: -1 if required fields are missing, otherwise the
844+
* count of common properties.
845+
*/
846+
private int scoreDirectModelMatch(Map<String, Object> data, CodegenModel model) {
847+
// If any required property is missing, this model doesn't match
848+
for (CodegenProperty prop : model.requiredVars) {
849+
if (!data.containsKey(prop.baseName)) {
850+
return -1;
851+
}
852+
}
853+
int commonCount = 0;
854+
for (String key : data.keySet()) {
855+
for (CodegenProperty prop : model.vars) {
856+
if (key.equals(prop.baseName) && couldMatchEnum(data.get(key), prop)) {
857+
commonCount++;
858+
}
859+
}
860+
}
861+
return commonCount;
862+
}
863+
768864
// If the model is an enum and contains a valid list of allowed values,
769865
// it will check that 'value' is in the list.
770866
// Otherwise return true by default to avoid false negative.

0 commit comments

Comments
 (0)