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
8 changes: 8 additions & 0 deletions packages/http-client-java/emitter/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface License {
export interface EmitterOptions {
license?: License;
"dev-options"?: DevOptions;
"max-overload"?: "protocol" | "model";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment that this defaults to protocol if not set.

}

export const EmitterOptionsSchema: JSONSchemaType<EmitterOptions> = {
Expand Down Expand Up @@ -66,6 +67,13 @@ export const EmitterOptionsSchema: JSONSchemaType<EmitterOptions> = {
additionalProperties: false,
required: [],
},
"max-overload": {
type: "string",
description:
"Selects which max `WithResponse` overloads are generated for data-plane clients.",
nullable: true,
enum: ["protocol", "model"],
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a test spec in http-client-generator-test and update the Generate.ps1 script to run with protocol and model config to generate both variants.

},
},
additionalProperties: false,
required: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,9 @@ private JavaSettings(AutorestSettings autorestSettings) {
// The sync methods generation mode.
this.syncMethods = SyncMethodsGeneration.fromValue(getStringValue(host, "sync-methods", "essential"));

// The max overload generation mode for WithResponse APIs.
this.maxOverload = MaxOverload.fromValue(getStringValue(host, "max-overload", "protocol"));

// Whether to add a logger to the generated clients.
this.clientLogger = getBooleanValue(host, "client-logger", true);

Expand Down Expand Up @@ -962,6 +965,20 @@ public final SyncMethodsGeneration getSyncMethods() {
return syncMethods;
}

private final MaxOverload maxOverload;

public MaxOverload getMaxOverload() {
return maxOverload;
}

public boolean isGenerateProtocolMaxOverload() {
return maxOverload == MaxOverload.PROTOCOL;
}

public boolean isGenerateModelMaxOverload() {
return maxOverload == MaxOverload.MODEL;
}

/**
* Whether to generate async methods.
*
Expand Down Expand Up @@ -1052,6 +1069,21 @@ public static SyncMethodsGeneration fromValue(String value) {
}
}

public enum MaxOverload {
PROTOCOL,
MODEL;

public static MaxOverload fromValue(String value) {
if (value == null) {
return PROTOCOL;
} else if (value.equals("model")) {
return MODEL;
} else {
return PROTOCOL;
}
}
}

private final List<String> customTypes;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ private List<ClientMethod> createClientMethods(Operation operation, boolean isPr
final ClientMethodsReturnDescription methodsReturnDescription = ClientMethodsReturnDescription
.create(operation, isProtocolMethod, proxyMethod.isCustomHeaderIgnored());
final CreateMethodArgs createMethodArgs = new CreateMethodArgs(settings, isProtocolMethod,
methodsReturnDescription, methodNamer, getMethodOverloadType(paramsDetails));
methodsReturnDescription, methodNamer, getMethodOverloadType(paramsDetails),
operation.getConvenienceApi() != null);

if (operation.isPageable()) {
// Create Paging Client Methods.
Expand Down Expand Up @@ -789,6 +790,7 @@ private void createSimpleClientMethods(boolean isSync, ClientMethod baseMethod,
private void createSimpleWithResponseClientMethods(boolean isSync, ClientMethod baseMethod,
List<ClientMethod> methods, CreateMethodArgs createMethodArgs) {

final JavaSettings settings = createMethodArgs.settings;
final boolean isProtocolMethod = createMethodArgs.isProtocolMethod;
final MethodOverloadType methodOverloadType = createMethodArgs.methodOverloadType;
final ClientMethodsReturnDescription methodsReturnDescription = createMethodArgs.methodsReturnDescription;
Expand All @@ -805,10 +807,17 @@ private void createSimpleWithResponseClientMethods(boolean isSync, ClientMethod
methodName = methodNamer.getSimpleAsyncRestResponseMethodName();
clientMethodType = ClientMethodType.SimpleAsyncRestResponse;
}
final JavaVisibility methodVisibility
JavaVisibility methodVisibility
= methodVisibility(clientMethodType, methodOverloadType, false, isProtocolMethod);
final JavaVisibility methodWithContextVisibility
JavaVisibility methodWithContextVisibility
= methodVisibility(clientMethodType, methodOverloadType, true, isProtocolMethod);
if (settings.isDataPlaneClient()
&& isProtocolMethod
&& settings.getMaxOverload() == JavaSettings.MaxOverload.MODEL
&& createMethodArgs.hasConvenienceApi
&& isSimpleWithResponseType(clientMethodType)) {
methodWithContextVisibility = NOT_VISIBLE;
}
final boolean hasContextOverload = methodWithContextVisibility != NOT_GENERATE;

final ClientMethod withResponseMethod = baseMethod.newBuilder()
Expand All @@ -824,8 +833,12 @@ private void createSimpleWithResponseClientMethods(boolean isSync, ClientMethod
// Always generate an overload of WithResponse with non-required parameters without Context. It is only for sync
// proxy method, and is usually filtered out in methodVisibility function.
methods.add(withResponseMethod);
ClientMethod clientMethodWithContext
= addClientMethodWithContext(methods, withResponseMethod, methodWithContextVisibility, isProtocolMethod);
final boolean useRequestOptionsParam = !isProtocolMethod
&& settings.isDataPlaneClient()
&& settings.isGenerateModelMaxOverload()
&& isSimpleWithResponseType(clientMethodType);
ClientMethod clientMethodWithContext = addClientMethodWithContext(methods, withResponseMethod,
methodWithContextVisibility, useRequestOptionsParam || isProtocolMethod);

// Simple op '[Operation]WithResponse' overloads for versioning
createOverloadForVersioning(methods, withResponseMethod, clientMethodWithContext, methodWithContextVisibility,
Expand Down Expand Up @@ -959,6 +972,11 @@ protected JavaVisibility methodVisibility(ClientMethodType methodType, MethodOve
}
return VISIBLE;
} else {
if (isSimpleWithResponseType(methodType)
&& settings.isDataPlaneClient()
&& settings.isGenerateModelMaxOverload()) {
return hasContextParameter ? VISIBLE : NOT_GENERATE;
}
// at present, only generate convenience method for simple API and pageable API (no LRO)
return ((methodType == ClientMethodType.SimpleAsync && !hasContextParameter)
|| (methodType == ClientMethodType.SimpleSync && !hasContextParameter)
Expand Down Expand Up @@ -1044,22 +1062,24 @@ protected static class CreateMethodArgs {
public final MethodOverloadType methodOverloadType;
public final MethodNamer methodNamer;
public final boolean generateRequiredOnlyParamsMethodOverload;
public final boolean hasConvenienceApi;

CreateMethodArgs(JavaSettings settings, boolean isProtocolMethod,
ClientMethodsReturnDescription methodsReturnDescription, MethodNamer methodNamer,
MethodOverloadType methodOverloadType) {
MethodOverloadType methodOverloadType, boolean hasConvenienceApi) {
this.settings = settings;
this.isProtocolMethod = isProtocolMethod;
this.methodsReturnDescription = methodsReturnDescription;
this.methodOverloadType = methodOverloadType;
this.methodNamer = methodNamer;
this.hasConvenienceApi = hasConvenienceApi;
this.generateRequiredOnlyParamsMethodOverload = settings.isRequiredParameterClientMethods()
&& methodOverloadType == MethodOverloadType.OVERLOAD_MAXIMUM;
}

CreateMethodArgs forPaging(PagingMetadata pagingMetadata, ClientMethodParametersDetails paramsDetails) {
return new CreateMethodArgs(this.settings, this.isProtocolMethod, this.methodsReturnDescription,
this.methodNamer, getPageMethodOverloadType(pagingMetadata, paramsDetails));
this.methodNamer, getPageMethodOverloadType(pagingMetadata, paramsDetails), this.hasConvenienceApi);
}
}

Expand All @@ -1079,4 +1099,9 @@ private static MethodOverloadType getPageMethodOverloadType(PagingMetadata pagin
return MethodOverloadType.OVERLOAD_MINIMUM_MAXIMUM;
}
}

private static boolean isSimpleWithResponseType(ClientMethodType methodType) {
return methodType == ClientMethodType.SimpleSyncRestResponse
|| methodType == ClientMethodType.SimpleAsyncRestResponse;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

package com.microsoft.typespec.http.client.generator.core.template;

import com.microsoft.typespec.http.client.generator.core.extension.plugin.JavaSettings;
import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ArrayType;
import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClassType;
import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientMethod;
Expand Down Expand Up @@ -50,7 +51,8 @@ protected boolean isMethodIncluded(ClientMethod method) {

@Override
protected boolean isMethodIncluded(ConvenienceMethod method) {
return isMethodAsync(method.getProtocolMethod()) && isMethodVisible(method.getProtocolMethod())
return isMethodAsync(method.getProtocolMethod())
&& (isMethodVisible(method.getProtocolMethod()) || isModelOnlyProtocolWithResponse(method.getProtocolMethod()))
// for LRO, we actually choose the protocol method of "WithModel"
&& (method.getProtocolMethod().getType() != ClientMethodType.LongRunningBeginAsync
|| (method.getProtocolMethod().getImplementationDetails() != null
Expand All @@ -61,6 +63,12 @@ protected boolean isMethodIncluded(ConvenienceMethod method) {
.noneMatch(p -> p.getClientType() == ClassType.CONTEXT);
}

private boolean isModelOnlyProtocolWithResponse(ClientMethod protocolMethod) {
return JavaSettings.getInstance().getMaxOverload() == JavaSettings.MaxOverload.MODEL
&& (protocolMethod.getType() == ClientMethodType.SimpleSyncRestResponse
|| protocolMethod.getType() == ClientMethodType.SimpleAsyncRestResponse);
}

protected void writeInvocationAndConversion(ClientMethod convenienceMethod, ClientMethod protocolMethod,
String invocationExpression, JavaBlock methodBlock, Set<GenericType> typeReferenceStaticClasses) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ protected void writeMethodImplementation(ClientMethod protocolMethod, ClientMeth
= findParametersForConvenienceMethod(convenienceMethod, protocolMethod);

// RequestOptions
createEmptyRequestOptions(methodBlock);
createEmptyRequestOptions(convenienceMethod, methodBlock);

// parameter transformation
final ParameterTransformations transformations = convenienceMethod.getParameterTransformations();
Expand Down Expand Up @@ -219,8 +219,19 @@ protected void addRequestCallback(JavaBlock javaBlock, String variableName) {
javaBlock.line("requestOptions.setBody(" + variableName + ");");
}

protected void createEmptyRequestOptions(JavaBlock methodBlock) {
methodBlock.line("RequestOptions requestOptions = new RequestOptions();");
protected void createEmptyRequestOptions(ClientMethod convenienceMethod, JavaBlock methodBlock) {
boolean hasRequestOptionsParameter = convenienceMethod.getMethodInputParameters()
.stream()
.anyMatch(p -> p.getClientType() == ClassType.REQUEST_OPTIONS);
if (hasRequestOptionsParameter) {
// Model max-overload WithResponse takes RequestOptions from the method signature.
// Ensure it is initialized when null.
methodBlock.ifBlock("requestOptions == null",
block -> block.line("requestOptions = new RequestOptions();"));
} else {
// Legacy convenience overloads synthesize RequestOptions internally.
methodBlock.line("RequestOptions requestOptions = new RequestOptions();");
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,20 @@ protected boolean isMethodIncluded(ClientMethod method) {

@Override
protected boolean isMethodIncluded(ConvenienceMethod method) {
return !isMethodAsync(method.getProtocolMethod()) && isMethodVisible(method.getProtocolMethod())
return !isMethodAsync(method.getProtocolMethod())
&& (isMethodVisible(method.getProtocolMethod()) || isModelOnlyProtocolWithResponse(method.getProtocolMethod()))
// for LRO, we actually choose the protocol method of "WithModel"
&& (method.getProtocolMethod().getType() != ClientMethodType.LongRunningBeginSync
|| (method.getProtocolMethod().getImplementationDetails() != null
&& method.getProtocolMethod().getImplementationDetails().isImplementationOnly()));
}

private boolean isModelOnlyProtocolWithResponse(ClientMethod protocolMethod) {
return JavaSettings.getInstance().getMaxOverload() == JavaSettings.MaxOverload.MODEL
&& (protocolMethod.getType() == ClientMethodType.SimpleSyncRestResponse
|| protocolMethod.getType() == ClientMethodType.SimpleAsyncRestResponse);
}

@Override
protected void writeMethodImplementation(ClientMethod protocolMethod, ClientMethod convenienceMethod,
JavaBlock methodBlock, Set<GenericType> typeReferenceStaticClasses) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package com.microsoft.typespec.http.client.generator.core.template.clientcore;

import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClassType;
import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientMethod;
import com.microsoft.typespec.http.client.generator.core.model.javamodel.JavaBlock;
import com.microsoft.typespec.http.client.generator.core.template.ConvenienceSyncMethodTemplate;

Expand All @@ -20,7 +21,7 @@ public static ClientCoreConvenienceSyncMethodTemplate getInstance() {
}

@Override
protected void createEmptyRequestOptions(JavaBlock methodBlock) {
protected void createEmptyRequestOptions(ClientMethod convenienceMethod, JavaBlock methodBlock) {
methodBlock.line("RequestContext requestContext = RequestContext.none();");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.microsoft.typespec.http.client.generator.core.model.clientmodel.AsyncSyncClient;
import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClassType;
import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientMethod;
import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientMethodType;
import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientModel;
import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientModelProperty;
import com.microsoft.typespec.http.client.generator.core.model.clientmodel.ClientModelPropertyAccess;
Expand Down Expand Up @@ -170,6 +171,8 @@ public static void getAsyncSyncClients(Client client, ServiceClient serviceClien

private static List<ConvenienceMethod> getConvenienceMethods(Supplier<List<ClientMethod>> clientMethods,
OperationGroup og) {
final JavaSettings settings = JavaSettings.getInstance();
final JavaSettings.MaxOverload maxOverload = settings.getMaxOverload();
return og.getOperations().stream().filter(o -> o.getConvenienceApi() != null).flatMap(o -> {
List<ClientMethod> cMethods = Mappers.getClientMethodMapper()
.map(o, false)
Expand All @@ -182,14 +185,23 @@ private static List<ConvenienceMethod> getConvenienceMethods(Supplier<List<Clien
return clientMethods.get()
.stream()
.filter(m -> proxyMethodBaseName.equals(m.getProxyMethod().getBaseName())
&& m.getMethodVisibility() == JavaVisibility.Public)
// In MODEL mode protocol WithResponse methods are intentionally non-public in wrapper clients,
// but they are still required as the invocation target for generated model overloads.
&& (m.getMethodVisibility() == JavaVisibility.Public
|| (maxOverload == JavaSettings.MaxOverload.MODEL
&& isSimpleWithResponseMethod(m))))
.map(m -> new ConvenienceMethod(m, cMethods));
} else {
return Stream.empty();
}
}).collect(Collectors.toList());
}

private static boolean isSimpleWithResponseMethod(ClientMethod method) {
return method.getType() == ClientMethodType.SimpleSyncRestResponse
|| method.getType() == ClientMethodType.SimpleAsyncRestResponse;
}

/**
* @param codeModel the code model
* @return the interface name of service client.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ public class MockUnitJavagen extends Javagen {
SETTINGS_MAP.put("namespace", "com.azure.mock");
}

public static void resetSettings() {
SETTINGS_MAP.clear();
SETTINGS_MAP.put("namespace", "com.azure.mock");
}

public static void setSetting(String key, Object value) {
SETTINGS_MAP.put(key, value);
}

public static class MockConnection extends Connection {

public MockConnection() {
Expand Down
Loading
Loading