Есть несколько серьёзных проблем:
1. Главная проблема: порядок выполнения
OASFilter выполняется во время сборки (build time), а ApiPlatformScanner с @Observes StartupEvent — во время запуска (runtime). Когда filterOperation() вызывается, ApiPlatformRegistry ещё пуст!
2. Массив CONTROLLERS пуст
private static final Class<?>[] CONTROLLERS = {
// com.banank.platform.services.profile.controller.PersonController.class // ← закомментировано!
};
3. Проблема с operationId
operationId в Quarkus обычно генерируется как read или PersonResource_read, а не содержит оба слова одновременно для проверки contains().
Решение: сканировать прямо в OASFilter
package com.banank.platform.services.profile.exception.utils;
import org.eclipse.microprofile.openapi.OASFactory;
import org.eclipse.microprofile.openapi.OASFilter;
import org.eclipse.microprofile.openapi.models.OpenAPI;
import org.eclipse.microprofile.openapi.models.Operation;
import org.eclipse.microprofile.openapi.models.PathItem;
import org.eclipse.microprofile.openapi.models.media.Content;
import org.eclipse.microprofile.openapi.models.responses.APIResponse;
import org.eclipse.microprofile.openapi.models.responses.APIResponses;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.Index;
import org.jboss.jandex.IndexReader;
import org.jboss.jandex.MethodInfo;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class PlatformApiOASFilter implements OASFilter {
private static final DotName PLATFORM_API_RESPONSE = DotName.createSimple(PlatformApiResponse.class.getName());
private static final DotName PLATFORM_API_RESPONSES = DotName.createSimple(PlatformApiResponses.class.getName());
// Кэш: operationId -> List<AnnotationInstance>
private final Map<String, List<AnnotationInstance>> responsesByOperation = new HashMap<>();
public PlatformApiOASFilter() {
loadAnnotationsFromJandex();
}
private void loadAnnotationsFromJandex() {
try (InputStream stream = Thread.currentThread()
.getContextClassLoader()
.getResourceAsStream("META-INF/jandex.idx")) {
if (stream == null) {
System.err.println("Jandex index not found! Add quarkus-jandex dependency.");
return;
}
Index index = new IndexReader(stream).read();
// Ищем все @PlatformApiResponse
for (AnnotationInstance annotation : index.getAnnotations(PLATFORM_API_RESPONSE)) {
processAnnotation(annotation);
}
// Ищем все @PlatformApiResponses (контейнер)
for (AnnotationInstance container : index.getAnnotations(PLATFORM_API_RESPONSES)) {
AnnotationInstance[] values = container.value().asNestedArray();
for (AnnotationInstance annotation : values) {
processAnnotation(annotation, container.target());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void processAnnotation(AnnotationInstance annotation) {
processAnnotation(annotation, annotation.target());
}
private void processAnnotation(AnnotationInstance annotation, org.jboss.jandex.AnnotationTarget target) {
if (target.kind() == org.jboss.jandex.AnnotationTarget.Kind.METHOD) {
MethodInfo method = target.asMethod();
String className = method.declaringClass().simpleName();
String methodName = method.name();
// Возможные варианты operationId в Quarkus
String key1 = methodName; // read
String key2 = className + "_" + methodName; // PersonResource_read
responsesByOperation.computeIfAbsent(key1, k -> new ArrayList<>()).add(annotation);
responsesByOperation.computeIfAbsent(key2, k -> new ArrayList<>()).add(annotation);
}
}
@Override
public Operation filterOperation(Operation operation) {
if (operation == null || operation.getOperationId() == null) {
return operation;
}
String operationId = operation.getOperationId();
List<AnnotationInstance> annotations = responsesByOperation.get(operationId);
if (annotations == null || annotations.isEmpty()) {
return operation;
}
APIResponses responses = operation.getResponses();
if (responses == null) {
responses = OASFactory.createAPIResponses();
operation.setResponses(responses);
}
for (AnnotationInstance annotation : annotations) {
String responseCode = annotation.value("responseCode") != null
? annotation.value("responseCode").asString()
: "default";
String description = annotation.value("description") != null
? annotation.value("description").asString()
: "";
// Не перезаписываем существующие responses
if (responses.getAPIResponse(responseCode) != null) {
continue;
}
APIResponse apiResponse = OASFactory.createAPIResponse()
.description(description);
// Обработка content если есть
if (annotation.value("content") != null) {
AnnotationInstance[] contents = annotation.value("content").asNestedArray();
if (contents.length > 0) {
Content content = OASFactory.createContent();
for (AnnotationInstance c : contents) {
String mediaType = c.value("mediaType") != null
? c.value("mediaType").asString()
: "application/json";
content.addMediaType(mediaType, OASFactory.createMediaType());
}
apiResponse.setContent(content);
}
}
responses.addAPIResponse(responseCode, apiResponse);
}
return operation;
}
}
Необходимые изменения:
1. Добавить зависимость Jandex (если нет):
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jandex</artifactId>
</dependency>
2. Зарегистрировать фильтр в application.properties:
mp.openapi.filter=com.banank.platform.services.profile.exception.utils.PlatformApiOASFilter
3. Убедиться что класс индексируется:
quarkus.index-dependency.your-module.group-id=com.banank
quarkus.index-dependency.your-module.artifact-id=your-artifact
Альтернатива: упрощённый вариант без Jandex
Если не хотите возиться с Jandex, можно сделать статическую регистрацию в static блоке:
public class PlatformApiOASFilter implements OASFilter {
private static final Map<String, List<ResponseInfo>> RESPONSES = new HashMap<>();
static {
// Регистрируем вручную или через рефлексию в static блоке
registerFromClass(PersonResource.class);
}
private static void registerFromClass(Class<?> clazz) {
for (var method : clazz.getDeclaredMethods()) {
List<PlatformApiResponse> annotations = new ArrayList<>();
PlatformApiResponses container = method.getAnnotation(PlatformApiResponses.class);
if (container != null) {
annotations.addAll(Arrays.asList(container.value()));
}
PlatformApiResponse single = method.getAnnotation(PlatformApiResponse.class);
if (single != null) {
annotations.add(single);
}
if (!annotations.isEmpty()) {
String key = method.getName(); // или clazz.getSimpleName() + "_" + method.getName()
RESPONSES.put(key, annotations.stream()
.map(a -> new ResponseInfo(a.responseCode(), a.description()))
.toList());
}
}
}
record ResponseInfo(String code, String description) {}
@Override
public Operation filterOperation(Operation operation) {
// ... использовать RESPONSES map
}
}
Но это требует явно перечислять классы контроллеров.