Skip to content

[Rust] Rust client generation using reqwest library #8804

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

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
31 changes: 31 additions & 0 deletions bin/rust-reqwest-petstore.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/sh

SCRIPT="$0"

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/swagger-codegen-cli/target/swagger-codegen-cli.jar"

if [ ! -f "$executable" ]
then
mvn clean package
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/swagger-codegen/src/main/resources/rust -i modules/swagger-codegen/src/test/resources/2_0/petstore.yaml -l rust --library reqwest -o samples/client/petstore/rust-reqwest -DpackageName=petstore_client $@"

java ${JAVA_OPTS} -jar ${executable} ${ags}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import io.swagger.models.properties.ArrayProperty;
import io.swagger.models.properties.MapProperty;
import io.swagger.models.properties.Property;
import io.swagger.models.parameters.Parameter;

import java.io.File;
import java.util.*;
Expand All @@ -15,10 +14,14 @@
import org.slf4j.LoggerFactory;

public class RustClientCodegen extends DefaultCodegen implements CodegenConfig {

static 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 = "swagger";
protected String packageVersion = "1.0.0";
protected String apiDocPath = "docs/";
Expand All @@ -42,7 +45,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 @@ -115,6 +117,16 @@ 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 @@ -141,20 +153,30 @@ 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("client.mustache", apiFolder, "client.rs"));
supportingFiles.add(new SupportingFile("api_mod.mustache", apiFolder, "mod.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.rs.mustache", "src", "lib.rs"));
supportingFiles.add(new SupportingFile("Cargo.mustache", "", "Cargo.toml"));

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 @@ -332,9 +354,32 @@ public Map<String, Object> postProcessOperations(Map<String, Object> objs) {
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 = camelize(operation.httpMethod.toLowerCase());
if (HYPER_LIBRARY.equals(getLibrary())) {
operation.httpMethod = camelize(operation.httpMethod.toLowerCase());
} else if (REQWEST_LIBRARY.equals(getLibrary())) {
operation.httpMethod = operation.httpMethod.toLowerCase();
}

// TODO Manage Rust var codestyle (snake case) and compile problems (headers with special chars, like '-').

// Collect all possible headers.
if (operation.authMethods != null) {
for (CodegenSecurity authMethod : operation.authMethods) {
if (authMethod.isApiKey && authMethod.isKeyInHeader) {
headerKeys.add(authMethod.keyParamName);
}
}
}

if (operation.headerParams != null) {
for (CodegenParameter parameter : operation.headerParams) {
headerKeys.add(parameter.baseName);
}
}

// update return type to conform to rust standard
/*
if (operation.returnType != null) {
Expand Down Expand Up @@ -386,6 +431,8 @@ public Map<String, Object> postProcessOperations(Map<String, Object> objs) {
}*/
}

additionalProperties.put("headerKeys", headerKeys);

return objs;
}

Expand Down
19 changes: 13 additions & 6 deletions modules/swagger-codegen/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 = ["Swagger Codegen team and contributors"]

