From 0729f02c77e0a62e1748f4e7f24617633a0dd312 Mon Sep 17 00:00:00 2001 From: anuchandy Date: Tue, 27 May 2025 16:20:49 -0700 Subject: [PATCH 1/4] ClientMethodTemplate: Remove method parameters that can be obtained from other parameters --- .../model/clientmodel/MethodPageDetails.java | 3 +- .../core/template/ClientMethodTemplate.java | 281 ++++++++---------- .../template/FluentClientMethodTemplate.java | 46 +-- 3 files changed, 141 insertions(+), 189 deletions(-) diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/model/clientmodel/MethodPageDetails.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/model/clientmodel/MethodPageDetails.java index 00a672a65a7..98a8bd4e0e1 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/model/clientmodel/MethodPageDetails.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/model/clientmodel/MethodPageDetails.java @@ -68,7 +68,8 @@ public ContinuationToken getContinuationToken() { } public boolean nonNullNextLink() { - return getNextLinkName() != null && !getNextLinkName().isEmpty(); + final String nextLinkName = getNextLinkName(); + return nextLinkName != null && !nextLinkName.isEmpty(); } public boolean shouldHideParameter(ClientMethodParameter parameter) { diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java index 08210e0d5c8..9e9df9a6cd7 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java @@ -20,6 +20,7 @@ import com.microsoft.typespec.http.client.generator.core.model.clientmodel.IterableType; import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ListType; import com.microsoft.typespec.http.client.generator.core.model.clientmodel.MethodPageDetails; +import com.microsoft.typespec.http.client.generator.core.model.clientmodel.MethodParameter; import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ModelPropertySegment; import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ParameterMapping; import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ParameterSynthesizedOrigin; @@ -38,7 +39,7 @@ import com.microsoft.typespec.http.client.generator.core.util.MethodNamer; import com.microsoft.typespec.http.client.generator.core.util.MethodUtil; import com.microsoft.typespec.http.client.generator.core.util.TemplateUtil; -import java.util.LinkedHashSet; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -65,54 +66,45 @@ public static ClientMethodTemplate getInstance() { * Adds validations to the client method. * * @param function The client method code block. - * @param expressionsToCheck Expressions to validate as non-null. - * @param validateExpressions Expressions to validate with a custom validation (key is the expression, value is the - * validation). - * @param clientMethodType type of the client method + * @param clientMethod the client method to add parameter validations for. * @param settings AutoRest generation settings, used to determine if validations should be added. */ - protected static void addValidations(JavaBlock function, List expressionsToCheck, - Map validateExpressions, ClientMethodType clientMethodType, JavaSettings settings) { + protected static void addValidations(JavaBlock function, ClientMethod clientMethod, JavaSettings settings) { if (!settings.isClientSideValidations()) { return; } - - // Iteration of validateExpressions uses expressionsToCheck effectively as a set lookup, may as well turn the - // expressionsToCheck to a set. - Set expressionsToCheckSet = new LinkedHashSet<>(expressionsToCheck); - - for (String expressionToCheck : expressionsToCheckSet) { - // TODO (alzimmer): Need to discuss if this can be changed to the more appropriate NullPointerException. - String exceptionExpression = "new IllegalArgumentException(\"Parameter " + expressionToCheck - + " is required and cannot be null.\")"; - - // TODO (alzimmer): Determine if the assumption being made here are always true. - // 1. Assumes that the expression is nullable. - // 2. Assumes that the client method returns a reactive response. - // 3. Assumes that the reactive response is a Mono. - JavaIfBlock nullCheck = function.ifBlock(expressionToCheck + " == null", ifBlock -> { - if (JavaSettings.getInstance().isSyncStackEnabled() && clientMethodType.isSync()) { + final boolean isSync = JavaSettings.getInstance().isSyncStackEnabled() && clientMethod.getType().isSync(); + // Parameter expressions to validate for non-null value. + final List paramReferenceExpressions = clientMethod.getRequiredNullableParameterExpressions(); + // Parameter expressions for custom validation (key is the expression, value is the validation). + final Map validateParamExpressions = new HashMap<>(clientMethod.getValidateExpressions()); + + for (String paramReferenceExpression : paramReferenceExpressions) { + final JavaIfBlock nullCheck = function.ifBlock(paramReferenceExpression + " == null", ifBlock -> { + final String paramRequiredException = "new IllegalArgumentException(\"Parameter " + + paramReferenceExpression + " is required and cannot be null.\")"; + if (isSync) { if (settings.isUseClientLogger()) { - ifBlock.line("throw LOGGER.atError().log(" + exceptionExpression + ");"); + ifBlock.line("throw LOGGER.atError().log(" + paramRequiredException + ");"); } else { - ifBlock.line("throw " + exceptionExpression + ";"); + ifBlock.line("throw " + paramRequiredException + ";"); } } else { - ifBlock.methodReturn("Mono.error(" + exceptionExpression + ")"); + ifBlock.methodReturn("Mono.error(" + paramRequiredException + ")"); } }); - String potentialValidateExpression = validateExpressions.get(expressionToCheck); - if (potentialValidateExpression != null) { - nullCheck.elseBlock(elseBlock -> elseBlock.line(potentialValidateExpression + ";")); + final String validateParamExpression = validateParamExpressions.remove(paramReferenceExpression); + if (validateParamExpression != null) { + nullCheck.elseBlock(elseBlock -> elseBlock.line(validateParamExpression + ";")); } } - for (Map.Entry validateExpression : validateExpressions.entrySet()) { - if (!expressionsToCheckSet.contains(validateExpression.getKey())) { - function.ifBlock(validateExpression.getKey() + " != null", - ifBlock -> ifBlock.line(validateExpression.getValue() + ";")); - } + for (Map.Entry e : validateParamExpressions.entrySet()) { + final String paramReferenceExpression = e.getKey(); + final String validateParamExpression = e.getValue(); + function.ifBlock(paramReferenceExpression + " != null", + ifBlock -> ifBlock.line(validateParamExpression + ";")); } } @@ -128,15 +120,12 @@ protected static void addOptionalVariables(JavaBlock function, ClientMethod clie } for (ClientMethodParameter parameter : clientMethod.getMethodParameters()) { - // Parameter is required and will be part of the method signature. if (parameter.isRequired()) { + // Parameter is required and will be part of the method signature. continue; } - - IType parameterClientType = parameter.getClientType(); - String defaultValue = parameterDefaultValueExpression(parameterClientType, parameter.getDefaultValue(), - JavaSettings.getInstance()); - function.line("final %s %s = %s;", parameterClientType, parameter.getName(), + final String defaultValue = parameterDefaultValueExpression(parameter); + function.line("final %s %s = %s;", parameter.getClientType(), parameter.getName(), defaultValue == null ? "null" : defaultValue); } } @@ -146,36 +135,24 @@ protected static void addOptionalVariables(JavaBlock function, ClientMethod clie * * @param function The client method code block. * @param clientMethod The client method. - * @param proxyMethodAndConstantParameters Proxy method constant parameters. * @param settings AutoRest generation settings. */ protected static void addOptionalAndConstantVariables(JavaBlock function, ClientMethod clientMethod, - List proxyMethodAndConstantParameters, JavaSettings settings) { - addOptionalAndConstantVariables(function, clientMethod, proxyMethodAndConstantParameters, settings, true, true, - true); - } + JavaSettings settings) { + final List proxyMethodParameters = clientMethod.getProxyMethod().getParameters(); + for (ProxyMethodParameter parameter : proxyMethodParameters) { + if (parameter.isFromClient()) { + // parameter is scoped to the client, hence no local variable instantiation for it. + continue; + } - /** - * Add optional and constant variables. - * - * @param function The client method code block. - * @param clientMethod The client method. - * @param proxyMethodAndConstantParameters Proxy method constant parameters. - * @param settings AutoRest generation settings. - * @param addOptional Whether optional variable instantiations are added, initialized to default or null. - * @param addConstant Whether constant variables are added, initialized to default. - * @param ignoreParameterNeedConvert When adding optional/constant variable, ignore those which need conversion from - * client type to wire type. Let "ConvertClientTypesToWireTypes" handle them. - */ - protected static void addOptionalAndConstantVariables(JavaBlock function, ClientMethod clientMethod, - List proxyMethodAndConstantParameters, JavaSettings settings, boolean addOptional, - boolean addConstant, boolean ignoreParameterNeedConvert) { - for (ProxyMethodParameter parameter : proxyMethodAndConstantParameters) { - IType parameterWireType = parameter.getWireType(); + IType parameterWireType; if (parameter.isNullable()) { - parameterWireType = parameterWireType.asNullable(); + parameterWireType = parameter.getWireType().asNullable(); + } else { + parameterWireType = parameter.getWireType(); } - IType parameterClientType = parameter.getClientType(); + final IType parameterClientType = parameter.getClientType(); // TODO (alzimmer): There are a few similar transforms like this but they all have slight nuances on output. // This always turns ArrayType and ListType into String, the case further down this file may not. @@ -186,16 +163,15 @@ protected static void addOptionalAndConstantVariables(JavaBlock function, Client parameterWireType = ClassType.STRING; } - // If the parameter isn't required and the client method only uses required parameters optional + // If the parameter isn't required and the client method only uses required parameters, optional // parameters are omitted and will need to instantiated in the method. boolean optionalOmitted = clientMethod.getOnlyRequiredParameters() && !parameter.isRequired(); // Optional variables and constants are always null if their wire type and client type differ and applying // conversions between the types is ignored. - boolean alwaysNull - = ignoreParameterNeedConvert && parameterWireType != parameterClientType && optionalOmitted; + boolean alwaysNull = parameterWireType != parameterClientType && optionalOmitted; - // Constants should be included if the parameter is a constant and it's either required or optional + // Constants should be included if the parameter is a constant, and it's either required or optional // constants aren't generated as enums. boolean includeConstant = parameter.isConstant() && (!settings.isOptionalConstantAsEnum() || parameter.isRequired()); @@ -203,27 +179,22 @@ protected static void addOptionalAndConstantVariables(JavaBlock function, Client // Client methods only add local variable instantiations when the parameter isn't passed by the caller, // isn't always null, is an optional parameter that was omitted or is a constant that is either required // or AutoRest isn't generating with optional constant as enums. - if (!parameter.isFromClient() - && !alwaysNull - && ((addOptional && optionalOmitted) || (addConstant && includeConstant))) { - String defaultValue - = parameterDefaultValueExpression(parameterClientType, parameter.getDefaultValue(), settings); + if (!alwaysNull && (optionalOmitted || includeConstant)) { + final String defaultValue = parameterDefaultValueExpression(parameter); function.line("final %s %s = %s;", parameterClientType, parameter.getParameterReference(), defaultValue == null ? "null" : defaultValue); } } } - private static String parameterDefaultValueExpression(IType parameterClientType, String parameterDefaultValue, - JavaSettings settings) { - String defaultValue; - if (settings.isNullByteArrayMapsToEmptyArray() && parameterClientType == ArrayType.BYTE_ARRAY) { + private static String parameterDefaultValueExpression(MethodParameter parameter) { + final IType clientType = parameter.getClientType(); + if (clientType == ArrayType.BYTE_ARRAY && JavaSettings.getInstance().isNullByteArrayMapsToEmptyArray()) { // there's no EMPTY_BYTE_ARRAY in clients, unlike that in models - defaultValue = "new byte[0]"; + return "new byte[0]"; } else { - defaultValue = parameterClientType.defaultValueExpression(parameterDefaultValue); + return clientType.defaultValueExpression(parameter.getDefaultValue()); } - return defaultValue; } /** @@ -345,11 +316,10 @@ protected static void applyParameterTransformations(JavaBlock function, ClientMe * * @param function The client method code block. * @param clientMethod The client method. - * @param autoRestMethodRetrofitParameters Rest API method parameters. */ - protected static void convertClientTypesToWireTypes(JavaBlock function, ClientMethod clientMethod, - List autoRestMethodRetrofitParameters) { - for (ProxyMethodParameter parameter : autoRestMethodRetrofitParameters) { + protected static void convertClientTypesToWireTypes(JavaBlock function, ClientMethod clientMethod) { + final List proxyMethodParameters = clientMethod.getProxyMethod().getParameters(); + for (ProxyMethodParameter parameter : proxyMethodParameters) { IType parameterWireType = parameter.getWireType(); if (parameter.isNullable()) { @@ -595,10 +565,6 @@ public void write(ClientMethod clientMethod, JavaType typeBlock) { JavaSettings settings = JavaSettings.getInstance(); ProxyMethod restAPIMethod = clientMethod.getProxyMethod(); - // IType restAPIMethodReturnBodyClientType = restAPIMethod.getReturnType().getClientType(); - - // MethodPageDetails pageDetails = clientMethod.getMethodPageDetails(); - generateJavadoc(clientMethod, typeBlock, restAPIMethod, writingInterface); switch (clientMethod.getType()) { @@ -611,46 +577,46 @@ public void write(ClientMethod clientMethod, JavaType typeBlock) { } } else { if (settings.isDataPlaneClient()) { - generateProtocolPagingSync(clientMethod, typeBlock, restAPIMethod, settings); + generateProtocolPagingSync(clientMethod, typeBlock, settings); } else { - generatePagingSync(clientMethod, typeBlock, restAPIMethod, settings); + generatePagingSync(clientMethod, typeBlock, settings); } } break; case PagingAsync: if (settings.isDataPlaneClient()) { - generateProtocolPagingAsync(clientMethod, typeBlock, restAPIMethod, settings); + generateProtocolPagingAsync(clientMethod, typeBlock, settings); } else { - generatePagingAsync(clientMethod, typeBlock, restAPIMethod, settings); + generatePagingAsync(clientMethod, typeBlock, settings); } break; case PagingSyncSinglePage: if (settings.isDataPlaneClient()) { - generateProtocolPagingSinglePage(clientMethod, typeBlock, restAPIMethod.toSync(), settings); + generateProtocolPagingSinglePage(clientMethod, typeBlock, settings); } else { - generatePagedSinglePage(clientMethod, typeBlock, restAPIMethod.toSync(), settings); + generatePagedSinglePage(clientMethod, typeBlock, settings); } break; case PagingAsyncSinglePage: if (settings.isDataPlaneClient()) { - generateProtocolPagingAsyncSinglePage(clientMethod, typeBlock, restAPIMethod, settings); + generateProtocolPagingAsyncSinglePage(clientMethod, typeBlock, settings); } else { - generatePagedAsyncSinglePage(clientMethod, typeBlock, restAPIMethod, settings); + generatePagedAsyncSinglePage(clientMethod, typeBlock, settings); } break; case LongRunningAsync: - generateLongRunningAsync(clientMethod, typeBlock, restAPIMethod, settings); + generateLongRunningAsync(clientMethod, typeBlock, settings); break; case LongRunningSync: if (settings.isSyncStackEnabled()) { - generateLongRunningPlainSync(clientMethod, typeBlock, restAPIMethod, settings); + generateLongRunningPlainSync(clientMethod, typeBlock, settings); } else { - generateSyncMethod(clientMethod, typeBlock, restAPIMethod, settings); + generateSyncMethod(clientMethod, typeBlock, settings); } break; @@ -658,7 +624,7 @@ public void write(ClientMethod clientMethod, JavaType typeBlock) { if (settings.isDataPlaneClient()) { generateProtocolLongRunningBeginAsync(clientMethod, typeBlock); } else { - generateLongRunningBeginAsync(clientMethod, typeBlock, restAPIMethod, settings); + generateLongRunningBeginAsync(clientMethod, typeBlock, settings); } break; @@ -667,7 +633,7 @@ public void write(ClientMethod clientMethod, JavaType typeBlock) { if (settings.isDataPlaneClient()) { generateProtocolLongRunningBeginSync(clientMethod, typeBlock); } else { - generateLongRunningBeginSync(clientMethod, typeBlock, restAPIMethod, settings); + generateLongRunningBeginSync(clientMethod, typeBlock, settings); } } else { generateLongRunningBeginSyncOverAsync(clientMethod, typeBlock); @@ -675,7 +641,7 @@ public void write(ClientMethod clientMethod, JavaType typeBlock) { break; case Resumable: - generateResumable(clientMethod, typeBlock, restAPIMethod, settings); + generateResumable(clientMethod, typeBlock, settings); break; case SimpleSync: @@ -688,18 +654,18 @@ public void write(ClientMethod clientMethod, JavaType typeBlock) { case SimpleSyncRestResponse: if (settings.isSyncStackEnabled()) { - generatePlainSyncMethod(clientMethod, typeBlock, restAPIMethod, settings); + generatePlainSyncMethod(clientMethod, typeBlock, settings); } else { - generateSyncMethod(clientMethod, typeBlock, restAPIMethod, settings); + generateSyncMethod(clientMethod, typeBlock, settings); } break; case SimpleAsyncRestResponse: - generateSimpleAsyncRestResponse(clientMethod, typeBlock, restAPIMethod, settings); + generateSimpleAsyncRestResponse(clientMethod, typeBlock, settings); break; case SimpleAsync: - generateSimpleAsync(clientMethod, typeBlock, restAPIMethod, settings); + generateSimpleAsync(clientMethod, typeBlock, settings); break; case SendRequestAsync: @@ -712,9 +678,8 @@ public void write(ClientMethod clientMethod, JavaType typeBlock) { } } - protected void generateProtocolPagingSync(ClientMethod clientMethod, JavaType typeBlock, ProxyMethod restAPIMethod, - JavaSettings settings) { - generatePagingSync(clientMethod, typeBlock, restAPIMethod, settings); + protected void generateProtocolPagingSync(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings) { + generatePagingSync(clientMethod, typeBlock, settings); } protected void generateProtocolPagingPlainSync(ClientMethod clientMethod, JavaType typeBlock, @@ -794,25 +759,24 @@ protected void generateProtocolPagingPlainSync(ClientMethod clientMethod, JavaTy } } - protected void generateProtocolPagingAsync(ClientMethod clientMethod, JavaType typeBlock, ProxyMethod restAPIMethod, - JavaSettings settings) { - generatePagingAsync(clientMethod, typeBlock, restAPIMethod, settings); + protected void generateProtocolPagingAsync(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings) { + generatePagingAsync(clientMethod, typeBlock, settings); } protected void generateProtocolPagingAsyncSinglePage(ClientMethod clientMethod, JavaType typeBlock, - ProxyMethod restAPIMethod, JavaSettings settings) { - generatePagedAsyncSinglePage(clientMethod, typeBlock, restAPIMethod, settings); + JavaSettings settings) { + generatePagedAsyncSinglePage(clientMethod, typeBlock, settings); } protected void generateProtocolPagingSinglePage(ClientMethod clientMethod, JavaType typeBlock, - ProxyMethod restAPIMethod, JavaSettings settings) { - generatePagedSinglePage(clientMethod, typeBlock, restAPIMethod, settings); + JavaSettings settings) { + generatePagedSinglePage(clientMethod, typeBlock, settings); } - private void generatePagedSinglePage(ClientMethod clientMethod, JavaType typeBlock, ProxyMethod restAPIMethod, - JavaSettings settings) { - addServiceMethodAnnotation(typeBlock, ReturnType.SINGLE); + private void generatePagedSinglePage(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings) { + final ProxyMethod restAPIMethod = clientMethod.getProxyMethod().toSync(); + addServiceMethodAnnotation(typeBlock, ReturnType.SINGLE); writeMethod(typeBlock, clientMethod.getMethodVisibility(), clientMethod.getDeclaration(), function -> { if (!settings.isSyncStackEnabled()) { function.methodReturn( @@ -821,11 +785,10 @@ private void generatePagedSinglePage(ClientMethod clientMethod, JavaType typeBlo return; } - addValidations(function, clientMethod.getRequiredNullableParameterExpressions(), - clientMethod.getValidateExpressions(), clientMethod.getType(), settings); - addOptionalAndConstantVariables(function, clientMethod, restAPIMethod.getParameters(), settings); + addValidations(function, clientMethod, settings); + addOptionalAndConstantVariables(function, clientMethod, settings); applyParameterTransformations(function, clientMethod, settings); - convertClientTypesToWireTypes(function, clientMethod, restAPIMethod.getParameters()); + convertClientTypesToWireTypes(function, clientMethod); boolean requestOptionsLocal = false; if (settings.isDataPlaneClient()) { @@ -913,8 +876,7 @@ protected void pagedSinglePageResponseConversion(ProxyMethod restAPIMethod, Clie } } - protected void generatePagingSync(ClientMethod clientMethod, JavaType typeBlock, ProxyMethod restAPIMethod, - JavaSettings settings) { + protected void generatePagingSync(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings) { addServiceMethodAnnotation(typeBlock, ReturnType.COLLECTION); writeMethod(typeBlock, clientMethod.getMethodVisibility(), clientMethod.getDeclaration(), function -> { addOptionalVariables(function, clientMethod); @@ -966,8 +928,7 @@ protected void generatePagingPlainSync(ClientMethod clientMethod, JavaType typeB } } - protected void generatePagingAsync(ClientMethod clientMethod, JavaType typeBlock, ProxyMethod restAPIMethod, - JavaSettings settings) { + protected void generatePagingAsync(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings) { addServiceMethodAnnotation(typeBlock, ReturnType.COLLECTION); if (clientMethod.getMethodPageDetails().nonNullNextLink()) { writeMethod(typeBlock, clientMethod.getMethodVisibility(), clientMethod.getDeclaration(), function -> { @@ -1006,19 +967,18 @@ private static void addServiceMethodAnnotation(JavaType typeBlock, ReturnType re typeBlock.annotation("ServiceMethod(returns = ReturnType." + returnType.name() + ")"); } - protected void generateResumable(ClientMethod clientMethod, JavaType typeBlock, ProxyMethod restAPIMethod, - JavaSettings settings) { + protected void generateResumable(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings) { + final ProxyMethod restAPIMethod = clientMethod.getProxyMethod(); + addServiceMethodAnnotation(typeBlock, ReturnType.SINGLE); typeBlock.publicMethod(clientMethod.getDeclaration(), function -> { ProxyMethodParameter parameter = restAPIMethod.getParameters().get(0); - addValidations(function, clientMethod.getRequiredNullableParameterExpressions(), - clientMethod.getValidateExpressions(), clientMethod.getType(), settings); + addValidations(function, clientMethod, settings); function.methodReturn("service." + restAPIMethod.getName() + "(" + parameter.getName() + ")"); }); } - protected void generateSimpleAsync(ClientMethod clientMethod, JavaType typeBlock, ProxyMethod restAPIMethod, - JavaSettings settings) { + protected void generateSimpleAsync(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings) { addServiceMethodAnnotation(typeBlock, ReturnType.SINGLE); writeMethod(typeBlock, clientMethod.getMethodVisibility(), clientMethod.getDeclaration(), (function -> { addOptionalVariables(function, clientMethod); @@ -1089,8 +1049,7 @@ private void generateSimplePlainSyncMethod(ClientMethod clientMethod, JavaType t })); } - protected void generateSyncMethod(ClientMethod clientMethod, JavaType typeBlock, ProxyMethod restAPIMethod, - JavaSettings settings) { + protected void generateSyncMethod(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings) { String asyncMethodName = MethodNamer.getSimpleAsyncMethodName(clientMethod.getName()); if (clientMethod.getType() == ClientMethodType.SimpleSyncRestResponse) { asyncMethodName = clientMethod.getProxyMethod().getSimpleAsyncRestResponseMethodName(); @@ -1167,17 +1126,17 @@ protected void generateSyncMethod(ClientMethod clientMethod, JavaType typeBlock, }); } - protected void generatePlainSyncMethod(ClientMethod clientMethod, JavaType typeBlock, ProxyMethod restAPIMethod, - JavaSettings settings) { - String effectiveProxyMethodName = clientMethod.getProxyMethod().getName(); + protected void generatePlainSyncMethod(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings) { + final ProxyMethod restAPIMethod = clientMethod.getProxyMethod(); + String effectiveProxyMethodName = restAPIMethod.getName(); + addServiceMethodAnnotation(typeBlock, ReturnType.SINGLE); writeMethod(typeBlock, clientMethod.getMethodVisibility(), clientMethod.getDeclaration(), function -> { - addValidations(function, clientMethod.getRequiredNullableParameterExpressions(), - clientMethod.getValidateExpressions(), clientMethod.getType(), settings); - addOptionalAndConstantVariables(function, clientMethod, restAPIMethod.getParameters(), settings); + addValidations(function, clientMethod, settings); + addOptionalAndConstantVariables(function, clientMethod, settings); applyParameterTransformations(function, clientMethod, settings); - convertClientTypesToWireTypes(function, clientMethod, restAPIMethod.getParameters()); + convertClientTypesToWireTypes(function, clientMethod); boolean requestOptionsLocal = false; if (settings.isDataPlaneClient()) { @@ -1283,9 +1242,9 @@ protected static String parameterDescriptionOrDefault(ClientMethodParameter para return paramJavadoc; } - protected void generatePagedAsyncSinglePage(ClientMethod clientMethod, JavaType typeBlock, - ProxyMethod restAPIMethod, JavaSettings settings) { + protected void generatePagedAsyncSinglePage(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings) { addServiceMethodAnnotation(typeBlock, ReturnType.SINGLE); + final ProxyMethod restAPIMethod = clientMethod.getProxyMethod(); writeMethod(typeBlock, clientMethod.getMethodVisibility(), clientMethod.getDeclaration(), function -> { if (clientMethod.hasWithContextOverload()) { @@ -1301,11 +1260,10 @@ protected void generatePagedAsyncSinglePage(ClientMethod clientMethod, JavaType return; } - addValidations(function, clientMethod.getRequiredNullableParameterExpressions(), - clientMethod.getValidateExpressions(), clientMethod.getType(), settings); - addOptionalAndConstantVariables(function, clientMethod, restAPIMethod.getParameters(), settings); + addValidations(function, clientMethod, settings); + addOptionalAndConstantVariables(function, clientMethod, settings); applyParameterTransformations(function, clientMethod, settings); - convertClientTypesToWireTypes(function, clientMethod, restAPIMethod.getParameters()); + convertClientTypesToWireTypes(function, clientMethod); boolean requestOptionsLocal = false; if (settings.isDataPlaneClient()) { @@ -1464,7 +1422,9 @@ private static String checkAndReplaceParamNameCollision(ClientMethod clientMetho } protected void generateSimpleAsyncRestResponse(ClientMethod clientMethod, JavaType typeBlock, - ProxyMethod restAPIMethod, JavaSettings settings) { + JavaSettings settings) { + final ProxyMethod restAPIMethod = clientMethod.getProxyMethod(); + addServiceMethodAnnotation(typeBlock, ReturnType.SINGLE); writeMethod(typeBlock, clientMethod.getMethodVisibility(), clientMethod.getDeclaration(), function -> { if (clientMethod.hasWithContextOverload()) { @@ -1480,11 +1440,10 @@ protected void generateSimpleAsyncRestResponse(ClientMethod clientMethod, JavaTy return; } - addValidations(function, clientMethod.getRequiredNullableParameterExpressions(), - clientMethod.getValidateExpressions(), clientMethod.getType(), settings); - addOptionalAndConstantVariables(function, clientMethod, restAPIMethod.getParameters(), settings); + addValidations(function, clientMethod, settings); + addOptionalAndConstantVariables(function, clientMethod, settings); applyParameterTransformations(function, clientMethod, settings); - convertClientTypesToWireTypes(function, clientMethod, restAPIMethod.getParameters()); + convertClientTypesToWireTypes(function, clientMethod); boolean requestOptionsLocal = false; if (settings.isDataPlaneClient()) { @@ -1510,11 +1469,9 @@ protected boolean contextInParameters(ClientMethod clientMethod) { * * @param clientMethod client method * @param typeBlock type block - * @param restAPIMethod proxy method * @param settings java settings */ - protected void generateLongRunningAsync(ClientMethod clientMethod, JavaType typeBlock, ProxyMethod restAPIMethod, - JavaSettings settings) { + protected void generateLongRunningAsync(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings) { } @@ -1523,11 +1480,9 @@ protected void generateLongRunningAsync(ClientMethod clientMethod, JavaType type * * @param clientMethod client method * @param typeBlock type block - * @param restAPIMethod proxy method * @param settings java settings */ - protected void generateLongRunningPlainSync(ClientMethod clientMethod, JavaType typeBlock, - ProxyMethod restAPIMethod, JavaSettings settings) { + protected void generateLongRunningPlainSync(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings) { } @@ -1536,11 +1491,9 @@ protected void generateLongRunningPlainSync(ClientMethod clientMethod, JavaType * * @param clientMethod client method * @param typeBlock type block - * @param restAPIMethod proxy method * @param settings java settings */ - protected void generateLongRunningBeginAsync(ClientMethod clientMethod, JavaType typeBlock, - ProxyMethod restAPIMethod, JavaSettings settings) { + protected void generateLongRunningBeginAsync(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings) { String contextParam; if (clientMethod.getParameters().stream().anyMatch(p -> p.getClientType().equals(ClassType.CONTEXT))) { contextParam = "context"; @@ -1584,11 +1537,9 @@ protected void generateLongRunningBeginSyncOverAsync(ClientMethod clientMethod, * * @param clientMethod client method * @param typeBlock type block - * @param restAPIMethod proxy method * @param settings java settings */ - protected void generateLongRunningBeginSync(ClientMethod clientMethod, JavaType typeBlock, - ProxyMethod restAPIMethod, JavaSettings settings) { + protected void generateLongRunningBeginSync(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings) { typeBlock.annotation("ServiceMethod(returns = ReturnType.LONG_RUNNING_OPERATION)"); String contextParam; if (clientMethod.getParameters().stream().anyMatch(p -> p.getClientType().equals(ClassType.CONTEXT))) { diff --git a/packages/http-client-java/generator/http-client-generator-mgmt/src/main/java/com/microsoft/typespec/http/client/generator/mgmt/template/FluentClientMethodTemplate.java b/packages/http-client-java/generator/http-client-generator-mgmt/src/main/java/com/microsoft/typespec/http/client/generator/mgmt/template/FluentClientMethodTemplate.java index d7ba2ed8e12..5a74946a91c 100644 --- a/packages/http-client-java/generator/http-client-generator-mgmt/src/main/java/com/microsoft/typespec/http/client/generator/mgmt/template/FluentClientMethodTemplate.java +++ b/packages/http-client-java/generator/http-client-generator-mgmt/src/main/java/com/microsoft/typespec/http/client/generator/mgmt/template/FluentClientMethodTemplate.java @@ -25,8 +25,8 @@ public static FluentClientMethodTemplate getInstance() { } @Override - protected void generatePagedAsyncSinglePage(ClientMethod clientMethod, JavaType typeBlock, - ProxyMethod restAPIMethod, JavaSettings settings) { + protected void generatePagedAsyncSinglePage(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings) { + final ProxyMethod restAPIMethod = clientMethod.getProxyMethod(); boolean addContextParameter = !contextInParameters(clientMethod); boolean mergeContextParameter = contextInParameters(clientMethod); boolean isLroPagination = GenericType.Mono(GenericType.Response(GenericType.FLUX_BYTE_BUFFER)) @@ -40,11 +40,10 @@ protected void generatePagedAsyncSinglePage(ClientMethod clientMethod, JavaType String serviceMethodCall = String.format("service.%s(%s)", restAPIMethod.getName(), restAPIMethodArgumentList); if (clientMethod.getMethodPageDetails().nonNullNextLink()) { writeMethod(typeBlock, clientMethod.getMethodVisibility(), clientMethod.getDeclaration(), function -> { - addValidations(function, clientMethod.getRequiredNullableParameterExpressions(), - clientMethod.getValidateExpressions(), clientMethod.getType(), settings); - addOptionalAndConstantVariables(function, clientMethod, restAPIMethod.getParameters(), settings); + addValidations(function, clientMethod, settings); + addOptionalAndConstantVariables(function, clientMethod, settings); applyParameterTransformations(function, clientMethod, settings); - convertClientTypesToWireTypes(function, clientMethod, restAPIMethod.getParameters()); + convertClientTypesToWireTypes(function, clientMethod); if (mergeContextParameter) { function .line(String.format("context = %s.mergeContext(context);", clientMethod.getClientReference())); @@ -125,11 +124,10 @@ protected void generatePagedAsyncSinglePage(ClientMethod clientMethod, JavaType }); } else { writeMethod(typeBlock, clientMethod.getMethodVisibility(), clientMethod.getDeclaration(), function -> { - addValidations(function, clientMethod.getRequiredNullableParameterExpressions(), - clientMethod.getValidateExpressions(), clientMethod.getType(), settings); - addOptionalAndConstantVariables(function, clientMethod, restAPIMethod.getParameters(), settings); + addValidations(function, clientMethod, settings); + addOptionalAndConstantVariables(function, clientMethod, settings); applyParameterTransformations(function, clientMethod, settings); - convertClientTypesToWireTypes(function, clientMethod, restAPIMethod.getParameters()); + convertClientTypesToWireTypes(function, clientMethod); if (mergeContextParameter) { function .line(String.format("context = %s.mergeContext(context);", clientMethod.getClientReference())); @@ -241,17 +239,17 @@ protected void pagedSinglePageResponseConversion(ProxyMethod restAPIMethod, Clie @Override protected void generateSimpleAsyncRestResponse(ClientMethod clientMethod, JavaType typeBlock, - ProxyMethod restAPIMethod, JavaSettings settings) { + JavaSettings settings) { + final ProxyMethod restAPIMethod = clientMethod.getProxyMethod(); boolean addContextParameter = !contextInParameters(clientMethod); boolean mergeContextParameter = !addContextParameter; typeBlock.annotation("ServiceMethod(returns = ReturnType.SINGLE)"); writeMethod(typeBlock, clientMethod.getMethodVisibility(), clientMethod.getDeclaration(), function -> { - addValidations(function, clientMethod.getRequiredNullableParameterExpressions(), - clientMethod.getValidateExpressions(), clientMethod.getType(), settings); - addOptionalAndConstantVariables(function, clientMethod, restAPIMethod.getParameters(), settings); + addValidations(function, clientMethod, settings); + addOptionalAndConstantVariables(function, clientMethod, settings); applyParameterTransformations(function, clientMethod, settings); - convertClientTypesToWireTypes(function, clientMethod, restAPIMethod.getParameters()); + convertClientTypesToWireTypes(function, clientMethod); String restAPIMethodArgumentList = String.join(", ", clientMethod.getProxyMethodArguments(settings)); String serviceMethodCall @@ -273,8 +271,9 @@ protected void generateSimpleAsyncRestResponse(ClientMethod clientMethod, JavaTy } @Override - protected void generateLongRunningAsync(ClientMethod clientMethod, JavaType typeBlock, ProxyMethod restAPIMethod, - JavaSettings settings) { + protected void generateLongRunningAsync(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings) { + final ProxyMethod restAPIMethod = clientMethod.getProxyMethod(); + typeBlock.annotation("ServiceMethod(returns = ReturnType.SINGLE)"); String beginAsyncMethodName = MethodNamer.getLroBeginAsyncMethodName(restAPIMethod.getName()); writeMethod(typeBlock, clientMethod.getMethodVisibility(), clientMethod.getDeclaration(), function -> { @@ -289,8 +288,9 @@ protected void generateLongRunningAsync(ClientMethod clientMethod, JavaType type } @Override - protected void generateLongRunningPlainSync(ClientMethod clientMethod, JavaType typeBlock, - ProxyMethod restAPIMethod, JavaSettings settings) { + protected void generateLongRunningPlainSync(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings) { + final ProxyMethod restAPIMethod = clientMethod.getProxyMethod(); + typeBlock.annotation("ServiceMethod(returns = ReturnType.SINGLE)"); writeMethod(typeBlock, clientMethod.getMethodVisibility(), clientMethod.getDeclaration(), function -> { addOptionalVariables(function, clientMethod); @@ -305,8 +305,7 @@ protected void generateLongRunningPlainSync(ClientMethod clientMethod, JavaType } @Override - protected void generateLongRunningBeginAsync(ClientMethod clientMethod, JavaType typeBlock, - ProxyMethod restAPIMethod, JavaSettings settings) { + protected void generateLongRunningBeginAsync(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings) { boolean mergeContextParameter = contextInParameters(clientMethod); String contextParam = mergeContextParameter ? "context" : String.format("%s.getContext()", clientMethod.getClientReference());; @@ -342,9 +341,10 @@ protected void generateLongRunningBeginAsync(ClientMethod clientMethod, JavaType } @Override - protected void generateLongRunningBeginSync(ClientMethod clientMethod, JavaType typeBlock, - ProxyMethod restAPIMethod, JavaSettings settings) { + protected void generateLongRunningBeginSync(ClientMethod clientMethod, JavaType typeBlock, JavaSettings settings) { + final ProxyMethod restAPIMethod = clientMethod.getProxyMethod(); boolean contextInParameters = contextInParameters(clientMethod); + typeBlock.annotation("ServiceMethod(returns = ReturnType.LONG_RUNNING_OPERATION)"); typeBlock.publicMethod(clientMethod.getDeclaration(), function -> { addOptionalVariables(function, clientMethod); From 2ce99ae1e28eaa3f952bcc1418c0a9c0d08f8810 Mon Sep 17 00:00:00 2001 From: anuchandy Date: Wed, 28 May 2025 13:13:09 -0700 Subject: [PATCH 2/4] Prepare ClientModelTemplate::convertClientTypesToWireTypes() to be extensible --- .../core/template/ClientMethodTemplate.java | 309 +++++++++++------- 1 file changed, 199 insertions(+), 110 deletions(-) diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java index 9e9df9a6cd7..7a7a9a82d50 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java @@ -345,127 +345,46 @@ protected static void convertClientTypesToWireTypes(JavaBlock function, ClientMe String parameterName = parameter.getParameterReference(); String parameterWireName = parameter.getParameterReferenceConverted(); - boolean addedConversion = false; - boolean alwaysNull = clientMethod.getOnlyRequiredParameters() && !parameter.isRequired(); - - RequestParameterLocation parameterLocation = parameter.getRequestParameterLocation(); - if (parameterLocation != RequestParameterLocation.BODY && - // parameterLocation != RequestParameterLocation.FormData && - (parameterClientType instanceof ArrayType || parameterClientType instanceof IterableType)) { + final boolean alwaysNull = clientMethod.getOnlyRequiredParameters() && !parameter.isRequired(); + final RequestParameterLocation location = parameter.getRequestParameterLocation(); + boolean addedConversion = false; + if (location != RequestParameterLocation.BODY) { if (parameterClientType == ArrayType.BYTE_ARRAY) { - String expression = "null"; - if (!alwaysNull) { - String methodCall = (parameterWireType == ClassType.STRING) - ? "Base64Util.encodeToString" - : (ClassType.BASE_64_URL.getName() + ".encode"); - expression = methodCall + "(" + parameterName + ")"; + // The only expected 'ArrayType' is 'ArrayType.BYTE_ARRAY' (See definition of ArrayType). + if (alwaysNull) { + function.line("%s %s = %s;", parameterWireType, parameterWireName, "null"); + } else { + final String expression = byteArrayToBase64UrlEncodedString(parameterName, parameterWireType); + function.line("%s %s = %s;", parameterWireType, parameterWireName, expression); } - - function.line(parameterWireType + " " + parameterWireName + " = " + expression + ";"); addedConversion = true; } else if (parameterClientType instanceof IterableType) { - boolean alreadyNullChecked - = clientMethod.getRequiredNullableParameterExpressions().contains(parameterName); - IType elementType = ((IterableType) parameterClientType).getElementType(); - String expression; + boolean shouldNullCheck + = !clientMethod.getRequiredNullableParameterExpressions().contains(parameterName); if (alwaysNull) { - expression = "null"; + function.line("%s %s = %s;", parameterWireType, parameterWireName, "null"); } else if (!parameter.getExplode()) { - CollectionFormat collectionFormat = parameter.getCollectionFormat(); - String delimiter = ClassType.STRING.defaultValueExpression(collectionFormat.getDelimiter()); - if (elementType instanceof EnumType) { - // EnumTypes should provide a toString implementation that represents the wire value. - // Circumvent the use of JacksonAdapter and handle this manually. - - // If the parameter is null, the converted value is null. - // Otherwise, convert the parameter to a string, mapping each element to the toString - // value, finally joining with the collection format. - EnumType enumType = (EnumType) elementType; - // Not enums will be backed by Strings. Get the backing value before converting to string - // it, this - // will prevent using the enum name rather than the enum value when it isn't a String-based - // enum. Ex, a long-based enum with value 100 called HIGH will return "100" rather than - // "HIGH". - String enumToString = enumType.getElementType() == ClassType.STRING - ? "paramItemValue" - : "paramItemValue == null ? null : paramItemValue." + enumType.getToMethodName() + "()"; - if (alreadyNullChecked) { - expression = parameterName + ".stream()\n" - + " .map(paramItemValue -> Objects.toString(" + enumToString + ", \"\"))\n" - + " .collect(Collectors.joining(" + delimiter + "))"; - } else { - expression = "(" + parameterName + " == null) ? null : " + parameterName + ".stream()\n" - + " .map(paramItemValue -> Objects.toString(" + enumToString + ", \"\"))\n" - + " .collect(Collectors.joining(" + delimiter + "))"; - } - } else { - if (elementType == ClassType.STRING - || (elementType instanceof ClassType && ((ClassType) elementType).isBoxedType())) { - String streamSource = parameterName; - if (!alreadyNullChecked) { - streamSource = "(" + parameterName + " == null) ? null : " + parameterName; - } - expression = streamSource + ".stream()\n" - + " .map(paramItemValue -> Objects.toString(paramItemValue, \"\"))\n" - + " .collect(Collectors.joining(" + delimiter + "))"; - } else { - // this logic depends on rawType of proxy method parameter be List - // alternative would be check wireType of client method parameter - IType elementWireType = parameter.getRawType() instanceof IterableType - ? ((IterableType) parameter.getRawType()).getElementType() - : elementType; - - String serializeIterableInput = parameterName; - if (elementWireType != elementType) { - // convert List to List, if necessary - serializeIterableInput = String.format( - "%s.stream().map(paramItemValue -> %s).collect(Collectors.toList())", - parameterName, elementWireType.convertFromClientType("paramItemValue")); - } - - // convert List to String - if (JavaSettings.getInstance().isAzureV1()) { - // Always use serializeIterable as Iterable supports both Iterable and List. - expression = String.format( - "JacksonAdapter.createDefaultSerializerAdapter().serializeIterable(%s, CollectionFormat.%s)", - serializeIterableInput, collectionFormat.toString().toUpperCase(Locale.ROOT)); - } else { - String streamSource = serializeIterableInput; - if (!alreadyNullChecked) { - streamSource = "(" + parameterName + " == null) ? null : " + parameterName; - } - // mostly code from - // https://github.com/Azure/azure-sdk-for-java/blob/e1f8f21b1111f8ac9372e0b039f3de92485a5a66/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/JacksonAdapter.java#L250-L304 - String serializeItemValueCode - = TemplateUtil.loadTextFromResource("ClientMethodSerializeItemValue.java"); - expression = streamSource + ".stream().map(" + serializeItemValueCode - + ").collect(Collectors.joining(" + delimiter + "))"; - } - } - } + final IterableType iterableType = (IterableType) parameterClientType; + final String iterableToStringExpression = iterableToDelimitedStringOfWireValues(parameterName, + shouldNullCheck, iterableType, parameter); + function.line("%s %s = %s;", parameterWireType, parameterWireName, iterableToStringExpression); } else { - if (alreadyNullChecked) { - expression - = parameterName + ".stream()\n" + " .map(item -> Objects.toString(item, \"\"))\n" - + " .collect(Collectors.toList())"; - } else { - expression = "(" + parameterName + " == null) ? new ArrayList<>()\n" + ": " + parameterName - + ".stream().map(item -> Objects.toString(item, \"\")).collect(Collectors.toList())"; - } + String iterableToListExpression + = iterableToWireStringValuesList(parameterName, shouldNullCheck); + function.line("%s %s = %s;", parameterWireType, parameterWireName, iterableToListExpression); } - function.line("%s %s = %s;", parameterWireType, parameterWireName, expression); addedConversion = true; } - } - - if (parameter.getWireType().isUsedInXml() - && parameterClientType instanceof IterableType - && (parameterLocation - == RequestParameterLocation.BODY /* || parameterLocation == RequestParameterLocation.FormData */)) { - function.line("%s %s = new %s(%s);", parameter.getWireType(), parameterWireName, - parameter.getWireType(), alwaysNull ? "null" : parameterName); - addedConversion = true; + } else { + // RequestParameterLocation.BODY + if (parameterClientType instanceof IterableType) { + if (parameter.getWireType().isUsedInXml()) { + function.line("%s %s = new %s(%s);", parameter.getWireType(), parameterWireName, + parameter.getWireType(), alwaysNull ? "null" : parameterName); + addedConversion = true; + } + } } if (!addedConversion) { @@ -476,6 +395,176 @@ protected static void convertClientTypesToWireTypes(JavaBlock function, ClientMe } } + /** + * Obtain the Java code that converts an iterable to a delimited string of wire values. + * + * @param parameterName the name of the parameter to convert. + * @param shouldNullCheck whether to check if the parameter is null before converting it. + * @param iterableType the type of the iterable to convert. + * @param parameter the proxy method parameter that is being converted. + * @return Java code that converts an iterable to a delimited string of wire values. + */ + private static String iterableToDelimitedStringOfWireValues(String parameterName, boolean shouldNullCheck, + IterableType iterableType, ProxyMethodParameter parameter) { + final IType elementType = iterableType.getElementType(); + final boolean isEnumElementType = elementType instanceof EnumType; + final boolean isPrimitiveElementType = elementType == ClassType.STRING + || (elementType instanceof ClassType && ((ClassType) elementType).isBoxedType()); + final CollectionFormat collectionFormat = parameter.getCollectionFormat(); + final String delimiter = ClassType.STRING.defaultValueExpression(collectionFormat.getDelimiter()); + + final String iterableToStringExpression; + if (isEnumElementType) { + final EnumType enumType = (EnumType) elementType; + iterableToStringExpression + = enumIterableToDelimitedStringOfWireValues(parameterName, shouldNullCheck, delimiter, enumType); + } else if (isPrimitiveElementType) { + iterableToStringExpression + = primitiveIterableToDelimitedStringOfWireValues(parameterName, shouldNullCheck, delimiter); + } else { + iterableToStringExpression = modelIterableToDelimitedStringOfWireValues(parameterName, shouldNullCheck, + delimiter, collectionFormat, elementType, parameter); + } + return iterableToStringExpression; + } + + /** + * Obtain the Java code that converts an enum iterable parameter to a string of delimiter seperated wire values. + * + * @param parameterName the name of the enum iterable parameter to convert. + * @param shouldCheckNull whether to check if the parameter is null before converting it. + * @param delimiter the delimiter to use when joining the enum values into a string. + * @param enumType the enum type of the parameter to convert. + * @return Java code that converts an enum iterable parameter to a string of delimiter separated values. + */ + private static String enumIterableToDelimitedStringOfWireValues(String parameterName, boolean shouldCheckNull, + String delimiter, EnumType enumType) { + // EnumTypes should provide a 'toString' implementation that represents the wire value. + // + // If the parameter is null, the converted value is null. Otherwise, convert the parameter to a string, + // mapping each element to the toString value, finally joining with the delimiter (collection format). + // + // Not all enums will be backed by Strings. Get the backing value before converting to string, this will prevent + // using the enum name rather than the enum value when it isn't a String-based enum. Ex, a long-based enum with + // value 100 called HIGH will return "100" rather than "HIGH". + // + final String enumToString = enumType.getElementType() == ClassType.STRING + ? "paramItemValue" + : "paramItemValue == null ? null : paramItemValue." + enumType.getToMethodName() + "()"; + final String streamToString = parameterName + ".stream()" + ".map(paramItemValue -> Objects.toString(" + + enumToString + ", \"\"))" + ".collect(Collectors.joining(" + delimiter + "))"; + if (shouldCheckNull) { + return "(" + parameterName + " == null) ? null : " + streamToString; + } else { + return streamToString; + } + } + + /** + * Obtain the Java code that converts a primitive iterable parameter to a string of delimiter seperated string wire + * values. + *

+ * The primitive types are Boolean, Byte, Integer, Long, Float, Double, String. + *

+ * + * @param parameterName the name of the primitive iterable parameter to convert. + * @param shouldCheckNull whether to check if the parameter is null before converting it. + * @param delimiter the delimiter to use when joining the enum values into a string. + * @return Java code that converts a primitive iterable parameter to a string of delimiter separated values. + */ + private static String primitiveIterableToDelimitedStringOfWireValues(String parameterName, boolean shouldCheckNull, + String delimiter) { + final String streamToString + = parameterName + ".stream()" + ".map(paramItemValue -> Objects.toString(paramItemValue, \"\"))" + + ".collect(Collectors.joining(" + delimiter + "))"; + if (shouldCheckNull) { + return "(" + parameterName + " == null) ? null : " + streamToString; + } else { + return streamToString; + } + } + + /** + * Obtain the Java code that converts an iterable of model types to a delimited string of serialized wire values. + * + * @param parameterName the name of the parameter to convert. + * @param shouldCheckNull whether to check if the parameter is null before converting it. + * @param delimiter the delimiter to use when joining the deserialized model values into a string. + * @param collectionFormat the collection format to use when serializing the iterable to a string. + * @param elementType the type of the elements in the iterable. + * @param parameter the proxy method parameter that is being converted. + * @return Java code that converts an iterable of model types to a delimited string of wire values. + */ + private static String modelIterableToDelimitedStringOfWireValues(String parameterName, boolean shouldCheckNull, + String delimiter, CollectionFormat collectionFormat, IType elementType, ProxyMethodParameter parameter) { + // this logic depends on rawType of proxy method parameter be List + // alternative would be to check wireType of client method parameter. + IType elementWireType = parameter.getRawType() instanceof IterableType + ? ((IterableType) parameter.getRawType()).getElementType() + : elementType; + + final String iterableToSerialize; + if (elementWireType != elementType) { + // convert List to List. + iterableToSerialize = parameterName + ".stream()" + ".map(paramItemValue -> " + + elementWireType.convertFromClientType("paramItemValue") + ")" + ".collect(Collectors.toList())"; + } else { + iterableToSerialize = parameterName; + } + + // Use serializer to convert List to String. + // + if (JavaSettings.getInstance().isAzureV1()) { + // Always use serializeIterable as Iterable supports both Iterable and List. + return String.format( + "JacksonAdapter.createDefaultSerializerAdapter().serializeIterable(%s, CollectionFormat.%s)", + iterableToSerialize, collectionFormat.toString().toUpperCase(Locale.ROOT)); + } else { + // mostly code from + // https://github.com/Azure/azure-sdk-for-java/blob/e1f8f21b1111f8ac9372e0b039f3de92485a5a66/sdk/core/azure-core/src/main/java/com/azure/core/util/serializer/JacksonAdapter.java#L250-L304 + final String serializeItemValueCode + = TemplateUtil.loadTextFromResource("ClientMethodSerializeItemValue.java"); + final String streamToString = iterableToSerialize + ".stream()" + ".map(" + serializeItemValueCode + ")" + + ".collect(Collectors.joining(" + delimiter + "))"; + if (shouldCheckNull) { + return "(" + parameterName + " == null) ? null : " + streamToString; + } else { + return streamToString; + } + } + } + + /** + * Obtain the Java code that converts an iterable to a list of string values. + * + * @param parameterName the name of the iterable parameter to convert. + * @param shouldCheckNull whether to use an empty list if the parameter value is null. + * @return Java code that converts an iterable to a list of string values. + */ + private static String iterableToWireStringValuesList(String parameterName, boolean shouldCheckNull) { + final String stringList = parameterName + ".stream()" + ".map(item -> Objects.toString(item, \"\"))" + + ".collect(Collectors.toList())"; + if (shouldCheckNull) { + return "(" + parameterName + " == null) ? new ArrayList<>()" + ": " + stringList; + } else { + return stringList; + } + } + + /** + * Obtain the Java code that converts a parameter of type byte[] to a Base64 URL encoded string. + * + * @param parameterName the name of the byte[] parameter to convert. + * @param wireType the wire type of the parameter, used to determine the method to call for encoding. + * @return Java code that converts a byte[] parameter to a Base64 URL encoded string. + */ + private static String byteArrayToBase64UrlEncodedString(String parameterName, IType wireType) { + final String methodCall = (wireType == ClassType.STRING) + ? "Base64Util.encodeToString" + : (ClassType.BASE_64_URL.getName() + ".encode"); + return methodCall + "(" + parameterName + ")"; + } + private static boolean addSpecialHeadersToRequestOptions(JavaBlock function, ClientMethod clientMethod) { // logic only works for DPG, protocol API, on RequestOptions From 3ec4fefa4a81484c5a9f8e5d86b737ba05a15219 Mon Sep 17 00:00:00 2001 From: anuchandy Date: Wed, 28 May 2025 15:54:34 -0700 Subject: [PATCH 3/4] ClientMethodTemplate: make parameterWireType identifcation code similar to the rest of the code in convertClientTypesToWireTypes --- .../core/template/ClientMethodTemplate.java | 67 +++++++++++-------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java index 7a7a9a82d50..14f5a902d05 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java @@ -145,14 +145,13 @@ protected static void addOptionalAndConstantVariables(JavaBlock function, Client // parameter is scoped to the client, hence no local variable instantiation for it. continue; } - + final IType parameterClientType = parameter.getClientType(); IType parameterWireType; if (parameter.isNullable()) { parameterWireType = parameter.getWireType().asNullable(); } else { parameterWireType = parameter.getWireType(); } - final IType parameterClientType = parameter.getClientType(); // TODO (alzimmer): There are a few similar transforms like this but they all have slight nuances on output. // This always turns ArrayType and ListType into String, the case further down this file may not. @@ -320,33 +319,44 @@ protected static void applyParameterTransformations(JavaBlock function, ClientMe protected static void convertClientTypesToWireTypes(JavaBlock function, ClientMethod clientMethod) { final List proxyMethodParameters = clientMethod.getProxyMethod().getParameters(); for (ProxyMethodParameter parameter : proxyMethodParameters) { - IType parameterWireType = parameter.getWireType(); + final RequestParameterLocation location = parameter.getRequestParameterLocation(); + final IType parameterClientType = parameter.getClientType(); + IType parameterWireType; if (parameter.isNullable()) { - parameterWireType = parameterWireType.asNullable(); + parameterWireType = parameter.getWireType().asNullable(); + } else { + parameterWireType = parameter.getWireType(); } - IType parameterClientType = parameter.getClientType(); - - // TODO (alzimmer): Reconcile the logic here with that earlier in the file. - // This check parameter explosion but earlier in the file it doesn't. - if (parameterWireType != ClassType.BASE_64_URL - && parameter.getRequestParameterLocation() != RequestParameterLocation.BODY - // && parameter.getRequestParameterLocation() != RequestParameterLocation.FormData && - && (parameterClientType instanceof ArrayType || parameterClientType instanceof IterableType)) { - parameterWireType = (parameter.getExplode()) ? new ListType(ClassType.STRING) : ClassType.STRING; + if (location != RequestParameterLocation.BODY) { + if (parameterClientType == ArrayType.BYTE_ARRAY) { + if (parameterWireType != ClassType.BASE_64_URL) { + // A byte[] with wire-type not as Base64-encoded URL is converted to Base64-encoded 'String'. + // Refer byteArrayToBase64Encoded(..) + parameterWireType = ClassType.STRING; + } + } else if (parameterClientType instanceof IterableType) { + if (!parameter.getExplode()) { + // Iterable gets converted to a delimited 'String' of wire values. + // Refer iterableToDelimitedStringOfWireValues(..) + parameterWireType = ClassType.STRING; + } else { + // Iterable gets converted to 'List'. + // Refer iterableToWireStringValuesList(..)' + parameterWireType = new ListType(ClassType.STRING); + } + } } - // If the wire type and client type are the same there is no conversion needed. if (parameterWireType == parameterClientType) { + // If the wire type and client type are the same there is no conversion needed. continue; } - String parameterName = parameter.getParameterReference(); - String parameterWireName = parameter.getParameterReferenceConverted(); - + final String parameterName = parameter.getParameterReference(); + final String parameterWireName = parameter.getParameterReferenceConverted(); final boolean alwaysNull = clientMethod.getOnlyRequiredParameters() && !parameter.isRequired(); - final RequestParameterLocation location = parameter.getRequestParameterLocation(); boolean addedConversion = false; if (location != RequestParameterLocation.BODY) { @@ -355,7 +365,7 @@ protected static void convertClientTypesToWireTypes(JavaBlock function, ClientMe if (alwaysNull) { function.line("%s %s = %s;", parameterWireType, parameterWireName, "null"); } else { - final String expression = byteArrayToBase64UrlEncodedString(parameterName, parameterWireType); + final String expression = byteArrayToBase64Encoded(parameterName, parameterWireType); function.line("%s %s = %s;", parameterWireType, parameterWireName, expression); } addedConversion = true; @@ -552,17 +562,20 @@ private static String iterableToWireStringValuesList(String parameterName, boole } /** - * Obtain the Java code that converts a parameter of type byte[] to a Base64 URL encoded string. + * Obtain the Java code that converts a parameter of type byte[] to Base64 encoded form (encoded as string or url). * * @param parameterName the name of the byte[] parameter to convert. - * @param wireType the wire type of the parameter, used to determine the method to call for encoding. - * @return Java code that converts a byte[] parameter to a Base64 URL encoded string. + * @param wireType the wire type of the parameter, used to determine whether to encode as a string or url. + * @return Java code that converts a byte[] parameter to Base64 encoded form. */ - private static String byteArrayToBase64UrlEncodedString(String parameterName, IType wireType) { - final String methodCall = (wireType == ClassType.STRING) - ? "Base64Util.encodeToString" - : (ClassType.BASE_64_URL.getName() + ".encode"); - return methodCall + "(" + parameterName + ")"; + private static String byteArrayToBase64Encoded(String parameterName, IType wireType) { + if ((wireType == ClassType.STRING)) { + // byte[] to Base64-encoded String. + return "Base64Util.encodeToString" + "(" + parameterName + ")"; + } else { + // byte[] to Base64-encoded URL. + return ClassType.BASE_64_URL.getName() + ".encode" + "(" + parameterName + ")"; + } } private static boolean addSpecialHeadersToRequestOptions(JavaBlock function, ClientMethod clientMethod) { From cff5f079bffb9afd041d94cf30020ce7d682af4e Mon Sep 17 00:00:00 2001 From: anuchandy Date: Wed, 28 May 2025 17:32:25 -0700 Subject: [PATCH 4/4] invert getExplode() check for readability --- .../core/template/ClientMethodTemplate.java | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java index 14f5a902d05..47686ac3252 100644 --- a/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java +++ b/packages/http-client-java/generator/http-client-generator-core/src/main/java/com/microsoft/typespec/http/client/generator/core/template/ClientMethodTemplate.java @@ -337,14 +337,14 @@ protected static void convertClientTypesToWireTypes(JavaBlock function, ClientMe parameterWireType = ClassType.STRING; } } else if (parameterClientType instanceof IterableType) { - if (!parameter.getExplode()) { - // Iterable gets converted to a delimited 'String' of wire values. - // Refer iterableToDelimitedStringOfWireValues(..) - parameterWireType = ClassType.STRING; - } else { + if (parameter.getExplode()) { // Iterable gets converted to 'List'. // Refer iterableToWireStringValuesList(..)' parameterWireType = new ListType(ClassType.STRING); + } else { + // Iterable gets converted to a delimited 'String' of wire values. + // Refer iterableToDelimitedStringOfWireValues(..) + parameterWireType = ClassType.STRING; } } } @@ -374,15 +374,15 @@ protected static void convertClientTypesToWireTypes(JavaBlock function, ClientMe = !clientMethod.getRequiredNullableParameterExpressions().contains(parameterName); if (alwaysNull) { function.line("%s %s = %s;", parameterWireType, parameterWireName, "null"); - } else if (!parameter.getExplode()) { + } else if (parameter.getExplode()) { + String iterableToListExpression + = iterableToWireStringValuesList(parameterName, shouldNullCheck); + function.line("%s %s = %s;", parameterWireType, parameterWireName, iterableToListExpression); + } else { final IterableType iterableType = (IterableType) parameterClientType; final String iterableToStringExpression = iterableToDelimitedStringOfWireValues(parameterName, shouldNullCheck, iterableType, parameter); function.line("%s %s = %s;", parameterWireType, parameterWireName, iterableToStringExpression); - } else { - String iterableToListExpression - = iterableToWireStringValuesList(parameterName, shouldNullCheck); - function.line("%s %s = %s;", parameterWireType, parameterWireName, iterableToListExpression); } addedConversion = true; } @@ -405,10 +405,27 @@ protected static void convertClientTypesToWireTypes(JavaBlock function, ClientMe } } + /** + * Obtain the Java code that converts an iterable to a list of string values. + * + * @param parameterName the name of the iterable parameter to convert. + * @param shouldCheckNull whether to use an empty list if the parameter value is null. + * @return Java code that converts an iterable to a list of string values. + */ + private static String iterableToWireStringValuesList(String parameterName, boolean shouldCheckNull) { + final String stringList = parameterName + ".stream()" + ".map(item -> Objects.toString(item, \"\"))" + + ".collect(Collectors.toList())"; + if (shouldCheckNull) { + return "(" + parameterName + " == null) ? new ArrayList<>()" + ": " + stringList; + } else { + return stringList; + } + } + /** * Obtain the Java code that converts an iterable to a delimited string of wire values. * - * @param parameterName the name of the parameter to convert. + * @param parameterName the name of the iterable parameter to convert. * @param shouldNullCheck whether to check if the parameter is null before converting it. * @param iterableType the type of the iterable to convert. * @param parameter the proxy method parameter that is being converted. @@ -497,7 +514,7 @@ private static String primitiveIterableToDelimitedStringOfWireValues(String para /** * Obtain the Java code that converts an iterable of model types to a delimited string of serialized wire values. * - * @param parameterName the name of the parameter to convert. + * @param parameterName the name of the iterable parameter to convert. * @param shouldCheckNull whether to check if the parameter is null before converting it. * @param delimiter the delimiter to use when joining the deserialized model values into a string. * @param collectionFormat the collection format to use when serializing the iterable to a string. @@ -544,23 +561,6 @@ private static String modelIterableToDelimitedStringOfWireValues(String paramete } } - /** - * Obtain the Java code that converts an iterable to a list of string values. - * - * @param parameterName the name of the iterable parameter to convert. - * @param shouldCheckNull whether to use an empty list if the parameter value is null. - * @return Java code that converts an iterable to a list of string values. - */ - private static String iterableToWireStringValuesList(String parameterName, boolean shouldCheckNull) { - final String stringList = parameterName + ".stream()" + ".map(item -> Objects.toString(item, \"\"))" - + ".collect(Collectors.toList())"; - if (shouldCheckNull) { - return "(" + parameterName + " == null) ? new ArrayList<>()" + ": " + stringList; - } else { - return stringList; - } - } - /** * Obtain the Java code that converts a parameter of type byte[] to Base64 encoded form (encoded as string or url). *