|
|
|
@ -62,7 +62,10 @@ 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.ContactBased; |
|
|
|
import org.thingsboard.server.common.data.cf.CalculatedField; |
|
|
|
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; |
|
|
|
import org.thingsboard.server.common.data.id.HasId; |
|
|
|
import org.thingsboard.server.exception.ThingsboardCredentialsExpiredResponse; |
|
|
|
import org.thingsboard.server.exception.ThingsboardErrorResponse; |
|
|
|
import org.thingsboard.server.service.security.auth.rest.LoginRequest; |
|
|
|
@ -355,7 +358,17 @@ public class SwaggerConfiguration { |
|
|
|
.addSchemas("LoginResponse", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(LoginResponse.class)).schema) |
|
|
|
.addSchemas("ThingsboardErrorResponse", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(ThingsboardErrorResponse.class)).schema) |
|
|
|
.addSchemas("ThingsboardCredentialsExpiredResponse", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(ThingsboardCredentialsExpiredResponse.class)).schema) |
|
|
|
.addSchemas("ThingsboardErrorCode", errorCodeSchema); |
|
|
|
.addSchemas("ThingsboardErrorCode", errorCodeSchema) |
|
|
|
// Pre-register types to prevent springdoc resolution-order issues:
|
|
|
|
// - Types referenced with @JsonIgnoreProperties on fields (e.g. CalculatedField
|
|
|
|
// via EntityExportData.calculatedFields) to prevent field-level ignore lists
|
|
|
|
// from polluting the global schema.
|
|
|
|
// - Intermediate interfaces/classes (e.g. ContactBased, HasId) that springdoc
|
|
|
|
// only creates as "*Object" byproducts; pre-registering the base name lets
|
|
|
|
// the duplicate removal pass clean them up.
|
|
|
|
.addSchemas("CalculatedField", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(CalculatedField.class)).schema) |
|
|
|
.addSchemas("ContactBased", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(ContactBased.class)).schema) |
|
|
|
.addSchemas("HasId", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(HasId.class)).schema); |
|
|
|
} |
|
|
|
|
|
|
|
private OperationCustomizer operationCustomizer() { |
|
|
|
@ -407,18 +420,14 @@ public class SwaggerConfiguration { |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
// Springdoc creates duplicate schemas with an "Object" suffix when a discriminated
|
|
|
|
// type (e.g. EntityExportData) is resolved through multiple paths: once via
|
|
|
|
// @Schema(implementation=...) and once via generic type resolution (e.g. from
|
|
|
|
// Map<..., List<EntityExportData<?>>>). The duplicate breaks allOf inheritance
|
|
|
|
// in generated clients. Remove it only when both schemas are structurally equal.
|
|
|
|
// Springdoc creates duplicate schemas with an "Object" suffix when a type is
|
|
|
|
// resolved through multiple inheritance paths or via generic type resolution.
|
|
|
|
// Remove the "*Object" duplicate when the base schema exists (either
|
|
|
|
// pre-registered in addDefaultSchemas or generated by springdoc).
|
|
|
|
for (String name : new ArrayList<>(schemas.keySet())) { |
|
|
|
if (!name.endsWith("Object")) continue; |
|
|
|
String baseName = name.substring(0, name.length() - "Object".length()); |
|
|
|
Schema<?> baseSchema = schemas.get(baseName); |
|
|
|
if (baseSchema == null) continue; |
|
|
|
Schema<?> objectSchema = schemas.get(name); |
|
|
|
if (!baseSchema.equals(objectSchema)) continue; |
|
|
|
if (!schemas.containsKey(baseName)) continue; |
|
|
|
|
|
|
|
schemas.remove(name); |
|
|
|
String refToRemove = "#/components/schemas/" + name; |
|
|
|
@ -427,9 +436,28 @@ public class SwaggerConfiguration { |
|
|
|
s.getAllOf().removeIf(allOfEntry -> refToRemove.equals(((Schema<?>) allOfEntry).get$ref())); |
|
|
|
} |
|
|
|
}); |
|
|
|
log.debug("Removed duplicate schema '{}' (identical to '{}')", name, baseName); |
|
|
|
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).
|
|
|
|
schemas.values().forEach(schema -> { |
|
|
|
if (schema.getAllOf() != null && schema.getAllOf().size() > 1) { |
|
|
|
List<Schema> allOf = schema.getAllOf(); |
|
|
|
List<Schema> deduplicated = new ArrayList<>(); |
|
|
|
for (Schema entry : allOf) { |
|
|
|
if (deduplicated.stream().noneMatch(entry::equals)) { |
|
|
|
deduplicated.add(entry); |
|
|
|
} |
|
|
|
} |
|
|
|
if (deduplicated.size() < allOf.size()) { |
|
|
|
allOf.clear(); |
|
|
|
allOf.addAll(deduplicated); |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// Fix polymorphic properties: replace inline oneOf with base type $ref
|
|
|
|
schemas.values().forEach(schema -> { |
|
|
|
replaceInlineOneOfProperties(schema, schemas); |
|
|
|
|