-
Couldn't load subscription status.
- Fork 107
Convert Spring Fox's ApiInfoBuilder into Swagger's Info
#805
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 9 commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
fb1a4c4
prototype recipe for transforming programmatic springfox to springdoc
dsgrieve 42458a8
progress on ApiInfo to Info recipe
dsgrieve 9cf4dae
fix where name and url are added
dsgrieve a0b1321
Add early returns to appease the bot
timtebeek f6c7f96
Move recipe to defaults method
timtebeek 4d7d911
accumulate method calls to reconstruct fluent chain
dsgrieve 95c90c0
remove unused matchers
dsgrieve 03f32a6
Apply suggestions from code review
dsgrieve 5be7c89
Separate the concerns in converting ApiInfoBuilder
timtebeek 54362cc
Autoformat after removing `build()`
timtebeek 6da5c44
Update test type table
timtebeek a65a3b3
Remove unused imports
timtebeek 4e1befc
Update recipe descriptions
timtebeek e5c91b1
Definitely autoformat
timtebeek c3b2856
add test
dsgrieve 27f5ed3
unit test updates
dsgrieve 2c5d911
apply Tim's suggestions for handling license
dsgrieve b5f0ab1
Format input and outputs
timtebeek f0445e8
Merge the template methods into the apply
timtebeek 7ea7948
Rename test class to match declarative recipe
timtebeek ebd8bcd
Delete older approach TransformApiInfo.java
timtebeek 208058a
No need for `mavenProject` here
timtebeek File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
54 changes: 54 additions & 0 deletions
54
src/main/java/org/openrewrite/java/spring/swagger/RemoveBuild.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| /* | ||
| * Copyright 2025 the original author or authors. | ||
| * <p> | ||
| * Licensed under the Moderne Source Available License (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * <p> | ||
| * https://docs.moderne.io/licensing/moderne-source-available-license | ||
| * <p> | ||
| * 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.java.spring.swagger; | ||
|
|
||
| import org.openrewrite.ExecutionContext; | ||
| import org.openrewrite.Preconditions; | ||
| import org.openrewrite.Recipe; | ||
| import org.openrewrite.TreeVisitor; | ||
| import org.openrewrite.java.JavaParser; | ||
| import org.openrewrite.java.JavaTemplate; | ||
timtebeek marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| import org.openrewrite.java.JavaVisitor; | ||
| import org.openrewrite.java.MethodMatcher; | ||
| import org.openrewrite.java.search.UsesMethod; | ||
| import org.openrewrite.java.tree.J; | ||
|
|
||
| public class RemoveBuild extends Recipe { | ||
| private static final MethodMatcher BUILD_MATCHER = new MethodMatcher("springfox.documentation.builders.ApiInfoBuilder build()"); | ||
|
|
||
| @Override | ||
| public String getDisplayName() { | ||
| return "Replace SpringDoc `Contact` with Swagger `Contact`"; | ||
| } | ||
|
|
||
| @Override | ||
| public String getDescription() { | ||
| return "Replace three argument constructor with immutable builder."; | ||
| } | ||
|
|
||
| @Override | ||
| public TreeVisitor<?, ExecutionContext> getVisitor() { | ||
| return Preconditions.check(new UsesMethod<>(BUILD_MATCHER), new JavaVisitor<ExecutionContext>() { | ||
| @Override | ||
| public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { | ||
| if (BUILD_MATCHER.matches(method)) { | ||
| return method.getSelect().withPrefix(method.getPrefix()); | ||
| } | ||
| return super.visitMethodInvocation(method, ctx); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
65 changes: 65 additions & 0 deletions
65
src/main/java/org/openrewrite/java/spring/swagger/ReplaceContact.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| /* | ||
| * Copyright 2025 the original author or authors. | ||
| * <p> | ||
| * Licensed under the Moderne Source Available License (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * <p> | ||
| * https://docs.moderne.io/licensing/moderne-source-available-license | ||
| * <p> | ||
| * 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.java.spring.swagger; | ||
|
|
||
| import org.jspecify.annotations.Nullable; | ||
| import org.openrewrite.ExecutionContext; | ||
timtebeek marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| import org.openrewrite.Preconditions; | ||
| import org.openrewrite.Recipe; | ||
| import org.openrewrite.TreeVisitor; | ||
| import org.openrewrite.java.JavaParser; | ||
| import org.openrewrite.java.JavaTemplate; | ||
| import org.openrewrite.java.JavaVisitor; | ||
| import org.openrewrite.java.MethodMatcher; | ||
| import org.openrewrite.java.search.UsesMethod; | ||
| import org.openrewrite.java.tree.Expression; | ||
| import org.openrewrite.java.tree.J; | ||
| import org.openrewrite.java.tree.JRightPadded; | ||
timtebeek marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| import org.openrewrite.java.tree.Space; | ||
|
|
||
| public class ReplaceContact extends Recipe { | ||
| private static final MethodMatcher CONTACT_MATCHER = new MethodMatcher("springfox.documentation.service.Contact <constructor>(String, String, String)"); | ||
|
|
||
| @Override | ||
| public String getDisplayName() { | ||
| return "Replace SpringDoc `Contact` with Swagger `Contact`"; | ||
| } | ||
|
|
||
| @Override | ||
| public String getDescription() { | ||
| return "Replace three argument constructor with immutable builder."; | ||
| } | ||
|
|
||
| @Override | ||
| public TreeVisitor<?, ExecutionContext> getVisitor() { | ||
| return Preconditions.check(new UsesMethod<>(CONTACT_MATCHER), new JavaVisitor<ExecutionContext>() { | ||
| // Replace `Contact` constructor | ||
| @Override | ||
| public J visitNewClass(J.NewClass newClass, ExecutionContext ctx) { | ||
| if (CONTACT_MATCHER.matches(newClass)) { | ||
| maybeRemoveImport("springfox.documentation.service.Contact"); | ||
| maybeAddImport("io.swagger.v3.oas.models.info.Contact"); | ||
| return JavaTemplate.builder("new Contact().name(#{any(String)}).url(#{any(String)}).email(#{any(String)})") | ||
| .imports("io.swagger.v3.oas.models.info.Contact") | ||
| .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "swagger-models")) | ||
| .build() | ||
| .apply(getCursor(), newClass.getCoordinates().replace(), newClass.getArguments().toArray()); | ||
| } | ||
| return super.visitNewClass(newClass, ctx); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
80 changes: 80 additions & 0 deletions
80
src/main/java/org/openrewrite/java/spring/swagger/ReplaceLicenseUrl.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| /* | ||
| * Copyright 2025 the original author or authors. | ||
| * <p> | ||
| * Licensed under the Moderne Source Available License (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * <p> | ||
| * https://docs.moderne.io/licensing/moderne-source-available-license | ||
| * <p> | ||
| * 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.java.spring.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.JavaParser; | ||
| import org.openrewrite.java.JavaTemplate; | ||
| import org.openrewrite.java.JavaVisitor; | ||
| import org.openrewrite.java.MethodMatcher; | ||
| import org.openrewrite.java.search.UsesMethod; | ||
| import org.openrewrite.java.tree.Expression; | ||
| import org.openrewrite.java.tree.J; | ||
| import org.openrewrite.java.tree.JRightPadded; | ||
| import org.openrewrite.java.tree.Space; | ||
timtebeek marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| public class ReplaceLicenseUrl extends Recipe { | ||
| private static final MethodMatcher LICENSE_MATCHER = new MethodMatcher("springfox.documentation.builders.ApiInfoBuilder license(String)"); | ||
| private static final MethodMatcher LICENSEURL_MATCHER = new MethodMatcher("springfox.documentation.builders.ApiInfoBuilder licenseUrl(String)"); | ||
|
|
||
| @Override | ||
| public String getDisplayName() { | ||
| return "Transform SpringFox `ApiInfo` to Swagger v3 `Info`"; | ||
| } | ||
|
|
||
| @Override | ||
| public String getDescription() { | ||
| return "Transforms SpringFox `ApiInfoBuilder` to Swagger v3 `Info` fluent API pattern."; | ||
| } | ||
|
|
||
| @Override | ||
| public TreeVisitor<?, ExecutionContext> getVisitor() { | ||
| TreeVisitor<?, ExecutionContext> preconditions = Preconditions.or(new UsesMethod<>(LICENSE_MATCHER), new UsesMethod<>(LICENSEURL_MATCHER)); | ||
| return Preconditions.check(preconditions, new JavaVisitor<ExecutionContext>() { | ||
| @Nullable Expression license; | ||
| @Nullable Expression licenseUrl; | ||
|
|
||
| @Override | ||
| public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { | ||
| J.MethodInvocation mi = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); | ||
| if (LICENSE_MATCHER.matches(mi)) { | ||
| license = mi.getArguments().get(0); | ||
| } else if (LICENSEURL_MATCHER.matches(mi)) { | ||
timtebeek marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| licenseUrl = mi.getArguments().get(0); | ||
| } else { | ||
| return mi; | ||
| } | ||
|
|
||
| // Combine `license` & `licenseUrl` | ||
| if (license != null && licenseUrl != null) { | ||
| maybeAddImport("io.swagger.v3.oas.models.info.License"); | ||
| return JavaTemplate.builder("#{any(io.swagger.v3.oas.models.info.Info)}\n.license(new License().name(#{any(String)}).url(#{any(String)}))") | ||
| .imports("io.swagger.v3.oas.models.info.License") | ||
| .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "swagger-models")) | ||
| .build() | ||
| .apply(getCursor(), mi.getCoordinates().replace(), mi.getSelect(), license, licenseUrl); | ||
| } | ||
|
|
||
| // Remove the method itself already | ||
| return mi.getSelect(); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
154 changes: 154 additions & 0 deletions
154
src/main/java/org/openrewrite/java/spring/swagger/TransformApiInfo.java
dsgrieve marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,154 @@ | ||
| /* | ||
| * Copyright 2025 the original author or authors. | ||
| * <p> | ||
| * Licensed under the Moderne Source Available License (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * <p> | ||
| * https://docs.moderne.io/licensing/moderne-source-available-license | ||
| * <p> | ||
| * 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.java.spring.swagger; | ||
|
|
||
| import org.openrewrite.ExecutionContext; | ||
| import org.openrewrite.Preconditions; | ||
| import org.openrewrite.Recipe; | ||
| import org.openrewrite.TreeVisitor; | ||
| import org.openrewrite.java.ChangeType; | ||
| import org.openrewrite.java.JavaIsoVisitor; | ||
| import org.openrewrite.java.JavaTemplate; | ||
| import org.openrewrite.java.MethodMatcher; | ||
| import org.openrewrite.java.search.UsesType; | ||
| import org.openrewrite.java.tree.J; | ||
timtebeek marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
timtebeek marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| import org.openrewrite.java.tree.TypeUtils; | ||
|
|
||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
| import java.util.function.BiFunction; | ||
| import java.util.function.Function; | ||
|
|
||
| import static java.util.stream.Collectors.joining; | ||
|
|
||
| public class TransformApiInfo extends Recipe { | ||
dsgrieve marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
dsgrieve marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| @Override | ||
| public String getDisplayName() { | ||
| return "Transform SpringFox `ApiInfo` to Swagger v3 `Info`"; | ||
| } | ||
|
|
||
| @Override | ||
| public String getDescription() { | ||
| return "Transforms SpringFox `ApiInfoBuilder` to Swagger v3 `Info` fluent API pattern."; | ||
| } | ||
|
|
||
| @Override | ||
| public TreeVisitor<?, ExecutionContext> getVisitor() { | ||
| return Preconditions.check( | ||
| new UsesType<>("springfox.documentation.builders.ApiInfoBuilder", true), | ||
| new TransformApiInfoVisitor() | ||
| ); | ||
| } | ||
|
|
||
| private static class TransformApiInfoVisitor extends JavaIsoVisitor<ExecutionContext> { | ||
| private final MethodMatcher apiInfoBuilderConstructor = new MethodMatcher("springfox.documentation.builders.ApiInfoBuilder <constructor>()"); | ||
|
|
||
| // accumulator for AppInfoBuilder methods | ||
| private static class Accumulator { | ||
| private final Map<String, Function<J.MethodInvocation, String>> transformations = new HashMap<>(); | ||
dsgrieve marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| private final Map<String, J.MethodInvocation> methodInvocations = new HashMap<>(); | ||
| private final BiFunction<J.MethodInvocation, J.MethodInvocation, String> licenseTransformer = | ||
| (name, url) -> { | ||
| if (name == null && url == null) { | ||
| return ""; | ||
| } | ||
| StringBuilder sb = new StringBuilder(".license(new License()"); | ||
| if (name != null) { | ||
| sb.append(".name(\"").append(name.getArguments().get(0)).append("\")"); | ||
| } | ||
| if (url != null) { | ||
| sb.append(".url(\"").append(url.getArguments().get(0)).append("\")"); | ||
| } | ||
| sb.append(')'); | ||
| return sb.toString(); | ||
| }; | ||
dsgrieve marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| private Accumulator() { | ||
| transformations.put("termsOfServiceUrl", mi -> String.format("termsOfService(\"%s\")", mi.getArguments().get(0))); | ||
| transformations.put("license", mi -> String.format("name(\"%s\")", mi.getArguments().get(0))); | ||
| transformations.put("licenseUrl", mi -> String.format("url(\"%s\")", mi.getArguments().get(0))); | ||
| transformations.put("contact", mi -> { | ||
| J.NewClass c = (J.NewClass) mi.getArguments().get(0); | ||
| return String.format("contact(new Contact().name(\"%s\").url(\"%s\").email(\"%s\"))", c.getArguments().get(0), c.getArguments().get(1), c.getArguments().get(2)); | ||
| }); | ||
| } | ||
|
|
||
| public void add(J.MethodInvocation mi) { | ||
| if (!"build".equals(mi.getSimpleName())) { | ||
| methodInvocations.put(mi.getSimpleName(), mi); | ||
| } | ||
| } | ||
|
|
||
| public String toTemplate() { | ||
|
|
||
| // special case for license and licenseUrl | ||
| J.MethodInvocation licenseMi = methodInvocations.remove("license"); | ||
| J.MethodInvocation licenseUrlMi = methodInvocations.remove("licenseUrl"); | ||
| String licenseFormat = licenseTransformer.apply(licenseMi, licenseUrlMi); | ||
|
|
||
| String template = methodInvocations.entrySet().stream() | ||
| .map(e -> transformations.getOrDefault(e.getKey(), | ||
| m -> String.format("%s(\"%s\")",m.getSimpleName(),m.getArguments().get(0))).apply(e.getValue())) | ||
| .collect(joining(".")); | ||
|
|
||
| return licenseFormat.isEmpty() ? template : template.concat(licenseFormat); | ||
| } | ||
| } | ||
| Accumulator accumulator = new Accumulator(); | ||
|
|
||
| @Override | ||
| public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { | ||
| J.MethodInvocation m = super.visitMethodInvocation(method, ctx); | ||
| accumulator.add(m); | ||
| return m; | ||
| } | ||
|
|
||
| @Override | ||
| public J.NewClass visitNewClass(J.NewClass newClass, ExecutionContext ctx) { | ||
| J.NewClass n = super.visitNewClass(newClass, ctx); | ||
|
|
||
| if (apiInfoBuilderConstructor.matches(n)) { | ||
| maybeAddImport("io.swagger.v3.oas.models.info.Info"); | ||
| maybeAddImport("io.swagger.v3.oas.models.info.License"); | ||
| maybeAddImport("io.swagger.v3.oas.models.info.Contact"); | ||
| maybeRemoveImport("springfox.documentation.builders.ApiInfoBuilder"); | ||
| return JavaTemplate.builder("new Info()") | ||
| .imports("io.swagger.v3.oas.models.info.Info") | ||
| .imports("io.swagger.v3.oas.models.info.License") | ||
| .imports("io.swagger.v3.oas.models.info.Contact") | ||
| .build() | ||
| .apply(getCursor(), n.getCoordinates().replace()); | ||
| } | ||
|
|
||
| return n; | ||
| } | ||
|
|
||
| @Override | ||
| public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { | ||
| J.MethodDeclaration md = super.visitMethodDeclaration(method, ctx); | ||
|
|
||
| // Transform return type from ApiInfo to Info | ||
| if (TypeUtils.isOfClassType(md.getType(), "springfox.documentation.service.ApiInfo")) { | ||
| // doAfterVisit here... | ||
| ChangeType changeType = new ChangeType("springfox.documentation.service.ApiInfo", "io.swagger.v3.oas.models.info.Info", true); | ||
| return (J.MethodDeclaration) changeType.getVisitor().visitNonNull(md, ctx); | ||
| } | ||
|
|
||
| return md; | ||
| } | ||
| } | ||
| } | ||
20 changes: 20 additions & 0 deletions
20
src/main/java/org/openrewrite/java/spring/swagger/package-info.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| /* | ||
| * Copyright 2025 the original author or authors. | ||
| * <p> | ||
| * Licensed under the Moderne Source Available License (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * <p> | ||
| * https://docs.moderne.io/licensing/moderne-source-available-license | ||
| * <p> | ||
| * 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. | ||
| */ | ||
| @NullMarked @NonNullFields | ||
| package org.openrewrite.java.spring.swagger; | ||
|
|
||
| import org.jspecify.annotations.NullMarked; | ||
| import org.openrewrite.internal.lang.NonNullFields; |
Binary file not shown.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.