Skip to content

Commit

Permalink
New feature: naming strategy for operations with optional path variables
Browse files Browse the repository at this point in the history
  • Loading branch information
altro3 committed Feb 5, 2025
1 parent 69946f3 commit 0352833
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
import static io.micronaut.openapi.visitor.ElementUtils.isWrappedBodyParameter;
import static io.micronaut.openapi.visitor.GeneratorUtils.addOperationDeprecatedExtension;
import static io.micronaut.openapi.visitor.GeneratorUtils.addParameterDeprecatedExtension;
import static io.micronaut.openapi.visitor.InternalExt.MICRONAUT_OP_POSTFIX;
import static io.micronaut.openapi.visitor.OpenApiModelProp.MICRONAUT_EXT_PARENT_RESPONSE;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_ADD_ALWAYS;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_ALLOW_EMPTY_VALUE;
Expand Down Expand Up @@ -1559,7 +1560,13 @@ private Map<PathItem, Operation> readOperations(String path, HttpMethod httpMeth
}

if (StringUtils.isEmpty(swaggerOperation.getOperationId())) {
swaggerOperation.setOperationId(prefix + element.getName() + suffix);

String postfix = StringUtils.EMPTY_STRING;
if (CollectionUtils.isNotEmpty(pathItem.getExtensions()) && pathItem.getExtensions().containsKey(MICRONAUT_OP_POSTFIX)) {
postfix = pathItem.getExtensions().get(MICRONAUT_OP_POSTFIX).toString();
}

swaggerOperation.setOperationId(prefix + element.getName() + postfix + suffix);
} else if (addAlways) {
swaggerOperation.setOperationId(prefix + swaggerOperation.getOperationId() + suffix);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.Element;
import io.micronaut.inject.visitor.VisitorContext;
import io.micronaut.openapi.visitor.UrlUtils.OpPath;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.PathItem;
Expand All @@ -38,6 +39,7 @@
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static io.micronaut.openapi.visitor.InternalExt.MICRONAUT_OP_POSTFIX;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_NAME;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_SCOPES;
import static io.micronaut.openapi.visitor.SchemaDefinitionUtils.toValue;
Expand Down Expand Up @@ -126,9 +128,14 @@ Map<String, List<PathItem>> resolvePathItems(VisitorContext context, List<UriMat
var segments = parsePathSegments(matchTemplate.toPathString());
var finalPaths = buildUrls(segments, context);

for (String finalPath : finalPaths) {
List<PathItem> resultPathItems = resultPathItemsMap.computeIfAbsent(finalPath, k -> new ArrayList<>());
resultPathItems.add(paths.computeIfAbsent(finalPath, key -> new PathItem()));
for (OpPath finalPath : finalPaths) {
List<PathItem> resultPathItems = resultPathItemsMap.computeIfAbsent(finalPath.url(), k -> new ArrayList<>());
var pathItem = paths.computeIfAbsent(finalPath.url(), key -> new PathItem());
var opIdPostfix = finalPath.opIdPostfix();
if (!opIdPostfix.isEmpty()) {
pathItem.addExtension(MICRONAUT_OP_POSTFIX, opIdPostfix);
}
resultPathItems.add(pathItem);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.micronaut.openapi.visitor;

public interface InternalExt {

String MICRONAUT_OP_POSTFIX = "x-micronaut-op-postfix";
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import java.util.function.BiConsumer;
import java.util.function.Function;

import static io.micronaut.openapi.visitor.InternalExt.MICRONAUT_OP_POSTFIX;
import static io.micronaut.openapi.visitor.OpenApiModelProp.MICRONAUT_EXT_PARENT_RESPONSE;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_DESCRIPTION;
import static io.micronaut.openapi.visitor.SchemaUtils.EMPTY_SIMPLE_SCHEMA;
Expand Down Expand Up @@ -83,6 +84,9 @@ public static void normalizeOpenApi(OpenAPI openAPI, VisitorContext context) {
}
openAPI.setPaths(sortedPaths);
for (PathItem pathItem : sortedPaths.values()) {
if (CollectionUtils.isNotEmpty(pathItem.getExtensions())) {
pathItem.getExtensions().remove(MICRONAUT_OP_POSTFIX);
}
if (CollectionUtils.isEmpty(pathItem.getExtensions())) {
pathItem.setExtensions(null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.micronaut.openapi.visitor;

import io.micronaut.core.annotation.Internal;
import io.micronaut.core.util.StringUtils;

/**
* String utilities.
Expand Down Expand Up @@ -44,4 +45,8 @@ public final class StringUtil {

private StringUtil() {
}

public static String capitalizedPathVar(final String value) {
return StringUtils.capitalize(value.replaceAll("[{}]", StringUtils.EMPTY_STRING));
}
}
85 changes: 66 additions & 19 deletions openapi/src/main/java/io/micronaut/openapi/visitor/UrlUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import static io.micronaut.openapi.visitor.StringUtil.OPEN_BRACE;
import static io.micronaut.openapi.visitor.StringUtil.SLASH;
import static io.micronaut.openapi.visitor.StringUtil.SLASH_CHAR;
import static io.micronaut.openapi.visitor.StringUtil.capitalizedPathVar;
import static io.micronaut.openapi.visitor.UrlUtils.SegmentType.CONST;
import static io.micronaut.openapi.visitor.UrlUtils.SegmentType.OPT_VAR;
import static io.micronaut.openapi.visitor.UrlUtils.SegmentType.PLACEHOLDER;
Expand All @@ -50,9 +51,9 @@ private UrlUtils() {
* @param context visitor context
* @return all possible URL variants by parsed segments.
*/
public static List<String> buildUrls(List<Segment> segments, VisitorContext context) {
public static List<OpPath> buildUrls(List<Segment> segments, VisitorContext context) {

var results = new ArrayList<StringBuilder>();
var results = new ArrayList<PathBuilders>();

Segment prevSegment = null;
for (var segment : segments) {
Expand All @@ -70,9 +71,9 @@ public static List<String> buildUrls(List<Segment> segments, VisitorContext cont
}
}

var resultStrings = new ArrayList<String>();
var resultOpPaths = new ArrayList<OpPath>();
for (var res : results) {
var url = res.toString();
var url = res.urlBuilder.toString();
if (url.endsWith(SLASH) && url.length() > 1) {
url = url.substring(0, url.length() - SLASH.length());
} else if (!url.startsWith(SLASH) && !url.startsWith(DOLLAR)) {
Expand All @@ -85,46 +86,65 @@ public static List<String> buildUrls(List<Segment> segments, VisitorContext cont
url = contextPath + url;
}

if (!resultStrings.contains(url)) {
resultStrings.add(url);
var alreadyAdded = false;
for (var opPath : resultOpPaths) {
if (opPath.url.equals(url)) {
alreadyAdded = true;
break;
}
}
if (alreadyAdded) {
continue;
}

resultOpPaths.add(new OpPath(url, res.opIdBuilder.toString()));
}

return resultStrings;
return resultOpPaths;
}

private static void appendSegment(Segment segment, Segment prevSegment, List<StringBuilder> results) {
private static void appendSegment(Segment segment, Segment prevSegment, List<PathBuilders> results) {
var type = segment.type;
var value = segment.value;
if (results.isEmpty()) {
if (type == PLACEHOLDER) {
results.add(new StringBuilder(value));
results.add(new PathBuilders(new StringBuilder(value), new StringBuilder(), true));
return;
}
var builder = new StringBuilder();
builder.append(value);
results.add(builder);

var isFirst = true;
StringBuilder opIdBuilder;
if (type == OPT_VAR) {
opIdBuilder = new StringBuilder("With").append(capitalizedPathVar(value));
isFirst = false;
} else {
opIdBuilder = new StringBuilder();
}
results.add(new PathBuilders(new StringBuilder(value), opIdBuilder, isFirst));
// case without optional path var
if (type == OPT_VAR) {
results.add(new StringBuilder());
results.add(new PathBuilders(new StringBuilder(), new StringBuilder(), true));
}
return;
}
if (type == CONST || type == REQ_VAR || type == PLACEHOLDER) {
for (var result : results) {
result.append(value);
result.urlBuilder.append(value);
}
return;
}

var newResults = new ArrayList<StringBuilder>();
var newResults = new ArrayList<PathBuilders>();
for (var result : results) {
newResults.add(new StringBuilder(result));
newResults.add(new PathBuilders(new StringBuilder(result.urlBuilder), new StringBuilder(result.opIdBuilder), result.isFirst));
}
for (var result : results) {
if (prevSegment.type == OPT_VAR && result.indexOf(prevSegment.value) < 0) {
if (prevSegment.type == OPT_VAR && result.urlBuilder.indexOf(prevSegment.value) < 0) {
continue;
}
result.append(SLASH_CHAR).append(value);
result.urlBuilder.append(SLASH_CHAR).append(value);
result.opIdBuilder.append(result.isFirst ? "With" : "And").append(capitalizedPathVar(value));
result.isFirst = false;
}
results.addAll(newResults);
}
Expand Down Expand Up @@ -221,10 +241,37 @@ public record Segment(
) {
}

/**
* Final path and operation ID builders.
*/
static class PathBuilders {
StringBuilder urlBuilder;
StringBuilder opIdBuilder;
boolean isFirst;

public PathBuilders(StringBuilder urlBuilder, StringBuilder opIdBuilder, boolean isFirst) {
this.urlBuilder = urlBuilder;
this.opIdBuilder = opIdBuilder;
this.isFirst = isFirst;
}
}

/**
* Final operation path URL and operation ID.
*
* @param url url
* @param opIdPostfix operation ID postfix
*/
public record OpPath(
String url,
String opIdPostfix
) {
}

/**
* Type of segment.
*/
public enum SegmentType {
enum SegmentType {
REQ_VAR,
OPT_VAR,
CONST,
Expand Down

0 comments on commit 0352833

Please sign in to comment.