diff --git a/core/src/main/java/io/smallrye/openapi/runtime/io/ModelIO.java b/core/src/main/java/io/smallrye/openapi/runtime/io/ModelIO.java index b311a86aa..c6c4917f9 100644 --- a/core/src/main/java/io/smallrye/openapi/runtime/io/ModelIO.java +++ b/core/src/main/java/io/smallrye/openapi/runtime/io/ModelIO.java @@ -6,9 +6,10 @@ import java.util.Locale; import java.util.Map; import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; import java.util.function.Predicate; import java.util.stream.Collector; -import java.util.stream.Collectors; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; @@ -139,7 +140,25 @@ protected static Predicate not(Predicate predicate) { } protected static Collector, ?, Map> toLinkedMap() { - return Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1, LinkedHashMap::new); + BiConsumer, Map.Entry> accumulator = (map, entry) -> { + String k = entry.getKey(); + T v = entry.getValue(); + + if (map.containsKey(k)) { + throw new IllegalStateException(String.format( + "Duplicate key %s (attempted merging values %s and %s)", + k, map.get(k), v)); + } + + map.put(k, v); + }; + + BinaryOperator> combiner = (m1, m2) -> { + m2.entrySet().forEach(entry -> accumulator.accept(m1, entry)); + return m1; + }; + + return Collector.of(LinkedHashMap::new, accumulator, combiner); } public AnnotationInstance getAnnotation(AnnotationTarget target) { diff --git a/core/src/test/java/io/smallrye/openapi/runtime/io/security/SecuritySchemeIOTest.java b/core/src/test/java/io/smallrye/openapi/runtime/io/security/SecuritySchemeIOTest.java new file mode 100644 index 000000000..fd4d582d3 --- /dev/null +++ b/core/src/test/java/io/smallrye/openapi/runtime/io/security/SecuritySchemeIOTest.java @@ -0,0 +1,63 @@ +package io.smallrye.openapi.runtime.io.security; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.microprofile.openapi.annotations.enums.SecuritySchemeType; +import org.eclipse.microprofile.openapi.annotations.security.OAuthFlow; +import org.eclipse.microprofile.openapi.annotations.security.OAuthFlows; +import org.eclipse.microprofile.openapi.annotations.security.OAuthScope; +import org.eclipse.microprofile.openapi.annotations.security.SecurityScheme; +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.ClassInfo; +import org.junit.jupiter.api.Test; + +import io.smallrye.openapi.api.models.OpenAPIImpl; +import io.smallrye.openapi.runtime.io.IOContext; +import io.smallrye.openapi.runtime.scanner.FilteredIndexView; +import io.smallrye.openapi.runtime.scanner.IndexScannerTestBase; +import io.smallrye.openapi.runtime.scanner.spi.AnnotationScannerContext; + +class SecuritySchemeIOTest extends IndexScannerTestBase { + + @Test + void testReadFlow() { + FilteredIndexView index = new FilteredIndexView(IndexScannerTestBase.indexOf(Endpoint1.class), emptyConfig()); + + AnnotationScannerContext context = new AnnotationScannerContext(index, Thread.currentThread().getContextClassLoader(), + Collections.emptyList(), + emptyConfig(), new OpenAPIImpl()); + + ClassInfo clazz = index.getClassByName(Endpoint1.class); + AnnotationInstance annotation = clazz.annotation(SecurityScheme.class); + AnnotationInstance flowAnnotation = annotation + .value("flows").asNested() + .value("implicit").asNested(); + AnnotationInstance scopeAnnotation = flowAnnotation + .value("scopes").asNestedArray()[0]; + + IOContext ioContext = IOContext.forScanning(context); + + OAuthScopeIO scopeIO = ioContext.oauthScopeIO(); + String value = scopeIO.read(scopeAnnotation); + assertNull(value); + + OAuthFlowIO flowIO = ioContext.oauthFlowIO(); + org.eclipse.microprofile.openapi.models.security.OAuthFlow flow = flowIO.read(flowAnnotation); + Map expected = new HashMap<>(); + expected.put("foo", null); + assertEquals(expected, flow.getScopes()); + } + + @SecurityScheme(securitySchemeName = "OAuth2Authorization", + type = SecuritySchemeType.OAUTH2, + description = "authentication needed to delete a profile", + flows = @OAuthFlows(implicit = @OAuthFlow(authorizationUrl = "https://example.com", + scopes = @OAuthScope(name = "foo")))) + static class Endpoint1 { + } +}