Skip to content

Commit

Permalink
[Rust] Client library choice between hyper and reqwest (#1258)
Browse files Browse the repository at this point in the history
* Port of PR swagger-api/swagger-codegen#8804.

* Correction of conflict with PR #528 (missing template file).

* Add rust-reqwest samples to Circle CI tests.

* Add integration test pom.xml file with launcher to trigger cargo execution.

* Deduplicate Maven project name.

* Fix "api_key" header for Petstore.

* Better API key management.

* Fix query param for lists of objects other than strings (numbers, etc.).

* Update to reqwest 0.9, and refactor of header management (using reqwest transition feature).

* Merge scripts generating rust-hyper and rust-reqwest samples.

* Consistent full stops.

* Use raw variables in all Rust mustache templates.

* Replace production build in CI with a quick simple check.

* Update samples.

* Finish Reqwest 0.9 migration (removing "hyper 0.11" transition feature).

* Configuration implements Default trait.

* API template reorganized: HashMap is not required anymore.

* Revert "Merge scripts generating rust-hyper and rust-reqwest samples."

This reverts commit 970f996.

* Remove deprecated "-XX:MaxPermSize" java arg.
  • Loading branch information
bcourtine authored and bjgill committed Oct 26, 2018
1 parent eb4dfe0 commit f4eb969
Show file tree
Hide file tree
Showing 55 changed files with 2,910 additions and 110 deletions.
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 $@"

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}
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);
}

@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}}
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

0 comments on commit f4eb969

Please sign in to comment.