Skip to content

Commit

Permalink
Support lists of primitives. (#1249)
Browse files Browse the repository at this point in the history
Support list-of-primitives header/query params.
  • Loading branch information
jkozlowski authored May 10, 2021
1 parent 1c60023 commit fffe882
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 18 deletions.
5 changes: 5 additions & 0 deletions changelog/@unreleased/pr-1249.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type: improvement
improvement:
description: Support list-of-primitives header/query params.
links:
- https://github.com/palantir/dialogue/pull/1249
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import com.palantir.dialogue.Response;
import com.palantir.dialogue.annotations.Request;
import com.palantir.myservice.example.PutFileRequest.PutFileRequestSerializer;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.UUID;

Expand Down Expand Up @@ -59,12 +61,16 @@ String greet(
@Request(method = HttpMethod.POST, path = "/params/{myPathParam}/{myPathParam2}")
void params(
@Request.QueryParam("q") String query,
// Lists of primitive types are supported for @QueryParam and @Header
@Request.QueryParam("q1") List<String> query1,
// Path parameter variable name must match the request path component
@Request.PathParam UUID myPathParam,
@Request.PathParam(encoder = MyCustomParamTypeEncoder.class) MyCustomParamType myPathParam2,
@Request.Header("Custom-Header") int requestHeaderValue,
// Headers can be optional
@Request.Header("Custom-Optional-Header") OptionalInt maybeRequestHeaderValue,
// Optional lists of primitives are supported too!
@Request.Header("Custom-Optional-Header1") Optional<List<Integer>> maybeRequestHeaderValue1,
// Custom encoding classes may be provided for the request and response.
// JSON should be easiest (default?).
// By changing this to MySpecialJson.class you can have
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,14 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.UUID;
import org.assertj.core.api.AbstractStringAssert;
import org.assertj.core.api.OptionalAssert;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -247,12 +251,15 @@ private void testParams(OptionalInt customHeaderOptionalValue) {
undertowHandler = exchange -> {
exchange.assertMethod(HttpMethod.POST);
exchange.assertPath("/params/90a8481a-2ef5-4c64-83fc-04a9b369e2b8/my-custom-param-value");
assertThat(exchange.exchange.getQueryParameters()).containsOnlyKeys("q");
assertThat(exchange.exchange.getQueryParameters()).containsOnlyKeys("q", "q1");
assertThat(exchange.exchange.getQueryParameters().get("q")).containsOnly("query");
assertThat(exchange.exchange.getQueryParameters().get("q1")).containsOnly("Query", "Params");
exchange.assertAccept().isNull();
exchange.assertContentType().isEqualTo("application/json");
exchange.assertSingleValueHeader(HttpString.tryFromString("Custom-Header"))
.isEqualTo("2");
exchange.assertMultiValueHeader("Custom-Optional-Header1")
.hasValueSatisfying(values -> assertThat(values).containsExactly("1", "2"));
if (customHeaderOptionalValue.isPresent()) {
exchange.assertSingleValueHeader(HttpString.tryFromString("Custom-Optional-Header"))
.isEqualTo(Integer.toString(customHeaderOptionalValue.getAsInt()));
Expand All @@ -273,10 +280,12 @@ private void testParams(OptionalInt customHeaderOptionalValue) {
UUID uuid = UUID.fromString("90a8481a-2ef5-4c64-83fc-04a9b369e2b8");
myServiceDialogue.params(
"query",
Arrays.asList("Query", "Params"),
uuid,
new MyCustomParamType("my-custom-param-value"),
2,
customHeaderOptionalValue,
Optional.of(Arrays.asList(1, 2)),
ImmutableMySerializableType.builder()
.value("my-serializable-type-value")
.build());
Expand Down Expand Up @@ -314,6 +323,14 @@ public AbstractStringAssert<?> assertSingleValueHeader(HttpString header) {
return assertThat(headerValues.isEmpty() ? null : headerValues.getFirst());
}

public OptionalAssert<List<String>> assertMultiValueHeader(String header) {
HeaderValues headerValues = exchange.getRequestHeaders().get(header);
if (headerValues == null) {
return assertThat(Optional.empty());
}
return assertThat(Optional.of(headerValues));
}

public AbstractStringAssert<?> assertBodyUtf8() {
try {
return assertThat(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@
package com.palantir.dialogue.annotations.processor.data;

import com.squareup.javapoet.TypeName;
import java.util.Optional;
import org.derive4j.Data;
import org.immutables.value.Value;

@Data
public interface ArgumentType {
interface Cases<R> {
/** Should be handled by {@link com.palantir.dialogue.annotations.ParameterSerializer}. */
R primitive(TypeName javaTypeName, String parameterSerializerMethodName);
R primitive(TypeName javaTypeName, String parameterSerializerMethodName, Optional<TypeName> listInnerType);

R rawRequestBody(TypeName requestBodyType);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.TypeName;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.UUID;
Expand Down Expand Up @@ -67,7 +68,8 @@ public final class ArgumentTypesResolver {
public ArgumentTypesResolver(ResolverContext context) {
this.context = context;
TypeName integerType = context.getTypeName(Integer.class);
this.integerArgumentType = ArgumentTypes.primitive(integerType, planSerDeMethodName(integerType));
this.integerArgumentType =
ArgumentTypes.primitive(integerType, planSerDeMethodName(integerType), Optional.empty());
}

public Optional<ArgumentType> getArgumentType(VariableElement param) {
Expand All @@ -78,8 +80,14 @@ public Optional<ArgumentType> getArgumentType(VariableElement param) {
private Optional<ArgumentType> getArgumentTypeImpl(Element paramContext, TypeMirror actualTypeMirror) {
TypeName typeName = TypeName.get(actualTypeMirror);
Optional<OptionalType> optionalType = getOptionalType(paramContext, actualTypeMirror);
Optional<TypeMirror> listType = getListType(actualTypeMirror);
if (isPrimitive(typeName)) {
return Optional.of(ArgumentTypes.primitive(typeName, planSerDeMethodName(typeName)));
return Optional.of(ArgumentTypes.primitive(typeName, planSerDeMethodName(typeName), Optional.empty()));
} else if (listType.map(innerType -> isPrimitive(TypeName.get(innerType)))
.orElse(false)) {
TypeName innerTypeName = TypeName.get(listType.get());
return Optional.of(
ArgumentTypes.primitive(typeName, planSerDeMethodName(innerTypeName), Optional.of(innerTypeName)));
} else if (isRawRequestBody(actualTypeMirror)) {
return Optional.of(ArgumentTypes.rawRequestBody(TypeName.get(actualTypeMirror)));
} else if (optionalType.isPresent()) {
Expand All @@ -94,6 +102,10 @@ private boolean isPrimitive(TypeName in) {
return PARAMETER_SERIALIZER_TYPES.containsKey(in.box());
}

private Optional<TypeMirror> getListType(TypeMirror in) {
return context.getGenericInnerType(List.class, in);
}

private String planSerDeMethodName(TypeName in) {
String typeName = PARAMETER_SERIALIZER_TYPES.get(in.box());
return Preconditions.checkNotNull(typeName, "Unknown type");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ public Optional<TypeMirror> getGenericInnerType(Class<?> clazz, TypeMirror typeM
}

TypeMirror innerType = Iterables.getOnlyElement(declaredType.getTypeArguments());
DeclaredType genericOptional = types.getDeclaredType(getTypeElement(clazz), innerType);
DeclaredType erasedType = types.getDeclaredType(getTypeElement(clazz), innerType);

if (types.isSameType(genericOptional, declaredType)) {
if (types.isAssignable(declaredType, erasedType)) {
return Optional.of(innerType);
} else {
return Optional.empty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ private MethodSpec clientImpl(EndpointDefinition def) {
List<ParameterSpec> params = def.arguments().stream()
.map(arg -> ParameterSpec.builder(
ArgumentTypes.caseOf(arg.argType())
.primitive((javaTypeName, _unused) -> javaTypeName)
.primitive(
(javaTypeName, _parameterSerializerMethodName, _isList) -> javaTypeName)
.rawRequestBody(typeName -> typeName)
.optional((optionalJavaType, _unused) -> optionalJavaType)
.customType(typeName -> typeName),
Expand Down Expand Up @@ -143,7 +144,7 @@ private FieldSpec bindEndpointChannel(EndpointDefinition endpoint) {
private FieldSpec serializer(
ArgumentDefinition argumentDefinition, TypeName serializerType, String serializerFieldName) {
TypeName className = ArgumentTypes.caseOf(argumentDefinition.argType())
.primitive((javaTypeName, _unused) -> javaTypeName)
.primitive((javaTypeName, _parameterSerializerMethodName, _isList) -> javaTypeName)
.customType(typeName -> typeName)
.otherwiseEmpty()
.get();
Expand Down Expand Up @@ -194,7 +195,7 @@ public Class<?> listParam() {

private TypeName underlyingCustomType(ArgumentType argumentType) {
return ArgumentTypes.caseOf(argumentType)
.primitive((javaTypeName, _unused) -> javaTypeName)
.primitive((javaTypeName, _parameterSerializerMethodName, _isList) -> javaTypeName)
.optional((_optionalJavaType, optionalType) -> underlyingCustomType(optionalType.underlyingType()))
.customType(Function.identity())
.otherwiseEmpty()
Expand Down Expand Up @@ -275,17 +276,31 @@ private CodeBlock generatePlainSerializer(
Optional<ParameterEncoderType> maybeParameterEncoderType) {
return type.match(new ArgumentType.Cases<>() {
@Override
public CodeBlock primitive(TypeName _unused, String parameterSerializerMethodName) {
return maybeParameterEncoderType
.map(this::parameterEncoderType)
.orElseGet(() -> CodeBlock.of(
"$L.$L($S, $L.$L($L));",
REQUEST,
singleValueMethod,
key,
public CodeBlock primitive(
TypeName _unused, String parameterSerializerMethodName, Optional<TypeName> innerListType) {
return maybeParameterEncoderType.map(this::parameterEncoderType).orElseGet(() -> {
if (innerListType.isPresent()) {
CodeBlock asList = CodeBlock.of(
"$L.stream().map($L::$L).collect($T.toList())",
argName,
PARAMETER_SERIALIZER,
parameterSerializerMethodName,
argName));
Collectors.class);
return CodeBlock.builder()
.add("$L.$L($S,", REQUEST, multiValueMethod, key)
.add(asList)
.add(");")
.build();
}
return CodeBlock.of(
"$L.$L($S, $L.$L($L));",
REQUEST,
singleValueMethod,
key,
PARAMETER_SERIALIZER,
parameterSerializerMethodName,
argName);
});
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.palantir.dialogue.Response;
import com.palantir.dialogue.annotations.Request;
import com.palantir.tokens.auth.AuthHeader;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.UUID;
Expand Down Expand Up @@ -67,6 +68,8 @@ void params(
Optional<MyCustomParamType> query2,
@Request.QueryParam(value = "q3", encoder = MyCustomStringParameterEncoder.class) String query3,
@Request.QueryParam(value = "q4", encoder = MyCustomStringParameterEncoder.class) Optional<String> query4,
@Request.QueryParam(value = "q5") List<String> query5,
@Request.QueryParam(value = "q6") Optional<List<String>> query6,
// Path parameter variable name must match the request path component
@Request.PathParam UUID myPathParam,
@Request.PathParam(encoder = MyCustomParamTypeParameterEncoder.class) MyCustomParamType myPathParam2,
Expand All @@ -76,6 +79,8 @@ void params(
@Request.Header("Custom-Optional-Header2") OptionalInt maybeCustomOptionalHeader2Value,
@Request.Header(value = "Custom-Optional-Header3", encoder = MyCustomParamTypeParameterEncoder.class)
Optional<MyCustomParamType> maybeCustomOptionalHeader3Value,
@Request.Header("Custom-Header1") List<String> customListHeader,
@Request.Header("Custom-Optional-Header3") Optional<List<String>> customOptionalListHeader,
// Custom encoding classes may be provided for the request and response.
// JSON should be easiest (default?).
// By changing this to MySpecialJson.class you can have
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ import com.palantir.tokens.auth.AuthHeader;
import java.lang.Override;
import java.lang.String;
import java.lang.Void;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.annotation.processing.Generated;

@Generated("com.palantir.dialogue.annotations.processor.generate.DialogueServiceFactoryGenerator")
Expand Down Expand Up @@ -120,12 +122,16 @@ public final class MyServiceDialogueServiceFactory implements DialogueServiceFac
Optional<MyCustomParamType> query2,
String query3,
Optional<String> query4,
List<String> query5,
Optional<List<String>> query6,
UUID myPathParam,
MyCustomParamType myPathParam2,
int requestHeaderValue,
Optional<String> maybeCustomOptionalHeader1Value,
OptionalInt maybeCustomOptionalHeader2Value,
Optional<MyCustomParamType> maybeCustomOptionalHeader3Value,
List<String> customListHeader,
Optional<List<String>> customOptionalListHeader,
MySerializableType body) {
Request.Builder _request = Request.builder();
_request.putQueryParams("q", _parameterSerializer.serializeString(query));
Expand All @@ -137,6 +143,18 @@ public final class MyServiceDialogueServiceFactory implements DialogueServiceFac
if (query4.isPresent()) {
_request.putAllQueryParams("q4", paramsQuery4Encoder.toParamValues(query4.get()));
}
_request.putAllQueryParams(
"q5",
query5.stream()
.map(_parameterSerializer::serializeString)
.collect(Collectors.toList()));
if (query6.isPresent()) {
_request.putAllQueryParams(
"q6",
query6.get().stream()
.map(_parameterSerializer::serializeString)
.collect(Collectors.toList()));
}
_request.putPathParams("myPathParam", _parameterSerializer.serializeUuid(myPathParam));
_request.putPathParams("myPathParam2", paramsMyPathParam2Encoder.toParamValue(myPathParam2));
_request.putHeaderParams("Custom-Header", _parameterSerializer.serializeInteger(requestHeaderValue));
Expand All @@ -156,6 +174,18 @@ public final class MyServiceDialogueServiceFactory implements DialogueServiceFac
paramsMaybeCustomOptionalHeader3ValueEncoder.toParamValues(
maybeCustomOptionalHeader3Value.get()));
}
_request.putAllHeaderParams(
"Custom-Header1",
customListHeader.stream()
.map(_parameterSerializer::serializeString)
.collect(Collectors.toList()));
if (customOptionalListHeader.isPresent()) {
_request.putAllHeaderParams(
"Custom-Optional-Header3",
customOptionalListHeader.get().stream()
.map(_parameterSerializer::serializeString)
.collect(Collectors.toList()));
}
_request.body(paramsSerializer.serialize(body));
runtime.clients().callBlocking(paramsChannel, _request.build(), paramsDeserializer);
}
Expand Down

0 comments on commit fffe882

Please sign in to comment.