diff --git a/build.gradle.kts b/build.gradle.kts index e6e43aa..fabd831 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,5 +18,8 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-engine:latest.release") + testRuntimeOnly("io.swagger:swagger-annotations:1.6.13") + testRuntimeOnly("io.swagger.core.v3:swagger-annotations:2.2.20") + testRuntimeOnly("org.gradle:gradle-tooling-api:latest.release") } diff --git a/src/main/java/org/openrewrite/openapi/swagger/ConvertApiResponseCodesToStrings.java b/src/main/java/org/openrewrite/openapi/swagger/ConvertApiResponseCodesToStrings.java new file mode 100644 index 0000000..c40ceb9 --- /dev/null +++ b/src/main/java/org/openrewrite/openapi/swagger/ConvertApiResponseCodesToStrings.java @@ -0,0 +1,82 @@ +/* + * 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.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.AnnotationMatcher; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; + +public class ConvertApiResponseCodesToStrings extends Recipe { + + private static final AnnotationMatcher ANNOTATION_MATCHER = new AnnotationMatcher("@io.swagger.v3.oas.annotations.responses.ApiResponse"); + + @Override + public String getDisplayName() { + return "Convert API response codes to strings"; + } + + @Override + public String getDescription() { + return "Convert API response codes to strings."; + } + + @Override + public TreeVisitor getVisitor() { + // https://docs.swagger.io/swagger-core/v1.5.0/apidocs/io/swagger/annotations/ApiResponse.html + // https://docs.swagger.io/swagger-core/v2.0.0/apidocs/io/swagger/v3/oas/annotations/responses/ApiResponse.html + return Preconditions.check( + new UsesType<>("io.swagger.v3.oas.annotations.responses.ApiResponse", true), + new JavaIsoVisitor() { + @Override + public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext ctx) { + J.Annotation an = super.visitAnnotation(annotation, ctx); + if (ANNOTATION_MATCHER.matches(an)) { + return an.withArguments(ListUtils.map(an.getArguments(), this::maybeReplaceResponseCodeTypeAndValue)); + } + return an; + } + + private Expression maybeReplaceResponseCodeTypeAndValue(Expression arg) { + if (arg instanceof J.Assignment) { + J.Assignment assignment = (J.Assignment) arg; + boolean matchesField = assignment.getVariable() instanceof J.Identifier && + "responseCode".equals(((J.Identifier) assignment.getVariable()).getSimpleName()); + boolean usesNumberLiteral = assignment.getAssignment() instanceof J.Literal && + ((J.Literal) assignment.getAssignment()).getValue() instanceof Number; + if (matchesField && usesNumberLiteral) { + J.Literal assignedLiteral = (J.Literal) assignment.getAssignment(); + return assignment + .withType(JavaType.Primitive.String) + .withAssignment(assignedLiteral + .withValue(String.valueOf(assignedLiteral.getValue())) + .withValueSource("\"" + assignedLiteral.getValue() + "\"") + .withType(JavaType.Primitive.String)); + } + } + return arg; + } + } + ); + } +} diff --git a/src/main/java/org/openrewrite/openapi/package-info.java b/src/main/java/org/openrewrite/openapi/swagger/package-info.java similarity index 94% rename from src/main/java/org/openrewrite/openapi/package-info.java rename to src/main/java/org/openrewrite/openapi/swagger/package-info.java index 3e4da54..7caf322 100644 --- a/src/main/java/org/openrewrite/openapi/package-info.java +++ b/src/main/java/org/openrewrite/openapi/swagger/package-info.java @@ -15,7 +15,7 @@ */ @NonNullApi @NonNullFields -package org.openrewrite.openapi; +package org.openrewrite.openapi.swagger; import org.openrewrite.internal.lang.NonNullApi; import org.openrewrite.internal.lang.NonNullFields; diff --git a/src/main/resources/META-INF/rewrite/category.yml b/src/main/resources/META-INF/rewrite/category.yml index f0bac4e..2795383 100644 --- a/src/main/resources/META-INF/rewrite/category.yml +++ b/src/main/resources/META-INF/rewrite/category.yml @@ -18,4 +18,9 @@ type: specs.openrewrite.org/v1beta/category name: OpenAPI packageName: org.openrewrite.openapi -description: Recipes to perform [OpenAPI](https://openapi.com/) migration tasks. +description: Recipes to perform [OpenAPI](https://www.openapis.org/) migration tasks. +--- +type: specs.openrewrite.org/v1beta/category +name: Swagger +packageName: org.openrewrite.openapi.swagger +description: Recipes to perform [Swagger](https://swagger.io/) migration tasks. diff --git a/src/main/resources/META-INF/rewrite/openapi-3.yml b/src/main/resources/META-INF/rewrite/openapi-3.yml deleted file mode 100644 index bd90bee..0000000 --- a/src/main/resources/META-INF/rewrite/openapi-3.yml +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright 2023 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. -# - ---- -type: specs.openrewrite.org/v1beta/recipe -name: org.openrewrite.openapi.UpgradeOpenAPI3 -displayName: Migrate to OpenAPI 3.x -description: This recipe will apply changes commonly needed when migrating to OpenAPI 3.x. -recipeList: - - org.openrewrite.java.dependencies.UpgradeDependencyVersion: - groupId: org.springdoc - artifactId: "*" - newVersion: 1.7.x \ No newline at end of file diff --git a/src/main/resources/META-INF/rewrite/swagger-2.yml b/src/main/resources/META-INF/rewrite/swagger-2.yml new file mode 100644 index 0000000..71a46eb --- /dev/null +++ b/src/main/resources/META-INF/rewrite/swagger-2.yml @@ -0,0 +1,178 @@ +# +# 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. +# + +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.openapi.swagger.SwaggerToOpenAPI +displayName: Migrate from Swagger to OpenAPI +description: Migrate from Swagger to OpenAPI. +tags: + - swagger + - openapi +recipeList: + # https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations + # https://springdoc.org/#migrating-from-springfox + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: io.swagger.annotations.Tag + newFullyQualifiedTypeName: io.swagger.v3.oas.annotations.tags.Tag + - org.openrewrite.openapi.swagger.MigrateApiOperationToOperation + - org.openrewrite.openapi.swagger.MigrateApiResponsesToApiResponses + - org.openrewrite.openapi.swagger.MigrateApiImplicitParamsToParameters + - org.openrewrite.openapi.swagger.MigrateApiToTag + - org.openrewrite.openapi.swagger.MigrateApiParamToParameter + - org.openrewrite.openapi.swagger.MigrateApiModelPropertyToSchema + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: io.swagger.annotations.ApiModel + newFullyQualifiedTypeName: io.swagger.v3.oas.annotations.media.Schema + +# todo add swagger-core to common-dependencies + +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.openapi.swagger.MigrateApiOperationToOperation +displayName: Migrate from @ApiOperation to @Operation +description: Converts the @ApiOperation annotation to @Operation and converts the directly mappable attributes + and removes the others. +tags: + - swagger + - openapi +recipeList: + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: io.swagger.annotations.ApiOperation + newFullyQualifiedTypeName: io.swagger.v3.oas.annotations.Operation + - org.openrewrite.java.ChangeAnnotationAttributeName: + annotationType: io.swagger.v3.oas.annotations.Operation + oldAttributeName: "notes" + newAttributeName: "description" + - org.openrewrite.java.ChangeAnnotationAttributeName: + annotationType: io.swagger.v3.oas.annotations.Operation + oldAttributeName: "value" + newAttributeName: "summary" + - org.openrewrite.java.RemoveAnnotationAttribute: + annotationType: io.swagger.v3.oas.annotations.Operation + attributeName: response + - org.openrewrite.java.RemoveAnnotationAttribute: + annotationType: io.swagger.v3.oas.annotations.Operation + attributeName: consumes + - org.openrewrite.java.RemoveAnnotationAttribute: + annotationType: io.swagger.v3.oas.annotations.Operation + attributeName: produces + +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.openapi.swagger.MigrateApiResponsesToApiResponses +displayName: Migrate from @ApiResponses to @ApiResponses +description: Changes the namespace of the @ApiResponses and @ApiResponse annotations and converts its attributes + (ex. code -> responseCode, message -> description). +tags: + - swagger + - openapi +recipeList: + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: io.swagger.annotations.ApiResponses + newFullyQualifiedTypeName: io.swagger.v3.oas.annotations.responses.ApiResponses + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: io.swagger.annotations.ApiResponse + newFullyQualifiedTypeName: io.swagger.v3.oas.annotations.responses.ApiResponse + - org.openrewrite.java.ChangeAnnotationAttributeName: + annotationType: io.swagger.v3.oas.annotations.responses.ApiResponse + oldAttributeName: "code" + newAttributeName: "responseCode" + - org.openrewrite.java.ChangeAnnotationAttributeName: + annotationType: io.swagger.v3.oas.annotations.responses.ApiResponse + oldAttributeName: "message" + newAttributeName: "description" + - org.openrewrite.openapi.swagger.ConvertApiResponseCodesToStrings + +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.openapi.swagger.MigrateApiImplicitParamsToParameters +displayName: Migrate from @ApiImplicitParams to @Parameters +description: Converts @ApiImplicitParams to @Parameters and the @ApiImplicitParam annotation to @Parameter and converts + the directly mappable attributes and removes the others. +tags: + - swagger + - openapi +recipeList: + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: io.swagger.annotations.ApiImplicitParams + newFullyQualifiedTypeName: io.swagger.v3.oas.annotations.Parameters + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: io.swagger.annotations.ApiImplicitParam + newFullyQualifiedTypeName: io.swagger.v3.oas.annotations.Parameter + - org.openrewrite.java.ChangeAnnotationAttributeName: + annotationType: io.swagger.v3.oas.annotations.Parameter + oldAttributeName: "value" + newAttributeName: "description" + - org.openrewrite.java.RemoveAnnotationAttribute: + annotationType: io.swagger.v3.oas.annotations.Parameter + attributeName: dataType + - org.openrewrite.java.RemoveAnnotationAttribute: + annotationType: io.swagger.v3.oas.annotations.Parameter + attributeName: paramType + - org.openrewrite.java.RemoveAnnotationAttribute: + annotationType: io.swagger.v3.oas.annotations.Parameter + attributeName: allowMultiple + +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.openapi.swagger.MigrateApiToTag +displayName: Migrate from @Api to @Tag +description: Converts @Api to @Tag annotation and converts the directly mappable attributes and removes the others. +tags: + - swagger + - openapi +recipeList: + - org.openrewrite.java.ChangeAnnotationAttributeName: + annotationType: io.swagger.annotations.Api + oldAttributeName: "value" + newAttributeName: "name" + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: io.swagger.annotations.Api + newFullyQualifiedTypeName: io.swagger.v3.oas.annotations.tags.Tag + +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.openapi.swagger.MigrateApiParamToParameter +displayName: Migrate from @ApiParam to @Parameter +description: Converts the @ApiParam annotation to @Parameter and converts the directly mappable attributes. +tags: + - swagger + - openapi +recipeList: + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: io.swagger.annotations.ApiParam + newFullyQualifiedTypeName: io.swagger.v3.oas.annotations.Parameter + - org.openrewrite.java.ChangeAnnotationAttributeName: + annotationType: io.swagger.v3.oas.annotations.Parameter + oldAttributeName: "value" + newAttributeName: "description" + +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.openapi.swagger.MigrateApiModelPropertyToSchema +displayName: Migrate from @ApiModelProperty to @Schema +description: Converts the @ApiModelProperty annotation to @Schema and converts the "value" attribute to "description". +tags: + - swagger + - openapi +recipeList: + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: io.swagger.annotations.ApiModelProperty + newFullyQualifiedTypeName: io.swagger.v3.oas.annotations.media.Schema + - org.openrewrite.java.ChangeAnnotationAttributeName: + annotationType: io.swagger.v3.oas.annotations.media.Schema + oldAttributeName: "value" + newAttributeName: "description" diff --git a/src/test/java/org/openrewrite/openapi/UpgradeOpenAPI3Test.java b/src/test/java/org/openrewrite/openapi/UpgradeOpenAPI3Test.java deleted file mode 100644 index 8fca599..0000000 --- a/src/test/java/org/openrewrite/openapi/UpgradeOpenAPI3Test.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2023 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; - -import org.junit.jupiter.api.Test; -import org.openrewrite.DocumentExample; -import org.openrewrite.java.JavaParser; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import java.util.regex.Pattern; - -import static org.openrewrite.java.Assertions.java; -import static org.openrewrite.maven.Assertions.pomXml; - -class UpgradeOpenAPI3Test implements RewriteTest { - - @Override - public void defaults(RecipeSpec spec) { - spec.recipeFromResources("org.openrewrite.openapi.UpgradeOpenAPI3") - .parser(JavaParser.fromJavaVersion() -// .classpathFromResources(new InMemoryExecutionContext(),"todo") - ); - } - - @Test - @DocumentExample - void upgradeMaven() { - rewriteRun( - // language=xml - pomXml( - """ - - 4.0.0 - org.example - example - 1.0-SNAPSHOT - - - org.springdoc - springdoc-openapi - 1.5.13 - - - - """, - after -> after.after(actual -> { - String version = Pattern.compile("(1\\.7\\..*)") - .matcher(actual) - .results() - .map(m -> m.group(1)) - .findFirst() - .orElseThrow(); - return """ - - 4.0.0 - org.example - example - 1.0-SNAPSHOT - - - org.springdoc - springdoc-openapi - %s - - - - """.formatted(version); - }) - ) - ); - } -} diff --git a/src/test/java/org/openrewrite/openapi/swagger/ConvertApiResponseCodesToStringsTest.java b/src/test/java/org/openrewrite/openapi/swagger/ConvertApiResponseCodesToStringsTest.java new file mode 100644 index 0000000..8c516ea --- /dev/null +++ b/src/test/java/org/openrewrite/openapi/swagger/ConvertApiResponseCodesToStringsTest.java @@ -0,0 +1,75 @@ +/* + * 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.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.*; + +class ConvertApiResponseCodesToStringsTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.recipeFromResources("org.openrewrite.openapi.swagger.MigrateApiResponsesToApiResponses") + .parser(JavaParser.fromJavaVersion().classpath("swagger-annotations-1.+", "swagger-annotations-2.+")); + } + + @Test + @DocumentExample + void convertApiResponseCodesToStrings() { + rewriteRun( + //language=java + java( + """ + import io.swagger.annotations.ApiResponse; + + class A { + @ApiResponse(code = 200, message = "OK") + void method() {} + } + """, + """ + import io.swagger.v3.oas.annotations.responses.ApiResponse; + + class A { + @ApiResponse(responseCode = "200", description = "OK") + void method() {} + } + """ + ) + ); + } + + @Test + void noChangeOnAlreadyConverted() { + rewriteRun( + //language=java + java( + """ + import io.swagger.v3.oas.annotations.responses.ApiResponse; + + class A { + @ApiResponse(responseCode = "200", description = "OK") + void method() {} + } + """ + ) + ); + } +} \ No newline at end of file diff --git a/src/test/java/org/openrewrite/openapi/swagger/SwaggerToOpenAPITest.java b/src/test/java/org/openrewrite/openapi/swagger/SwaggerToOpenAPITest.java new file mode 100644 index 0000000..78da9b9 --- /dev/null +++ b/src/test/java/org/openrewrite/openapi/swagger/SwaggerToOpenAPITest.java @@ -0,0 +1,36 @@ +/* + * 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.junit.jupiter.api.Test; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +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.+")); + } + + @Test + void loadYamlRecipesToTriggerValidation() { + rewriteRun( + spec-> spec.printRecipe(() -> System.out::println) + ); + } +}