Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Rust] Client library choice between hyper and reqwest #1258

Merged
merged 19 commits into from
Oct 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ef0a2a2
Port of PR https://github.com/swagger-api/swagger-codegen/pull/8804.
bcourtine Oct 17, 2018
aac88a5
Correction of conflict with PR #528 (missing template file).
bcourtine Oct 17, 2018
a347327
Add rust-reqwest samples to Circle CI tests.
bcourtine Oct 17, 2018
7b4dfc2
Add integration test pom.xml file with launcher to trigger cargo exec…
bcourtine Oct 17, 2018
e7c99c5
Deduplicate Maven project name.
bcourtine Oct 17, 2018
3c7c52a
Fix "api_key" header for Petstore.
bcourtine Oct 17, 2018
0ef969d
Better API key management.
bcourtine Oct 18, 2018
a1c865c
Fix query param for lists of objects other than strings (numbers, etc.).
bcourtine Oct 19, 2018
e5f514a
Update to reqwest 0.9, and refactor of header management (using reqwe…
bcourtine Oct 21, 2018
970f996
Merge scripts generating rust-hyper and rust-reqwest samples.
bcourtine Oct 22, 2018
556231e
Consistent full stops.
bcourtine Oct 22, 2018
457ff19
Use raw variables in all Rust mustache templates.
bcourtine Oct 22, 2018
9327601
Replace production build in CI with a quick simple check.
bcourtine Oct 22, 2018
0b1f506
Update samples.
bcourtine Oct 22, 2018
0eff82b
Finish Reqwest 0.9 migration (removing "hyper 0.11" transition feature).
bcourtine Oct 22, 2018
595a4fc
Configuration implements Default trait.
bcourtine Oct 22, 2018
4208034
API template reorganized: HashMap is not required anymore.
bcourtine Oct 22, 2018
e13b3e1
Revert "Merge scripts generating rust-hyper and rust-reqwest samples."
bcourtine Oct 23, 2018
605f950
Remove deprecated "-XX:MaxPermSize" java arg.
bcourtine Oct 23, 2018
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
4 changes: 2 additions & 2 deletions bin/rust-petstore.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ then
fi

# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="generate -t modules/openapi-generator/src/main/resources/rust -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g rust -o samples/client/petstore/rust -DpackageName=petstore_client $@"
export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="generate -t modules/openapi-generator/src/main/resources/rust -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g rust -o samples/client/petstore/rust -DpackageName=petstore_client --library=hyper $@"
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is back-compatible. As such, I would suggest creating this against the branch for the next major release - 4.0.x, I believe. See https://github.com/OpenAPITools/openapi-generator/wiki/Git-Branches

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This "library" modification is optional. If no library is chosen for generation, Hyper remains the default library. I may be wrong, but I think it is backward compatible.

Copy link
Member

Choose a reason for hiding this comment

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

If hyper is the default, the change should be backward-compatible.

If reqwest is the default, the change should target 3.4.x branch as it's a breaking change with fallbacks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From RustClientCodegen.java :

        CliOption libraryOption = new CliOption(CodegenConstants.LIBRARY, "library template (sub-template) to use");
        libraryOption.setEnum(supportedLibraries);
        // set hyper as the default
        libraryOption.setDefault(HYPER_LIBRARY);
        cliOptions.add(libraryOption);
        setLibrary(HYPER_LIBRARY);


java ${JAVA_OPTS} -jar ${executable} ${ags}
32 changes: 32 additions & 0 deletions bin/rust-reqwest-petstore.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/bin/sh

SCRIPT="$0"
echo "# START SCRIPT: $SCRIPT"

while [ -h "$SCRIPT" ] ; do
ls=$(ls -ld "$SCRIPT")
link=$(expr "$ls" : '.*-> \(.*\)$')
if expr "$link" : '/.*' > /dev/null; then
SCRIPT="$link"
else
SCRIPT=$(dirname "$SCRIPT")/"$link"
fi
done

if [ ! -d "${APP_DIR}" ]; then
APP_DIR=$(dirname "$SCRIPT")/..
APP_DIR=$(cd "${APP_DIR}"; pwd)
fi

executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar"

if [ ! -f "$executable" ]
then
mvn -B clean package
fi

# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="generate -t modules/openapi-generator/src/main/resources/rust -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g rust -o samples/client/petstore/rust-reqwest -DpackageName=petstore_client --library=reqwest $@"

java ${JAVA_OPTS} -jar ${executable} ${ags}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why have the rust-reqwest sample generation in a separate script? Could you not extend the rust sample generation to do both, reducing the risk that someone will do one and forget to do the other?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't have a particular reason. I will merge both scripts.

Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,27 @@

package org.openapitools.codegen.languages;

import com.google.common.base.Strings;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Schema;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.CliOption;
import org.openapitools.codegen.CodegenConfig;
import org.openapitools.codegen.CodegenConstants;
import org.openapitools.codegen.CodegenOperation;
import org.openapitools.codegen.CodegenProperty;
import org.openapitools.codegen.CodegenType;
import org.openapitools.codegen.DefaultCodegen;
import org.openapitools.codegen.SupportingFile;
import org.openapitools.codegen.*;
import org.openapitools.codegen.utils.ModelUtils;
import org.openapitools.codegen.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.*;


public class RustClientCodegen extends DefaultCodegen implements CodegenConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(RustClientCodegen.class);
public static final String PACKAGE_NAME = "packageName";
public static final String PACKAGE_VERSION = "packageVersion";

public static final String HYPER_LIBRARY = "hyper";
public static final String REQWEST_LIBRARY = "reqwest";

protected String packageName = "openapi";
protected String packageVersion = "1.0.0";
protected String apiDocPath = "docs/";
Expand All @@ -68,7 +61,6 @@ public RustClientCodegen() {
super();
outputFolder = "generated-code/rust";
modelTemplateFiles.put("model.mustache", ".rs");
apiTemplateFiles.put("api.mustache", ".rs");

modelDocTemplateFiles.put("model_doc.mustache", ".md");
apiDocTemplateFiles.put("api_doc.mustache", ".md");
Expand Down Expand Up @@ -141,6 +133,15 @@ public RustClientCodegen() {
cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, CodegenConstants.HIDE_GENERATION_TIMESTAMP_DESC)
.defaultValue(Boolean.TRUE.toString()));

supportedLibraries.put(HYPER_LIBRARY, "HTTP client: Hyper.");
supportedLibraries.put(REQWEST_LIBRARY, "HTTP client: Reqwest.");

CliOption libraryOption = new CliOption(CodegenConstants.LIBRARY, "library template (sub-template) to use.");
libraryOption.setEnum(supportedLibraries);
// set hyper as the default
libraryOption.setDefault(HYPER_LIBRARY);
cliOptions.add(libraryOption);
setLibrary(HYPER_LIBRARY);
}

