diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiEndpointVisitor.java b/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiEndpointVisitor.java index 842299792c..4b4d931dfb 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiEndpointVisitor.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiEndpointVisitor.java @@ -163,6 +163,9 @@ private static RequestBody mergeRequestBody(RequestBody rq1, RequestBody rq2) { * @param context The visitor context */ public void visitClass(ClassElement element, VisitorContext context) { + if (!Utils.isOpenApiEnabled()) { + return; + } if (ignore(element, context)) { return; } @@ -359,6 +362,9 @@ private boolean hasNoBindingAnnotationOrType(TypedElement parameter) { * @param context The visitor context */ public void visitMethod(MethodElement element, VisitorContext context) { + if (!Utils.isOpenApiEnabled()) { + return; + } if (ignore(element, context)) { return; } 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 a3fe029914..0d82e520ea 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/ConvertUtils.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/ConvertUtils.java @@ -199,6 +199,7 @@ public static SecurityRequirement mapToSecurityRequirement(AnnotationValue { + /** + * System property that enables or disables open api annotation processing. + *
+ * Default: true + */ + public static final String MICRONAUT_OPENAPI_ENABLED = "micronaut.openapi.enabled"; /** * System property that enables setting the open api config file. */ @@ -266,6 +273,9 @@ public class OpenApiApplicationVisitor extends AbstractOpenApiVisitor implements @Override public void visitClass(ClassElement element, VisitorContext context) { + if (!Utils.isOpenApiEnabled()) { + return; + } incrementVisitedElements(context); context.info("Generating OpenAPI Documentation"); OpenAPI openAPI = readOpenAPI(element, context); @@ -965,20 +975,23 @@ private static OpenAPI resolvePropertyPlaceHolders(OpenAPI openAPI, VisitorConte } @Override - public void finish(VisitorContext visitorContext) { - if (visitedElements == visitedElements(visitorContext)) { + public void finish(VisitorContext context) { + if (!Utils.isOpenApiEnabled()) { + return; + } + if (visitedElements == visitedElements(context)) { // nothing new visited, avoid rewriting the files. return; } - Optional attr = visitorContext.get(Utils.ATTR_OPENAPI, OpenAPI.class); + Optional attr = context.get(Utils.ATTR_OPENAPI, OpenAPI.class); if (!attr.isPresent()) { return; } OpenAPI openAPI = attr.get(); - processEndpoints(visitorContext); - applyPropertyNamingStrategy(openAPI, visitorContext); - applyPropertyServerContextPath(openAPI, visitorContext); - openAPI = resolvePropertyPlaceHolders(openAPI, visitorContext); + processEndpoints(context); + applyPropertyNamingStrategy(openAPI, context); + applyPropertyServerContextPath(openAPI, context); + openAPI = resolvePropertyPlaceHolders(openAPI, context); sortOpenAPI(openAPI); // Process after sorting so order is stable new JacksonDiscriminatorPostProcessor().addMissingDiscriminatorType(openAPI); @@ -1009,7 +1022,7 @@ public void finish(VisitorContext visitorContext) { removeEmtpyComponents(openAPI); - String isJson = getConfigurationProperty(MICRONAUT_OPENAPI_JSON_FORMAT, visitorContext); + String isJson = getConfigurationProperty(MICRONAUT_OPENAPI_JSON_FORMAT, context); boolean isYaml = !(StringUtils.isNotEmpty(isJson) && isJson.equalsIgnoreCase(StringUtils.TRUE)); String ext = isYaml ? EXT_YML : EXT_JSON; @@ -1026,19 +1039,19 @@ public void finish(VisitorContext visitorContext) { } fileName = documentTitle + ext; } - String fileNameFromConfig = getConfigurationProperty(MICRONAUT_OPENAPI_FILENAME, visitorContext); + String fileNameFromConfig = getConfigurationProperty(MICRONAUT_OPENAPI_FILENAME, context); if (StringUtils.isNotEmpty(fileNameFromConfig)) { - fileName = replacePlaceholders(fileNameFromConfig, visitorContext) + ext; + fileName = replacePlaceholders(fileNameFromConfig, context) + ext; if (fileName.contains("${version}")) { fileName = fileName.replaceAll("\\$\\{version}", info != null && info.getVersion() != null ? info.getVersion() : StringUtils.EMPTY_STRING); } if (fileName.contains("${")) { - visitorContext.warn("Can't set some placeholders in fileName: " + fileName, null); + context.warn("Can't set some placeholders in fileName: " + fileName, null); } } - writeYamlToFile(openAPI, fileName, documentTitle, visitorContext, isYaml); - visitedElements = visitedElements(visitorContext); + writeYamlToFile(openAPI, fileName, documentTitle, context, isYaml); + visitedElements = visitedElements(context); } private void removeEmtpyComponents(OpenAPI openAPI) { @@ -1168,24 +1181,24 @@ private Writer getFileWriter(Optional specFile) throws IOException { } } - private void processEndpoints(VisitorContext visitorContext) { - EndpointsConfiguration endpointsCfg = endPointsConfiguration(visitorContext); + private void processEndpoints(VisitorContext context) { + EndpointsConfiguration endpointsCfg = endPointsConfiguration(context); if (endpointsCfg.isEnabled() && !endpointsCfg.getEndpoints().isEmpty()) { OpenApiEndpointVisitor visitor = new OpenApiEndpointVisitor(true); endpointsCfg.getEndpoints().values().stream() .filter(endpoint -> endpoint.getClassElement().isPresent()) .forEach(endpoint -> { ClassElement element = endpoint.getClassElement().get(); - visitorContext.put(MICRONAUT_OPENAPI_ENDPOINT_CLASS_TAGS, endpoint.getTags()); - visitorContext.put(MICRONAUT_OPENAPI_ENDPOINT_SERVERS, endpoint.getServers()); - visitorContext.put(MICRONAUT_OPENAPI_ENDPOINT_SECURITY_REQUIREMENTS, endpoint.getSecurityRequirements()); - visitor.visitClass(element, visitorContext); + context.put(MICRONAUT_OPENAPI_ENDPOINT_CLASS_TAGS, endpoint.getTags()); + context.put(MICRONAUT_OPENAPI_ENDPOINT_SERVERS, endpoint.getServers()); + context.put(MICRONAUT_OPENAPI_ENDPOINT_SECURITY_REQUIREMENTS, endpoint.getSecurityRequirements()); + visitor.visitClass(element, context); element.getEnclosedElements(ElementQuery.ALL_METHODS .modifiers(mods -> !mods.contains(ElementModifier.STATIC) && !mods.contains(ElementModifier.PRIVATE)) .named(name -> !name.contains("$")) ) - .forEach(method -> visitor.visitMethod(method, visitorContext)); - }); + .forEach(method -> visitor.visitMethod(method, context)); + }); } } diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiControllerVisitor.java b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiControllerVisitor.java index 04c7c6d8f2..1b8dd120f4 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiControllerVisitor.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiControllerVisitor.java @@ -26,6 +26,8 @@ import java.util.Set; import java.util.stream.Collectors; +import javax.annotation.processing.SupportedOptions; + import io.micronaut.context.RequiresCondition; import io.micronaut.context.annotation.Requires; import io.micronaut.context.env.Environment; @@ -53,6 +55,7 @@ import com.fasterxml.jackson.annotation.JsonAnySetter; +import static io.micronaut.openapi.visitor.OpenApiApplicationVisitor.MICRONAUT_OPENAPI_ENABLED; import static io.micronaut.openapi.visitor.Utils.DEFAULT_MEDIA_TYPES; /** @@ -61,6 +64,7 @@ * @author graemerocher * @since 1.0 */ +@SupportedOptions(MICRONAUT_OPENAPI_ENABLED) public class OpenApiControllerVisitor extends AbstractOpenApiEndpointVisitor implements TypeElementVisitor { private final String customUri; diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiEndpointVisitor.java b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiEndpointVisitor.java index 0b656adda1..dff54583d8 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiEndpointVisitor.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiEndpointVisitor.java @@ -21,6 +21,8 @@ import java.util.List; import java.util.stream.Collectors; +import javax.annotation.processing.SupportedOptions; + import io.micronaut.core.annotation.AnnotationValue; import io.micronaut.core.naming.NameUtils; import io.micronaut.core.util.ArrayUtils; @@ -40,6 +42,7 @@ import com.fasterxml.jackson.annotation.JsonAnySetter; +import static io.micronaut.openapi.visitor.OpenApiApplicationVisitor.MICRONAUT_OPENAPI_ENABLED; import static io.micronaut.openapi.visitor.Utils.DEFAULT_MEDIA_TYPES; /** @@ -49,6 +52,7 @@ * @author croudet * @since 1.4 */ +@SupportedOptions(MICRONAUT_OPENAPI_ENABLED) public class OpenApiEndpointVisitor extends AbstractOpenApiEndpointVisitor implements TypeElementVisitor { private String id; @@ -80,6 +84,9 @@ public OpenApiEndpointVisitor(boolean enabled, @Override public void visitClass(ClassElement element, VisitorContext context) { + if (!Utils.isOpenApiEnabled()) { + return; + } EndpointsConfiguration cfg = OpenApiApplicationVisitor.endPointsConfiguration(context); if (enabled == null) { enabled = cfg.isEnabled(); diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiIncludeVisitor.java b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiIncludeVisitor.java index edea791c5f..909d2ac5b1 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiIncludeVisitor.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiIncludeVisitor.java @@ -18,6 +18,8 @@ import java.util.List; import java.util.Optional; +import javax.annotation.processing.SupportedOptions; + import io.micronaut.core.annotation.AnnotationValue; import io.micronaut.core.util.ArrayUtils; import io.micronaut.http.annotation.Controller; @@ -31,15 +33,21 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; +import static io.micronaut.openapi.visitor.OpenApiApplicationVisitor.MICRONAUT_OPENAPI_ENABLED; + /** * A {@link TypeElementVisitor} that builds the Swagger model from Micronaut controllers included by @{@link OpenAPIInclude} at the compile time. * * @author Denis Stepanov */ +@SupportedOptions(MICRONAUT_OPENAPI_ENABLED) public class OpenApiIncludeVisitor implements TypeElementVisitor { @Override - public void visitClass(ClassElement element, VisitorContext visitorContext) { + public void visitClass(ClassElement element, VisitorContext context) { + if (!Utils.isOpenApiEnabled()) { + return; + } for (AnnotationValue includeAnnotation : element.getAnnotationValuesByType(OpenAPIInclude.class)) { String[] classes = includeAnnotation.stringValues(); if (ArrayUtils.isNotEmpty(classes)) { @@ -50,12 +58,12 @@ public void visitClass(ClassElement element, VisitorContext visitorContext) { OpenApiControllerVisitor controllerVisitor = new OpenApiControllerVisitor(tags, security, customUri.orElse(null)); OpenApiEndpointVisitor endpointVisitor = new OpenApiEndpointVisitor(true, tags.isEmpty() ? null : tags, security.isEmpty() ? null : security); for (String className : classes) { - visitorContext.getClassElement(className) + context.getClassElement(className) .ifPresent(ce -> { if (ce.isAnnotationPresent(Controller.class)) { - visit(controllerVisitor, visitorContext, ce); + visit(controllerVisitor, context, ce); } else if (ce.isAnnotationPresent("io.micronaut.management.endpoint.annotation.Endpoint")) { - visit(endpointVisitor, visitorContext, ce); + visit(endpointVisitor, context, ce); } }); } diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiJacksonVisitor.java b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiJacksonVisitor.java index a0a56895dc..b083b67096 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiJacksonVisitor.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiJacksonVisitor.java @@ -20,6 +20,8 @@ import java.util.List; import java.util.Set; +import javax.annotation.processing.SupportedOptions; + import io.micronaut.core.annotation.AnnotationClassValue; import io.micronaut.core.annotation.AnnotationValue; import io.micronaut.core.annotation.NonNull; @@ -33,6 +35,8 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import static io.micronaut.openapi.visitor.OpenApiApplicationVisitor.MICRONAUT_OPENAPI_ENABLED; + /** * A {@link TypeElementVisitor} that builds appropriate {@link Schema} annotation for the parent class of a hierarchy * when using Jackson {@link JsonTypeInfo} and {@link JsonSubTypes}. @@ -40,6 +44,7 @@ * @author Iván López * @since 3.0.0 */ +@SupportedOptions(MICRONAUT_OPENAPI_ENABLED) public class OpenApiJacksonVisitor implements TypeElementVisitor { @Override @@ -62,7 +67,10 @@ public int getOrder() { } @Override - public void visitClass(ClassElement element, VisitorContext visitorContext) { + public void visitClass(ClassElement element, VisitorContext context) { + if (!Utils.isOpenApiEnabled()) { + return; + } AnnotationValue jsonSubTypesDecAnn = element.getDeclaredAnnotation(JsonSubTypes.class); AnnotationValue jsonTypeInfoDecAnn = element.getDeclaredAnnotation(JsonTypeInfo.class); AnnotationValue schemaAnn = element.getDeclaredAnnotation(Schema.class); diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/Utils.java b/openapi/src/main/java/io/micronaut/openapi/visitor/Utils.java index d762479dbd..37064d0a26 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/Utils.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/Utils.java @@ -32,6 +32,7 @@ import io.micronaut.core.convert.ArgumentConversionContext; import io.micronaut.core.convert.DefaultConversionService; import io.micronaut.core.util.CollectionUtils; +import io.micronaut.core.util.StringUtils; import io.micronaut.core.value.PropertyResolver; import io.micronaut.http.MediaType; import io.micronaut.inject.ast.ClassElement; @@ -40,6 +41,8 @@ import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; +import static io.micronaut.openapi.visitor.OpenApiApplicationVisitor.MICRONAUT_OPENAPI_ENABLED; + /** * Some util methods. * @@ -68,12 +71,17 @@ public final class Utils { private Utils() { } + public static boolean isOpenApiEnabled() { + String isEnabledStr = System.getProperty(MICRONAUT_OPENAPI_ENABLED, StringUtils.TRUE); + return !StringUtils.isNotEmpty(isEnabledStr) || !isEnabledStr.equalsIgnoreCase(StringUtils.FALSE); + } + public static Path getProjectPath(VisitorContext context) { return context.getProjectDir().orElse(Utils.isTestMode() ? Paths.get(System.getProperty("user.dir")) : null); } /** - * @return An Instance of sdefault {@link PropertyPlaceholderResolver} to resolve placeholders. + * @return An Instance of default {@link PropertyPlaceholderResolver} to resolve placeholders. */ public static PropertyPlaceholderResolver getPropertyPlaceholderResolver() { if (propertyPlaceholderResolver == null) { diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiApplicationVisitorSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiApplicationVisitorSpec.groovy index 3aea8d4929..069a9b4d25 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiApplicationVisitorSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiApplicationVisitorSpec.groovy @@ -786,4 +786,75 @@ class MyBean {} withRef.scheme == null withRef.$ref == '#/components/securitySchemes/foo' } + + void "test disable openapi"() { + + given: "An API definition" + Utils.testReference = null + Utils.testReferenceAfterPlaceholders = null + System.setProperty(OpenApiApplicationVisitor.MICRONAUT_OPENAPI_ENABLED, "false") + + when: + buildBeanDefinition('test.MyBean', ''' +package test; + +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Post; +import io.reactivex.Single; +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.media.Schema; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * @author graemerocher + * @since 1.0 + */ +@Controller("/pets") +interface PetOperations { + + @Post + Single save(String name, int age); +} + +class Pet { + + private int privateAge; + protected int protectedAge; + int packageAge; + + public int age; + + // ignored by json + @JsonIgnore + public int ignored; + // hidden by swagger + @Hidden + public int hidden; + // hidden by swagger + @Schema(hidden = true) + public int hidden2; + + // private should not be included + private String name; + + // protected should not be included + protected String protectme; + + // static should not be included + public static String CONST; +} + +@jakarta.inject.Singleton +class MyBean {} +''') + then: "the state is correct" + !Utils.testReference + !Utils.testReferenceAfterPlaceholders + + cleanup: + System.clearProperty(OpenApiApplicationVisitor.MICRONAUT_OPENAPI_FIELD_VISIBILITY_LEVEL) + System.clearProperty(OpenApiApplicationVisitor.MICRONAUT_OPENAPI_ENABLED) + } + }