Skip to content
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

Fix parsing UrlTemplates #1687

Merged
merged 1 commit into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@
import static io.micronaut.openapi.visitor.SchemaUtils.setOperationOnPathItem;
import static io.micronaut.openapi.visitor.SchemaUtils.setSpecVersion;
import static io.micronaut.openapi.visitor.StringUtil.CLOSE_BRACE;
import static io.micronaut.openapi.visitor.StringUtil.DOLLAR;
import static io.micronaut.openapi.visitor.StringUtil.OPEN_BRACE;
import static io.micronaut.openapi.visitor.StringUtil.THREE_DOTS;
import static io.micronaut.openapi.visitor.Utils.DEFAULT_MEDIA_TYPES;
Expand Down Expand Up @@ -533,6 +534,8 @@ private void addParamsByUriTemplate(String path, Map<String, UriMatchVariable> p
var pathVar = entry.getValue();
if (pathVar.isExploded()
|| !path.contains(OPEN_BRACE + varName + CLOSE_BRACE)
// skip placeholders
|| path.contains(DOLLAR + OPEN_BRACE + varName + CLOSE_BRACE)
|| isAlreadyAdded(varName, operation)) {
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public final class StringUtil {
public static final String OPEN_BRACE = "{";
public static final String CLOSE_BRACE = "}";
public static final String SLASH = "/";
public static final char SLASH_CHAR = '/';
public static final String DOLLAR = "$";
public static final String DOT = ".";
public static final String COMMA = ",";
Expand Down
58 changes: 30 additions & 28 deletions openapi/src/main/java/io/micronaut/openapi/visitor/UrlUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,14 @@
import java.util.List;

import static io.micronaut.openapi.visitor.StringUtil.CLOSE_BRACE;
import static io.micronaut.openapi.visitor.StringUtil.DOLLAR;
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.UrlUtils.SegmentType.CONST;
import static io.micronaut.openapi.visitor.UrlUtils.SegmentType.OPT_VAR;
import static io.micronaut.openapi.visitor.UrlUtils.SegmentType.PLACEHOLDER;
import static io.micronaut.openapi.visitor.UrlUtils.SegmentType.REQ_VAR;

/**
* URL and URL paths util methods.
Expand All @@ -48,10 +54,14 @@ public static List<String> buildUrls(List<Segment> segments) {
var resultStrings = new ArrayList<String>();
for (var res : results) {
var url = res.toString();
if (url.endsWith(SLASH) && url.length() > 1) {
url = url.substring(0, url.length() - SLASH.length());
} else if (!url.startsWith(SLASH) && !url.startsWith(DOLLAR)) {
url = SLASH + url;
} else if (url.startsWith(SLASH + DOLLAR)) {
url = url.substring(1);
}
if (!resultStrings.contains(url)) {
if (url.endsWith(SLASH)) {
url = url.substring(0, url.length() - SLASH.length());
}
resultStrings.add(url);
}
}
Expand All @@ -63,26 +73,21 @@ private static void appendSegment(Segment segment, Segment prevSegment, List<Str
var type = segment.type;
var value = segment.value;
if (results.isEmpty()) {
if (type == SegmentType.PLACEHOLDER) {
if (type == PLACEHOLDER) {
results.add(new StringBuilder(value));
return;
}
var builder = new StringBuilder(SLASH).append(value);
if (!value.endsWith(SLASH)) {
builder.append(SLASH);
}
var builder = new StringBuilder();
builder.append(value);
results.add(builder);
if (type == SegmentType.OPT_VAR) {
results.add(new StringBuilder(SLASH));
if (type == OPT_VAR) {
results.add(new StringBuilder());
}
return;
}
if (type == SegmentType.CONST || type == SegmentType.REQ_VAR || type == SegmentType.PLACEHOLDER) {
if (type == CONST || type == REQ_VAR || type == PLACEHOLDER) {
for (var result : results) {
result.append(value);
if (type != SegmentType.PLACEHOLDER) {
result.append(SLASH);
}
}
return;
}
Expand All @@ -92,10 +97,10 @@ private static void appendSegment(Segment segment, Segment prevSegment, List<Str
newResults.add(new StringBuilder(result));
}
for (var result : results) {
if (prevSegment.type == SegmentType.OPT_VAR && result.indexOf(prevSegment.value + SLASH) < 0) {
if (prevSegment.type == OPT_VAR && result.indexOf(prevSegment.value) < 0) {
continue;
}
result.append(value).append(SLASH);
result.append(SLASH_CHAR).append(value);
}
results.addAll(newResults);
}
Expand Down Expand Up @@ -128,12 +133,15 @@ public static List<Segment> parsePathSegments(String pathString) {

// process placeholders
if (varStartPos >= 1 && pathString.charAt(varStartPos - 1) == '$') {
segments.add(new Segment(SegmentType.PLACEHOLDER, pathString.substring(varStartPos - 1, varEndPos + 1)));
if (!constSegment.isEmpty()) {
addConstValue(constSegment.substring(0, constSegment.length() - 1), segments);
}
segments.add(new Segment(PLACEHOLDER, pathString.substring(varStartPos - 1, varEndPos + 1)));
startPos = varEndPos + 1;
continue;
}

SegmentType type = nextChar == '/' ? SegmentType.OPT_VAR : SegmentType.REQ_VAR;
SegmentType type = nextChar == '/' ? OPT_VAR : REQ_VAR;

if (!constSegment.isEmpty()) {
addConstValue(constSegment, segments);
Expand All @@ -159,31 +167,25 @@ public static List<Segment> parsePathSegments(String pathString) {
}

if (segments.isEmpty()) {
segments.add(new Segment(SegmentType.CONST, SLASH));
segments.add(new Segment(CONST, SLASH));
}

return segments;
}

private static void addConstValue(String constValue, List<Segment> segments) {
if (constValue.startsWith(SLASH)) {
constValue = constValue.substring(1);
}
if (constValue.endsWith(SLASH)) {
constValue = constValue.substring(0, constValue.length() - SLASH.length());
}
if (!constValue.isEmpty()) {
segments.add(new Segment(SegmentType.CONST, constValue));
segments.add(new Segment(CONST, constValue));
}
}

private record Segment(
public record Segment(
SegmentType type,
String value
) {
}

private enum SegmentType {
public enum SegmentType {
REQ_VAR,
OPT_VAR,
CONST,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,82 @@ class MyBean {}
pathItem3.get.parameters[3].in == 'query'
}

void "test parse URL with :"() {

given: "An API definition"
when:
buildBeanDefinition('test.MyBean', '''
package test;

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;import io.micronaut.http.annotation.PathVariable;

@Controller("/")
interface Test {

@Get("/hello/{pathVar2}${placeholder}/tete:test4")
String test1(@PathVariable String pathVar2);

@Get("/hello/{pathVar2}:test2/${placeholder}:test4")
String test2(@PathVariable String pathVar2);

@Get("/hello{/optVar1}:test5/{pathVar1}:test/{pathVar2}:test2/${placeholder}:test4/const:test3")
String test3(@PathVariable String pathVar1);
}

class Greeting {
public String message;
}

@jakarta.inject.Singleton
class MyBean {}
''')
then: "the state is correct"
Utils.testReference != null

when: "The OpenAPI is retrieved"
OpenAPI openAPI = Utils.testReference

def pathItem1 = openAPI.paths.get("/hello/{pathVar2}\${placeholder}/tete:test4")
def pathItem2 = openAPI.paths.get("/hello/{pathVar2}:test2/\${placeholder}:test4")
def pathItem3 = openAPI.paths.get("/hello/{optVar1}:test5/{pathVar1}:test/{pathVar2}:test2/\${placeholder}:test4/const:test3")
def pathItem4 = openAPI.paths.get("/hello:test5/{pathVar1}:test/{pathVar2}:test2/\${placeholder}:test4/const:test3")

then:
pathItem1.get.parameters
pathItem1.get.parameters.size() == 1
pathItem1.get.parameters[0].name == 'pathVar2'
pathItem1.get.parameters[0].required
pathItem1.get.parameters[0].in == 'path'

pathItem2.get.parameters
pathItem2.get.parameters.size() == 1
pathItem2.get.parameters[0].name == 'pathVar2'
pathItem2.get.parameters[0].required
pathItem2.get.parameters[0].in == 'path'

pathItem3.get.parameters
pathItem3.get.parameters.size() == 3
pathItem3.get.parameters[0].name == 'pathVar1'
pathItem3.get.parameters[0].required
pathItem3.get.parameters[0].in == 'path'
pathItem3.get.parameters[1].name == 'optVar1'
pathItem3.get.parameters[1].required
pathItem3.get.parameters[1].in == 'path'
pathItem3.get.parameters[2].name == 'pathVar2'
pathItem3.get.parameters[2].required
pathItem3.get.parameters[2].in == 'path'

pathItem4.get.parameters
pathItem4.get.parameters.size() == 2
pathItem4.get.parameters[0].name == 'pathVar1'
pathItem4.get.parameters[0].required
pathItem4.get.parameters[0].in == 'path'
pathItem4.get.parameters[1].name == 'pathVar2'
pathItem4.get.parameters[1].required
pathItem4.get.parameters[1].in == 'path'
}

void "test @Parameter in header and explode is true"() {

given: "An API definition"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package io.micronaut.openapi.visitor

import io.micronaut.openapi.AbstractOpenApiTypeElementSpec
import io.swagger.v3.oas.models.OpenAPI
import io.swagger.v3.oas.models.Operation

class OpenApiPathParamRegexSpec extends AbstractOpenApiTypeElementSpec {

Expand All @@ -20,7 +19,7 @@ import io.swagger.v3.oas.annotations.Operation;
class OpenApiController {

@Operation(summary = "Update tag", description = "Updates an existing tag", tags = "users_tag")
@Post("/tags/{tagId: \\\\d+}/{path:.*}{.ext}/update/{?max,offset}{/id:[a-zA-Z]+}")
@Post("/tags/{tagId: \\\\d+}/{path:.*}{.ext}/update{?max,offset}{/id:[a-zA-Z]+}")
public void postRaw() {
}
}
Expand Down
Loading