@Override
Expand All @@ -165,21 +166,34 @@ public void processOpts() {
additionalProperties.put("apiDocPath", apiDocPath);
additionalProperties.put("modelDocPath", modelDocPath);

if ( HYPER_LIBRARY.equals(getLibrary())){
additionalProperties.put(HYPER_LIBRARY, "true");
} else if (REQWEST_LIBRARY.equals(getLibrary())) {
additionalProperties.put(REQWEST_LIBRARY, "true");
} else {
LOGGER.error("Unknown library option (-l/--library): {}", getLibrary());
}

apiTemplateFiles.put(getLibrary() + "/api.mustache", ".rs");

modelPackage = packageName;
apiPackage = packageName;

supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh"));
supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore"));
supportingFiles.add(new SupportingFile("configuration.mustache", apiFolder, "configuration.rs"));
supportingFiles.add(new SupportingFile(".travis.yml", "", ".travis.yml"));

supportingFiles.add(new SupportingFile("api_mod.mustache", apiFolder, "mod.rs"));
supportingFiles.add(new SupportingFile("client.mustache", apiFolder, "client.rs"));
supportingFiles.add(new SupportingFile("request.rs", apiFolder, "request.rs"));
supportingFiles.add(new SupportingFile("model_mod.mustache", modelFolder, "mod.rs"));
supportingFiles.add(new SupportingFile("lib.rs", "src", "lib.rs"));
supportingFiles.add(new SupportingFile("lib.mustache", "src", "lib.rs"));
supportingFiles.add(new SupportingFile("Cargo.mustache", "", "Cargo.toml"));

if (HYPER_LIBRARY.equals(getLibrary())) {
supportingFiles.add(new SupportingFile("request.rs", apiFolder, "request.rs"));
}

supportingFiles.add(new SupportingFile(getLibrary() + "/configuration.mustache", apiFolder, "configuration.rs"));
supportingFiles.add(new SupportingFile(getLibrary() + "/client.mustache", apiFolder, "client.rs"));
supportingFiles.add(new SupportingFile(getLibrary() + "/api_mod.mustache", apiFolder, "mod.rs"));
}