[dependencies]
serde = "1.0"
serde_derive = "1.0"
serde = "^1.0"
serde_derive = "^1.0"
serde_json = "^1.0"
hyper = "~0.11"
url = "1.5"
{{#hyper}}
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.8"
{{/reqwest}}

[dev-dependencies]
{{#hyper}}
tokio-core = "*"
{{/hyper}}
16 changes: 16 additions & 0 deletions modules/swagger-codegen/src/main/resources/rust/lib.rs.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#[macro_use]
extern crate serde_derive;

extern crate hyper;
extern crate serde;
extern crate serde_json;
extern crate url;
{{#hyper}}
extern crate futures;
{{/hyper}}
{{#reqwest}}
extern crate reqwest;
{{/reqwest}}

pub mod apis;
pub mod models;
139 changes: 139 additions & 0 deletions modules/swagger-codegen/src/main/resources/rust/reqwest/api.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
{{>partial_header}}
use std::rc::Rc;
use std::borrow::Borrow;
use std::borrow::Cow;
use std::collections::HashMap;

use serde_json;
use serde_json::Value;

use reqwest;

use super::{Error, configuration};

pub struct {{{classname}}}Client {
configuration: Rc<configuration::Configuration>,
}

impl {{{classname}}}Client {
pub fn new(configuration: Rc<configuration::Configuration>) -> {{{classname}}}Client {
{{{classname}}}Client {
configuration: configuration,
}
}
}

pub trait {{classname}} {
{{#operations}}
{{#operation}}
fn {{{operationId}}}(&self, {{#allParams}}{{paramName}}: {{#isString}}&str{{/isString}}{{#isUuid}}&str{{/isUuid}}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}::models::{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) -> Result<{{^returnType}}(){{/returnType}}{{#returnType}}{{{returnType}}}{{/returnType}}, Error>;
{{/operation}}
{{/operations}}
}


impl {{classname}} for {{classname}}Client {
{{#operations}}
{{#operation}}
fn {{{operationId}}}(&self, {{#allParams}}{{paramName}}: {{#isString}}&str{{/isString}}{{#isUuid}}&str{{/isUuid}}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}::models::{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) -> Result<{{^returnType}}(){{/returnType}}{{#returnType}}{{{returnType}}}{{/returnType}}, Error> {
let configuration: &configuration::Configuration = self.configuration.borrow();
let client = &configuration.client;

{{#hasAuthMethods}}{{#authMethods}}{{#isApiKey}}{{#isKeyInQuery}}
let mut auth_query = HashMap::<String, String>::new();
if let Some(ref apikey) = configuration.api_key {
let key = apikey.key.clone();
let val = match apikey.prefix {
Some(ref prefix) => format!("{} {}", prefix, key),
None => key,
};

auth_query.insert("{{keyParamName}}".to_owned(), val);

};
{{/isKeyInQuery}}{{/isApiKey}}{{/authMethods}}{{/hasAuthMethods}}

let query_string = {
let mut query = ::url::form_urlencoded::Serializer::new(String::new());
{{#queryParams}}
query.append_pair("{{baseName}}", &{{paramName}}{{#isListContainer}}.join(","){{/isListContainer}}.to_string());
{{/queryParams}}
{{#hasAuthMethods}}{{#authMethods}}{{#isApiKey}}{{#isKeyInQuery}}
for (key, val) in &auth_query {
query.append_pair(key, val);
}
{{/isKeyInQuery}}{{/isApiKey}}{{/authMethods}}{{/hasAuthMethods}}
query.finish()
};
let uri_str = format!("{}{{{path}}}?{}", configuration.base_path, query_string{{#pathParams}}, {{baseName}}={{paramName}}{{#isListContainer}}.join(",").as_ref(){{/isListContainer}}{{/pathParams}});

let mut req_builder = client.{{httpMethod}}(uri_str.as_str());

if let Some(ref user_agent) = configuration.user_agent {
req_builder.header(reqwest::header::UserAgent::new(Cow::Owned(user_agent.clone())));
}

{{#hasHeaderParams}}
{{#headerParams}}
req_builder.header(configuration::Configuration::header_{{baseName}}({{paramName}}{{#isListContainer}}.join(","){{/isListContainer}}.to_string()));
{{/headerParams}}
{{/hasHeaderParams}}

{{#hasAuthMethods}}
{{#authMethods}}
{{#isApiKey}}{{#isKeyInHeader}}
if let Some(ref apikey) = configuration.api_key {
let key = apikey.key.clone();
let val = match apikey.prefix {
Some(ref prefix) => format!("{} {}", prefix, key),
None => key,
};

req_builder.header(configuration::Configuration::header_{{keyParamName}}(val));
};
{{/isKeyInHeader}}{{/isApiKey}}
{{#isBasic}}
if let Some(ref auth_conf) = configuration.basic_auth {
let auth = reqwest::header::Authorization(
reqwest::header::Basic {
username: auth_conf.0.to_owned(),
password: auth_conf.1.to_owned(),
}
);
req_builder.header(auth.to_owned());
};
{{/isBasic}}
{{#isOAuth}}
if let Some(ref token) = configuration.oauth_access_token {
let auth = reqwest::header::Authorization(
reqwest::header::Bearer {
token: token.to_owned(),
}
);
req_builder.header(auth.to_owned());
};
{{/isOAuth}}
{{/authMethods}}
{{/hasAuthMethods}}

{{#hasBodyParam}}
{{#bodyParams}}
req_builder.json(&{{paramName}});
{{/bodyParams}}
{{/hasBodyParam}}

// send request
let req = req_builder.build()?;

{{^returnType}}
client.execute(req)?.error_for_status()?;
Ok(())
{{/returnType}}
{{#returnType}}
Ok(client.execute(req)?.error_for_status()?.json()?)
{{/returnType}}
}

{{/operation}}
{{/operations}}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use reqwest;
use serde_json;

#[derive(Debug)]
pub enum Error {
Reqwest(reqwest::Error),
Serde(serde_json::Error),
}

impl From<reqwest::Error> for Error {
fn from(e: reqwest::Error) -> Self {
return Error::Reqwest(e)
}
}

impl From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Self {
return Error::Serde(e)
}
}

use super::models::*;

{{#apiInfo}}
{{#apis}}
mod {{classFilename}};
{{#operations}}
{{#operation}}
{{#-last}}
pub use self::{{classFilename}}::{ {{classname}}, {{classname}}Client };
{{/-last}}
{{/operation}}
{{/operations}}
{{/apis}}
{{/apiInfo}}

pub mod configuration;
pub mod client;
Loading