diff --git a/openapi-annotations/src/main/java/io/micronaut/openapi/annotation/OpenAPIGroupInfo.java b/openapi-annotations/src/main/java/io/micronaut/openapi/annotation/OpenAPIGroupInfo.java index b732689198..82dcc1df1f 100644 --- a/openapi-annotations/src/main/java/io/micronaut/openapi/annotation/OpenAPIGroupInfo.java +++ b/openapi-annotations/src/main/java/io/micronaut/openapi/annotation/OpenAPIGroupInfo.java @@ -23,6 +23,7 @@ import io.micronaut.context.annotation.AliasFor; import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.security.SecurityScheme; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -54,5 +55,12 @@ /** * @return OpenAPI object describing information about group. */ - OpenAPIDefinition info(); + OpenAPIDefinition info() default @OpenAPIDefinition; + + /** + * @return Security schemes for OpenAPI group. + * + * @since 6.6.0 + */ + SecurityScheme[] securitySchemes() default {}; } diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiVisitor.java b/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiVisitor.java index c03d9b5454..5a8b32b23c 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiVisitor.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiVisitor.java @@ -101,6 +101,7 @@ import io.swagger.v3.oas.annotations.media.Schema.AdditionalPropertiesValue; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.security.OAuthScope; +import io.swagger.v3.oas.annotations.security.SecurityScheme; import io.swagger.v3.oas.annotations.servers.Server; import io.swagger.v3.oas.annotations.servers.ServerVariable; import io.swagger.v3.oas.models.Components; @@ -114,7 +115,6 @@ import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.media.StringSchema; import io.swagger.v3.oas.models.security.SecurityRequirement; -import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.tags.Tag; import com.fasterxml.jackson.annotation.JsonAnySetter; @@ -136,8 +136,8 @@ import static io.micronaut.openapi.visitor.ConfigUtils.isJsonViewDefaultInclusion; import static io.micronaut.openapi.visitor.ContextUtils.warn; import static io.micronaut.openapi.visitor.ConvertUtils.parseJsonString; -import static io.micronaut.openapi.visitor.ConvertUtils.resolveExtensions; import static io.micronaut.openapi.visitor.ConvertUtils.setDefaultValueObject; +import static io.micronaut.openapi.visitor.ConvertUtils.toTupleSubMap; import static io.micronaut.openapi.visitor.ElementUtils.isElementNotNullable; import static io.micronaut.openapi.visitor.ElementUtils.isFileUpload; import static io.micronaut.openapi.visitor.ElementUtils.isNullable; @@ -279,11 +279,11 @@ Map> resolvePathItems(VisitorContext context, List> resultPathItemsMap = new HashMap<>(); + var resultPathItemsMap = new HashMap>(); for (UriMatchTemplate matchTemplate : matchTemplates) { - StringBuilder result = new StringBuilder(); + var result = new StringBuilder(); boolean optionalPathVar = false; boolean varProcess = false; @@ -347,10 +347,10 @@ Map> resolvePathItems(VisitorContext context, List finalPaths = new HashMap<>(); + var finalPaths = new HashMap(); finalPaths.put(-1, resultPath); if (CollectionUtils.isNotEmpty(matchTemplate.getVariables())) { - List optionalVars = new ArrayList<>(); + var optionalVars = new ArrayList(); // need check not required path variables for (UriMatchVariable var : matchTemplate.getVariables()) { if (var.isQuery() || !var.isOptional() || var.isExploded()) { @@ -388,13 +388,14 @@ Map> resolvePathItems(VisitorContext context, List addOptionalVars(List paths, String var, int level) { - List additionalPaths = new ArrayList<>(paths); + var additionalPaths = new ArrayList<>(paths); if (paths.isEmpty()) { additionalPaths.add("/{" + var + '}'); - } else { - for (String path : paths) { - additionalPaths.add(path + "/{" + var + '}'); - } + return additionalPaths; + } + + for (String path : paths) { + additionalPaths.add(path + "/{" + var + '}'); } return additionalPaths; } @@ -409,7 +410,7 @@ private List addOptionalVars(List paths, String var, int level) * @return The map */ protected Map toValueMap(Map values, VisitorContext context, @Nullable ClassElement jsonViewClass) { - Map newValues = new HashMap<>(values.size()); + var newValues = new HashMap(values.size()); for (Map.Entry entry : values.entrySet()) { CharSequence key = entry.getKey(); Object value = entry.getValue(); @@ -723,19 +724,6 @@ private Map resolveAnnotationValues(VisitorContext context return valueMap; } - private Map toTupleSubMap(Object[] a, String entryKey, String entryValue) { - Map params = new LinkedHashMap<>(); - for (Object o : a) { - AnnotationValue sv = (AnnotationValue) o; - final Optional n = sv.stringValue(entryKey); - final Optional expr = sv.stringValue(entryValue); - if (n.isPresent() && expr.isPresent()) { - params.put(n.get(), expr.get()); - } - } - return params; - } - private boolean isTypeNullable(ClassElement type) { return type.isAssignable(Optional.class); } @@ -2638,84 +2626,16 @@ private Schema getPrimitiveType(ClassElement type, String typeName) { } /** - * Processes {@link io.swagger.v3.oas.annotations.security.SecurityScheme} + * Processes {@link SecurityScheme} * annotations. * * @param element The element * @param context The visitor context */ protected void processSecuritySchemes(ClassElement element, VisitorContext context) { - final List> values = element - .getAnnotationValuesByType(io.swagger.v3.oas.annotations.security.SecurityScheme.class); - final OpenAPI openAPI = Utils.resolveOpenApi(context); - for (AnnotationValue securityRequirementAnnotationValue : values) { - - final Map map = toValueMap(securityRequirementAnnotationValue.getValues(), context, null); - - securityRequirementAnnotationValue.stringValue("name") - .ifPresent(name -> { - if (map.containsKey("paramName")) { - map.put("name", map.remove("paramName")); - } - - Utils.normalizeEnumValues(map, CollectionUtils.mapOf("type", SecurityScheme.Type.class, "in", SecurityScheme.In.class)); - - String type = (String) map.get("type"); - if (!SecurityScheme.Type.APIKEY.toString().equals(type)) { - removeAndWarnSecSchemeProp(map, "name", context, false); - removeAndWarnSecSchemeProp(map, "in", context); - } - if (!SecurityScheme.Type.OAUTH2.toString().equals(type)) { - removeAndWarnSecSchemeProp(map, "flows", context); - } - if (!SecurityScheme.Type.OPENIDCONNECT.toString().equals(type)) { - removeAndWarnSecSchemeProp(map, "openIdConnectUrl", context); - } - if (!SecurityScheme.Type.HTTP.toString().equals(type)) { - removeAndWarnSecSchemeProp(map, "scheme", context); - removeAndWarnSecSchemeProp(map, "bearerFormat", context); - } - - if (SecurityScheme.Type.HTTP.toString().equals(type)) { - if (!map.containsKey("scheme")) { - warn("Can't use http security scheme without 'scheme' property", context); - } else if (!map.get("scheme").equals("bearer") && map.containsKey("bearerFormat")) { - warn("Should NOT have a `bearerFormat` property without `scheme: bearer` being set", context); - } - } - - if (map.containsKey("ref") || map.containsKey("$ref")) { - Object ref = map.get("ref"); - if (ref == null) { - ref = map.get("$ref"); - } - map.clear(); - map.put("$ref", ref); - } - - try { - JsonNode node = toJson(map, context, null); - SecurityScheme securityScheme = ConvertUtils.treeToValue(node, SecurityScheme.class, context); - if (securityScheme != null) { - resolveExtensions(node).ifPresent(extensions -> BeanMap.of(securityScheme).put("extensions", extensions)); - resolveComponents(openAPI).addSecuritySchemes(name, securityScheme); - } - } catch (JsonProcessingException e) { - // ignore - } - }); - } - } - - private void removeAndWarnSecSchemeProp(Map map, String prop, VisitorContext context) { - removeAndWarnSecSchemeProp(map, prop, context, true); - } - - private void removeAndWarnSecSchemeProp(Map map, String prop, VisitorContext context, boolean withWarn) { - if (map.containsKey(prop) && withWarn) { - warn("'" + prop + "' property can't set for securityScheme with type " + map.get("type") + ". Skip it", context); - } - map.remove(prop); + var values = element.getAnnotationValuesByType(SecurityScheme.class); + final OpenAPI openApi = Utils.resolveOpenApi(context); + ConvertUtils.addSecuritySchemes(openApi, values, context); } /** diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/ConvertUtils.java b/openapi/src/main/java/io/micronaut/openapi/visitor/ConvertUtils.java index 2ac33f3db6..d127e050fd 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/ConvertUtils.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/ConvertUtils.java @@ -48,6 +48,7 @@ import io.micronaut.core.beans.BeanMap; import io.micronaut.core.util.ArrayUtils; import io.micronaut.core.util.CollectionUtils; +import io.micronaut.core.util.StringUtils; import io.micronaut.inject.ast.ClassElement; import io.micronaut.inject.ast.Element; import io.micronaut.inject.ast.ElementQuery; @@ -56,13 +57,16 @@ import io.micronaut.inject.visitor.VisitorContext; import io.micronaut.openapi.swagger.core.util.PrimitiveType; import io.swagger.v3.oas.annotations.extensions.Extension; +import io.swagger.v3.oas.annotations.security.OAuthScope; import io.swagger.v3.oas.annotations.servers.Server; import io.swagger.v3.oas.annotations.servers.ServerVariable; +import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.media.Content; import io.swagger.v3.oas.models.media.MediaType; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.responses.ApiResponse; import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonProcessingException; @@ -75,6 +79,7 @@ import static io.micronaut.openapi.visitor.ContextUtils.warn; import static io.micronaut.openapi.visitor.SchemaUtils.TYPE_OBJECT; import static io.micronaut.openapi.visitor.SchemaUtils.processExtensions; +import static io.micronaut.openapi.visitor.Utils.resolveComponents; /** * Convert utilities methods. @@ -124,7 +129,7 @@ public static JsonNode toJson(Map values, VisitorContext c } public static Map toValueMap(Map values, VisitorContext context) { - Map newValues = new HashMap<>(values.size()); + var newValues = new HashMap(values.size()); for (Map.Entry entry : values.entrySet()) { CharSequence key = entry.getKey(); Object value = entry.getValue(); @@ -171,6 +176,9 @@ public static Map toValueMap(Map val servers.add(variables); } newValues.put(key, servers); + } else if (OAuthScope.class.getName().equals(annotationName)) { + Map params = toTupleSubMap(a, "name", "description"); + newValues.put(key, params); } else if (ServerVariable.class.getName().equals(annotationName)) { Map> variables = new LinkedHashMap<>(); for (Object o : a) { @@ -344,6 +352,80 @@ public static Optional> resolveExtensions(JsonNode jn) { return Optional.empty(); } + public static void addSecuritySchemes(OpenAPI openApi, + List> values, + VisitorContext context) { + for (var securityRequirementAnnValue : values) { + + final Map map = toValueMap(securityRequirementAnnValue.getValues(), context); + + var name = securityRequirementAnnValue.stringValue("name").orElse(null); + if (StringUtils.isEmpty(name)) { + continue; + } + if (map.containsKey("paramName")) { + map.put("name", map.remove("paramName")); + } + + Utils.normalizeEnumValues(map, CollectionUtils.mapOf("type", SecurityScheme.Type.class, "in", SecurityScheme.In.class)); + + String type = (String) map.get("type"); + if (!SecurityScheme.Type.APIKEY.toString().equals(type)) { + removeAndWarnSecSchemeProp(map, "name", context, false); + removeAndWarnSecSchemeProp(map, "in", context); + } + if (!SecurityScheme.Type.OAUTH2.toString().equals(type)) { + removeAndWarnSecSchemeProp(map, "flows", context); + } + if (!SecurityScheme.Type.OPENIDCONNECT.toString().equals(type)) { + removeAndWarnSecSchemeProp(map, "openIdConnectUrl", context); + } + if (!SecurityScheme.Type.HTTP.toString().equals(type)) { + removeAndWarnSecSchemeProp(map, "scheme", context); + removeAndWarnSecSchemeProp(map, "bearerFormat", context); + } + + if (SecurityScheme.Type.HTTP.toString().equals(type)) { + if (!map.containsKey("scheme")) { + warn("Can't use http security scheme without 'scheme' property", context); + } else if (!map.get("scheme").equals("bearer") && map.containsKey("bearerFormat")) { + warn("Should NOT have a `bearerFormat` property without `scheme: bearer` being set", context); + } + } + + if (map.containsKey("ref") || map.containsKey("$ref")) { + Object ref = map.get("ref"); + if (ref == null) { + ref = map.get("$ref"); + } + map.clear(); + map.put("$ref", ref); + } + + try { + JsonNode node = toJson(map, context); + SecurityScheme securityScheme = ConvertUtils.treeToValue(node, SecurityScheme.class, context); + if (securityScheme != null) { + resolveExtensions(node).ifPresent(extensions -> BeanMap.of(securityScheme).put("extensions", extensions)); + resolveComponents(openApi).addSecuritySchemes(name, securityScheme); + } + } catch (JsonProcessingException e) { + // ignore + } + } + } + + private static void removeAndWarnSecSchemeProp(Map map, String prop, VisitorContext context) { + removeAndWarnSecSchemeProp(map, prop, context, true); + } + + private static void removeAndWarnSecSchemeProp(Map map, String prop, VisitorContext context, boolean withWarn) { + if (map.containsKey(prop) && withWarn) { + warn("'" + prop + "' property can't set for securityScheme with type " + map.get("type") + ". Skip it", context); + } + map.remove(prop); + } + /** * Maps annotation value to {@link io.swagger.v3.oas.annotations.security.SecurityRequirement}. * Correct format is: @@ -558,4 +640,17 @@ public static Object parseByTypeAndFormat(String valueStr, String type, String f return valueStr; } + + public static Map toTupleSubMap(Object[] a, String entryKey, String entryValue) { + var params = new LinkedHashMap(); + for (Object o : a) { + AnnotationValue sv = (AnnotationValue) o; + final Optional n = sv.stringValue(entryKey); + final Optional expr = sv.stringValue(entryValue); + if (n.isPresent() && expr.isPresent()) { + params.put(n.get(), expr.get()); + } + } + return params; + } } diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiApplicationVisitor.java b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiApplicationVisitor.java index cfcb628013..3b1f5bca0a 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiApplicationVisitor.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiApplicationVisitor.java @@ -49,6 +49,8 @@ import io.micronaut.inject.visitor.TypeElementVisitor; import io.micronaut.inject.visitor.VisitorContext; import io.micronaut.openapi.OpenApiUtils; +import io.micronaut.openapi.annotation.OpenAPIGroupInfo; +import io.micronaut.openapi.annotation.OpenAPIGroupInfos; import io.micronaut.openapi.postprocessors.JacksonDiscriminatorPostProcessor; import io.micronaut.openapi.postprocessors.OpenApiOperationsPostProcessor; import io.micronaut.openapi.view.OpenApiViewConfig; @@ -107,6 +109,7 @@ import static io.micronaut.openapi.visitor.SchemaUtils.copyOpenApi; import static io.micronaut.openapi.visitor.SchemaUtils.getOperationOnPathItem; import static io.micronaut.openapi.visitor.SchemaUtils.setOperationOnPathItem; +import static io.micronaut.openapi.visitor.Utils.resolveComponents; import static io.swagger.v3.oas.models.Components.COMPONENTS_SCHEMAS_REF; /** @@ -115,7 +118,7 @@ * @author graemerocher * @since 1.0 */ -public class OpenApiApplicationVisitor extends AbstractOpenApiVisitor implements TypeElementVisitor { +public class OpenApiApplicationVisitor extends AbstractOpenApiVisitor implements TypeElementVisitor { private ClassElement classElement; private int visitedElements = -1; @@ -128,10 +131,14 @@ public Set getSupportedOptions() { @Override public void visitClass(ClassElement element, VisitorContext context) { try { - incrementVisitedElements(context); if (!isOpenApiEnabled(context) || !isSpecGenerationEnabled(context)) { return; } + if (ignore(element, context)) { + return; + } + incrementVisitedElements(context); + info("Generating OpenAPI Documentation", context); OpenAPI openApi = readOpenApi(element, context); @@ -187,6 +194,13 @@ public void visitClass(ClassElement element, VisitorContext context) { } } + private boolean ignore(ClassElement element, VisitorContext context) { + + return !element.isAnnotationPresent(OpenAPIDefinition.class) + && !element.isAnnotationPresent(OpenAPIGroupInfo.class) + && !element.isAnnotationPresent(OpenAPIGroupInfos.class); + } + /** * Merge the OpenAPI YAML and JSON files into one single file. * @@ -474,10 +488,10 @@ private Map, OpenApiInfo> divideOpenapiByGroupsAndVersions( return Collections.singletonMap(Pair.NULL_STRING_PAIR, new OpenApiInfo(openApi)); } - List commonEndpoints = new ArrayList<>(); + var commonEndpoints = new ArrayList(); // key version, groupName - Map, OpenApiInfo> result = new HashMap<>(); + var result = new HashMap, OpenApiInfo>(); for (List endpointInfos : endpointInfosMap.values()) { for (EndpointInfo endpointInfo : endpointInfos) { @@ -510,13 +524,13 @@ private Map, OpenApiInfo> divideOpenapiByGroupsAndVersions( continue; } - OpenAPI openAPI = entry.getValue().getOpenApi(); + OpenAPI groupOpenApi = entry.getValue().getOpenApi(); for (EndpointInfo commonEndpoint : commonEndpoints) { if (CollectionUtils.isNotEmpty(commonEndpoint.getExcludedGroups()) && commonEndpoint.getExcludedGroups().contains(group)) { continue; } - addOperation(commonEndpoint, openAPI); + addOperation(commonEndpoint, groupOpenApi); } } @@ -541,8 +555,9 @@ private void addOperation(EndpointInfo endpointInfo, OpenAPI openApi) { setOperationOnPathItem(pathItem, endpointInfo.getHttpMethod(), SchemaUtils.mergeOperations(operation, endpointInfo.getOperation())); } - private OpenAPI addOpenApiInfo(String group, String version, OpenAPI openApi, Map, - OpenApiInfo> openApiInfoMap, VisitorContext context) { + private OpenAPI addOpenApiInfo(String group, String version, OpenAPI openApi, + Map, OpenApiInfo> openApiInfoMap, + VisitorContext context) { GroupProperties groupProperties = getGroupProperties(group, context); boolean hasGroupProperties = groupProperties != null; @@ -586,8 +601,23 @@ private OpenAPI addOpenApiInfo(String group, String version, OpenAPI openApi, Ma newOpenApi.setExternalDocs(openApiCopy.getExternalDocs()); } - newOpenApi.setComponents(openApiCopy.getComponents()); + // if we have SecuritySchemes specified only for group + var groupSecuritySchemes = newOpenApi.getComponents() != null ? newOpenApi.getComponents().getSecuritySchemes() : null; + if (CollectionUtils.isNotEmpty(groupSecuritySchemes) + && openApiCopy.getComponents() != null + && CollectionUtils.isNotEmpty(openApiCopy.getComponents().getSecuritySchemes())) { + for (var entry : openApiCopy.getComponents().getSecuritySchemes().entrySet()) { + if (!groupSecuritySchemes.containsKey(entry.getKey())) { + groupSecuritySchemes.put(entry.getKey(), entry.getValue()); + } + } + } + + newOpenApi.setComponents(openApiCopy.getComponents()); + if (CollectionUtils.isNotEmpty(groupSecuritySchemes)) { + resolveComponents(newOpenApi).setSecuritySchemes(groupSecuritySchemes); + } } else { newOpenApi = openApiInfo.getOpenApi(); } diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiGroupInfoVisitor.java b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiGroupInfoVisitor.java index 8736bcafe1..fc349d842d 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiGroupInfoVisitor.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiGroupInfoVisitor.java @@ -33,6 +33,7 @@ import io.micronaut.openapi.annotation.OpenAPIGroupInfo; import io.swagger.v3.oas.annotations.OpenAPIDefinition; import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.security.SecurityScheme; import io.swagger.v3.oas.models.OpenAPI; import static io.micronaut.openapi.visitor.ConfigUtils.isOpenApiEnabled; @@ -127,6 +128,11 @@ private void addOpenApis(List> annotationValue } openApi.setSecurity(securityRequirements); + var securitySchemeAnns = infoAnn.getAnnotations("securitySchemes", SecurityScheme.class); + if (CollectionUtils.isNotEmpty(securitySchemeAnns)) { + ConvertUtils.addSecuritySchemes(openApi, securitySchemeAnns, context); + } + for (var groupName : infoAnn.stringValues("names")) { openApis.put(groupName, openApi); } diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiGroupSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiGroupSpec.groovy new file mode 100644 index 0000000000..a89ddbb2f0 --- /dev/null +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiGroupSpec.groovy @@ -0,0 +1,108 @@ +package io.micronaut.openapi.visitor + +import io.micronaut.openapi.AbstractOpenApiTypeElementSpec +import io.swagger.v3.oas.models.security.SecurityScheme + +class OpenApiGroupSpec extends AbstractOpenApiTypeElementSpec { + + void "test group security schemes"() { + + when: + buildBeanDefinition("test.MyBean", ''' +package test; + +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.openapi.annotation.OpenAPIGroup; +import io.micronaut.openapi.annotation.OpenAPIGroupInfo; +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.security.SecurityScheme; + +@OpenAPIGroup("private") +@Controller +class MyController { + + @OpenAPIGroup("private") + @Get("/id/{id}") + String get(String id) { + return null; + } + + @OpenAPIGroup("public") + @Get("/name/{name}") + String getByName(String name) { + return null; + } + + // common + @Get("/all") + String getAll() { + return null; + } +} + +@OpenAPIGroupInfo( + names = "private", + info = @OpenAPIDefinition( + info = @Info( + title = "Private api" + ) + ) +) +@OpenAPIGroupInfo( + names = "public", + info = @OpenAPIDefinition( + info = @Info( + title = "Public api" + ), + security = { + @SecurityRequirement(name = "authorizer") + } + ), + securitySchemes = { + @SecurityScheme( + name = "authorizer", + type = SecuritySchemeType.APIKEY, + paramName = "Authorization" + ) + } +) +@SecurityScheme( + name = "common", + type = SecuritySchemeType.HTTP, + scheme = "basic", + in = SecuritySchemeIn.HEADER +) +class Application { +} + +@jakarta.inject.Singleton +public class MyBean {} + +''') + + then: + def openApis = Utils.testReferences + openApis + openApis.size() == 2 + + def apiPrivate = openApis.get(Pair.of("private", null)).getOpenApi() + def apiPublic = openApis.get(Pair.of("public", null)).getOpenApi() + + apiPrivate.components.securitySchemes + apiPrivate.components.securitySchemes.size() == 1 + apiPrivate.components.securitySchemes.common + apiPrivate.components.securitySchemes.common.type == SecurityScheme.Type.HTTP + + apiPublic.components.securitySchemes + apiPublic.components.securitySchemes.size() == 2 + apiPublic.components.securitySchemes.common + apiPublic.components.securitySchemes.common.type == SecurityScheme.Type.HTTP + apiPublic.components.securitySchemes.authorizer + apiPublic.components.securitySchemes.authorizer.type == SecurityScheme.Type.APIKEY + } +}