diff --git a/build.gradle.kts b/build.gradle.kts
index 0d0c30d..c6e8b0e 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -21,6 +21,7 @@ dependencies {
testImplementation("org.junit.jupiter:junit-jupiter-engine:latest.release")
testRuntimeOnly("io.swagger:swagger-annotations:1.6.13")
+ testRuntimeOnly("jakarta.ws.rs:jakarta.ws.rs-api:3.1.0")
testRuntimeOnly("org.gradle:gradle-tooling-api:latest.release")
}
diff --git a/src/main/java/org/openrewrite/openapi/swagger/AnnotationUtils.java b/src/main/java/org/openrewrite/openapi/swagger/AnnotationUtils.java
new file mode 100644
index 0000000..104bc31
--- /dev/null
+++ b/src/main/java/org/openrewrite/openapi/swagger/AnnotationUtils.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.openrewrite.openapi.swagger;
+
+import lombok.experimental.UtilityClass;
+import org.openrewrite.java.tree.Expression;
+import org.openrewrite.java.tree.J;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static java.util.Collections.emptyMap;
+
+@UtilityClass
+class AnnotationUtils {
+ public static Map extractAnnotationArgumentAssignments(J.Annotation annotation) {
+ if (annotation.getArguments() == null ||
+ annotation.getArguments().isEmpty() ||
+ annotation.getArguments().get(0) instanceof J.Empty) {
+ return emptyMap();
+ }
+ Map map = new HashMap<>();
+ for (Expression expression : annotation.getArguments()) {
+ if (expression instanceof J.Assignment) {
+ J.Assignment a = (J.Assignment) expression;
+ String simpleName = ((J.Identifier) a.getVariable()).getSimpleName();
+ map.put(simpleName, a.getAssignment());
+ }
+ }
+ return map;
+ }
+}
diff --git a/src/main/java/org/openrewrite/openapi/swagger/MigrateApiToTag.java b/src/main/java/org/openrewrite/openapi/swagger/MigrateApiToTag.java
index 214d240..4194bac 100644
--- a/src/main/java/org/openrewrite/openapi/swagger/MigrateApiToTag.java
+++ b/src/main/java/org/openrewrite/openapi/swagger/MigrateApiToTag.java
@@ -27,11 +27,9 @@
import org.openrewrite.java.tree.J;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import static java.util.Collections.emptyMap;
import static java.util.Comparator.comparing;
import static java.util.Objects.requireNonNull;
@@ -42,31 +40,33 @@ public class MigrateApiToTag extends Recipe {
private static final String FQN_TAGS = "io.swagger.v3.oas.annotations.tags.Tags";
@Language("java")
- private static final String TAGS_CLASS = "package io.swagger.v3.oas.annotations.tags;\n" +
- "import java.lang.annotation.ElementType;\n" +
- "import java.lang.annotation.Retention;\n" +
- "import java.lang.annotation.RetentionPolicy;\n" +
- "import java.lang.annotation.Target;\n" +
- "@Target({ElementType.METHOD, ElementType.TYPE, ElementType.ANNOTATION_TYPE})\n" +
- "@Retention(RetentionPolicy.RUNTIME)\n" +
- "public @interface Tags {\n" +
- " Tag[] value() default {};\n" +
- "}";
+ private static final String TAGS_CLASS =
+ "package io.swagger.v3.oas.annotations.tags;\n" +
+ "import java.lang.annotation.ElementType;\n" +
+ "import java.lang.annotation.Retention;\n" +
+ "import java.lang.annotation.RetentionPolicy;\n" +
+ "import java.lang.annotation.Target;\n" +
+ "@Target({ElementType.METHOD, ElementType.TYPE, ElementType.ANNOTATION_TYPE})\n" +
+ "@Retention(RetentionPolicy.RUNTIME)\n" +
+ "public @interface Tags {\n" +
+ " Tag[] value() default {};\n" +
+ "}";
@Language("java")
- private static final String TAG_CLASS = "package io.swagger.v3.oas.annotations.tags;\n" +
- "import java.lang.annotation.ElementType;\n" +
- "import java.lang.annotation.Repeatable;\n" +
- "import java.lang.annotation.Retention;\n" +
- "import java.lang.annotation.RetentionPolicy;\n" +
- "import java.lang.annotation.Target;\n" +
- "@Target({ElementType.METHOD, ElementType.TYPE, ElementType.ANNOTATION_TYPE})\n" +
- "@Retention(RetentionPolicy.RUNTIME)\n" +
- "@Repeatable(Tags.class)\n" +
- "public @interface Tag {\n" +
- " String name();\n" +
- " String description() default \"\";\n" +
- "}";
+ private static final String TAG_CLASS =
+ "package io.swagger.v3.oas.annotations.tags;\n" +
+ "import java.lang.annotation.ElementType;\n" +
+ "import java.lang.annotation.Repeatable;\n" +
+ "import java.lang.annotation.Retention;\n" +
+ "import java.lang.annotation.RetentionPolicy;\n" +
+ "import java.lang.annotation.Target;\n" +
+ "@Target({ElementType.METHOD, ElementType.TYPE, ElementType.ANNOTATION_TYPE})\n" +
+ "@Retention(RetentionPolicy.RUNTIME)\n" +
+ "@Repeatable(Tags.class)\n" +
+ "public @interface Tag {\n" +
+ " String name();\n" +
+ " String description() default \"\";\n" +
+ "}";
@Override
public String getDisplayName() {
@@ -89,7 +89,7 @@ public TreeVisitor, ExecutionContext> getVisitor() {
public J.@Nullable Annotation visitAnnotation(J.Annotation annotation, ExecutionContext ctx) {
J.Annotation ann = super.visitAnnotation(annotation, ctx);
if (apiMatcher.matches(ann)) {
- Map annotationArgumentAssignments = extractAnnotationArgumentAssignments(ann);
+ Map annotationArgumentAssignments = AnnotationUtils.extractAnnotationArgumentAssignments(ann);
if (annotationArgumentAssignments.get("tags") != null) {
// Remove @Api and add @Tag or @Tags at class level
getCursor().putMessageOnFirstEnclosing(J.ClassDeclaration.class, FQN_API, annotationArgumentAssignments);
@@ -102,23 +102,6 @@ public TreeVisitor, ExecutionContext> getVisitor() {
return ann;
}
- private Map extractAnnotationArgumentAssignments(J.Annotation apiAnnotation) {
- if (apiAnnotation.getArguments() == null ||
- apiAnnotation.getArguments().isEmpty() ||
- apiAnnotation.getArguments().get(0) instanceof J.Empty) {
- return emptyMap();
- }
- Map map = new HashMap<>();
- for (Expression expression : apiAnnotation.getArguments()) {
- if (expression instanceof J.Assignment) {
- J.Assignment a = (J.Assignment) expression;
- String simpleName = ((J.Identifier) a.getVariable()).getSimpleName();
- map.put(simpleName, a.getAssignment());
- }
- }
- return map;
- }
-
@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx);
diff --git a/src/main/java/org/openrewrite/openapi/swagger/MigrateSwaggerDefinitionToOpenAPIDefinition.java b/src/main/java/org/openrewrite/openapi/swagger/MigrateSwaggerDefinitionToOpenAPIDefinition.java
new file mode 100644
index 0000000..6b35f18
--- /dev/null
+++ b/src/main/java/org/openrewrite/openapi/swagger/MigrateSwaggerDefinitionToOpenAPIDefinition.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.openrewrite.openapi.swagger;
+
+import org.jspecify.annotations.Nullable;
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.Preconditions;
+import org.openrewrite.Recipe;
+import org.openrewrite.TreeVisitor;
+import org.openrewrite.java.*;
+import org.openrewrite.java.search.UsesType;
+import org.openrewrite.java.tree.Expression;
+import org.openrewrite.java.tree.J;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class MigrateSwaggerDefinitionToOpenAPIDefinition extends Recipe {
+
+ private static final String FQN_SWAGGER_DEFINITION = "io.swagger.annotations.SwaggerDefinition";
+ private static final String FQN_OPENAPI_DEFINITION = "io.swagger.v3.oas.annotations.OpenAPIDefinition";
+ private static final String FQN_SERVER = "io.swagger.v3.oas.annotations.servers.Server";
+
+ @Override
+ public String getDisplayName() {
+ return "Migrate from `@SwaggerDefinition` to `@OpenAPIDefinition`";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Migrate from `@SwaggerDefinition` to `@OpenAPIDefinition`.";
+ }
+
+ @Override
+ public TreeVisitor, ExecutionContext> getVisitor() {
+ return Preconditions.check(
+ new UsesType<>(FQN_SWAGGER_DEFINITION, false),
+ new JavaIsoVisitor() {
+ private final AnnotationMatcher annotationMatcher = new AnnotationMatcher(FQN_SWAGGER_DEFINITION);
+
+ @Override
+ public J.@Nullable Annotation visitAnnotation(J.Annotation annotation, ExecutionContext ctx) {
+ J.Annotation ann = super.visitAnnotation(annotation, ctx);
+
+ if (annotationMatcher.matches(ann)) {
+ Map args = AnnotationUtils.extractAnnotationArgumentAssignments(ann);
+
+ StringBuilder tpl = new StringBuilder("@OpenAPIDefinition(\n");
+ List tplArgs = new ArrayList<>();
+ List parts = new ArrayList<>();
+
+ Expression basePath = args.get("basePath");
+ Expression host = args.get("host");
+ Expression schemes = args.get("schemes");
+ String servers = "";
+ if (basePath != null && host != null && schemes != null) {
+ tpl.append("servers = {\n");
+ if (schemes instanceof J.FieldAccess) {
+ servers += "@Server(url = \"" + ((J.FieldAccess) schemes).getSimpleName().toLowerCase() + "://" + host + basePath + "\")";
+ } else if (schemes instanceof J.NewArray) {
+ for (Expression scheme : ((J.NewArray) schemes).getInitializer()) {
+ if (!servers.isEmpty()) {
+ servers += ",\n";
+ }
+ String schemeName = ((J.FieldAccess) scheme).getSimpleName().toLowerCase();
+ servers += "@Server(url = \"" + schemeName + "://" + host + basePath + "\")";
+ }
+ }
+ servers += "\n}";
+ parts.add(servers);
+ }
+
+ args.remove("basePath");
+ args.remove("host");
+ args.remove("schemes");
+ args.remove("produces");
+ args.remove("consumes");
+ for (Map.Entry arg : args.entrySet()) {
+ parts.add(arg.getKey() + " = #{any()}");
+ tplArgs.add(arg.getValue());
+ }
+ tpl.append(String.join(",\n", parts));
+ tpl.append("\n)");
+
+ ann = JavaTemplate.builder(tpl.toString())
+ .imports(FQN_OPENAPI_DEFINITION, FQN_SERVER)
+ .javaParser(JavaParser.fromJavaVersion().classpath("swagger-annotations"))
+ .build()
+ .apply(updateCursor(ann), ann.getCoordinates().replace(), tplArgs.toArray());
+ maybeRemoveImport(FQN_SWAGGER_DEFINITION);
+ maybeAddImport(FQN_OPENAPI_DEFINITION, false);
+ maybeAddImport(FQN_SERVER, false);
+ ann = maybeAutoFormat(annotation, ann, ctx);
+ }
+
+ doAfterVisit(new RemoveUnusedImports().getVisitor());
+ return ann;
+ }
+ }
+ );
+ }
+}
diff --git a/src/main/resources/META-INF/rewrite/swagger-2.yml b/src/main/resources/META-INF/rewrite/swagger-2.yml
index c56a588..0865a56 100644
--- a/src/main/resources/META-INF/rewrite/swagger-2.yml
+++ b/src/main/resources/META-INF/rewrite/swagger-2.yml
@@ -43,6 +43,9 @@ recipeList:
- org.openrewrite.java.ChangeType:
oldFullyQualifiedTypeName: io.swagger.annotations.Tag
newFullyQualifiedTypeName: io.swagger.v3.oas.annotations.tags.Tag
+ - org.openrewrite.java.ChangeType:
+ oldFullyQualifiedTypeName: io.swagger.annotations.Info
+ newFullyQualifiedTypeName: io.swagger.v3.oas.annotations.info.Info
- org.openrewrite.java.ChangeType:
oldFullyQualifiedTypeName: springfox.documentation.annotations.ApiIgnore
newFullyQualifiedTypeName: io.swagger.v3.oas.annotations.Hidden
@@ -53,6 +56,7 @@ recipeList:
- org.openrewrite.openapi.swagger.MigrateApiParamToParameter
- org.openrewrite.openapi.swagger.MigrateApiModelPropertyToSchema
- org.openrewrite.openapi.swagger.MigrateApiModelToSchema
+ - org.openrewrite.openapi.swagger.MigrateSwaggerDefinitionToOpenAPIDefinition
# todo add swagger-core to common-dependencies
diff --git a/src/test/java/org/openrewrite/openapi/swagger/SwaggerToOpenAPITest.java b/src/test/java/org/openrewrite/openapi/swagger/SwaggerToOpenAPITest.java
index b48d1f1..a0f8ea4 100644
--- a/src/test/java/org/openrewrite/openapi/swagger/SwaggerToOpenAPITest.java
+++ b/src/test/java/org/openrewrite/openapi/swagger/SwaggerToOpenAPITest.java
@@ -30,7 +30,7 @@ class SwaggerToOpenAPITest implements RewriteTest {
@Override
public void defaults(RecipeSpec spec) {
spec.recipeFromResources("org.openrewrite.openapi.swagger.SwaggerToOpenAPI")
- .parser(JavaParser.fromJavaVersion().classpath("swagger-annotations-1.+", "swagger-annotations-2.+"));
+ .parser(JavaParser.fromJavaVersion().classpath("swagger-annotations-1.+", "swagger-annotations-2.+", "rs-api"));
}
@Test
@@ -47,8 +47,6 @@ void shouldChangeSwaggerArtifacts() {
//language=java
java(
"""
- package example.org;
-
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@@ -59,8 +57,6 @@ class Example {
}
""",
"""
- package example.org;
-
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(name="ApiModelExampleValue", description="ApiModelExampleDescription")
@@ -158,4 +154,82 @@ public void create(Example foo) {
)
);
}
+
+ @Test
+ void migrateSwaggerDefinitionsToOpenAPIDefinitionSingleSchema() {
+ rewriteRun(
+ //language=java
+ java(
+ """
+ import io.swagger.annotations.Info;
+ import io.swagger.annotations.SwaggerDefinition;
+ import jakarta.ws.rs.core.MediaType;
+
+ @SwaggerDefinition(
+ basePath = "/api",
+ host="example.com",
+ info = @Info(title = "Example", version = "V1.0"),
+ consumes = { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML },
+ produces = { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML },
+ schemes = SwaggerDefinition.Scheme.HTTPS
+ )
+ class Example {
+ }
+ """,
+ """
+ import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+ import io.swagger.v3.oas.annotations.info.Info;
+ import io.swagger.v3.oas.annotations.servers.Server;
+
+ @OpenAPIDefinition(
+ servers = {
+ @Server(url = "https://example.com/api")
+ },
+ info = @Info(title = "Example", version = "V1.0")
+ )
+ class Example {
+ }
+ """
+ )
+ );
+ }
+
+ @Test
+ void migrateSwaggerDefinitionsToOpenAPIDefinitionMultipleSchema() {
+ rewriteRun(
+ //language=java
+ java(
+ """
+ import io.swagger.annotations.Info;
+ import io.swagger.annotations.SwaggerDefinition;
+ import jakarta.ws.rs.core.MediaType;
+
+ @SwaggerDefinition(
+ basePath = "/api",
+ host="example.com",
+ info = @Info(title = "Example", version = "V1.0"),
+ consumes = { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML },
+ produces = { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML },
+ schemes = { SwaggerDefinition.Scheme.HTTP, SwaggerDefinition.Scheme.HTTPS })
+ class Example {
+ }
+ """,
+ """
+ import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+ import io.swagger.v3.oas.annotations.info.Info;
+ import io.swagger.v3.oas.annotations.servers.Server;
+
+ @OpenAPIDefinition(
+ servers = {
+ @Server(url = "http://example.com/api"),
+ @Server(url = "https://example.com/api")
+ },
+ info = @Info(title = "Example", version = "V1.0")
+ )
+ class Example {
+ }
+ """
+ )
+ );
+ }
}