@Override
Expand Down Expand Up @@ -209,7 +223,7 @@ public String toVarName(String name) {
return name;

// snake_case, e.g. PetId => pet_id
name = org.openapitools.codegen.utils.StringUtils.underscore(name);
name = StringUtils.underscore(name);

// for reserved word or word starting with number, append _
if (isReservedWord(name))
Expand All @@ -231,16 +245,17 @@ public String toParamName(String name) {
public String toModelName(String name) {
// camelize the model name
// phone_number => PhoneNumber
return org.openapitools.codegen.utils.StringUtils.camelize(toModelFilename(name));
return StringUtils.camelize(toModelFilename(name));
}

@Override
public String toModelFilename(String name) {
if (!StringUtils.isEmpty(modelNamePrefix)) {

if (!Strings.isNullOrEmpty(modelNamePrefix)) {
name = modelNamePrefix + "_" + name;
}

if (!StringUtils.isEmpty(modelNameSuffix)) {
if (!Strings.isNullOrEmpty(modelNameSuffix)) {
name = name + "_" + modelNameSuffix;
}

Expand All @@ -258,7 +273,7 @@ public String toModelFilename(String name) {
name = "model_" + name; // e.g. 200Response => Model200Response (after camelize)
}

return org.openapitools.codegen.utils.StringUtils.underscore(name);
return StringUtils.underscore(name);
bcourtine marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
Expand All @@ -267,7 +282,7 @@ public String toApiFilename(String name) {
name = name.replaceAll("-", "_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.

// e.g. PetApi.rs => pet_api.rs
return org.openapitools.codegen.utils.StringUtils.underscore(name) + "_api";
return StringUtils.underscore(name) + "_api";
}

@Override
Expand Down Expand Up @@ -340,11 +355,11 @@ public String toOperationId(String operationId) {

// method name cannot use reserved keyword, e.g. return
if (isReservedWord(sanitizedOperationId)) {
LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + org.openapitools.codegen.utils.StringUtils.underscore("call_" + operationId));
LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + StringUtils.underscore("call_" + operationId));
sanitizedOperationId = "call_" + sanitizedOperationId;
}

return org.openapitools.codegen.utils.StringUtils.underscore(sanitizedOperationId);
return StringUtils.underscore(sanitizedOperationId);
}

@Override
Expand All @@ -353,9 +368,15 @@ public Map<String, Object> postProcessOperationsWithModels(Map<String, Object> o
Map<String, Object> objectMap = (Map<String, Object>) objs.get("operations");
@SuppressWarnings("unchecked")
List<CodegenOperation> operations = (List<CodegenOperation>) objectMap.get("operation");
Set<String> headerKeys = new HashSet<>();
for (CodegenOperation operation : operations) {
// http method verb conversion (e.g. PUT => Put)
operation.httpMethod = org.openapitools.codegen.utils.StringUtils.camelize(operation.httpMethod.toLowerCase(Locale.ROOT));
// http method verb conversion, depending on client library (e.g. Hyper: PUT => Put, Reqwest: PUT => put)
if (HYPER_LIBRARY.equals(getLibrary())) {
operation.httpMethod = StringUtils.camelize(operation.httpMethod.toLowerCase(Locale.ROOT));
} else if (REQWEST_LIBRARY.equals(getLibrary())) {
operation.httpMethod = operation.httpMethod.toLowerCase(Locale.ROOT);
}

// update return type to conform to rust standard
/*
if (operation.returnType != null) {
Expand Down Expand Up @@ -407,6 +428,9 @@ public Map<String, Object> postProcessOperationsWithModels(Map<String, Object> o
}*/
}

additionalProperties.put("headerKeys", headerKeys);
additionalProperties.putIfAbsent("authHeaderKey", "api-key");

return objs;
}

Expand Down
19 changes: 13 additions & 6 deletions modules/openapi-generator/src/main/resources/rust/Cargo.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,21 @@ version = "{{{packageVersion}}}"
authors = ["OpenAPI Generator team and contributors"]

[dependencies]
serde = "1.0"
serde_derive = "1.0"
serde = "^1.0"
serde_derive = "^1.0"
serde_json = "^1.0"
url = "1.5"
{{#hyper}}
hyper = "~0.11"
serde_yaml = "0.7"
serde_json = "1.0"
base64 = "~0.7.0"
futures = "0.1.16"
hyper = "0.11.6"
url = "1.5"
futures = "0.1.23"
{{/hyper}}
{{#reqwest}}
reqwest = "~0.9"
{{/reqwest}}

[dev-dependencies]
{{#hyper}}
tokio-core = "*"
{{/hyper}}
Copy link
Contributor

Choose a reason for hiding this comment

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

My highest-level question - why make the decision at codegen time rather than at compile time? You could presumably achieve the same effect with Rust features. I'm not saying you're wrong - merely curious as to why you chose as you did.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't have a good reason for that. I am a beginner in Rust development and learning.

I don't know what purpose this dev lib deserves. Since I could generate my project without it in the Cargo file, I dropped it, simply to keep the file as tiny as possible.

If you think it is a mistake, I can put it back.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I probably misunderstood your question (I thought it was about tokio-core lib only).

For the whole library choice, the main reason is that since I'm learning Rust, I don't know how to use it yet.

Another reason whould be that hyper and reqwest templates are quite different: API main methods currently don't have the same return type (Result vs Box<Future<Result>>>. So I presume that generating a client managing both libs with features would have much more complex code (but since I don't know features, I may be wrong).

Copy link
Contributor

Choose a reason for hiding this comment

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

That seems reasonable. I could well imagine that trying to have both in the same crate could cause problems if they ever had incompatible dependencies.

20 changes: 10 additions & 10 deletions modules/openapi-generator/src/main/resources/rust/README.mustache
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Rust API client for {{packageName}}
# Rust API client for {{{packageName}}}

{{#appDescription}}
{{{appDescription}}}
Expand All @@ -7,34 +7,34 @@
## Overview
This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the [openapi-spec](https://openapis.org) from a remote server, you can easily generate an API client.

- API version: {{appVersion}}
- Package version: {{packageVersion}}
- API version: {{{appVersion}}}
- Package version: {{{packageVersion}}}
{{^hideGenerationTimestamp}}
- Build date: {{generatedDate}}
- Build date: {{{generatedDate}}}
{{/hideGenerationTimestamp}}
- Build package: {{generatorClass}}
- Build package: {{{generatorClass}}}
{{#infoUrl}}
For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}})
{{/infoUrl}}

## Installation
Put the package under your project folder and add the following in import:
```
"./{{packageName}}"
"./{{{packageName}}}"
```

## Documentation for API Endpoints

All URIs are relative to *{{basePath}}*
All URIs are relative to *{{{basePath}}}*

Class | Method | HTTP request | Description
------------ | ------------- | ------------- | -------------
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}}
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{{classname}}}* | [**{{{operationId}}}**]({{{apiDocPath}}}{{classname}}.md#{{{operationIdLowerCase}}}) | **{{{httpMethod}}}** {{{path}}} | {{#summary}}{{{summary}}}{{/summary}}
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}

## Documentation For Models

{{#models}}{{#model}} - [{{{classname}}}]({{modelDocPath}}{{{classname}}}.md)
{{#models}}{{#model}} - [{{{classname}}}]({{{modelDocPath}}}{{{classname}}}.md)
{{/model}}{{/models}}

## Documentation For Authorization
Expand Down Expand Up @@ -92,5 +92,5 @@ Or via OAuth2 module to automatically refresh tokens and perform user authentica

## Author

{{#apiInfo}}{{#apis}}{{^hasMore}}{{infoEmail}}
{{#apiInfo}}{{#apis}}{{^hasMore}}{{{infoEmail}}}
{{/hasMore}}{{/apis}}{{/apiInfo}}
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# {{invokerPackage}}\{{classname}}{{#description}}
{{description}}{{/description}}
# {{{invokerPackage}}}\{{{classname}}}{{#description}}
{{{description}}}{{/description}}

All URIs are relative to *{{basePath}}*
All URIs are relative to *{{{basePath}}}*

Method | HTTP request | Description
------------- | ------------- | -------------
{{#operations}}{{#operation}}[**{{operationId}}**]({{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}}
{{#operations}}{{#operation}}[**{{{operationId}}}**]({{{classname}}}.md#{{{operationId}}}) | **{{{httpMethod}}}** {{{path}}} | {{#summary}}{{{summary}}}{{/summary}}
{{/operation}}{{/operations}}

{{#operations}}
{{#operation}}
# **{{{operationId}}}**
> {{#returnType}}{{{returnType}}} {{/returnType}}{{{operationId}}}({{#authMethods}}ctx, {{/authMethods}}{{#allParams}}{{#required}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/required}}{{/allParams}}{{#hasOptionalParams}}optional{{/hasOptionalParams}})
> {{#returnType}}{{{returnType}}} {{/returnType}}{{{operationId}}}({{#authMethods}}ctx, {{/authMethods}}{{#allParams}}{{#required}}{{{paramName}}}{{#hasMore}}, {{/hasMore}}{{/required}}{{/allParams}}{{#hasOptionalParams}}optional{{/hasOptionalParams}})
{{{summary}}}{{#notes}}

{{{notes}}}{{/notes}}
Expand All @@ -21,19 +21,19 @@ Method | HTTP request | Description
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------{{#authMethods}}
**ctx** | **context.Context** | context containing the authentication | nil if no authentication{{/authMethods}}{{/-last}}{{/allParams}}{{#allParams}}{{#required}}
**{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{dataType}}**]({{baseType}}.md){{/isPrimitiveType}}| {{description}} | {{#defaultValue}}[default to {{defaultValue}}]{{/defaultValue}}{{/required}}{{/allParams}}{{#hasOptionalParams}}
**{{{paramName}}}** | {{#isPrimitiveType}}**{{{dataType}}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{{dataType}}}**]({{{baseType}}}.md){{/isPrimitiveType}}| {{{description}}} | {{#defaultValue}}[default to {{{defaultValue}}}]{{/defaultValue}}{{/required}}{{/allParams}}{{#hasOptionalParams}}
**optional** | **map[string]interface{}** | optional parameters | nil if no parameters

### Optional Parameters
Optional parameters are passed through a map[string]interface{}.
{{#allParams}}{{#-last}}
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------{{/-last}}{{/allParams}}{{#allParams}}
**{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{dataType}}**]({{baseType}}.md){{/isPrimitiveType}}| {{description}} | {{#defaultValue}}[default to {{defaultValue}}]{{/defaultValue}}{{/allParams}}{{/hasOptionalParams}}
**{{{paramName}}}** | {{#isPrimitiveType}}**{{{dataType}}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{{dataType}}}**]({{{baseType}}}.md){{/isPrimitiveType}}| {{{description}}} | {{#defaultValue}}[default to {{{defaultValue}}}]{{/defaultValue}}{{/allParams}}{{/hasOptionalParams}}

### Return type

{{#returnType}}{{#returnTypeIsPrimitive}}**{{{returnType}}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{{returnType}}}**]({{returnBaseType}}.md){{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}} (empty response body){{/returnType}}
{{#returnType}}{{#returnTypeIsPrimitive}}**{{{returnType}}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{{returnType}}}**]({{{returnBaseType}}}.md){{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}} (empty response body){{/returnType}}

### Authorization

Expand Down
Loading