Skip to content
Draft
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
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@typespec/http-client-java"
---

Reuse override parameter groups containing query and header parameters in Java protocol methods.
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ private List<ClientMethod> createClientMethods(Operation operation, boolean isPr
continue;
}
final ClientMethodParametersDetails paramsDetails = ClientMethodParameterProcessor.process(request,
getConvenienceRequest(operation, request, isProtocolMethod),
proxyMethod.hasParameterOfType(ClassType.BINARY_DATA), isProtocolMethod);

final ClientMethod baseMethod = builder.proxyMethod(proxyMethod)
Expand Down Expand Up @@ -316,6 +317,21 @@ private static List<Request> getCodeModelRequests(Operation operation, boolean i
}
}

private static Request getConvenienceRequest(Operation operation, Request request, boolean isProtocolMethod) {
if (!isProtocolMethod
|| operation.getConvenienceApi() == null
|| operation.getConvenienceApi().getRequests() == null
|| operation.getConvenienceApi().getRequests().isEmpty()) {
return null;
}

int requestIndex = operation.getRequests().indexOf(request);
List<Request> convenienceRequests = operation.getConvenienceApi().getRequests();
return requestIndex >= 0 && requestIndex < convenienceRequests.size()
? convenienceRequests.get(requestIndex)
: convenienceRequests.get(0);
}

