Skip to content

Support Multi-Value QueryParam/Headers/Cookies #167

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 21 commits into from
Mar 1, 2023
Merged
Show file tree
Hide file tree
Changes from 6 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
22 changes: 19 additions & 3 deletions http-api/src/main/java/io/avaje/http/api/PathTypeConversion.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

import java.math.BigDecimal;
import java.time.*;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
* Helper type conversion methods.
Expand All @@ -11,6 +15,8 @@
*/
public final class PathTypeConversion {

private PathTypeConversion() {}

/**
* Return the value if non-null and otherwise the default value.
*
Expand All @@ -22,6 +28,10 @@ public static String withDefault(String value, String defaultValue) {
return value != null ? value : defaultValue;
}

public static List<String> withDefault(List<String> value, String defaultValue) {
return value != null && !value.isEmpty() ? value : List.of(defaultValue);
}

/**
* Check for null for a required property throwing RequiredArgumentException
* if the value is null.
Expand All @@ -41,9 +51,15 @@ private static void checkNull(String value) {
}
}

/**
* Convert to int.
*/
public static <T> List<T> list(Function<String, T> func, List<String> params) {
return params.stream().map(func).collect(Collectors.toList());
}

public static <T> Set<T> set(Function<String, T> func, List<String> params) {
return params.stream().map(func).collect(Collectors.toSet());
}

/** Convert to int. */
public static int asInt(String value) {
checkNull(value);
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;

import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
Expand All @@ -19,7 +20,9 @@ class PathTypeConversionTest {
void withDefault() {
assertEquals("a", PathTypeConversion.withDefault("a", "myVal"));
assertEquals("", PathTypeConversion.withDefault("", "myVal"));
assertEquals("myVal", PathTypeConversion.withDefault(null, "myVal"));
String nully = null;
assertEquals("myVal", PathTypeConversion.withDefault(nully, "myVal"));
assertThat(PathTypeConversion.withDefault(List.of(), "myVal")).anyMatch(s -> "myVal".equals(s));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,14 @@ public HttpClientRequest header(String name, String value) {

@Override
public HttpClientRequest header(String name, Object value) {

if (value instanceof Collection) {
for (final var e : (Collection) value) {
header(name, e);
}
return this;
}

return value != null ? header(name, value.toString()) : this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,10 @@ public interface HttpClientRequest {
HttpClientRequest header(String name, String value);

/**
* Add the header to the request implicitly converting the value to a String.
* Add the header to the request implicitly converting the value to a String. If the value is a
* collection then it's values are appended with the same key
*
* @param name The header name
* @param name The header name
* @param value The header value
* @return The request being built
*/
Expand Down Expand Up @@ -218,7 +219,7 @@ public interface HttpClientRequest {
HttpClientRequest queryParam(String name, String value);

/**
* Add a query parameter
* Add a query parameter, if value is a collection then it's values are appended with the same key
*
* @param name The name of the query parameter
* @param value The value of the query parameter which can be null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Map;

/**
Expand Down Expand Up @@ -84,6 +85,14 @@ public UrlBuilder queryParam(String name, String value) {
* The name and value parameters are url encoded.
*/
public UrlBuilder queryParam(String name, Object value) {

if (value instanceof Collection) {
for (var e : (Collection) value) {
queryParam(name, e);
}
return this;
}

if (value != null) {
addQueryParam(name, value.toString());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,17 @@ public void writeReadParameter(Append writer, ParamType paramType, String paramN
public void writeReadParameter(Append writer, ParamType paramType, String paramName, String paramDefault) {

}

@Override
public void writeReadCollectionParameter(
Append writer, ParamType paramType, String paramName) {
}

@Override
public void writeReadCollectionParameter(
Append writer,
ParamType paramType,
String paramName,
String paramDefault) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static io.avaje.http.generator.core.ParamType.RESPONSE_HANDLER;

import java.util.Objects;
import java.util.Optional;

import javax.lang.model.element.Element;
Expand All @@ -24,6 +25,7 @@ public class ElementReader {
private final boolean formMarker;
private final boolean contextType;
private final boolean useValidation;
private final boolean specialParam;

private String paramName;
private ParamType paramType;
Expand All @@ -32,6 +34,7 @@ public class ElementReader {
private String paramDefault;

private boolean notNullKotlin;
private boolean isParamCollection;
//private boolean notNullJavax;

ElementReader(Element element, ProcessingContext ctx, ParamType defaultType, boolean formMarker) {
Expand All @@ -46,18 +49,14 @@ public class ElementReader {
this.shortType = Util.shortName(rawType);
this.contextType = ctx.platform().isContextType(rawType);

if (type != null
&& (defaultType == ParamType.FORMPARAM || defaultType == ParamType.QUERYPARAM)
&& Optional.ofNullable(ctx.typeElement(type.mainType()))
.map(TypeElement::getKind)
.filter(ElementKind.ENUM::equals)
.isPresent()) {
this.specialParam =
defaultType == ParamType.FORMPARAM
|| defaultType == ParamType.QUERYPARAM
|| defaultType == ParamType.HEADER
|| defaultType == ParamType.COOKIE;

this.typeHandler = TypeMap.enumParamHandler(type);
} else {
typeHandler = initTypeHandler();

this.typeHandler = TypeMap.get(rawType);
}
this.formMarker = formMarker;
this.varName = element.getSimpleName().toString();
this.snakeName = Util.snakeCase(varName);
Expand All @@ -71,6 +70,46 @@ public class ElementReader {
}
}

TypeHandler initTypeHandler() {

if (specialParam) {

final var typeOp =
Optional.ofNullable(type).or(() -> Optional.of(UType.parse(element.asType())));

final var mainTypeEnum =
typeOp
.flatMap(t -> Optional.ofNullable(ctx.typeElement(t.mainType())))
.map(TypeElement::getKind)
.filter(ElementKind.ENUM::equals)
.isPresent();

final var isCollection =
typeOp
.filter(t -> t.isGeneric() && !t.mainType().startsWith("java.util.Map"))
.isPresent();

if (mainTypeEnum) {
return TypeMap.enumParamHandler(type);
} else if (isCollection) {
this.isParamCollection = true;
if (paramType == ParamType.FORMPARAM) {
throw new IllegalStateException("You can't have a single Form Parameter be a list");
}
final var isEnumCollection =
typeOp
.flatMap(t -> Optional.ofNullable(ctx.typeElement(t.param0())))
.map(TypeElement::getKind)
.filter(ElementKind.ENUM::equals)
.isPresent();

return TypeMap.collectionHandler(typeOp.orElseThrow(), isEnumCollection);
}
}

return TypeMap.get(rawType);
}

private boolean useValidation() {
if (typeHandler != null) {
return false;
Expand Down Expand Up @@ -182,10 +221,8 @@ private String handlerShortType() {

void addImports(ControllerReader bean) {
if (typeHandler != null) {
String importType = typeHandler.importType();
if (importType != null) {
bean.addImportType(rawType);
}
typeHandler.importTypes().stream().filter(Objects::nonNull).forEach(bean::addImportType);

} else {
bean.addImportType(rawType);
}
Expand Down Expand Up @@ -285,19 +322,24 @@ private boolean setValue(Append writer, PathSegments segments, String shortType)
// this is a body (POST, PATCH)
writer.append(ctx.platform().bodyAsClass(type));

} else {
} else if (isParamCollection && specialParam) {
if (hasParamDefault()) {
ctx.platform().writeReadParameter(writer, paramType, paramName, paramDefault);
ctx.platform().writeReadCollectionParameter(writer, paramType, paramName, paramDefault);
} else {
boolean checkNull = notNullKotlin || (paramType == ParamType.FORMPARAM && typeHandler.isPrimitive());
if (checkNull) {
writer.append("checkNull(");
}
ctx.platform().writeReadParameter(writer, paramType, paramName);
//writer.append("ctx.%s(\"%s\")", paramType, paramName);
if (checkNull) {
writer.append(", \"%s\")", paramName);
}
ctx.platform().writeReadCollectionParameter(writer, paramType, paramName);
}
} else if (hasParamDefault()) {
ctx.platform().writeReadParameter(writer, paramType, paramName, paramDefault);
} else {
final var checkNull =
notNullKotlin || (paramType == ParamType.FORMPARAM && typeHandler.isPrimitive());
if (checkNull) {
writer.append("checkNull(");
}
ctx.platform().writeReadParameter(writer, paramType, paramName);
// writer.append("ctx.%s(\"%s\")", paramType, paramName);
if (checkNull) {
writer.append(", \"%s\")", paramName);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,9 @@ public interface PlatformAdapter {
void writeReadParameter(Append writer, ParamType paramType, String paramName);

void writeReadParameter(Append writer, ParamType paramType, String paramName, String paramDefault);

void writeReadCollectionParameter(Append writer, ParamType paramType, String paramName);

void writeReadCollectionParameter(Append writer, ParamType paramType, String paramName, String paramDefault);

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.avaje.http.generator.core;

import java.util.List;

/**
* Handles type conversion for path and query parameters.
*/
Expand All @@ -18,7 +20,7 @@ interface TypeHandler {
/**
* The type for adding to imports.
*/
String importType();
List<String> importTypes();

/**
* The short name.
Expand Down
Loading