Skip to content

Commit

Permalink
fix: include interface generic type parameters in resolution stack (#…
Browse files Browse the repository at this point in the history
…2115)

* fix: include interface generic type parameters in resolution stack

Signed-off-by: Michael Edgar <[email protected]>

* Add test case for record with name from implemented interface

Signed-off-by: Michael Edgar <[email protected]>

---------

Signed-off-by: Michael Edgar <[email protected]>
  • Loading branch information
MikeEdgar authored Jan 6, 2025
1 parent c6ae3ba commit 849a5b9
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ private TypeResolver(AnnotationScannerContext context, UnaryOperator<String> nam
}
}

private void replaceResolutionStack(Deque<Map<String, Type>> replacementStack) {
resolutionStack.clear();
resolutionStack.addAll(replacementStack);
}

/**
* Get the declaring class of the annotation target.
*
Expand Down Expand Up @@ -525,10 +530,7 @@ public static Map<String, TypeResolver> getAllFields(AnnotationScannerContext co
for (Map.Entry<ClassInfo, Type> entry : chain.entrySet()) {
ClassInfo currentClass = entry.getKey();
Type currentType = entry.getValue();

if (currentType.kind() == Type.Kind.PARAMETERIZED_TYPE) {
stack.push(buildParamTypeResolutionMap(currentClass, currentType));
}
maybeAddResolutionParams(stack, currentType, currentClass);

if (skipPropertyScan || (!currentType.equals(leaf) && TypeUtil.isAllOf(context, leafKlazz, currentType))
|| TypeUtil.knownJavaType(currentClass.name())) {
Expand Down Expand Up @@ -556,7 +558,11 @@ public static Map<String, TypeResolver> getAllFields(AnnotationScannerContext co
index.interfaces(currentClass)
.stream()
.filter(type -> !TypeUtil.knownJavaType(type.name()))
.map(index::getClass)
.map(type -> {
ClassInfo clazz = index.getClass(type);
maybeAddResolutionParams(stack, type, clazz);
return clazz;
})
.filter(Objects::nonNull)
.flatMap(clazz -> methods(context, clazz).stream())
.filter(method -> method.name().chars().allMatch(Character::isJavaIdentifierPart))
Expand All @@ -578,6 +584,12 @@ public static Map<String, TypeResolver> getAllFields(AnnotationScannerContext co
return sorted(context, properties, chain.keySet());
}

private static void maybeAddResolutionParams(Deque<Map<String, Type>> stack, Type type, ClassInfo clazz) {
if (type.kind() == Type.Kind.PARAMETERIZED_TYPE && clazz != null) {
stack.push(buildParamTypeResolutionMap(clazz, type));
}
}

private static List<MethodInfo> methods(AnnotationScannerContext context, ClassInfo currentClass) {
if (context.getConfig().sortedPropertiesEnable()) {
return currentClass.methods();
Expand Down Expand Up @@ -935,9 +947,10 @@ private static TypeResolver updateTypeResolvers(AnnotationScannerContext context

if (properties.containsKey(propertyName)) {
resolver = properties.get(propertyName);
Type resolvedPropertyType = getResolvedType(propertyType, stack);

// Only store the accessor/mutator methods if the type of property matches
if (!TypeUtil.equalTypes(resolver.getUnresolvedType(), propertyType)) {
if (!TypeUtil.equalTypes(resolver.resolveType(), resolvedPropertyType)) {
return resolver;
}
} else {
Expand All @@ -951,10 +964,12 @@ private static TypeResolver updateTypeResolvers(AnnotationScannerContext context
if (isWriteMethod) {
if (isHigherPriority(resolver.targetComparator, method, resolver.getWriteMethod())) {
resolver.setWriteMethod(method);
resolver.replaceResolutionStack(stack);
}
} else {
if (isHigherPriority(resolver.targetComparator, method, resolver.getReadMethod())) {
resolver.setReadMethod(method);
resolver.replaceResolutionStack(stack);
}
}

Expand Down
1 change: 1 addition & 0 deletions testsuite/data/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@
<configuration>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<java.util.logging.config.file>${project.build.testOutputDirectory}/logging.properties</java.util.logging.config.file>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
package io.smallrye.openapi.testdata;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.spi.ConfigSource;
import org.eclipse.microprofile.openapi.OASConfig;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.models.OpenAPI;
import org.eclipse.microprofile.openapi.models.media.Schema.SchemaType;
import org.jboss.jandex.Index;
import org.jboss.jandex.IndexReader;
import org.jboss.logging.Logger;
Expand All @@ -21,9 +31,12 @@
import org.junit.jupiter.params.provider.ValueSource;
import org.skyscreamer.jsonassert.JSONAssert;

import com.fasterxml.jackson.annotation.JsonProperty;

import io.smallrye.config.PropertiesConfigSource;
import io.smallrye.config.SmallRyeConfigBuilder;
import io.smallrye.openapi.api.OpenApiConfig;
import io.smallrye.openapi.api.SmallRyeOpenAPI;
import io.smallrye.openapi.runtime.OpenApiProcessor;
import io.smallrye.openapi.runtime.io.Format;
import io.smallrye.openapi.runtime.io.OpenApiSerializer;
Expand Down Expand Up @@ -119,6 +132,42 @@ void testKotlinResourceWithUnwrappedFlowSSE() throws Exception {
assertJsonEquals("components.schemas.kotlin-flow-unwrapped.json", result);
}

interface Named<T> {
@JsonProperty("nombre")
T name();
}

@Test
void testRecordInheritsInterfacePropertyName() throws Exception {
@Schema(name = "Widget")
record Widget(String name, int number) implements Named<String> {
}

@Path("widgets")
class WidgetsResource {

@GET
@Operation(summary = "Get a widget")
public Widget get() {
return new Widget("foo", 42);
}
}

Index index = Index.of(Named.class, Widget.class, WidgetsResource.class);
OpenAPI result = SmallRyeOpenAPI.builder()
.withIndex(index)
.enableModelReader(false)
.enableStandardFilter(false)
.enableStandardStaticFiles(false)
.build()
.model();

printToConsole(result);
var nameModel = result.getComponents().getSchemas().get("Widget").getProperties().get("nombre");
assertNotNull(nameModel);
assertEquals(List.of(SchemaType.STRING), nameModel.getType());
}

@Test
void testSyntheticClassesAndInterfacesIgnoredByDefault() throws Exception {
try (InputStream source = getClass().getResourceAsStream("/smallrye-open-api-testsuite-data.idx")) {
Expand Down

0 comments on commit 849a5b9

Please sign in to comment.