Skip to content

Commit

Permalink
Support body string in protocol client (#1062)
Browse files Browse the repository at this point in the history
* Support body string in protocol client

* Filter group parameters in low level client methods

* Add javadocs & fix enum test
  • Loading branch information
jianghaolu authored Jun 22, 2021
1 parent c119759 commit c539a85
Show file tree
Hide file tree
Showing 15 changed files with 1,079 additions and 868 deletions.
3 changes: 3 additions & 0 deletions generate.bat
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
set VANILLA_ARGUMENTS=--version=3.1.3 --java --use=. --output-folder=vanilla-tests --sync-methods=all --client-side-validations --add-context-parameter --required-parameter-client-methods
set AZURE_ARGUMENTS=--version=3.1.3 --java --use=. --output-folder=azure-tests --sync-methods=all --client-side-validations --add-context-parameter --required-parameter-client-methods
set ARM_ARGUMENTS=--version=3.1.3 --java --use=. --output-folder=azure-tests --azure-arm --fluent=lite --regenerate-pom=false
set PROTOCOL_ARGUMENTS=--version=3.1.3 --java --use=./ --output-folder=protocol-tests --sync-methods=all --generate-client-as-impl --add-context-parameter --context-client-method-parameter --generate-sync-async-clients --low-level-client

call autorest %VANILLA_ARGUMENTS% --input-file=https://raw.githubusercontent.com/Azure/autorest.testserver/master/swagger/additionalProperties.json --namespace=fixtures.additionalproperties
call autorest %VANILLA_ARGUMENTS% --input-file=https://raw.githubusercontent.com/Azure/autorest.testserver/master/swagger/body-array.json --namespace=fixtures.bodyarray
Expand Down Expand Up @@ -51,6 +52,8 @@ rem call autorest %ARM_ARGUMENTS% --input-file=https://raw.githubusercontent.com
rem call autorest %ARM_ARGUMENTS% --input-file=https://raw.githubusercontent.com/Azure/autorest.testserver/master/swagger/lro-parameterized-endpoints.json --namespace=fixtures.lroparameterizedendpoints
rem del azure-tests\src\main\java\module-info.java

call autorest $PROTOCOL_ARGUMENTS --input-file=https://raw.githubusercontent.com/Azure/autorest.testserver/master/swagger/body-string.json --namespace=fixtures.bodystring

call autorest --use:. customization-tests/swagger

call autorest --use:. docs/samples/specification/azure_key_credential/readme.md
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,6 @@ private static ModuleInfo moduleInfo() {

List<ModuleInfo.RequireModule> requireModules = moduleInfo.getRequireModules();
requireModules.add(new ModuleInfo.RequireModule("com.azure.core", true));
requireModules.add(new ModuleInfo.RequireModule("com.azure.core.experimental", true));

List<ModuleInfo.ExportModule> exportModules = moduleInfo.getExportModules();
exportModules.add(new ModuleInfo.ExportModule(settings.getPackage()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ public List<ClientMethod> map(Operation operation) {

List<Parameter> codeModelParameters;
if (settings.isLowLevelClient()) {
codeModelParameters = request.getParameters().stream().filter(Value::isRequired).collect(Collectors.toList());
codeModelParameters = request.getParameters().stream().filter(p ->
p.isRequired() && !(p.getSchema().getType() == Schema.AllSchemaTypes.GROUP)).collect(Collectors.toList());
} else {
codeModelParameters = request.getParameters().stream().filter(p -> !p.isFlattened()).collect(Collectors.toList());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,21 +157,23 @@ public Map<Request, ProxyMethod> map(Operation operation) {
}
}
// RequestOptions
ProxyMethodParameter requestOptions = new ProxyMethodParameter.Builder()
.description("The options to configure the HTTP request before HTTP client sends it")
.wireType(ClassType.RequestOptions)
.clientType(ClassType.RequestOptions)
.name("requestOptions")
.requestParameterLocation(RequestParameterLocation.None)
.requestParameterName("requestOptions")
.alreadyEncoded(true)
.isConstant(false)
.isRequired(false)
.isNullable(false)
.fromClient(false)
.parameterReference("requestOptions")
.build();
parameters.add(requestOptions);
if (settings.isLowLevelClient()) {
ProxyMethodParameter requestOptions = new ProxyMethodParameter.Builder()
.description("The options to configure the HTTP request before HTTP client sends it")
.wireType(ClassType.RequestOptions)
.clientType(ClassType.RequestOptions)
.name("requestOptions")
.requestParameterLocation(RequestParameterLocation.None)
.requestParameterName("requestOptions")
.alreadyEncoded(true)
.isConstant(false)
.isRequired(false)
.isNullable(false)
.fromClient(false)
.parameterReference("requestOptions")
.build();
parameters.add(requestOptions);
}

if (settings.getAddContextParameter()) {
ClassType contextClassType = getContextClass();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,27 @@ public ProxyMethodParameter map(Parameter parameter) {
}
builder.headerCollectionPrefix(headerCollectionPrefix);

RequestParameterLocation parameterRequestLocation = parameter.getProtocol().getHttp().getIn();
builder.requestParameterLocation(parameterRequestLocation);

boolean parameterIsServiceClientProperty = parameter.getImplementation() == Parameter.ImplementationLocation.CLIENT;
builder.fromClient(parameterIsServiceClientProperty);

Schema ParameterJvWireType = parameter.getSchema();
IType wireType = Mappers.getSchemaMapper().map(ParameterJvWireType);
if (parameter.isNullable() || !parameter.isRequired()) {
wireType = wireType.asNullable();
}
IType clientType = wireType.getClientType();
if (settings.isLowLevelClient() && !(clientType instanceof PrimitiveType)) {
if (parameterRequestLocation == RequestParameterLocation.Body /*&& parameterRequestLocation != RequestParameterLocation.FormData*/) {
clientType = ClassType.BinaryData;
} else {
clientType = ClassType.String;
}
}
builder.clientType(clientType);

RequestParameterLocation parameterRequestLocation = parameter.getProtocol().getHttp().getIn();
builder.requestParameterLocation(parameterRequestLocation);

boolean parameterIsServiceClientProperty = parameter.getImplementation() == Parameter.ImplementationLocation.CLIENT;
builder.fromClient(parameterIsServiceClientProperty);

if (wireType instanceof ListType && settings.shouldGenerateXmlSerialization() && parameterRequestLocation == RequestParameterLocation.Body){
String parameterTypePackage = settings.getPackage(settings.getImplementationSubpackage());
String parameterTypeName = CodeNamer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public class ClassType implements IType {
public static final ClassType AndroidRetryPolicy = new ClassType.Builder().packageName("com.azure.android.core.http.policy").name("RetryPolicy").build();
public static final ClassType JsonPatchDocument =
new ClassType.Builder().knownClass(com.azure.core.models.JsonPatchDocument.class).build();
public static final ClassType BinaryData = new ClassType.Builder().knownClass(com.azure.core.util.BinaryData.class).build();
public static final ClassType BinaryData = new ClassType.Builder().knownClass(com.azure.core.util.BinaryData.class).defaultValueExpressionConverter((String defaultValueExpression) -> java.lang.String.format("BinaryData.fromObject(\"%s\")", defaultValueExpression)).build();
public static final ClassType RequestOptions = new Builder().packageName("com.azure.core.http").name("RequestOptions").build();


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,7 @@ public final String convertFromClientType(String source, String target) {
//C# TO JAVA CONVERTER NOTE: Java does not support optional parameters. Overloaded method(s) are created above:
//ORIGINAL LINE: public string ConvertFromClientType(string source, string target, bool alwaysNull = false, bool alwaysNonNull = false)
public final String convertFromClientType(String source, String target, boolean alwaysNull, boolean alwaysNonNull) {
IType clientType = getWireType().getClientType();
if (clientType == getWireType()) {
if (getClientType() == getWireType()) {
return String.format("%1$s %2$s = %3$s;", getWireType(), target, source);
}
if (alwaysNull) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,18 @@
import com.azure.autorest.extension.base.plugin.JavaSettings;
import com.azure.autorest.model.clientmodel.ArrayType;
import com.azure.autorest.model.clientmodel.ClassType;
import com.azure.autorest.model.clientmodel.ClientEnumValue;
import com.azure.autorest.model.clientmodel.ClientMethod;
import com.azure.autorest.model.clientmodel.ClientMethodParameter;
import com.azure.autorest.model.clientmodel.ClientMethodType;
import com.azure.autorest.model.clientmodel.ClientModel;
import com.azure.autorest.model.clientmodel.ClientModelProperty;
import com.azure.autorest.model.clientmodel.ClientModels;
import com.azure.autorest.model.clientmodel.EnumType;
import com.azure.autorest.model.clientmodel.GenericType;
import com.azure.autorest.model.clientmodel.IType;
import com.azure.autorest.model.clientmodel.ListType;
import com.azure.autorest.model.clientmodel.MapType;
import com.azure.autorest.model.clientmodel.MethodTransformationDetail;
import com.azure.autorest.model.clientmodel.ParameterMapping;
import com.azure.autorest.model.clientmodel.PrimitiveType;
Expand All @@ -30,10 +36,13 @@
import com.azure.core.util.CoreUtils;
import io.netty.handler.codec.http.HttpResponseStatus;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -556,7 +565,13 @@ protected void generateSyncMethod(ClientMethod clientMethod, JavaType typeBlock,
*/
public static void generateJavadoc(ClientMethod clientMethod, JavaType typeBlock, ProxyMethod restAPIMethod, boolean useFullClassName) {
// interface need a fully-qualified exception class name, since exception is usually only included in ProxyMethod
typeBlock.javadocComment(comment -> generateJavadoc(clientMethod, comment, restAPIMethod, useFullClassName));
typeBlock.javadocComment(comment -> {
if (JavaSettings.getInstance().isLowLevelClient()) {
generateProtocolMethodJavadoc(clientMethod, comment);
} else {
generateJavadoc(clientMethod, comment, restAPIMethod, useFullClassName);
}
});
}

/**
Expand Down Expand Up @@ -601,6 +616,149 @@ protected static String parameterDescriptionOrDefault(ClientMethodParameter para
return paramJavadoc;
}

private static void generateProtocolMethodJavadoc(ClientMethod clientMethod, JavaJavadocComment commentBlock) {
commentBlock.description(clientMethod.getDescription());

List<ProxyMethodParameter> optionalQueryParameters = clientMethod.getProxyMethod().getParameters()
.stream().filter(p -> RequestParameterLocation.Query.equals(p.getRequestParameterLocation()) && !p.getIsRequired())
.collect(Collectors.toList());
if (!optionalQueryParameters.isEmpty()) {
optionalParametersJavadoc("Optional Query Parameters", optionalQueryParameters, commentBlock);
}

List<ProxyMethodParameter> optionalHeaderParameters = clientMethod.getProxyMethod().getParameters()
.stream().filter(p -> RequestParameterLocation.Header.equals(p.getRequestParameterLocation()) && !p.getIsRequired())
.collect(Collectors.toList());
if (!optionalHeaderParameters.isEmpty()) {
optionalParametersJavadoc("Optional Header Parameters", optionalHeaderParameters, commentBlock);
}

Set<IType> typesInJavadoc = new HashSet<>();
clientMethod.getMethodInputParameters()
.stream().filter(p -> RequestParameterLocation.Body.equals(p.getLocation()))
.map(ClientMethodParameter::getClientType)
.findFirst()
.ifPresent(iType -> requestBodySchemaJavadoc(iType, commentBlock, typesInJavadoc));

IType responseBodyType = clientMethod.getProxyMethod().getResponseBodyType();
if (responseBodyType != null && !responseBodyType.equals(PrimitiveType.Void)) {
responseBodySchemaJavadoc(responseBodyType, commentBlock, typesInJavadoc);
}

clientMethod.getProxyMethod().getParameters()
.stream().filter(p -> p.getIsRequired() && !p.getFromClient() && !p.getIsConstant()
&& p.getRequestParameterLocation() != RequestParameterLocation.Body)
.forEach(parameter ->
commentBlock.param(parameter.getName(), parameterDescriptionOrDefault(parameter)));

commentBlock.methodReturns("a DynamicRequest where customizations can be made before sent to the service");
}

private static void optionalParametersJavadoc(String title, List<ProxyMethodParameter> parameters, JavaJavadocComment commentBlock) {
commentBlock.line(String.format("<p><strong>%s</strong></p>", title));
commentBlock.line("<table border=\"1\">");
commentBlock.line(String.format(" <caption>%s</caption>", title));
commentBlock.line(" <tr><th>Name</th><th>Type</th><th>Description</th></tr>");
for (ProxyMethodParameter parameter : parameters) {
commentBlock.line(String.format(" <tr><td>%s</td><td>%s</td><td>%s</td></tr>",
parameter.getName(), CodeNamer.escapeXmlComment(parameter.getClientType().toString()), parameterDescriptionOrDefault(parameter)));
}
commentBlock.line("</table>");
}

private static void requestBodySchemaJavadoc(IType requestBodyType, JavaJavadocComment commentBlock, Set<IType> typesInJavadoc) {
if (requestBodyType == null) {
return;
}
commentBlock.line("<p><strong>Request Body Schema</strong></p>");
commentBlock.line("<pre>{@code");
bodySchemaJavadoc(requestBodyType, commentBlock, "", null, typesInJavadoc);
commentBlock.line("}</pre>");
}

private static void responseBodySchemaJavadoc(IType responseBodyType, JavaJavadocComment commentBlock, Set<IType> typesInJavadoc) {
if (responseBodyType == null) {
return;
}
commentBlock.line("<p><strong>Response Body Schema</strong></p>");
commentBlock.line("<pre>{@code");
bodySchemaJavadoc(responseBodyType, commentBlock, "", null, typesInJavadoc);
commentBlock.line("}</pre>");
}

private static void bodySchemaJavadoc(IType type, JavaJavadocComment commentBlock, String indent, String name, Set<IType> typesInJavadoc) {
String nextIndent = indent + " ";
if (type instanceof ClassType
&& ((ClassType) type).getPackage().startsWith(JavaSettings.getInstance().getPackage())
&& !typesInJavadoc.contains(type)) {
typesInJavadoc.add(type);
ClientModel model = ClientModels.Instance.getModel(((ClassType) type).getName());
if (name != null) {
commentBlock.line(indent + name + ": {");
} else {
commentBlock.line(indent + "{");
}
List<ClientModelProperty> properties = new ArrayList<>();
traverseProperties(model, properties);
for (ClientModelProperty property : properties) {
bodySchemaJavadoc(property.getClientType(), commentBlock, nextIndent, property.getName(), typesInJavadoc);
}
commentBlock.line(indent + "}");
} else if (typesInJavadoc.contains(type)) {
if (name != null) {
commentBlock.line(indent + name + ": (recursive schema, see " + name + " above)");
} else {
commentBlock.line(indent + "(recursive schema, see above)");
}
} else if (type instanceof ListType) {
if (name != null) {
commentBlock.line(indent + name + ": [");
} else {
commentBlock.line(indent + "[");
}
bodySchemaJavadoc(((ListType) type).getElementType(), commentBlock, nextIndent, null, typesInJavadoc);
commentBlock.line(indent + "]");
} else if (type instanceof EnumType) {
String values = ((EnumType) type).getValues().stream()
.map(ClientEnumValue::getValue)
.collect(Collectors.joining("/"));
if (name != null) {
commentBlock.line(indent + name + ": String(" + values + ")");
} else {
commentBlock.line(indent + "String(" + values + ")");
}
} else if (type instanceof MapType) {
if (name != null) {
commentBlock.line(indent + name + ": {");
} else {
commentBlock.line(indent + "{");
}
bodySchemaJavadoc(((MapType) type).getValueType(), commentBlock, nextIndent, "String", typesInJavadoc);
commentBlock.line(indent + "}");
} else {
if (name != null) {
commentBlock.line(indent + name + ": " + type.toString());
} else {
commentBlock.line(indent + type.toString());
}
}
}

private static void traverseProperties(ClientModel model, List<ClientModelProperty> properties) {
if (model.getParentModelName() != null) {
traverseProperties(ClientModels.Instance.getModel(model.getParentModelName()), properties);
}
properties.addAll(model.getProperties());
}

private static String parameterDescriptionOrDefault(ProxyMethodParameter parameter) {
String paramJavadoc = parameter.getDescription();
if (CoreUtils.isNullOrEmpty(paramJavadoc)) {
paramJavadoc = String.format("The %1$s parameter", parameter.getName());
}
return CodeNamer.escapeXmlComment(paramJavadoc);
}

protected void generatePagedAsyncSinglePage(ClientMethod clientMethod, JavaType typeBlock, ProxyMethod restAPIMethod, JavaSettings settings) {
typeBlock.annotation("ServiceMethod(returns = ReturnType.SINGLE)");

Expand Down
Loading

0 comments on commit c539a85

Please sign in to comment.