diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index 9797042ed9..c7345742b6 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -62,6 +62,7 @@ import org.springframework.context.annotation.Profile; import org.springframework.http.HttpStatus; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.ai.model.chat.AiChatModelConfig; import org.thingsboard.server.common.data.cf.CalculatedField; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.exception.ThingsboardCredentialsExpiredResponse; @@ -77,6 +78,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Deque; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -361,7 +363,8 @@ public class SwaggerConfiguration { // (e.g. CalculatedField via EntityExportData.calculatedFields) to prevent // field-level ignore lists from polluting the global schema when resolution // order varies. - .addSchemas("CalculatedField", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(CalculatedField.class)).schema); + .addSchemas("CalculatedField", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(CalculatedField.class)).schema) + .addSchemas("AiChatModelConfig", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(AiChatModelConfig.class)).schema); } private OperationCustomizer operationCustomizer() { @@ -432,20 +435,40 @@ public class SwaggerConfiguration { log.debug("Removed duplicate schema '{}' (base '{}' exists)", name, baseName); } - // Remove duplicate inline entries in allOf (springdoc can generate identical - // property blocks when resolving a type through multiple parent paths). + // Remove duplicate or redundant inline entries in allOf. Springdoc can + // generate multiple inline property blocks when resolving a type through + // multiple parent paths (e.g. record + sealed interface). One block may be + // a strict subset of another (same properties, but the superset has extras + // like "modelType"). Keep only the superset in that case. schemas.values().forEach(schema -> { if (schema.getAllOf() != null && schema.getAllOf().size() > 1) { List allOf = schema.getAllOf(); - List deduplicated = new ArrayList<>(); - for (Schema entry : allOf) { - if (deduplicated.stream().noneMatch(entry::equals)) { - deduplicated.add(entry); + Set redundant = new HashSet<>(); + for (int i = 0; i < allOf.size(); i++) { + if (redundant.contains(i)) continue; + Schema a = allOf.get(i); + if (a.get$ref() != null || a.getProperties() == null) continue; + for (int j = i + 1; j < allOf.size(); j++) { + if (redundant.contains(j)) continue; + Schema b = allOf.get(j); + if (b.get$ref() != null || b.getProperties() == null) continue; + if (a.getProperties().entrySet().containsAll(b.getProperties().entrySet())) { + redundant.add(j); // b is a subset of a + } else if (b.getProperties().entrySet().containsAll(a.getProperties().entrySet())) { + redundant.add(i); // a is a subset of b + break; + } } } - if (deduplicated.size() < allOf.size()) { + if (!redundant.isEmpty()) { + List filtered = new ArrayList<>(); + for (int i = 0; i < allOf.size(); i++) { + if (!redundant.contains(i)) { + filtered.add(allOf.get(i)); + } + } allOf.clear(); - allOf.addAll(deduplicated); + allOf.addAll(filtered); } } });