/**
* Gets the visibility for the client methods when generator configured to generate the wrapper clients.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,28 @@
import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ParameterTransformations;
import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ProxyMethodParameter;
import com.microsoft.typespec.http.client.generator.core.util.MethodUtil;
import com.microsoft.typespec.http.client.generator.core.util.SchemaUtil;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

final class ClientMethodParameterProcessor {

static ClientMethodParametersDetails process(Request request, boolean mapFluxByteBufferToBinaryData,
boolean isProtocolMethod) {
static ClientMethodParametersDetails process(Request request, Request convenienceRequest,
boolean mapFluxByteBufferToBinaryData, boolean isProtocolMethod) {

final List<Parameter> codeModelParameters = getCodeModelParameters(request, isProtocolMethod);
final List<Parameter> protocolParameterGroups
= getProtocolParameterGroups(request, convenienceRequest, isProtocolMethod);
final List<Parameter> codeModelParameters
= getCodeModelParameters(request, protocolParameterGroups, isProtocolMethod);
final Set<Parameter> signatureParameters
= getSignatureParameters(request, protocolParameterGroups, isProtocolMethod);
final List<ParametersTuple> parametersTuples = new ArrayList<>();
final List<String> requiredNullableParameterExpressions = new ArrayList<>();
final Map<String, String> validateParameterExpressions = new LinkedHashMap<>();
Expand All @@ -37,7 +46,7 @@ static ClientMethodParametersDetails process(Request request, boolean mapFluxByt
final ClientMethodParameter clientMethodParameter = toClientMethodParameter(codeModelParameter, isJsonPatch,
mapFluxByteBufferToBinaryData, isProtocolMethod);
final ParametersTuple tuple = new ParametersTuple(codeModelParameter, clientMethodParameter);
if (request.getSignatureParameters().contains(codeModelParameter)) {
if (signatureParameters.contains(codeModelParameter)) {
parametersTuples.add(tuple);
}
transformationProcessor.addParameter(tuple);
Expand Down Expand Up @@ -69,31 +78,115 @@ static ClientMethodParametersDetails process(Request request, boolean mapFluxByt
validateParameterExpressions, parameterTransformations);
}

private static List<Parameter> getCodeModelParameters(Request request, boolean isProtocolMethod) {
private static List<Parameter> getCodeModelParameters(Request request, List<Parameter> protocolParameterGroups,
boolean isProtocolMethod) {
final Stream<Parameter> codeModelParameters;
if (isProtocolMethod) {
// Required path, body, header and query parameters are allowed
// Required path, body, header and query parameters are allowed. Query and header parameters grouped by an
// override are also needed to transform the group back into the HTTP request.
codeModelParameters = request.getParameters().stream().filter(p -> {
RequestParameterLocation location = p.getProtocol().getHttp().getIn();
return p.isRequired()
&& (location == RequestParameterLocation.PATH
|| location == RequestParameterLocation.BODY
|| location == RequestParameterLocation.HEADER
|| location == RequestParameterLocation.QUERY);
RequestParameterLocation location = getRequestParameterLocation(p);
return isProtocolParameterLocation(location)
&& (p.isRequired() || findProtocolParameterGroup(p, protocolParameterGroups) != null);
});
} else {
codeModelParameters = request.getParameters().stream().filter(p -> !p.isFlattened());
}
return codeModelParameters.collect(Collectors.toList());
List<Parameter> parameters = codeModelParameters.collect(Collectors.toList());
parameters.addAll(protocolParameterGroups);
return parameters;
}

private static Set<Parameter> getSignatureParameters(Request request, List<Parameter> protocolParameterGroups,
boolean isProtocolMethod) {
if (!isProtocolMethod) {
return new LinkedHashSet<>(request.getSignatureParameters());
}

Set<Parameter> signatureParameters = new LinkedHashSet<>();
for (Parameter parameter : request.getParameters()) {
Parameter groupParameter = findProtocolParameterGroup(parameter, protocolParameterGroups);
if (groupParameter != null) {
signatureParameters.add(groupParameter);
} else if (parameter.isRequired()
&& !parameter.isConstant()
&& parameter.getImplementation() != Parameter.ImplementationLocation.CLIENT
&& isProtocolParameterLocation(getRequestParameterLocation(parameter))) {
signatureParameters.add(parameter);
}
}
return signatureParameters;
}

private static List<Parameter> getProtocolParameterGroups(Request request, Request convenienceRequest,
boolean isProtocolMethod) {
if (!isProtocolMethod || convenienceRequest == null || convenienceRequest.getSignatureParameters() == null) {
return List.of();
}

return convenienceRequest.getSignatureParameters().stream().filter(groupParameter -> {
List<Parameter> groupedParameters = request.getParameters()
.stream()
.filter(parameter -> isSameParameterGroup(parameter.getGroupedBy(), groupParameter))
.collect(Collectors.toList());
return !groupedParameters.isEmpty()
&& groupedParameters.stream().allMatch(ClientMethodParameterProcessor::isQueryOrHeaderParameter);
}).collect(Collectors.toList());
}

private static Parameter findProtocolParameterGroup(Parameter parameter, List<Parameter> protocolParameterGroups) {
return protocolParameterGroups.stream()
.filter(groupParameter -> isSameParameterGroup(parameter.getGroupedBy(), groupParameter))
.findFirst()
.orElse(null);
}

private static boolean isSameParameterGroup(Parameter left, Parameter right) {
if (left == null || right == null || left.getSchema() == null || right.getSchema() == null) {
return false;
}

String leftSchemaName = SchemaUtil.getJavaName(left.getSchema());
String rightSchemaName = SchemaUtil.getJavaName(right.getSchema());
String leftParameterName = SchemaUtil.getJavaName(left);
String rightParameterName = SchemaUtil.getJavaName(right);
return leftSchemaName != null
&& rightSchemaName != null
&& leftParameterName != null
&& rightParameterName != null
&& Objects.equals(leftSchemaName, rightSchemaName)
&& Objects.equals(left.getSchema().getLanguage().getJava().getNamespace(),
right.getSchema().getLanguage().getJava().getNamespace())
&& Objects.equals(leftParameterName, rightParameterName);
}

private static boolean isQueryOrHeaderParameter(Parameter parameter) {
RequestParameterLocation location = getRequestParameterLocation(parameter);
return location == RequestParameterLocation.QUERY || location == RequestParameterLocation.HEADER;
}

private static boolean isProtocolParameterLocation(RequestParameterLocation location) {
return location == RequestParameterLocation.PATH
|| location == RequestParameterLocation.BODY
|| location == RequestParameterLocation.HEADER
|| location == RequestParameterLocation.QUERY;
}

private static RequestParameterLocation getRequestParameterLocation(Parameter parameter) {
return parameter.getProtocol() == null || parameter.getProtocol().getHttp() == null
? null
: parameter.getProtocol().getHttp().getIn();
}

private static ClientMethodParameter toClientMethodParameter(Parameter parameter, boolean isJsonPatch,
boolean mapFluxByteBufferToBinaryData, boolean isProtocolMethod) {
final ClientMethodParameter clientMethodParameter;
boolean mapAsProtocolParameter
= isProtocolMethod && parameter.getGroupedBy() == null && getRequestParameterLocation(parameter) != null;
if (isJsonPatch) {
clientMethodParameter = CustomClientParameterMapper.getInstance().map(parameter, isProtocolMethod);
clientMethodParameter = CustomClientParameterMapper.getInstance().map(parameter, mapAsProtocolParameter);
} else {
clientMethodParameter = Mappers.getClientParameterMapper().map(parameter, isProtocolMethod);
clientMethodParameter = Mappers.getClientParameterMapper().map(parameter, mapAsProtocolParameter);
}

if (mapFluxByteBufferToBinaryData && clientMethodParameter.getClientType() == GenericType.FLUX_BYTE_BUFFER) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

/**
* Holds the details of the parameters for a {@link ClientMethod}, produced by
* {@link ClientMethodParameterProcessor#process(Request, boolean, boolean)}.
* {@link ClientMethodParameterProcessor#process(Request, Request, boolean, boolean)}.
*/
final class ClientMethodParametersDetails {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public ParametersTransformationProcessor(boolean isProtocolMethod) {
*/
void addParameter(ParametersTuple tuple) {
final Parameter codeModelParameter = tuple.codeModelParameter;
if (isProtocolMethod || codeModelParameter.getSchema() instanceof ConstantSchema) {
if ((isProtocolMethod && codeModelParameter.getGroupedBy() == null)
|| codeModelParameter.getSchema() instanceof ConstantSchema) {
return;
}
if (codeModelParameter.getGroupedBy() == null && codeModelParameter.getOriginalParameter() == null) {
Expand Down Expand Up @@ -180,6 +181,10 @@ private static OutMapping processOutputMapping(ClientMethodParameter clientMetho
}

private List<Parameter> flattenedParameters(Request request) {
if (isProtocolMethod) {
return List.of();
}

// build a list of original-parameters those were already been accounted for by process(..) while
// processing 'this.parameters'.
final List<Parameter> originalParameters = parametersTuples.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
Expand Down Expand Up @@ -656,6 +657,8 @@ private static boolean addSpecialHeadersToRequestOptions(JavaBlock function, Cli
// logic only works for DPG, protocol API, on RequestOptions

boolean requestOptionsLocal = false;
final List<ConvenienceMethodTemplateBase.MethodParameter> groupedOptionalParameters
= getGroupedOptionalRequestParameters(clientMethod);

final boolean repeatabilityRequestHeaders
= MethodUtil.isMethodIncludeRepeatableRequestHeaders(clientMethod.getProxyMethod());
Expand All @@ -680,7 +683,7 @@ private static boolean addSpecialHeadersToRequestOptions(JavaBlock function, Cli
final boolean contentTypeRequestHeaders = bodyParameterOptional && singleContentType;

// need a "final" variable for RequestOptions
if (repeatabilityRequestHeaders || contentTypeRequestHeaders) {
if (repeatabilityRequestHeaders || contentTypeRequestHeaders || !groupedOptionalParameters.isEmpty()) {
requestOptionsLocal = true;
function.line(
"RequestOptions requestOptionsLocal = requestOptions == null ? new RequestOptions() : requestOptions;");
Expand Down Expand Up @@ -709,9 +712,46 @@ private static boolean addSpecialHeadersToRequestOptions(JavaBlock function, Cli
function.line("});");
}

for (ConvenienceMethodTemplateBase.MethodParameter parameter : groupedOptionalParameters) {
RequestParameterLocation location = parameter.getProxyMethodParameter().getRequestParameterLocation();
if (location == RequestParameterLocation.QUERY) {
ConvenienceSyncMethodTemplate.getInstance().writeQueryParam(parameter, function, "requestOptionsLocal");
} else if (location == RequestParameterLocation.HEADER) {
ConvenienceMethodTemplateBase.writeHeader(parameter, function, "requestOptionsLocal");
}
}

return requestOptionsLocal;
}

private static List<ConvenienceMethodTemplateBase.MethodParameter>
getGroupedOptionalRequestParameters(ClientMethod clientMethod) {
List<ProxyMethodParameter> proxyMethodParameters = clientMethod.getProxyMethod().getParameters();
List<ProxyMethodParameter> allProxyMethodParameters = clientMethod.getProxyMethod().getAllParameters();

return clientMethod.getParameterTransformations()
.asStream()
.filter(ParameterTransformation::isGroupBy)
.map(ParameterTransformation::getOutParameter)
.map(clientParameter -> {
ProxyMethodParameter proxyParameter = allProxyMethodParameters.stream()
.filter(parameter -> clientParameter.getName()
.equals(CodeNamer.getEscapedReservedClientMethodParameterName(parameter.getName())))
.findFirst()
.orElse(null);
return proxyParameter == null || proxyMethodParameters.contains(proxyParameter)
? null
: new ConvenienceMethodTemplateBase.MethodParameter(proxyParameter, clientParameter);
})
.filter(Objects::nonNull)
.filter(parameter -> !parameter.getProxyMethodParameter().isRequired())
.filter(parameter -> {
RequestParameterLocation location = parameter.getProxyMethodParameter().getRequestParameterLocation();
return location == RequestParameterLocation.QUERY || location == RequestParameterLocation.HEADER;
})
.collect(Collectors.toList());
}

private static void requestOptionsSetHeaderIfAbsent(JavaBlock function, String expression, String headerName) {
function.line("requestOptionsLocal.addRequestCallback(requestLocal -> {");
function.indent(() -> function.ifBlock(
Expand Down
Loading