mirror of
https://github.com/OpenAPITools/openapi-generator.git
synced 2025-07-03 22:20:56 +00:00
add rust client, server generator
This commit is contained in:
parent
9b5c71fdb2
commit
d40c28b2b9
@ -3972,9 +3972,7 @@ public class DefaultCodegen implements CodegenConfig {
|
|||||||
LOGGER.debug("debugging fromRequestBodyToFormParameters= " + body);
|
LOGGER.debug("debugging fromRequestBodyToFormParameters= " + body);
|
||||||
Schema schema = getSchemaFromBody(body);
|
Schema schema = getSchemaFromBody(body);
|
||||||
if (StringUtils.isNotBlank(schema.get$ref())) {
|
if (StringUtils.isNotBlank(schema.get$ref())) {
|
||||||
schema = schemas.get(
|
schema = schemas.get(getSimpleRef(schema.get$ref()));
|
||||||
getSimpleRef(schema.get$ref())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (schema.getProperties() != null && !schema.getProperties().isEmpty()) {
|
if (schema.getProperties() != null && !schema.getProperties().isEmpty()) {
|
||||||
Map<String, Schema> properties = schema.getProperties();
|
Map<String, Schema> properties = schema.getProperties();
|
||||||
|
@ -0,0 +1,485 @@
|
|||||||
|
package org.openapitools.codegen.languages;
|
||||||
|
|
||||||
|
import org.openapitools.codegen.*;
|
||||||
|
import org.openapitools.codegen.utils.*;
|
||||||
|
import org.openapitools.codegen.mustache.*;
|
||||||
|
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||||
|
import io.swagger.v3.oas.models.*;
|
||||||
|
import io.swagger.v3.oas.models.media.*;
|
||||||
|
import io.swagger.v3.oas.models.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.models.parameters.*;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
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";
|
||||||
|
|
||||||
|
protected String packageName = "swagger";
|
||||||
|
protected String packageVersion = "1.0.0";
|
||||||
|
protected String apiDocPath = "docs/";
|
||||||
|
protected String modelDocPath = "docs/";
|
||||||
|
protected String apiFolder = "src/apis";
|
||||||
|
protected String modelFolder= "src/models";
|
||||||
|
|
||||||
|
public CodegenType getTag() {
|
||||||
|
return CodegenType.CLIENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return "rust";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHelp() {
|
||||||
|
return "Generates a Rust client library (beta).";
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
embeddedTemplateDir = templateDir = "rust";
|
||||||
|
|
||||||
|
setReservedWordsLowerCase(
|
||||||
|
Arrays.asList(
|
||||||
|
"abstract", "alignof", "as", "become", "box",
|
||||||
|
"break", "const", "continue", "crate", "do",
|
||||||
|
"else", "enum", "extern", "false", "final",
|
||||||
|
"fn", "for", "if", "impl", "in",
|
||||||
|
"let", "loop", "macro", "match", "mod",
|
||||||
|
"move", "mut", "offsetof", "override", "priv",
|
||||||
|
"proc", "pub", "pure", "ref", "return",
|
||||||
|
"Self", "self", "sizeof", "static", "struct",
|
||||||
|
"super", "trait", "true", "type", "typeof",
|
||||||
|
"unsafe", "unsized", "use", "virtual", "where",
|
||||||
|
"while", "yield"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
defaultIncludes = new HashSet<String>(
|
||||||
|
Arrays.asList(
|
||||||
|
"map",
|
||||||
|
"array")
|
||||||
|
);
|
||||||
|
|
||||||
|
languageSpecificPrimitives = new HashSet<String>(
|
||||||
|
Arrays.asList(
|
||||||
|
"i8", "i16", "i32", "i64",
|
||||||
|
"u8", "u16", "u32", "u64",
|
||||||
|
"f32", "f64",
|
||||||
|
"char", "bool", "String", "Vec<u8>", "File")
|
||||||
|
);
|
||||||
|
|
||||||
|
instantiationTypes.clear();
|
||||||
|
/*instantiationTypes.put("array", "GoArray");
|
||||||
|
instantiationTypes.put("map", "GoMap");*/
|
||||||
|
|
||||||
|
typeMapping.clear();
|
||||||
|
typeMapping.put("integer", "i32");
|
||||||
|
typeMapping.put("long", "i64");
|
||||||
|
typeMapping.put("number", "f32");
|
||||||
|
typeMapping.put("float", "f32");
|
||||||
|
typeMapping.put("double", "f64");
|
||||||
|
typeMapping.put("boolean", "bool");
|
||||||
|
typeMapping.put("string", "String");
|
||||||
|
typeMapping.put("UUID", "String");
|
||||||
|
typeMapping.put("date", "string");
|
||||||
|
typeMapping.put("DateTime", "String");
|
||||||
|
typeMapping.put("password", "String");
|
||||||
|
// TODO(farcaller): map file
|
||||||
|
typeMapping.put("file", "File");
|
||||||
|
typeMapping.put("binary", "Vec<u8>");
|
||||||
|
typeMapping.put("ByteArray", "String");
|
||||||
|
typeMapping.put("object", "Value");
|
||||||
|
|
||||||
|
// no need for rust
|
||||||
|
//importMapping = new HashMap<String, String>();
|
||||||
|
|
||||||
|
cliOptions.clear();
|
||||||
|
cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "Rust package name (convention: lowercase).")
|
||||||
|
.defaultValue("swagger"));
|
||||||
|
cliOptions.add(new CliOption(CodegenConstants.PACKAGE_VERSION, "Rust package version.")
|
||||||
|
.defaultValue("1.0.0"));
|
||||||
|
cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "hides the timestamp when files were generated")
|
||||||
|
.defaultValue(Boolean.TRUE.toString()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void processOpts() {
|
||||||
|
super.processOpts();
|
||||||
|
|
||||||
|
// default HIDE_GENERATION_TIMESTAMP to true
|
||||||
|
if (!additionalProperties.containsKey(CodegenConstants.HIDE_GENERATION_TIMESTAMP)) {
|
||||||
|
additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, Boolean.TRUE.toString());
|
||||||
|
} else {
|
||||||
|
additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP,
|
||||||
|
Boolean.valueOf(additionalProperties().get(CodegenConstants.HIDE_GENERATION_TIMESTAMP).toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) {
|
||||||
|
setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setPackageName("swagger");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_VERSION)) {
|
||||||
|
setPackageVersion((String) additionalProperties.get(CodegenConstants.PACKAGE_VERSION));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setPackageVersion("1.0.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName);
|
||||||
|
additionalProperties.put(CodegenConstants.PACKAGE_VERSION, packageVersion);
|
||||||
|
|
||||||
|
additionalProperties.put("apiDocPath", apiDocPath);
|
||||||
|
additionalProperties.put("modelDocPath", modelDocPath);
|
||||||
|
|
||||||
|
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("Cargo.mustache", "", "Cargo.toml"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String escapeReservedWord(String name)
|
||||||
|
{
|
||||||
|
if (this.reservedWordsMappings().containsKey(name)) {
|
||||||
|
return this.reservedWordsMappings().get(name);
|
||||||
|
}
|
||||||
|
return '_' + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String apiFileFolder() {
|
||||||
|
return (outputFolder + File.separator + apiFolder).replace("/", File.separator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String modelFileFolder() {
|
||||||
|
return (outputFolder + File.separator + modelFolder).replace("/", File.separator);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toVarName(String name) {
|
||||||
|
// replace - with _ e.g. created-at => created_at
|
||||||
|
name = sanitizeName(name.replaceAll("-", "_"));
|
||||||
|
|
||||||
|
// if it's all uppper case, do nothing
|
||||||
|
if (name.matches("^[A-Z_]*$"))
|
||||||
|
return name;
|
||||||
|
|
||||||
|
// snake_case, e.g. PetId => pet_id
|
||||||
|
name = underscore(name);
|
||||||
|
|
||||||
|
// for reserved word or word starting with number, append _
|
||||||
|
if (isReservedWord(name))
|
||||||
|
name = escapeReservedWord(name);
|
||||||
|
|
||||||
|
// for reserved word or word starting with number, append _
|
||||||
|
if (name.matches("^\\d.*"))
|
||||||
|
name = "var_" + name;
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toParamName(String name) {
|
||||||
|
return toVarName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toModelName(String name) {
|
||||||
|
// camelize the model name
|
||||||
|
// phone_number => PhoneNumber
|
||||||
|
return camelize(toModelFilename(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toModelFilename(String name) {
|
||||||
|
if (!StringUtils.isEmpty(modelNamePrefix)) {
|
||||||
|
name = modelNamePrefix + "_" + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StringUtils.isEmpty(modelNameSuffix)) {
|
||||||
|
name = name + "_" + modelNameSuffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
name = sanitizeName(name);
|
||||||
|
|
||||||
|
// model name cannot use reserved keyword, e.g. return
|
||||||
|
if (isReservedWord(name)) {
|
||||||
|
LOGGER.warn(name + " (reserved word) cannot be used as model name. Renamed to " + ("model_" + name));
|
||||||
|
name = "model_" + name; // e.g. return => ModelReturn (after camelize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// model name starts with number
|
||||||
|
if (name.matches("^\\d.*")) {
|
||||||
|
LOGGER.warn(name + " (model name starts with number) cannot be used as model name. Renamed to " + ("model_" + name));
|
||||||
|
name = "model_" + name; // e.g. 200Response => Model200Response (after camelize)
|
||||||
|
}
|
||||||
|
|
||||||
|
return underscore(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toApiFilename(String name) {
|
||||||
|
// replace - with _ e.g. created-at => created_at
|
||||||
|
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 underscore(name) + "_api";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String apiDocFileFolder() {
|
||||||
|
return (outputFolder + "/" + apiDocPath).replace('/', File.separatorChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String modelDocFileFolder() {
|
||||||
|
return (outputFolder + "/" + modelDocPath).replace('/', File.separatorChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toModelDocFilename(String name) {
|
||||||
|
return toModelName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toApiDocFilename(String name) {
|
||||||
|
return toApiName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTypeDeclaration(Schema p) {
|
||||||
|
if (p instanceof ArraySchema) {
|
||||||
|
ArraySchema ap = (ArraySchema) p;
|
||||||
|
Schema inner = ap.getItems();
|
||||||
|
return "Vec<" + getTypeDeclaration(inner) + ">";
|
||||||
|
}
|
||||||
|
else if (isMapSchema(p)) {
|
||||||
|
MapSchema mp = (MapSchema) p;
|
||||||
|
Schema inner = (Schema) mp.getAdditionalProperties();
|
||||||
|
return "::std::collections::HashMap<String, " + getTypeDeclaration(inner) + ">";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not using the supertype invocation, because we want to UpperCamelize
|
||||||
|
// the type.
|
||||||
|
String schemaType = getSchemaType(p);
|
||||||
|
if (typeMapping.containsKey(schemaType)) {
|
||||||
|
return typeMapping.get(schemaType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeMapping.containsValue(schemaType)) {
|
||||||
|
return schemaType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (languageSpecificPrimitives.contains(schemaType)) {
|
||||||
|
return schemaType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return fully-qualified model name
|
||||||
|
// ::models::{{classnameFile}}::{{classname}}
|
||||||
|
return "::models::" + toModelName(schemaType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSchemaType(Schema p) {
|
||||||
|
String schemaType = super.getSchemaType(p);
|
||||||
|
String type = null;
|
||||||
|
if(typeMapping.containsKey(schemaType)) {
|
||||||
|
type = typeMapping.get(schemaType);
|
||||||
|
if(languageSpecificPrimitives.contains(type))
|
||||||
|
return (type);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
type = schemaType;
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toOperationId(String operationId) {
|
||||||
|
String sanitizedOperationId = sanitizeName(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 " + underscore("call_" + operationId));
|
||||||
|
sanitizedOperationId = "call_" + sanitizedOperationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return underscore(sanitizedOperationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> postProcessOperations(Map<String, Object> objs) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> objectMap = (Map<String, Object>) objs.get("operations");
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<CodegenOperation> operations = (List<CodegenOperation>) objectMap.get("operation");
|
||||||
|
for (CodegenOperation operation : operations) {
|
||||||
|
// http method verb conversion (e.g. PUT => Put)
|
||||||
|
operation.httpMethod = camelize(operation.httpMethod.toLowerCase());
|
||||||
|
// update return type to conform to rust standard
|
||||||
|
/*
|
||||||
|
if (operation.returnType != null) {
|
||||||
|
if ( operation.returnType.startsWith("Vec") && !languageSpecificPrimitives.contains(operation.returnBaseType)) {
|
||||||
|
// array of model
|
||||||
|
String rt = operation.returnType;
|
||||||
|
int end = rt.lastIndexOf(">");
|
||||||
|
if ( end > 0 ) {
|
||||||
|
operation.vendorExtensions.put("x-returnTypeInMethod", "Vec<super::" + rt.substring("Vec<".length(), end).trim() + ">");
|
||||||
|
operation.returnContainer = "List";
|
||||||
|
}
|
||||||
|
} else if (operation.returnType.startsWith("::std::collections::HashMap<String, ") && !languageSpecificPrimitives.contains(operation.returnBaseType)) {
|
||||||
|
LOGGER.info("return base type:" + operation.returnBaseType);
|
||||||
|
// map of model
|
||||||
|
String rt = operation.returnType;
|
||||||
|
int end = rt.lastIndexOf(">");
|
||||||
|
if ( end > 0 ) {
|
||||||
|
operation.vendorExtensions.put("x-returnTypeInMethod", "::std::collections::HashMap<String, super::" + rt.substring("::std::collections::HashMap<String, ".length(), end).trim() + ">");
|
||||||
|
operation.returnContainer = "Map";
|
||||||
|
}
|
||||||
|
} else if (!languageSpecificPrimitives.contains(operation.returnType)) {
|
||||||
|
// add super:: to model, e.g. super::pet
|
||||||
|
operation.vendorExtensions.put("x-returnTypeInMethod", "super::" + operation.returnType);
|
||||||
|
} else {
|
||||||
|
// primitive type or array/map of primitive type
|
||||||
|
operation.vendorExtensions.put("x-returnTypeInMethod", operation.returnType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (CodegenParameter p : operation.allParams) {
|
||||||
|
if (p.isListContainer && !languageSpecificPrimitives.contains(p.dataType)) {
|
||||||
|
// array of model
|
||||||
|
String rt = p.dataType;
|
||||||
|
int end = rt.lastIndexOf(">");
|
||||||
|
if ( end > 0 ) {
|
||||||
|
p.dataType = "Vec<" + rt.substring("Vec<".length(), end).trim() + ">";
|
||||||
|
}
|
||||||
|
} else if (p.isMapContainer && !languageSpecificPrimitives.contains(p.dataType)) {
|
||||||
|
// map of model
|
||||||
|
String rt = p.dataType;
|
||||||
|
int end = rt.lastIndexOf(">");
|
||||||
|
if ( end > 0 ) {
|
||||||
|
p.dataType = "::std::collections::HashMap<String, super::" + rt.substring("::std::collections::HashMap<String, ".length(), end).trim() + ">";
|
||||||
|
}
|
||||||
|
} else if (!languageSpecificPrimitives.contains(p.dataType)) {
|
||||||
|
// add super:: to model, e.g. super::pet
|
||||||
|
p.dataType = "super::" + p.dataType;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
return objs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean needToImport(String type) {
|
||||||
|
return !defaultIncludes.contains(type)
|
||||||
|
&& !languageSpecificPrimitives.contains(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPackageName(String packageName) {
|
||||||
|
this.packageName = packageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPackageVersion(String packageVersion) {
|
||||||
|
this.packageVersion = packageVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String escapeQuotationMark(String input) {
|
||||||
|
// remove " to avoid code injection
|
||||||
|
return input.replace("\"", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String escapeUnsafeCharacters(String input) {
|
||||||
|
return input.replace("*/", "*_/").replace("/*", "/_*");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toEnumValue(String value, String datatype) {
|
||||||
|
if ("int".equals(datatype) || "double".equals(datatype) || "float".equals(datatype)) {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
return escapeText(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toEnumDefaultValue(String value, String datatype) {
|
||||||
|
return datatype + "_" + value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toEnumVarName(String name, String datatype) {
|
||||||
|
if (name.length() == 0) {
|
||||||
|
return "EMPTY";
|
||||||
|
}
|
||||||
|
|
||||||
|
// number
|
||||||
|
if ("int".equals(datatype) || "double".equals(datatype) || "float".equals(datatype)) {
|
||||||
|
String varName = name;
|
||||||
|
varName = varName.replaceAll("-", "MINUS_");
|
||||||
|
varName = varName.replaceAll("\\+", "PLUS_");
|
||||||
|
varName = varName.replaceAll("\\.", "_DOT_");
|
||||||
|
return varName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for symbol, e.g. $, #
|
||||||
|
if (getSymbolName(name) != null) {
|
||||||
|
return getSymbolName(name).toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
// string
|
||||||
|
String enumName = sanitizeName(underscore(name).toUpperCase());
|
||||||
|
enumName = enumName.replaceFirst("^_", "");
|
||||||
|
enumName = enumName.replaceFirst("_$", "");
|
||||||
|
|
||||||
|
if (isReservedWord(enumName) || enumName.matches("\\d.*")) { // reserved word or starts with number
|
||||||
|
return escapeReservedWord(enumName);
|
||||||
|
} else {
|
||||||
|
return enumName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toEnumName(CodegenProperty property) {
|
||||||
|
String enumName = underscore(toModelName(property.name)).toUpperCase();
|
||||||
|
|
||||||
|
// remove [] for array or map of enum
|
||||||
|
enumName = enumName.replace("[]", "");
|
||||||
|
|
||||||
|
if (enumName.matches("\\d.*")) { // starts with number
|
||||||
|
return "_" + enumName;
|
||||||
|
} else {
|
||||||
|
return enumName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -55,4 +55,5 @@ public class URLPathUtil {
|
|||||||
}
|
}
|
||||||
return LOCAL_HOST;
|
return LOCAL_HOST;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -49,6 +49,8 @@ org.openapitools.codegen.languages.RClientCodegen
|
|||||||
org.openapitools.codegen.languages.RubyClientCodegen
|
org.openapitools.codegen.languages.RubyClientCodegen
|
||||||
org.openapitools.codegen.languages.RubyOnRailsServerCodegen
|
org.openapitools.codegen.languages.RubyOnRailsServerCodegen
|
||||||
org.openapitools.codegen.languages.RubySinatraServerCodegen
|
org.openapitools.codegen.languages.RubySinatraServerCodegen
|
||||||
|
org.openapitools.codegen.languages.RustClientCodegen
|
||||||
|
org.openapitools.codegen.languages.RustServerCodegen
|
||||||
org.openapitools.codegen.languages.ScalatraServerCodegen
|
org.openapitools.codegen.languages.ScalatraServerCodegen
|
||||||
org.openapitools.codegen.languages.ScalaClientCodegen
|
org.openapitools.codegen.languages.ScalaClientCodegen
|
||||||
org.openapitools.codegen.languages.ScalaGatlingCodegen
|
org.openapitools.codegen.languages.ScalaGatlingCodegen
|
||||||
|
@ -9,32 +9,37 @@ license = "Unlicense"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["client", "server"]
|
default = ["client", "server"]
|
||||||
client = ["serde_json", {{#usesXml}}"serde-xml-rs", {{/usesXml}}"serde_ignored", "hyper", "hyper-openssl", "uuid"{{#apiHasFile}}, "multipart"{{/apiHasFile}}]
|
client = ["serde_json", {{#usesUrlEncodedForm}}"serde_urlencoded", {{/usesUrlEncodedForm}} {{#usesXml}}"serde-xml-rs", {{/usesXml}}"serde_ignored", "hyper", "hyper-tls", "native-tls", "openssl", "tokio-core", "url", "uuid"{{#apiHasFile}}, "multipart"{{/apiHasFile}}]
|
||||||
server = ["serde_json", {{#usesXml}}"serde-xml-rs", {{/usesXml}}"serde_ignored", "hyper", "iron", "router", "bodyparser", "urlencoded", "uuid"{{#apiHasFile}}, "multipart"{{/apiHasFile}}]
|
server = ["serde_json", {{#usesXml}}"serde-xml-rs", {{/usesXml}}"serde_ignored", "hyper", "hyper-tls", "native-tls", "openssl", "tokio-core", "tokio-proto", "tokio-tls", "regex", "percent-encoding", "url", "uuid"{{#apiHasFile}}, "multipart"{{/apiHasFile}}]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Required by example server.
|
# Required by example server.
|
||||||
#
|
#
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
hyper = {version = "0.10", optional = true}
|
hyper = {version = "0.11", optional = true}
|
||||||
hyper-openssl = {version = "0.2", optional = true }
|
hyper-tls = {version = "0.1.2", optional = true}
|
||||||
iron = {version = "0.5", optional = true}
|
swagger = "0.9"
|
||||||
swagger = "0.7"
|
|
||||||
|
|
||||||
# Not required by example server.
|
# Not required by example server.
|
||||||
#
|
#
|
||||||
bodyparser = {version = "0.7", optional = true}
|
|
||||||
url = "1.5"
|
|
||||||
lazy_static = "0.2"
|
lazy_static = "0.2"
|
||||||
log = "0.3.0"
|
log = "0.3.0"
|
||||||
multipart = {version = "0.13", optional = true}
|
mime = "0.3.3"
|
||||||
router = {version = "0.5", optional = true}
|
multipart = {version = "0.13.3", optional = true}
|
||||||
|
native-tls = {version = "0.1.4", optional = true}
|
||||||
|
openssl = {version = "0.9.14", optional = true}
|
||||||
|
percent-encoding = {version = "1.0.0", optional = true}
|
||||||
|
regex = {version = "0.2", optional = true}
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_ignored = {version = "0.0.4", optional = true}
|
serde_ignored = {version = "0.0.4", optional = true}
|
||||||
serde_json = {version = "1.0", optional = true}
|
serde_json = {version = "1.0", optional = true}
|
||||||
urlencoded = {version = "0.5", optional = true}
|
serde_urlencoded = {version = "0.5.1", optional = true}
|
||||||
|
tokio-core = {version = "0.1.6", optional = true}
|
||||||
|
tokio-proto = {version = "0.1.1", optional = true}
|
||||||
|
tokio-tls = {version = "0.1.3", optional = true, features = ["tokio-proto"]}
|
||||||
|
url = {version = "1.5", optional = true}
|
||||||
uuid = {version = "0.5", optional = true, features = ["serde", "v4"]}
|
uuid = {version = "0.5", optional = true, features = ["serde", "v4"]}
|
||||||
# ToDo: this should be updated to point at the official crate once
|
# ToDo: this should be updated to point at the official crate once
|
||||||
# https://github.com/RReverser/serde-xml-rs/pull/45 is accepted upstream
|
# https://github.com/RReverser/serde-xml-rs/pull/45 is accepted upstream
|
||||||
|
@ -0,0 +1,437 @@
|
|||||||
|
#![allow(unused_extern_crates)]
|
||||||
|
extern crate tokio_core;
|
||||||
|
extern crate native_tls;
|
||||||
|
extern crate hyper_tls;
|
||||||
|
extern crate openssl;
|
||||||
|
extern crate mime;
|
||||||
|
extern crate chrono;
|
||||||
|
extern crate url;
|
||||||
|
{{#apiHasFile}}extern crate multipart;{{/apiHasFile}}
|
||||||
|
{{#usesUrlEncodedForm}}extern crate serde_urlencoded;{{/usesUrlEncodedForm}}
|
||||||
|
|
||||||
|
{{#apiUsesUuid}}use uuid;{{/apiUsesUuid}}
|
||||||
|
{{#apiHasFile}}use self::multipart::client::lazy::Multipart;{{/apiHasFile}}
|
||||||
|
use hyper;
|
||||||
|
use hyper::header::{Headers, ContentType};
|
||||||
|
use hyper::Uri;
|
||||||
|
use self::url::percent_encoding::{utf8_percent_encode, PATH_SEGMENT_ENCODE_SET, QUERY_ENCODE_SET};
|
||||||
|
use futures;
|
||||||
|
use futures::{Future, Stream};
|
||||||
|
use futures::{future, stream};
|
||||||
|
use self::tokio_core::reactor::Handle;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::io::{Read, Error, ErrorKind};
|
||||||
|
use std::error;
|
||||||
|
use std::fmt;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::str;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use mimetypes;
|
||||||
|
|
||||||
|
use serde_json;
|
||||||
|
{{#usesXml}}use serde_xml_rs;{{/usesXml}}
|
||||||
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::collections::{HashMap, BTreeMap};
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use swagger;
|
||||||
|
|
||||||
|
use swagger::{Context, ApiError, XSpanId};
|
||||||
|
|
||||||
|
use {Api{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}},
|
||||||
|
{{operationId}}Response{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
|
||||||
|
};
|
||||||
|
use models;
|
||||||
|
|
||||||
|
/// Convert input into a base path, e.g. "http://example:123". Also checks the scheme as it goes.
|
||||||
|
fn into_base_path(input: &str, correct_scheme: Option<&'static str>) -> Result<String, ClientInitError> {
|
||||||
|
// First convert to Uri, since a base path is a subset of Uri.
|
||||||
|
let uri = Uri::from_str(input)?;
|
||||||
|
|
||||||
|
let scheme = uri.scheme().ok_or(ClientInitError::InvalidScheme)?;
|
||||||
|
|
||||||
|
// Check the scheme if necessary
|
||||||
|
if let Some(correct_scheme) = correct_scheme {
|
||||||
|
if scheme != correct_scheme {
|
||||||
|
return Err(ClientInitError::InvalidScheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let host = uri.host().ok_or_else(|| ClientInitError::MissingHost)?;
|
||||||
|
let port = uri.port().map(|x| format!(":{}", x)).unwrap_or_default();
|
||||||
|
Ok(format!("{}://{}{}", scheme, host, port))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A client that implements the API by making HTTP calls out to a server.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Client {
|
||||||
|
hyper_client: Arc<Fn(&Handle) -> Box<hyper::client::Service<Request=hyper::Request<hyper::Body>, Response=hyper::Response, Error=hyper::Error, Future=hyper::client::FutureResponse>> + Sync + Send>,
|
||||||
|
handle: Arc<Handle>,
|
||||||
|
base_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Client {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "Client {{ base_path: {} }}", self.base_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
|
||||||
|
/// Create an HTTP client.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `handle` - tokio reactor handle to use for execution
|
||||||
|
/// * `base_path` - base path of the client API, i.e. "www.my-api-implementation.com"
|
||||||
|
pub fn try_new_http(handle: Handle, base_path: &str) -> Result<Client, ClientInitError> {
|
||||||
|
let http_connector = swagger::http_connector();
|
||||||
|
Self::try_new_with_connector::<hyper::client::HttpConnector>(
|
||||||
|
handle,
|
||||||
|
base_path,
|
||||||
|
Some("http"),
|
||||||
|
http_connector,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a client with a TLS connection to the server.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `handle` - tokio reactor handle to use for execution
|
||||||
|
/// * `base_path` - base path of the client API, i.e. "www.my-api-implementation.com"
|
||||||
|
/// * `ca_certificate` - Path to CA certificate used to authenticate the server
|
||||||
|
pub fn try_new_https<CA>(
|
||||||
|
handle: Handle,
|
||||||
|
base_path: &str,
|
||||||
|
ca_certificate: CA,
|
||||||
|
) -> Result<Client, ClientInitError>
|
||||||
|
where
|
||||||
|
CA: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let https_connector = swagger::https_connector(ca_certificate);
|
||||||
|
Self::try_new_with_connector::<hyper_tls::HttpsConnector<hyper::client::HttpConnector>>(
|
||||||
|
handle,
|
||||||
|
base_path,
|
||||||
|
Some("https"),
|
||||||
|
https_connector,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a client with a mutually authenticated TLS connection to the server.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `handle` - tokio reactor handle to use for execution
|
||||||
|
/// * `base_path` - base path of the client API, i.e. "www.my-api-implementation.com"
|
||||||
|
/// * `ca_certificate` - Path to CA certificate used to authenticate the server
|
||||||
|
/// * `client_key` - Path to the client private key
|
||||||
|
/// * `client_certificate` - Path to the client's public certificate associated with the private key
|
||||||
|
pub fn try_new_https_mutual<CA, K, C, T>(
|
||||||
|
handle: Handle,
|
||||||
|
base_path: &str,
|
||||||
|
ca_certificate: CA,
|
||||||
|
client_key: K,
|
||||||
|
client_certificate: C,
|
||||||
|
) -> Result<Client, ClientInitError>
|
||||||
|
where
|
||||||
|
CA: AsRef<Path>,
|
||||||
|
K: AsRef<Path>,
|
||||||
|
C: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let https_connector =
|
||||||
|
swagger::https_mutual_connector(ca_certificate, client_key, client_certificate);
|
||||||
|
Self::try_new_with_connector::<hyper_tls::HttpsConnector<hyper::client::HttpConnector>>(
|
||||||
|
handle,
|
||||||
|
base_path,
|
||||||
|
Some("https"),
|
||||||
|
https_connector,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a client with a custom implementation of hyper::client::Connect.
|
||||||
|
///
|
||||||
|
/// Intended for use with custom implementations of connect for e.g. protocol logging
|
||||||
|
/// or similar functionality which requires wrapping the transport layer. When wrapping a TCP connection,
|
||||||
|
/// this function should be used in conjunction with
|
||||||
|
/// `swagger::{http_connector, https_connector, https_mutual_connector}`.
|
||||||
|
///
|
||||||
|
/// For ordinary tcp connections, prefer the use of `try_new_http`, `try_new_https`
|
||||||
|
/// and `try_new_https_mutual`, to avoid introducing a dependency on the underlying transport layer.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `handle` - tokio reactor handle to use for execution
|
||||||
|
/// * `base_path` - base path of the client API, i.e. "www.my-api-implementation.com"
|
||||||
|
/// * `protocol` - Which protocol to use when constructing the request url, e.g. `Some("http")`
|
||||||
|
/// * `connector_fn` - Function which returns an implementation of `hyper::client::Connect`
|
||||||
|
pub fn try_new_with_connector<C>(
|
||||||
|
handle: Handle,
|
||||||
|
base_path: &str,
|
||||||
|
protocol: Option<&'static str>,
|
||||||
|
connector_fn: Box<Fn(&Handle) -> C + Send + Sync>,
|
||||||
|
) -> Result<Client, ClientInitError>
|
||||||
|
where
|
||||||
|
C: hyper::client::Connect + hyper::client::Service,
|
||||||
|
{
|
||||||
|
let hyper_client = {
|
||||||
|
move |handle: &Handle| -> Box<
|
||||||
|
hyper::client::Service<
|
||||||
|
Request = hyper::Request<hyper::Body>,
|
||||||
|
Response = hyper::Response,
|
||||||
|
Error = hyper::Error,
|
||||||
|
Future = hyper::client::FutureResponse,
|
||||||
|
>,
|
||||||
|
> {
|
||||||
|
let connector = connector_fn(handle);
|
||||||
|
Box::new(hyper::Client::configure().connector(connector).build(
|
||||||
|
handle,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Client {
|
||||||
|
hyper_client: Arc::new(hyper_client),
|
||||||
|
handle: Arc::new(handle),
|
||||||
|
base_path: into_base_path(base_path, protocol)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructor for creating a `Client` by passing in a pre-made `hyper` client.
|
||||||
|
///
|
||||||
|
/// One should avoid relying on this function if possible, since it adds a dependency on the underlying transport
|
||||||
|
/// implementation, which it would be better to abstract away. Therefore, using this function may lead to a loss of
|
||||||
|
/// code generality, which may make it harder to move the application to a serverless environment, for example.
|
||||||
|
///
|
||||||
|
/// The reason for this function's existence is to support legacy test code, which did mocking at the hyper layer.
|
||||||
|
/// This is not a recommended way to write new tests. If other reasons are found for using this function, they
|
||||||
|
/// should be mentioned here.
|
||||||
|
pub fn try_new_with_hyper_client(hyper_client: Arc<Fn(&Handle) -> Box<hyper::client::Service<Request=hyper::Request<hyper::Body>, Response=hyper::Response, Error=hyper::Error, Future=hyper::client::FutureResponse>> + Sync + Send>,
|
||||||
|
handle: Handle,
|
||||||
|
base_path: &str)
|
||||||
|
-> Result<Client, ClientInitError>
|
||||||
|
{
|
||||||
|
Ok(Client {
|
||||||
|
hyper_client: hyper_client,
|
||||||
|
handle: Arc::new(handle),
|
||||||
|
base_path: into_base_path(base_path, None)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Api for Client {
|
||||||
|
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
|
||||||
|
fn {{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}(&self{{#allParams}}, param_{{paramName}}: {{^required}}{{#isFile}}Box<Future<Item={{/isFile}}Option<{{/required}}{{#isListContainer}}&{{/isListContainer}}{{{dataType}}}{{^required}}>{{#isFile}}, Error=Error> + Send>{{/isFile}}{{/required}}{{/allParams}}, context: &Context) -> Box<Future<Item={{operationId}}Response, Error=ApiError>> {
|
||||||
|
{{#queryParams}}{{#-first}}
|
||||||
|
// Query parameters
|
||||||
|
{{/-first}}{{#required}} let query_{{paramName}} = format!("{{baseName}}={{=<% %>=}}{<% paramName %>}<%={{ }}=%>&", {{paramName}}=param_{{paramName}}{{#isListContainer}}.join(","){{/isListContainer}}{{^isListContainer}}.to_string(){{/isListContainer}});
|
||||||
|
{{/required}}{{^required}} let query_{{paramName}} = param_{{paramName}}.map_or_else(String::new, |query| format!("{{baseName}}={{=<% %>=}}{<% paramName %>}<%={{ }}=%>&", {{paramName}}=query{{#isListContainer}}.join(","){{/isListContainer}}{{^isListContainer}}.to_string(){{/isListContainer}}));
|
||||||
|
{{/required}}{{/queryParams}}
|
||||||
|
|
||||||
|
let uri = format!(
|
||||||
|
"{}{{basePathWithoutHost}}{{path}}{{#queryParams}}{{#-first}}?{{/-first}}{{=<% %>=}}{<% paramName %>}<%={{ }}=%>{{/queryParams}}",
|
||||||
|
self.base_path{{#pathParams}}, {{baseName}}=utf8_percent_encode(¶m_{{paramName}}.to_string(), PATH_SEGMENT_ENCODE_SET){{/pathParams}}{{#queryParams}},
|
||||||
|
{{paramName}}=utf8_percent_encode(&query_{{paramName}}, QUERY_ENCODE_SET){{/queryParams}}
|
||||||
|
);
|
||||||
|
|
||||||
|
let uri = match Uri::from_str(&uri) {
|
||||||
|
Ok(uri) => uri,
|
||||||
|
Err(err) => return Box::new(futures::done(Err(ApiError(format!("Unable to build URI: {}", err))))),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut request = hyper::Request::new(hyper::Method::{{#vendorExtensions}}{{HttpMethod}}{{/vendorExtensions}}, uri);
|
||||||
|
|
||||||
|
{{#vendorExtensions}}{{#hasFile}} // Form data body
|
||||||
|
let mut multipart = Multipart::new();
|
||||||
|
|
||||||
|
// Helper function to convert a Stream into a String. The String can then be used to build the HTTP body.
|
||||||
|
fn convert_stream_to_string(stream: Box<Stream<Item=Vec<u8>, Error=Error> + Send>) -> Result<String, ApiError> {
|
||||||
|
|
||||||
|
stream.concat2()
|
||||||
|
.wait()
|
||||||
|
.map_err(|e| ApiError(format!("Unable to collect stream: {}", e)))
|
||||||
|
.and_then(|body| String::from_utf8(body)
|
||||||
|
.map_err(|e| ApiError(format!("Failed to convert utf8 stream to String: {}", e))))
|
||||||
|
}{{/hasFile}}{{/vendorExtensions}}{{#formParams}}{{#isFile}}
|
||||||
|
|
||||||
|
{{^required}} if let Ok(Some(param_{{paramName}})) = param_{{paramName}}.wait() { {{/required}}
|
||||||
|
{{^required}} {{/required}} match convert_stream_to_string(param_{{paramName}}) {
|
||||||
|
{{^required}} {{/required}} Ok(param_{{paramName}}) => {
|
||||||
|
// Add file to multipart form.
|
||||||
|
multipart.add_text("{{paramName}}", param_{{paramName}});
|
||||||
|
},
|
||||||
|
{{^required}} {{/required}} Err(err) => return Box::new(futures::done(Err(err))),
|
||||||
|
{{^required}} {{/required}} }
|
||||||
|
{{^required}}}{{/required}}{{/isFile}}{{/formParams}}{{#vendorExtensions}}{{#hasFile}}
|
||||||
|
|
||||||
|
let mut fields = match multipart.prepare() {
|
||||||
|
Ok(fields) => fields,
|
||||||
|
Err(err) => return Box::new(futures::done(Err(ApiError(format!("Unable to build request: {}", err))))),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut body_string = String::new();
|
||||||
|
let body = fields.to_body().read_to_string(&mut body_string);
|
||||||
|
let boundary = fields.boundary();
|
||||||
|
let multipart_header = match mime::Mime::from_str(&format!("multipart/form-data;boundary={}", boundary)) {
|
||||||
|
Ok(multipart_header) => multipart_header,
|
||||||
|
Err(err) => return Box::new(futures::done(Err(ApiError(format!("Unable to build multipart header: {:?}", err))))),
|
||||||
|
};{{/hasFile}}{{^hasFile}}{{#formParams}}{{#-first}} let params = &[{{/-first}}
|
||||||
|
("{{baseName}}", {{#vendorExtensions}}{{#required}}Some({{#isString}}param_{{paramName}}{{/isString}}{{^isString}}format!("{:?}", param_{{paramName}}){{/isString}}){{/required}}{{^required}}{{#isString}}param_{{paramName}}{{/isString}}{{^isString}}param_{{paramName}}.map(|param| format!("{:?}", param)){{/isString}}{{/required}}),{{/vendorExtensions}}{{#-last}}
|
||||||
|
];
|
||||||
|
let body = serde_urlencoded::to_string(params).expect("impossible to fail to serialize");
|
||||||
|
|
||||||
|
request.headers_mut().set(ContentType(mimetypes::requests::{{#vendorExtensions}}{{uppercase_operation_id}}{{/vendorExtensions}}.clone()));
|
||||||
|
request.set_body(body.into_bytes());{{/-last}}{{/formParams}}{{/hasFile}}{{/vendorExtensions}}{{#bodyParam}}{{#-first}}
|
||||||
|
// Body parameter
|
||||||
|
{{/-first}}{{#vendorExtensions}}{{#required}}{{#consumesPlainText}} let body = param_{{paramName}};{{/consumesPlainText}}{{#consumesXml}}
|
||||||
|
{{^has_namespace}} let body = serde_xml_rs::to_string(¶m_{{paramName}}).expect("impossible to fail to serialize");{{/has_namespace}}{{#has_namespace}}
|
||||||
|
let mut namespaces = BTreeMap::new();
|
||||||
|
// An empty string is used to indicate a global namespace in xmltree.
|
||||||
|
namespaces.insert("".to_string(), models::namespaces::{{uppercase_data_type}}.clone());
|
||||||
|
let body = serde_xml_rs::to_string_with_namespaces(¶m_{{paramName}}, namespaces).expect("impossible to fail to serialize");{{/has_namespace}}{{/consumesXml}}{{#consumesJson}}
|
||||||
|
let body = serde_json::to_string(¶m_{{paramName}}).expect("impossible to fail to serialize");{{/consumesJson}}
|
||||||
|
{{/required}}{{^required}}{{#consumesPlainText}} let body = param_{{paramName}};
|
||||||
|
{{/consumesPlainText}}{{^consumesPlainText}} let body = param_{{paramName}}.map(|ref body| {
|
||||||
|
{{#consumesXml}}
|
||||||
|
{{^has_namespace}} serde_xml_rs::to_string(body).expect("impossible to fail to serialize"){{/has_namespace}}{{#has_namespace}}
|
||||||
|
let mut namespaces = BTreeMap::new();
|
||||||
|
// An empty string is used to indicate a global namespace in xmltree.
|
||||||
|
namespaces.insert("".to_string(), models::namespaces::{{uppercase_data_type}}.clone());
|
||||||
|
serde_xml_rs::to_string_with_namespaces(body, namespaces).expect("impossible to fail to serialize"){{/has_namespace}}{{/consumesXml}}{{#consumesJson}}
|
||||||
|
serde_json::to_string(body).expect("impossible to fail to serialize"){{/consumesJson}}
|
||||||
|
});{{/consumesPlainText}}{{/required}}{{/vendorExtensions}}{{/bodyParam}}
|
||||||
|
|
||||||
|
{{#bodyParam}}{{^required}}if let Some(body) = body {
|
||||||
|
{{/required}} request.set_body(body.into_bytes());
|
||||||
|
{{^required}} }{{/required}}
|
||||||
|
|
||||||
|
request.headers_mut().set(ContentType(mimetypes::requests::{{#vendorExtensions}}{{uppercase_operation_id}}{{/vendorExtensions}}.clone()));
|
||||||
|
{{/bodyParam}}
|
||||||
|
context.x_span_id.as_ref().map(|header| request.headers_mut().set(XSpanId(header.clone())));
|
||||||
|
{{#authMethods}}{{#isBasic}} context.auth_data.as_ref().map(|auth_data| {
|
||||||
|
if let &swagger::AuthData::Basic(ref basic_header) = auth_data {
|
||||||
|
request.headers_mut().set(hyper::header::Authorization(
|
||||||
|
basic_header.clone(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
});{{/isBasic}}{{/authMethods}}{{#headerParams}}{{#-first}}
|
||||||
|
// Header parameters
|
||||||
|
{{/-first}}{{^isMapContainer}} header! { (Request{{vendorExtensions.typeName}}, "{{baseName}}") => {{#isListContainer}}({{{baseType}}})*{{/isListContainer}}{{^isListContainer}}[{{{dataType}}}]{{/isListContainer}} }
|
||||||
|
{{#required}} request.headers_mut().set(Request{{vendorExtensions.typeName}}(param_{{paramName}}{{#isListContainer}}.clone(){{/isListContainer}}));
|
||||||
|
{{/required}}{{^required}} param_{{paramName}}.map(|header| request.headers_mut().set(Request{{vendorExtensions.typeName}}(header{{#isListContainer}}.clone(){{/isListContainer}})));
|
||||||
|
{{/required}}{{/isMapContainer}}{{#isMapContainer}} let param_{{paramName}}: Option<{{{dataType}}}> = None;
|
||||||
|
{{/isMapContainer}}{{/headerParams}}
|
||||||
|
|
||||||
|
{{#vendorExtensions}}{{#hasFile}}
|
||||||
|
request.headers_mut().set(ContentType(multipart_header));
|
||||||
|
request.set_body(body_string.into_bytes());
|
||||||
|
{{/hasFile}}{{/vendorExtensions}}
|
||||||
|
|
||||||
|
let hyper_client = (self.hyper_client)(&*self.handle);
|
||||||
|
Box::new(hyper_client.call(request)
|
||||||
|
.map_err(|e| ApiError(format!("No response received: {}", e)))
|
||||||
|
.and_then(|mut response| {
|
||||||
|
match response.status().as_u16() {
|
||||||
|
{{#responses}}
|
||||||
|
{{code}} => {
|
||||||
|
{{#headers}} header! { (Response{{nameInCamelCase}}, "{{baseName}}") => [{{{datatype}}}] }
|
||||||
|
let response_{{name}} = match response.headers().get::<Response{{nameInCamelCase}}>() {
|
||||||
|
Some(response_{{name}}) => response_{{name}}.0.clone(),
|
||||||
|
None => return Box::new(future::err(ApiError(String::from("Required response header {{baseName}} for response {{code}} was not found.")))) as Box<Future<Item=_, Error=_>>,
|
||||||
|
};
|
||||||
|
{{/headers}}
|
||||||
|
{{^isFile}}let body = response.body();{{/isFile}}{{#isFile}}let body = Box::new(response.body()
|
||||||
|
.map(|chunk| chunk.to_vec())
|
||||||
|
.map_err(|_|
|
||||||
|
Error::new(ErrorKind::Other, "Received error reading response.")
|
||||||
|
));{{/isFile}}
|
||||||
|
Box::new(
|
||||||
|
{{#dataType}}{{^isFile}}
|
||||||
|
body
|
||||||
|
.concat2()
|
||||||
|
.map_err(|e| ApiError(format!("Failed to read response: {}", e)))
|
||||||
|
.and_then(|body| str::from_utf8(&body)
|
||||||
|
.map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))
|
||||||
|
.and_then(|body|
|
||||||
|
{{#vendorExtensions}}{{#producesXml}}
|
||||||
|
// ToDo: this will move to swagger-rs and become a standard From conversion trait
|
||||||
|
// once https://github.com/RReverser/serde-xml-rs/pull/45 is accepted upstream
|
||||||
|
serde_xml_rs::from_str::<{{{dataType}}}>(body)
|
||||||
|
.map_err(|e| ApiError(format!("Response body did not match the schema: {}", e)))
|
||||||
|
{{/producesXml}}{{#producesJson}}
|
||||||
|
serde_json::from_str::<{{{dataType}}}>(body)
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
{{/producesJson}}{{#producesPlainText}}
|
||||||
|
Ok(body.to_string())
|
||||||
|
{{/producesPlainText}}{{/vendorExtensions}}
|
||||||
|
))
|
||||||
|
.map(move |body|
|
||||||
|
{{/isFile}}{{#isFile}}
|
||||||
|
future::ok(
|
||||||
|
{{/isFile}}
|
||||||
|
{{operationId}}Response::{{#vendorExtensions}}{{x-responseId}}{{/vendorExtensions}}{{^headers}}(body){{/headers}}{{#headers}}{{#-first}}{ body: body, {{/-first}}{{name}}: response_{{name}}{{^-last}}, {{/-last}}{{#-last}} }{{/-last}}{{/headers}}
|
||||||
|
)
|
||||||
|
{{/dataType}}{{^dataType}}
|
||||||
|
future::ok(
|
||||||
|
{{operationId}}Response::{{#vendorExtensions}}{{x-responseId}}{{/vendorExtensions}}{{#headers}}{{#-first}}{ {{/-first}}{{^-first}}, {{/-first}}{{name}}: response_{{name}}{{#-last}} }{{/-last}}{{/headers}}
|
||||||
|
)
|
||||||
|
{{/dataType}}
|
||||||
|
) as Box<Future<Item=_, Error=_>>
|
||||||
|
},
|
||||||
|
{{/responses}}
|
||||||
|
code => {
|
||||||
|
let headers = response.headers().clone();
|
||||||
|
Box::new(response.body()
|
||||||
|
.take(100)
|
||||||
|
.concat2()
|
||||||
|
.then(move |body|
|
||||||
|
future::err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}",
|
||||||
|
code,
|
||||||
|
headers,
|
||||||
|
match body {
|
||||||
|
Ok(ref body) => match str::from_utf8(body) {
|
||||||
|
Ok(body) => Cow::from(body),
|
||||||
|
Err(e) => Cow::from(format!("<Body was not UTF8: {:?}>", e)),
|
||||||
|
},
|
||||||
|
Err(e) => Cow::from(format!("<Failed to read body: {}>", e)),
|
||||||
|
})))
|
||||||
|
)
|
||||||
|
) as Box<Future<Item=_, Error=_>>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
}
|
||||||
|
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ClientInitError {
|
||||||
|
InvalidScheme,
|
||||||
|
InvalidUri(hyper::error::UriError),
|
||||||
|
MissingHost,
|
||||||
|
SslError(openssl::error::ErrorStack)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<hyper::error::UriError> for ClientInitError {
|
||||||
|
fn from(err: hyper::error::UriError) -> ClientInitError {
|
||||||
|
ClientInitError::InvalidUri(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<openssl::error::ErrorStack> for ClientInitError {
|
||||||
|
fn from(err: openssl::error::ErrorStack) -> ClientInitError {
|
||||||
|
ClientInitError::SslError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ClientInitError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
(self as &fmt::Debug).fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for ClientInitError {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"Failed to produce a hyper client."
|
||||||
|
}
|
||||||
|
}
|
@ -1,342 +0,0 @@
|
|||||||
#![allow(unused_extern_crates)]
|
|
||||||
extern crate hyper_openssl;
|
|
||||||
extern crate chrono;
|
|
||||||
extern crate url;
|
|
||||||
{{#apiHasFile}}extern crate multipart;{{/apiHasFile}}
|
|
||||||
|
|
||||||
{{#apiHasFile}}use multipart::client::lazy::Multipart;{{/apiHasFile}}
|
|
||||||
use hyper;
|
|
||||||
use hyper::client::IntoUrl;
|
|
||||||
use hyper::mime;
|
|
||||||
use hyper::header::{Headers, ContentType};
|
|
||||||
use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value};
|
|
||||||
use hyper::Url;
|
|
||||||
use self::hyper_openssl::openssl;
|
|
||||||
use self::url::percent_encoding::{utf8_percent_encode, PATH_SEGMENT_ENCODE_SET, QUERY_ENCODE_SET};
|
|
||||||
use futures;
|
|
||||||
use futures::{Future, Stream};
|
|
||||||
use futures::{future, stream};
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::io::{Read, Error};
|
|
||||||
use std::error;
|
|
||||||
use std::fmt;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::str;
|
|
||||||
|
|
||||||
use mimetypes;
|
|
||||||
|
|
||||||
use serde_json;
|
|
||||||
{{#usesXml}}use serde_xml_rs;{{/usesXml}}
|
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use std::collections::{HashMap, BTreeMap};
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use swagger;
|
|
||||||
|
|
||||||
use swagger::{Context, ApiError, XSpanId};
|
|
||||||
|
|
||||||
use {Api{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}},
|
|
||||||
{{operationId}}Response{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
|
|
||||||
};
|
|
||||||
use models;
|
|
||||||
|
|
||||||
/// Convert input into a base path, e.g. "http://example:123". Also checks the scheme as it goes.
|
|
||||||
fn into_base_path<T: IntoUrl>(input: T, correct_scheme: Option<&'static str>) -> Result<String, ClientInitError> {
|
|
||||||
// First convert to Url, since a base path is a subset of Url.
|
|
||||||
let url = input.into_url()?;
|
|
||||||
|
|
||||||
let scheme = url.scheme();
|
|
||||||
|
|
||||||
// Check the scheme if necessary
|
|
||||||
if let Some(correct_scheme) = correct_scheme {
|
|
||||||
if scheme != correct_scheme {
|
|
||||||
return Err(ClientInitError::InvalidScheme);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let host = url.host().ok_or_else(|| ClientInitError::MissingHost)?;
|
|
||||||
let port = url.port().map(|x| format!(":{}", x)).unwrap_or_default();
|
|
||||||
Ok(format!("{}://{}{}", scheme, host, port))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A client that implements the API by making HTTP calls out to a server.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Client {
|
|
||||||
base_path: String,
|
|
||||||
hyper_client: Arc<Fn() -> hyper::client::Client + Sync + Send>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Client {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "Client {{ base_path: {} }}", self.base_path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Client {
|
|
||||||
pub fn try_new_http<T>(base_path: T) -> Result<Client, ClientInitError>
|
|
||||||
where T: IntoUrl
|
|
||||||
{
|
|
||||||
Ok(Client {
|
|
||||||
base_path: into_base_path(base_path, Some("http"))?,
|
|
||||||
hyper_client: Arc::new(hyper::client::Client::new),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_new_https<T, CA>(base_path: T,
|
|
||||||
ca_certificate: CA)
|
|
||||||
-> Result<Client, ClientInitError>
|
|
||||||
where T: IntoUrl,
|
|
||||||
CA: AsRef<Path>
|
|
||||||
{
|
|
||||||
let ca_certificate = ca_certificate.as_ref().to_owned();
|
|
||||||
|
|
||||||
let https_hyper_client = move || {
|
|
||||||
// SSL implementation
|
|
||||||
let mut ssl = openssl::ssl::SslConnectorBuilder::new(openssl::ssl::SslMethod::tls()).unwrap();
|
|
||||||
|
|
||||||
// Server authentication
|
|
||||||
ssl.set_ca_file(ca_certificate.clone()).unwrap();
|
|
||||||
|
|
||||||
let ssl = hyper_openssl::OpensslClient::from(ssl.build());
|
|
||||||
let connector = hyper::net::HttpsConnector::new(ssl);
|
|
||||||
hyper::client::Client::with_connector(connector)
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Client {
|
|
||||||
base_path: into_base_path(base_path, Some("https"))?,
|
|
||||||
hyper_client: Arc::new(https_hyper_client),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_new_https_mutual<T, CA, K, C>(base_path: T,
|
|
||||||
ca_certificate: CA,
|
|
||||||
client_key: K,
|
|
||||||
client_certificate: C)
|
|
||||||
-> Result<Client, ClientInitError>
|
|
||||||
where T: IntoUrl,
|
|
||||||
CA: AsRef<Path>,
|
|
||||||
K: AsRef<Path>,
|
|
||||||
C: AsRef<Path>
|
|
||||||
{
|
|
||||||
let ca_certificate = ca_certificate.as_ref().to_owned();
|
|
||||||
let client_key = client_key.as_ref().to_owned();
|
|
||||||
let client_certificate = client_certificate.as_ref().to_owned();
|
|
||||||
|
|
||||||
let https_mutual_hyper_client = move || {
|
|
||||||
// SSL implementation
|
|
||||||
let mut ssl = openssl::ssl::SslConnectorBuilder::new(openssl::ssl::SslMethod::tls()).unwrap();
|
|
||||||
|
|
||||||
// Server authentication
|
|
||||||
ssl.set_ca_file(ca_certificate.clone()).unwrap();
|
|
||||||
|
|
||||||
// Client authentication
|
|
||||||
ssl.set_private_key_file(client_key.clone(), openssl::x509::X509_FILETYPE_PEM).unwrap();
|
|
||||||
ssl.set_certificate_chain_file(client_certificate.clone()).unwrap();
|
|
||||||
ssl.check_private_key().unwrap();
|
|
||||||
|
|
||||||
let ssl = hyper_openssl::OpensslClient::from(ssl.build());
|
|
||||||
let connector = hyper::net::HttpsConnector::new(ssl);
|
|
||||||
hyper::client::Client::with_connector(connector)
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Client {
|
|
||||||
base_path: into_base_path(base_path, Some("https"))?,
|
|
||||||
hyper_client: Arc::new(https_mutual_hyper_client)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Constructor for creating a `Client` by passing in a pre-made `hyper` client.
|
|
||||||
///
|
|
||||||
/// One should avoid relying on this function if possible, since it adds a dependency on the underlying transport
|
|
||||||
/// implementation, which it would be better to abstract away. Therefore, using this function may lead to a loss of
|
|
||||||
/// code generality, which may make it harder to move the application to a serverless environment, for example.
|
|
||||||
///
|
|
||||||
/// The reason for this function's existence is to support legacy test code, which did mocking at the hyper layer.
|
|
||||||
/// This is not a recommended way to write new tests. If other reasons are found for using this function, they
|
|
||||||
/// should be mentioned here.
|
|
||||||
pub fn try_new_with_hyper_client<T>(base_path: T,
|
|
||||||
hyper_client: Arc<Fn() -> hyper::client::Client + Sync + Send>)
|
|
||||||
-> Result<Client, ClientInitError>
|
|
||||||
where T: IntoUrl
|
|
||||||
{
|
|
||||||
Ok(Client {
|
|
||||||
base_path: into_base_path(base_path, None)?,
|
|
||||||
hyper_client: hyper_client
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Api for Client {
|
|
||||||
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
|
|
||||||
fn {{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}(&self{{#allParams}}, param_{{paramName}}: {{^required}}{{#isFile}}Box<Future<Item={{/isFile}}Option<{{/required}}{{#isListContainer}}&{{/isListContainer}}{{{dataType}}}{{^required}}>{{#isFile}}, Error=Error> + Send>{{/isFile}}{{/required}}{{/allParams}}, context: &Context) -> Box<Future<Item={{operationId}}Response, Error=ApiError> + Send> {
|
|
||||||
{{#queryParams}}{{#-first}}
|
|
||||||
// Query parameters
|
|
||||||
{{/-first}}{{#required}} let query_{{paramName}} = format!("{{baseName}}={{=<% %>=}}{<% paramName %>}<%={{ }}=%>&", {{paramName}}=param_{{paramName}}{{#isListContainer}}.join(","){{/isListContainer}}{{^isListContainer}}.to_string(){{/isListContainer}});
|
|
||||||
{{/required}}{{^required}} let query_{{paramName}} = param_{{paramName}}.map_or_else(String::new, |query| format!("{{baseName}}={{=<% %>=}}{<% paramName %>}<%={{ }}=%>&", {{paramName}}=query{{#isListContainer}}.join(","){{/isListContainer}}{{^isListContainer}}.to_string(){{/isListContainer}}));
|
|
||||||
{{/required}}{{/queryParams}}
|
|
||||||
|
|
||||||
let url = format!(
|
|
||||||
"{}{{basePathWithoutHost}}{{path}}{{#queryParams}}{{#-first}}?{{/-first}}{{=<% %>=}}{<% paramName %>}<%={{ }}=%>{{/queryParams}}",
|
|
||||||
self.base_path{{#pathParams}}, {{baseName}}=utf8_percent_encode(¶m_{{paramName}}.to_string(), PATH_SEGMENT_ENCODE_SET){{/pathParams}}{{#queryParams}},
|
|
||||||
{{paramName}}=utf8_percent_encode(&query_{{paramName}}, QUERY_ENCODE_SET){{/queryParams}}
|
|
||||||
);
|
|
||||||
|
|
||||||
{{#vendorExtensions}}{{#hasFile}} // Form data body
|
|
||||||
let mut multipart = Multipart::new();{{/hasFile}}{{/vendorExtensions}}{{#formParams}}{{#isFile}}
|
|
||||||
|
|
||||||
{{^required}} if let Ok(Some(param_{{paramName}})) = param_{{paramName}}.wait() { {{/required}}
|
|
||||||
{{^required}} {{/required}} match convert_stream_to_string(param_{{paramName}}) {
|
|
||||||
{{^required}} {{/required}} Ok(param_{{paramName}}) => {
|
|
||||||
// Add file to multipart form.
|
|
||||||
multipart.add_text("{{paramName}}", param_{{paramName}});
|
|
||||||
},
|
|
||||||
{{^required}} {{/required}} Err(err) => return Box::new(futures::done(Err(err))),
|
|
||||||
{{^required}} {{/required}} }
|
|
||||||
{{^required}}}{{/required}}{{/isFile}}{{/formParams}}{{#vendorExtensions}}{{#hasFile}}
|
|
||||||
|
|
||||||
let mut fields = match multipart.prepare() {
|
|
||||||
Ok(fields) => fields,
|
|
||||||
Err(err) => return Box::new(futures::done(Err(ApiError(format!("Unable to build request: {}", err))))),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut body_string = String::new();
|
|
||||||
let body = fields.to_body().read_to_string(&mut body_string);
|
|
||||||
let boundary = fields.boundary();
|
|
||||||
let multipart_header = Mime(TopLevel::Multipart, SubLevel::FormData, vec![(Attr::Boundary, Value::Ext(boundary.to_string()))]);{{/hasFile}}{{/vendorExtensions}}{{#bodyParam}}{{#-first}}
|
|
||||||
// Body parameter
|
|
||||||
{{/-first}}{{#vendorExtensions}}{{#required}}{{#consumesPlainText}} let body = param_{{paramName}};{{/consumesPlainText}}{{#consumesXml}}
|
|
||||||
{{^has_namespace}} let body = serde_xml_rs::to_string(¶m_{{paramName}}).expect("impossible to fail to serialize");{{/has_namespace}}{{#has_namespace}}
|
|
||||||
let mut namespaces = BTreeMap::new();
|
|
||||||
// An empty string is used to indicate a global namespace in xmltree.
|
|
||||||
namespaces.insert("".to_string(), models::namespaces::{{uppercase_data_type}}.clone());
|
|
||||||
let body = serde_xml_rs::to_string_with_namespaces(¶m_{{paramName}}, namespaces).expect("impossible to fail to serialize");{{/has_namespace}}{{/consumesXml}}{{#consumesJson}}
|
|
||||||
let body = serde_json::to_string(¶m_{{paramName}}).expect("impossible to fail to serialize");{{/consumesJson}}
|
|
||||||
{{/required}}{{^required}}{{#consumesPlainText}} let body = param_{{paramName}};
|
|
||||||
{{/consumesPlainText}}{{^consumesPlainText}} let body = param_{{paramName}}.map(|ref body| {
|
|
||||||
{{#consumesXml}}
|
|
||||||
{{^has_namespace}} serde_xml_rs::to_string(body).expect("impossible to fail to serialize"){{/has_namespace}}{{#has_namespace}}
|
|
||||||
let mut namespaces = BTreeMap::new();
|
|
||||||
// An empty string is used to indicate a global namespace in xmltree.
|
|
||||||
namespaces.insert("".to_string(), models::namespaces::{{uppercase_data_type}}.clone());
|
|
||||||
serde_xml_rs::to_string_with_namespaces(body, namespaces).expect("impossible to fail to serialize"){{/has_namespace}}{{/consumesXml}}{{#consumesJson}}
|
|
||||||
serde_json::to_string(body).expect("impossible to fail to serialize"){{/consumesJson}}
|
|
||||||
});{{/consumesPlainText}}{{/required}}{{/vendorExtensions}}{{/bodyParam}}
|
|
||||||
let hyper_client = (self.hyper_client)();
|
|
||||||
let request = hyper_client.request(hyper::method::Method::{{#vendorExtensions}}{{HttpMethod}}{{/vendorExtensions}}, &url);
|
|
||||||
let mut custom_headers = hyper::header::Headers::new();
|
|
||||||
|
|
||||||
{{#bodyParam}}{{#required}} let request = request.body(&body);
|
|
||||||
{{/required}}{{^required}} let request = match body {
|
|
||||||
Some(ref body) => request.body(body),
|
|
||||||
None => request,
|
|
||||||
};
|
|
||||||
{{/required}}
|
|
||||||
|
|
||||||
custom_headers.set(ContentType(mimetypes::requests::{{#vendorExtensions}}{{uppercase_operation_id}}{{/vendorExtensions}}.clone()));
|
|
||||||
{{/bodyParam}}
|
|
||||||
context.x_span_id.as_ref().map(|header| custom_headers.set(XSpanId(header.clone())));
|
|
||||||
{{#headerParams}}{{#-first}}
|
|
||||||
// Header parameters
|
|
||||||
{{/-first}}{{^isMapContainer}} header! { (Request{{vendorExtensions.typeName}}, "{{baseName}}") => {{#isListContainer}}({{{baseType}}})*{{/isListContainer}}{{^isListContainer}}[{{{dataType}}}]{{/isListContainer}} }
|
|
||||||
{{#required}} custom_headers.set(Request{{vendorExtensions.typeName}}(param_{{paramName}}{{#isListContainer}}.clone(){{/isListContainer}}));
|
|
||||||
{{/required}}{{^required}} param_{{paramName}}.map(|header| custom_headers.set(Request{{vendorExtensions.typeName}}(header{{#isListContainer}}.clone(){{/isListContainer}})));
|
|
||||||
{{/required}}{{/isMapContainer}}{{#isMapContainer}} let param_{{paramName}}: Option<{{{dataType}}}> = None;
|
|
||||||
{{/isMapContainer}}{{/headerParams}}
|
|
||||||
|
|
||||||
let request = request.headers(custom_headers);{{#vendorExtensions}}{{#hasFile}}
|
|
||||||
let request = request.header(ContentType(multipart_header))
|
|
||||||
.body(&body_string);
|
|
||||||
{{/hasFile}}{{/vendorExtensions}}
|
|
||||||
|
|
||||||
// Helper function to provide a code block to use `?` in (to be replaced by the `catch` block when it exists).
|
|
||||||
fn parse_response(mut response: hyper::client::response::Response) -> Result<{{operationId}}Response, ApiError> {
|
|
||||||
match response.status.to_u16() {
|
|
||||||
{{#responses}}
|
|
||||||
{{code}} => {
|
|
||||||
{{#dataType}}{{^isFile}} let mut buf = String::new();
|
|
||||||
response.read_to_string(&mut buf).map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;{{#vendorExtensions}}{{#producesXml}}
|
|
||||||
// ToDo: this will move to swagger-rs and become a standard From conversion trait
|
|
||||||
// once https://github.com/RReverser/serde-xml-rs/pull/45 is accepted upstream
|
|
||||||
let body = serde_xml_rs::from_str::<{{{dataType}}}>(&buf)
|
|
||||||
.map_err(|e| ApiError(format!("Response body did not match the schema: {}", e)))?;{{/producesXml}}{{#producesPlainText}}
|
|
||||||
let body = buf;{{/producesPlainText}}{{#producesJson}}
|
|
||||||
let body = serde_json::from_str::<{{{dataType}}}>(&buf)?;{{/producesJson}}{{/vendorExtensions}}{{/isFile}}{{#isFile}}
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
response.read_to_end(&mut buf).map_err(|e| ApiError(format!("Received error reading response: {}", e)))?;
|
|
||||||
let body = Box::new(stream::once(Ok(buf)));{{/isFile}}{{/dataType}}
|
|
||||||
{{#headers}} header! { (Response{{nameInCamelCase}}, "{{baseName}}") => [{{{datatype}}}] }
|
|
||||||
let response_{{name}} = response.headers.get::<Response{{nameInCamelCase}}>().ok_or_else(|| "Required response header {{baseName}} for response {{code}} was not found.")?;
|
|
||||||
{{/headers}}
|
|
||||||
|
|
||||||
{{#dataType}} Ok({{operationId}}Response::{{#vendorExtensions}}{{x-responseId}}{{/vendorExtensions}}{{^headers}}(body){{/headers}}{{#headers}}{{#-first}}{ body: body, {{/-first}}{{name}}: response_{{name}}.0.clone(){{^-last}}, {{/-last}}{{#-last}} }{{/-last}}{{/headers}})
|
|
||||||
{{/dataType}}{{^dataType}} Ok({{operationId}}Response::{{#vendorExtensions}}{{x-responseId}}{{/vendorExtensions}}{{#headers}}{{#-first}}{ {{/-first}}{{^-first}}, {{/-first}}{{name}}: response_{{name}}.0.clone(){{#-last}} }{{/-last}}{{/headers}})
|
|
||||||
{{/dataType}}
|
|
||||||
},
|
|
||||||
{{/responses}}
|
|
||||||
code => {
|
|
||||||
let mut buf = [0; 100];
|
|
||||||
let debug_body = match response.read(&mut buf) {
|
|
||||||
Ok(len) => match str::from_utf8(&buf[..len]) {
|
|
||||||
Ok(body) => Cow::from(body),
|
|
||||||
Err(_) => Cow::from(format!("<Body was not UTF8: {:?}>", &buf[..len].to_vec())),
|
|
||||||
},
|
|
||||||
Err(e) => Cow::from(format!("<Failed to read body: {}>", e)),
|
|
||||||
};
|
|
||||||
Err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}",
|
|
||||||
code,
|
|
||||||
response.headers,
|
|
||||||
debug_body)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}{{#vendorExtensions}}{{#hasFile}}
|
|
||||||
|
|
||||||
// Helper function to convert a Stream into a String. The String can then be used to build the HTTP body.
|
|
||||||
fn convert_stream_to_string(stream: Box<Stream<Item=Vec<u8>, Error=Error> + Send>) -> Result<String, ApiError> {
|
|
||||||
|
|
||||||
stream.fold(Vec::new(), |mut body, chunk| {
|
|
||||||
body.extend(chunk.iter());
|
|
||||||
future::ok::<Vec<u8>,Error>(body)
|
|
||||||
}).wait()
|
|
||||||
.map_err(|e| ApiError(format!("Unable to fold stream: {}", e)))
|
|
||||||
.and_then(|body| String::from_utf8(body)
|
|
||||||
.map_err(|e| ApiError(format!("Failed to convert utf8 stream to String: {}", e))))
|
|
||||||
}{{/hasFile}}{{/vendorExtensions}}
|
|
||||||
|
|
||||||
let result = request.send().map_err(|e| ApiError(format!("No response received: {}", e))).and_then(parse_response);
|
|
||||||
Box::new(futures::done(result))
|
|
||||||
}
|
|
||||||
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum ClientInitError {
|
|
||||||
InvalidScheme,
|
|
||||||
InvalidUrl(hyper::error::ParseError),
|
|
||||||
MissingHost,
|
|
||||||
SslError(openssl::error::ErrorStack)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<hyper::error::ParseError> for ClientInitError {
|
|
||||||
fn from(err: hyper::error::ParseError) -> ClientInitError {
|
|
||||||
ClientInitError::InvalidUrl(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<openssl::error::ErrorStack> for ClientInitError {
|
|
||||||
fn from(err: openssl::error::ErrorStack) -> ClientInitError {
|
|
||||||
ClientInitError::SslError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ClientInitError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
(self as &fmt::Debug).fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl error::Error for ClientInitError {
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
"Failed to produce a hyper client."
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,9 +8,11 @@ extern crate swagger;
|
|||||||
#[allow(unused_extern_crates)]
|
#[allow(unused_extern_crates)]
|
||||||
extern crate uuid;
|
extern crate uuid;
|
||||||
extern crate clap;
|
extern crate clap;
|
||||||
|
extern crate tokio_core;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use futures::{Future, future, Stream, stream};
|
use futures::{Future, future, Stream, stream};
|
||||||
|
use tokio_core::reactor;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use {{externCrateName}}::{ApiNoContext, ContextWrapperExt,
|
use {{externCrateName}}::{ApiNoContext, ContextWrapperExt,
|
||||||
ApiError{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}},
|
ApiError{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}},
|
||||||
@ -42,18 +44,19 @@ fn main() {
|
|||||||
.help("Port to contact"))
|
.help("Port to contact"))
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
|
let mut core = reactor::Core::new().unwrap();
|
||||||
let is_https = matches.is_present("https");
|
let is_https = matches.is_present("https");
|
||||||
let base_url = format!("{}://{}:{}",
|
let base_url = format!("{}://{}:{}",
|
||||||
if is_https { "https" } else { "http" },
|
if is_https { "https" } else { "http" },
|
||||||
matches.value_of("host").unwrap(),
|
matches.value_of("host").unwrap(),
|
||||||
matches.value_of("port").unwrap());
|
matches.value_of("port").unwrap());
|
||||||
let client = if is_https {
|
let client = if matches.is_present("https") {
|
||||||
// Using Simple HTTPS
|
// Using Simple HTTPS
|
||||||
{{externCrateName}}::Client::try_new_https(&base_url, "examples/ca.pem")
|
{{externCrateName}}::Client::try_new_https(core.handle(), &base_url, "examples/ca.pem")
|
||||||
.expect("Failed to create HTTPS client")
|
.expect("Failed to create HTTPS client")
|
||||||
} else {
|
} else {
|
||||||
// Using HTTP
|
// Using HTTP
|
||||||
{{externCrateName}}::Client::try_new_http(&base_url)
|
{{externCrateName}}::Client::try_new_http(core.handle(), &base_url)
|
||||||
.expect("Failed to create HTTP client")
|
.expect("Failed to create HTTP client")
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -64,7 +67,7 @@ fn main() {
|
|||||||
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
|
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
|
||||||
{{#vendorExtensions}}{{#noClientExample}}// Disabled because there's no example.
|
{{#vendorExtensions}}{{#noClientExample}}// Disabled because there's no example.
|
||||||
// {{/noClientExample}}Some("{{operationId}}") => {
|
// {{/noClientExample}}Some("{{operationId}}") => {
|
||||||
{{#noClientExample}}// {{/noClientExample}} let result = client.{{operation_id}}{{/vendorExtensions}}({{#allParams}}{{^-first}}, {{/-first}}{{#vendorExtensions}}{{{example}}}{{/vendorExtensions}}{{/allParams}}).wait();
|
{{#noClientExample}}// {{/noClientExample}} let result = core.run(client.{{operation_id}}{{/vendorExtensions}}({{#allParams}}{{^-first}}, {{/-first}}{{#vendorExtensions}}{{{example}}}{{/vendorExtensions}}{{/allParams}}));
|
||||||
{{#vendorExtensions}}{{#noClientExample}}// {{/noClientExample}}{{/vendorExtensions}} println!("{:?} (X-Span-ID: {:?})", result, client.context().x_span_id.clone().unwrap_or(String::from("<none>")));
|
{{#vendorExtensions}}{{#noClientExample}}// {{/noClientExample}}{{/vendorExtensions}} println!("{:?} (X-Span-ID: {:?})", result, client.context().x_span_id.clone().unwrap_or(String::from("<none>")));
|
||||||
{{#vendorExtensions}}{{#noClientExample}}// {{/noClientExample}}{{/vendorExtensions}} },
|
{{#vendorExtensions}}{{#noClientExample}}// {{/noClientExample}}{{/vendorExtensions}} },
|
||||||
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
|
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
|
||||||
|
@ -6,8 +6,11 @@
|
|||||||
// extern crate <name of this crate>;
|
// extern crate <name of this crate>;
|
||||||
extern crate {{externCrateName}};
|
extern crate {{externCrateName}};
|
||||||
extern crate swagger;
|
extern crate swagger;
|
||||||
extern crate iron;
|
extern crate hyper;
|
||||||
extern crate hyper_openssl;
|
extern crate openssl;
|
||||||
|
extern crate native_tls;
|
||||||
|
extern crate tokio_proto;
|
||||||
|
extern crate tokio_tls;
|
||||||
extern crate clap;
|
extern crate clap;
|
||||||
|
|
||||||
// Imports required by server library.
|
// Imports required by server library.
|
||||||
@ -17,19 +20,20 @@ extern crate futures;
|
|||||||
extern crate chrono;
|
extern crate chrono;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate error_chain;
|
extern crate error_chain;
|
||||||
|
{{#apiUsesUuid}}extern crate uuid;{{/apiUsesUuid}}
|
||||||
|
|
||||||
use hyper_openssl::OpensslServer;
|
use openssl::x509::X509_FILETYPE_PEM;
|
||||||
use hyper_openssl::openssl::x509::X509_FILETYPE_PEM;
|
use openssl::ssl::{SslAcceptorBuilder, SslMethod};
|
||||||
use hyper_openssl::openssl::ssl::{SslAcceptorBuilder, SslMethod};
|
use openssl::error::ErrorStack;
|
||||||
use hyper_openssl::openssl::error::ErrorStack;
|
use hyper::server::Http;
|
||||||
|
use tokio_proto::TcpServer;
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
use iron::{Iron, Chain};
|
use swagger::auth::AllowAllAuthenticator;
|
||||||
use swagger::auth::AllowAllMiddleware;
|
|
||||||
|
|
||||||
mod server_lib;
|
mod server_lib;
|
||||||
|
|
||||||
/// Builds an SSL implementation for Simple HTTPS from some hard-coded file names
|
// Builds an SSL implementation for Simple HTTPS from some hard-coded file names
|
||||||
fn ssl() -> Result<OpensslServer, ErrorStack> {
|
fn ssl() -> Result<SslAcceptorBuilder, ErrorStack> {
|
||||||
let mut ssl = SslAcceptorBuilder::mozilla_intermediate_raw(SslMethod::tls())?;
|
let mut ssl = SslAcceptorBuilder::mozilla_intermediate_raw(SslMethod::tls())?;
|
||||||
|
|
||||||
// Server authentication
|
// Server authentication
|
||||||
@ -37,7 +41,7 @@ fn ssl() -> Result<OpensslServer, ErrorStack> {
|
|||||||
ssl.set_certificate_chain_file("examples/server-chain.pem")?;
|
ssl.set_certificate_chain_file("examples/server-chain.pem")?;
|
||||||
ssl.check_private_key()?;
|
ssl.check_private_key()?;
|
||||||
|
|
||||||
Ok(OpensslServer::from(ssl.build()))
|
Ok(ssl)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create custom server, wire it to the autogenerated router,
|
/// Create custom server, wire it to the autogenerated router,
|
||||||
@ -49,20 +53,22 @@ fn main() {
|
|||||||
.help("Whether to use HTTPS or not"))
|
.help("Whether to use HTTPS or not"))
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
let server = server_lib::server().unwrap();
|
let service_fn =
|
||||||
let router = {{externCrateName}}::router(server);
|
{{externCrateName}}::server::auth::NewService::new(
|
||||||
|
AllowAllAuthenticator::new(
|
||||||
let mut chain = Chain::new(router);
|
server_lib::NewService,
|
||||||
chain.link_before({{externCrateName}}::server::ExtractAuthData);
|
"cosmo"
|
||||||
// add authentication middlewares into the chain here
|
)
|
||||||
// for the purpose of this example, pretend we have authenticated a user
|
);
|
||||||
chain.link_before(AllowAllMiddleware::new("cosmo"));
|
|
||||||
|
|
||||||
|
let addr = "127.0.0.1:{{serverPort}}".parse().expect("Failed to parse bind address");
|
||||||
if matches.is_present("https") {
|
if matches.is_present("https") {
|
||||||
// Using Simple HTTPS
|
let ssl = ssl().expect("Failed to load SSL keys");
|
||||||
Iron::new(chain).https("localhost:{{serverPort}}", ssl().expect("Failed to load SSL keys")).expect("Failed to start HTTPS server");
|
let builder: native_tls::TlsAcceptorBuilder = native_tls::backend::openssl::TlsAcceptorBuilderExt::from_openssl(ssl);
|
||||||
|
let tls_acceptor = builder.build().expect("Failed to build TLS acceptor");
|
||||||
|
TcpServer::new(tokio_tls::proto::Server::new(Http::new(), tls_acceptor), addr).serve(service_fn);
|
||||||
} else {
|
} else {
|
||||||
// Using HTTP
|
// Using HTTP
|
||||||
Iron::new(chain).http("localhost:{{serverPort}}").expect("Failed to start HTTP server");
|
TcpServer::new(Http::new(), addr).serve(service_fn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,20 @@ mod errors {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub use self::errors::*;
|
pub use self::errors::*;
|
||||||
|
use std::io;
|
||||||
|
use hyper;
|
||||||
|
use {{externCrateName}};
|
||||||
|
|
||||||
/// Instantiate a new server.
|
pub struct NewService;
|
||||||
pub fn server() -> Result<server::Server> {
|
|
||||||
Ok(server::Server {})
|
impl hyper::server::NewService for NewService {
|
||||||
|
type Request = (hyper::Request, {{externCrateName}}::Context);
|
||||||
|
type Response = hyper::Response;
|
||||||
|
type Error = hyper::Error;
|
||||||
|
type Instance = {{externCrateName}}::server::Service<server::Server>;
|
||||||
|
|
||||||
|
/// Instantiate a new server.
|
||||||
|
fn new_service(&self) -> io::Result<Self::Instance> {
|
||||||
|
Ok({{externCrateName}}::server::Service::new(server::Server))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ use chrono;
|
|||||||
{{#apiHasFile}}use futures::Stream;{{/apiHasFile}}
|
{{#apiHasFile}}use futures::Stream;{{/apiHasFile}}
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
{{#apiHasFile}}use std::io::Error;{{/apiHasFile}}
|
{{#apiHasFile}}use std::io::Error;{{/apiHasFile}}
|
||||||
|
{{#apiUsesUuid}}use uuid;{{/apiUsesUuid}}
|
||||||
use swagger;
|
use swagger;
|
||||||
|
|
||||||
use {{externCrateName}}::{Api, ApiError, Context{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}},
|
use {{externCrateName}}::{Api, ApiError, Context{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}},
|
||||||
@ -20,7 +21,7 @@ pub struct Server;
|
|||||||
impl Api for Server {
|
impl Api for Server {
|
||||||
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
|
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
|
||||||
{{#summary}} /// {{{summary}}}{{/summary}}
|
{{#summary}} /// {{{summary}}}{{/summary}}
|
||||||
fn {{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}(&self{{#allParams}}, {{paramName}}: {{^required}}{{#isFile}}Box<Future<Item={{/isFile}}Option<{{/required}}{{#isListContainer}}&{{/isListContainer}}{{{dataType}}}{{^required}}>{{#isFile}}, Error=Error> + Send>{{/isFile}}{{/required}}{{/allParams}}, context: &Context) -> Box<Future<Item={{operationId}}Response, Error=ApiError> + Send> {
|
fn {{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}(&self{{#allParams}}, {{paramName}}: {{^required}}{{#isFile}}Box<Future<Item={{/isFile}}Option<{{/required}}{{#isListContainer}}&{{/isListContainer}}{{{dataType}}}{{^required}}>{{#isFile}}, Error=Error> + Send>{{/isFile}}{{/required}}{{/allParams}}, context: &Context) -> Box<Future<Item={{operationId}}Response, Error=ApiError>> {
|
||||||
let context = context.clone();
|
let context = context.clone();
|
||||||
println!("{{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}({{#allParams}}{{^isFile}}{{#vendorExtensions}}{{{formatString}}}{{/vendorExtensions}}{{#hasMore}}, {{/hasMore}}{{/isFile}}{{/allParams}}) - X-Span-ID: {:?}"{{#allParams}}{{^isFile}}, {{paramName}}{{/isFile}}{{/allParams}}, context.x_span_id.unwrap_or(String::from("<none>")).clone());{{#allParams}}{{#isFile}}
|
println!("{{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}({{#allParams}}{{^isFile}}{{#vendorExtensions}}{{{formatString}}}{{/vendorExtensions}}{{#hasMore}}, {{/hasMore}}{{/isFile}}{{/allParams}}) - X-Span-ID: {:?}"{{#allParams}}{{^isFile}}, {{paramName}}{{/isFile}}{{/allParams}}, context.x_span_id.unwrap_or(String::from("<none>")).clone());{{#allParams}}{{#isFile}}
|
||||||
let _ = {{paramName}}; //Suppresses unused param warning{{/isFile}}{{/allParams}}
|
let _ = {{paramName}}; //Suppresses unused param warning{{/isFile}}{{/allParams}}
|
||||||
|
@ -3,10 +3,10 @@ extern crate serde;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
{{#apiUsesUuid}}extern crate uuid;{{/apiUsesUuid}}
|
||||||
{{#usesXml}}extern crate serde_xml_rs;{{/usesXml}}
|
{{#usesXml}}extern crate serde_xml_rs;{{/usesXml}}
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
extern crate chrono;
|
extern crate chrono;
|
||||||
{{#apiHasFile}}extern crate multipart;{{/apiHasFile}}
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@ -32,6 +32,8 @@ mod mimetypes;
|
|||||||
|
|
||||||
pub use swagger::{ApiError, Context, ContextWrapper};
|
pub use swagger::{ApiError, Context, ContextWrapper};
|
||||||
|
|
||||||
|
pub const BASE_PATH: &'static str = "{{basePathWithoutHost}}";
|
||||||
|
|
||||||
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
|
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
|
||||||
{{^isResponseFile}}
|
{{^isResponseFile}}
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
@ -48,7 +50,7 @@ pub enum {{operationId}}Response {
|
|||||||
pub trait Api {
|
pub trait Api {
|
||||||
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
|
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
|
||||||
{{#summary}} /// {{{summary}}}{{/summary}}
|
{{#summary}} /// {{{summary}}}{{/summary}}
|
||||||
fn {{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}(&self{{#allParams}}, {{paramName}}: {{^required}}{{#isFile}}Box<Future<Item={{/isFile}}Option<{{/required}}{{#isListContainer}}&{{/isListContainer}}{{{dataType}}}{{^required}}>{{#isFile}}, Error=Error> + Send>{{/isFile}}{{/required}}{{/allParams}}, context: &Context) -> Box<Future<Item={{operationId}}Response, Error=ApiError> + Send>;
|
fn {{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}(&self{{#allParams}}, {{paramName}}: {{^required}}{{#isFile}}Box<Future<Item={{/isFile}}Option<{{/required}}{{#isListContainer}}&{{/isListContainer}}{{{dataType}}}{{^required}}>{{#isFile}}, Error=Error> + Send>{{/isFile}}{{/required}}{{/allParams}}, context: &Context) -> Box<Future<Item={{operationId}}Response, Error=ApiError>>;
|
||||||
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
|
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +58,7 @@ pub trait Api {
|
|||||||
pub trait ApiNoContext {
|
pub trait ApiNoContext {
|
||||||
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
|
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
|
||||||
{{#summary}} /// {{{summary}}}{{/summary}}
|
{{#summary}} /// {{{summary}}}{{/summary}}
|
||||||
fn {{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}(&self{{#allParams}}, {{paramName}}: {{^required}}{{#isFile}}Box<Future<Item={{/isFile}}Option<{{/required}}{{#isListContainer}}&{{/isListContainer}}{{{dataType}}}{{^required}}>{{#isFile}}, Error=Error> + Send>{{/isFile}}{{/required}}{{/allParams}}) -> Box<Future<Item={{operationId}}Response, Error=ApiError> + Send>;
|
fn {{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}(&self{{#allParams}}, {{paramName}}: {{^required}}{{#isFile}}Box<Future<Item={{/isFile}}Option<{{/required}}{{#isListContainer}}&{{/isListContainer}}{{{dataType}}}{{^required}}>{{#isFile}}, Error=Error> + Send>{{/isFile}}{{/required}}{{/allParams}}) -> Box<Future<Item={{operationId}}Response, Error=ApiError>>;
|
||||||
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
|
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +77,7 @@ impl<'a, T: Api + Sized> ContextWrapperExt<'a> for T {
|
|||||||
impl<'a, T: Api> ApiNoContext for ContextWrapper<'a, T> {
|
impl<'a, T: Api> ApiNoContext for ContextWrapper<'a, T> {
|
||||||
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
|
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
|
||||||
{{#summary}} /// {{{summary}}}{{/summary}}
|
{{#summary}} /// {{{summary}}}{{/summary}}
|
||||||
fn {{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}(&self{{#allParams}}, {{paramName}}: {{^required}}{{#isFile}}Box<Future<Item={{/isFile}}Option<{{/required}}{{#isListContainer}}&{{/isListContainer}}{{{dataType}}}{{^required}}>{{#isFile}}, Error=Error> + Send>{{/isFile}}{{/required}}{{/allParams}}) -> Box<Future<Item={{operationId}}Response, Error=ApiError> + Send> {
|
fn {{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}(&self{{#allParams}}, {{paramName}}: {{^required}}{{#isFile}}Box<Future<Item={{/isFile}}Option<{{/required}}{{#isListContainer}}&{{/isListContainer}}{{{dataType}}}{{^required}}>{{#isFile}}, Error=Error> + Send>{{/isFile}}{{/required}}{{/allParams}}) -> Box<Future<Item={{operationId}}Response, Error=ApiError>> {
|
||||||
self.api().{{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}({{#allParams}}{{paramName}}, {{/allParams}}&self.context())
|
self.api().{{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}({{#allParams}}{{paramName}}, {{/allParams}}&self.context())
|
||||||
}
|
}
|
||||||
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
|
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
|
||||||
@ -93,6 +95,6 @@ pub mod server;
|
|||||||
|
|
||||||
// Re-export router() as a top-level name
|
// Re-export router() as a top-level name
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
pub use self::server::router;
|
pub use self::server::Service;
|
||||||
|
|
||||||
pub mod models;
|
pub mod models;
|
||||||
|
@ -15,7 +15,11 @@ pub mod requests {
|
|||||||
use hyper::mime::*;
|
use hyper::mime::*;
|
||||||
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}{{#bodyParam}} /// Create Mime objects for the request content types for {{operationId}}
|
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}{{#bodyParam}} /// Create Mime objects for the request content types for {{operationId}}
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref {{#vendorExtensions}}{{uppercase_operation_id}}{{/vendorExtensions}}: Mime = "{{#consumes}}{{#-first}}{{{mediaType}}}{{/-first}}{{/consumes}}{{^consumes}}Application/Json{{/consumes}}".parse().unwrap();
|
pub static ref {{#vendorExtensions}}{{uppercase_operation_id}}{{/vendorExtensions}}: Mime = "{{#consumes}}{{#-first}}{{{mediaType}}}{{/-first}}{{/consumes}}{{^consumes}}application/json{{/consumes}}".parse().unwrap();
|
||||||
}
|
}
|
||||||
{{/bodyParam}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
|
{{/bodyParam}}{{^bodyParam}}{{#vendorExtensions}}{{#formParams}}{{#-first}}{{^hasFile}} /// Create Mime objects for the request content types for {{operationId}}
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref {{#vendorExtensions}}{{uppercase_operation_id}}{{/vendorExtensions}}: Mime = "{{#consumes}}{{#-first}}{{{mediaType}}}{{/-first}}{{/consumes}}{{^consumes}}application/x-www-form-urlencoded{{/consumes}}".parse().unwrap();
|
||||||
|
}
|
||||||
|
{{/hasFile}}{{/-first}}{{/formParams}}{{/vendorExtensions}}{{/bodyParam}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
|
||||||
}
|
}
|
||||||
|
0
modules/openapi-generator/src/main/resources/rust-server/models.mustache
Executable file → Normal file
0
modules/openapi-generator/src/main/resources/rust-server/models.mustache
Executable file → Normal file
@ -0,0 +1,95 @@
|
|||||||
|
use std::io;
|
||||||
|
use hyper;
|
||||||
|
use hyper::{Request, Response, Error, StatusCode};
|
||||||
|
use server::url::form_urlencoded;
|
||||||
|
use swagger::auth::{Authorization, AuthData, Scopes};
|
||||||
|
use Api;
|
||||||
|
|
||||||
|
pub struct NewService<T> where T: hyper::server::NewService<Request=(Request,Option<AuthData>), Response=Response, Error=Error> {
|
||||||
|
inner: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> NewService<T> where T: hyper::server::NewService<Request=(Request,Option<AuthData>), Response=Response, Error=Error> + 'static {
|
||||||
|
pub fn new(inner: T) -> NewService<T> {
|
||||||
|
NewService{inner}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> hyper::server::NewService for NewService<T> where T: hyper::server::NewService<Request=(Request,Option<AuthData>), Response=Response, Error=Error> + 'static {
|
||||||
|
type Request = Request;
|
||||||
|
type Response = Response;
|
||||||
|
type Error = Error;
|
||||||
|
type Instance = Service<T::Instance>;
|
||||||
|
|
||||||
|
fn new_service(&self) -> Result<Self::Instance, io::Error> {
|
||||||
|
self.inner.new_service().map(|s| Service::new(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Middleware to extract authentication data from request
|
||||||
|
pub struct Service<T> where T: hyper::server::Service<Request=(Request,Option<AuthData>), Response=Response, Error=Error> {
|
||||||
|
inner: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Service<T> where T: hyper::server::Service<Request=(Request,Option<AuthData>), Response=Response, Error=Error> {
|
||||||
|
pub fn new(inner: T) -> Service<T> {
|
||||||
|
Service{inner}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> hyper::server::Service for Service<T> where T: hyper::server::Service<Request=(Request,Option<AuthData>), Response=Response, Error=Error> {
|
||||||
|
type Request = Request;
|
||||||
|
type Response = Response;
|
||||||
|
type Error = Error;
|
||||||
|
type Future = T::Future;
|
||||||
|
|
||||||
|
fn call(&self, req: Self::Request) -> Self::Future {
|
||||||
|
{{#authMethods}}
|
||||||
|
{{#isBasic}}
|
||||||
|
{
|
||||||
|
use hyper::header::{Authorization, Basic, Bearer};
|
||||||
|
use std::ops::Deref;
|
||||||
|
if let Some(basic) = req.headers().get::<Authorization<Basic>>().cloned() {
|
||||||
|
let auth_data = AuthData::Basic(basic.deref().clone());
|
||||||
|
return self.inner.call((req, Some(auth_data)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{{/isBasic}}
|
||||||
|
{{#isOAuth}}
|
||||||
|
{
|
||||||
|
use hyper::header::{Authorization, Basic, Bearer};
|
||||||
|
use std::ops::Deref;
|
||||||
|
if let Some(bearer) = req.headers().get::<Authorization<Bearer>>().cloned() {
|
||||||
|
let auth_data = AuthData::Bearer(bearer.deref().clone());
|
||||||
|
return self.inner.call((req, Some(auth_data)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{{/isOAuth}}
|
||||||
|
{{#isApiKey}}
|
||||||
|
{{#isKeyInHeader}}
|
||||||
|
{
|
||||||
|
header! { (ApiKey{{-index}}, "{{keyParamName}}") => [String] }
|
||||||
|
if let Some(header) = req.headers().get::<ApiKey{{-index}}>().cloned() {
|
||||||
|
let auth_data = AuthData::ApiKey(header.0);
|
||||||
|
return self.inner.call((req, Some(auth_data)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{{/isKeyInHeader}}
|
||||||
|
{{#isKeyInQuery}}
|
||||||
|
{
|
||||||
|
let key = form_urlencoded::parse(req.query().unwrap_or_default().as_bytes())
|
||||||
|
.filter(|e| e.0 == "api_key_query")
|
||||||
|
.map(|e| e.1.clone().into_owned())
|
||||||
|
.nth(0);
|
||||||
|
if let Some(key) = key {
|
||||||
|
let auth_data = AuthData::ApiKey(key);
|
||||||
|
return self.inner.call((req, Some(auth_data)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{{/isKeyInQuery}}
|
||||||
|
{{/isApiKey}}
|
||||||
|
{{/authMethods}}
|
||||||
|
|
||||||
|
return self.inner.call((req, None));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,449 @@
|
|||||||
|
#![allow(unused_extern_crates)]
|
||||||
|
extern crate serde_ignored;
|
||||||
|
extern crate tokio_core;
|
||||||
|
extern crate native_tls;
|
||||||
|
extern crate hyper_tls;
|
||||||
|
extern crate openssl;
|
||||||
|
extern crate mime;
|
||||||
|
{{^apiUsesUuid}}extern crate uuid;{{/apiUsesUuid}}
|
||||||
|
extern crate chrono;
|
||||||
|
{{#apiHasFile}}extern crate multipart;{{/apiHasFile}}
|
||||||
|
extern crate percent_encoding;
|
||||||
|
extern crate url;
|
||||||
|
|
||||||
|
{{#apiUsesUuid}}use uuid;{{/apiUsesUuid}}
|
||||||
|
use std::sync::Arc;
|
||||||
|
use futures::{Future, future, Stream, stream};
|
||||||
|
use hyper;
|
||||||
|
use hyper::{Request, Response, Error, StatusCode};
|
||||||
|
use hyper::header::{Headers, ContentType};
|
||||||
|
use self::url::form_urlencoded;
|
||||||
|
use mimetypes;
|
||||||
|
{{#apiHasFile}}use self::multipart::server::Multipart;
|
||||||
|
use self::multipart::server::save::SaveResult;{{/apiHasFile}}
|
||||||
|
|
||||||
|
use serde_json;
|
||||||
|
{{#usesXml}}use serde_xml_rs;{{/usesXml}}
|
||||||
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::collections::{HashMap, BTreeMap};
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use swagger;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
pub use swagger::auth::Authorization;
|
||||||
|
use swagger::{ApiError, Context, XSpanId};
|
||||||
|
use swagger::auth::Scopes;
|
||||||
|
|
||||||
|
use {Api{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}},
|
||||||
|
{{operationId}}Response{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
|
||||||
|
};
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use models;
|
||||||
|
|
||||||
|
pub mod auth;
|
||||||
|
|
||||||
|
header! { (Warning, "Warning") => [String] }
|
||||||
|
|
||||||
|
mod paths {
|
||||||
|
extern crate regex;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref GLOBAL_REGEX_SET: regex::RegexSet = regex::RegexSet::new(&[
|
||||||
|
{{#pathSet}}
|
||||||
|
r"^{{basePathWithoutHost}}{{{pathRegEx}}}"{{^-last}},{{/-last}}
|
||||||
|
{{/pathSet}}
|
||||||
|
]).unwrap();
|
||||||
|
}
|
||||||
|
{{#pathSet}}
|
||||||
|
pub static ID_{{PATH_ID}}: usize = {{index}};
|
||||||
|
{{#hasPathParams}}
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref REGEX_{{PATH_ID}}: regex::Regex = regex::Regex::new(r"^{{basePathWithoutHost}}{{{pathRegEx}}}").unwrap();
|
||||||
|
}
|
||||||
|
{{/hasPathParams}}
|
||||||
|
{{/pathSet}}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NewService<T> {
|
||||||
|
api_impl: Arc<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> NewService<T> where T: Api + Clone + 'static {
|
||||||
|
pub fn new<U: Into<Arc<T>>>(api_impl: U) -> NewService<T> {
|
||||||
|
NewService{api_impl: api_impl.into()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> hyper::server::NewService for NewService<T> where T: Api + Clone + 'static {
|
||||||
|
type Request = (Request, Context);
|
||||||
|
type Response = Response;
|
||||||
|
type Error = Error;
|
||||||
|
type Instance = Service<T>;
|
||||||
|
|
||||||
|
fn new_service(&self) -> Result<Self::Instance, io::Error> {
|
||||||
|
Ok(Service::new(self.api_impl.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Service<T> {
|
||||||
|
api_impl: Arc<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Service<T> where T: Api + Clone + 'static {
|
||||||
|
pub fn new<U: Into<Arc<T>>>(api_impl: U) -> Service<T> {
|
||||||
|
Service{api_impl: api_impl.into()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> hyper::server::Service for Service<T> where T: Api + Clone + 'static {
|
||||||
|
type Request = (Request, Context);
|
||||||
|
type Response = Response;
|
||||||
|
type Error = Error;
|
||||||
|
type Future = Box<Future<Item=Response, Error=Error>>;
|
||||||
|
|
||||||
|
fn call(&self, (req, mut context): Self::Request) -> Self::Future {
|
||||||
|
let api_impl = self.api_impl.clone();
|
||||||
|
let (method, uri, _, headers, body) = req.deconstruct();
|
||||||
|
let path = paths::GLOBAL_REGEX_SET.matches(uri.path());
|
||||||
|
match &method {
|
||||||
|
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
|
||||||
|
// {{operationId}} - {{httpMethod}} {{path}}
|
||||||
|
&hyper::Method::{{vendorExtensions.HttpMethod}} if path.matched(paths::ID_{{vendorExtensions.PATH_ID}}) => {
|
||||||
|
if context.x_span_id.is_none() {
|
||||||
|
context.x_span_id = Some(headers.get::<XSpanId>().map(XSpanId::to_string).unwrap_or_else(|| self::uuid::Uuid::new_v4().to_string()));
|
||||||
|
}
|
||||||
|
{{#hasAuthMethods}}
|
||||||
|
{
|
||||||
|
let authorization = match context.authorization.as_ref() {
|
||||||
|
Some(authorization) => authorization,
|
||||||
|
None => return Box::new(future::ok(Response::new()
|
||||||
|
.with_status(StatusCode::Forbidden)
|
||||||
|
.with_body("Unauthenticated"))),
|
||||||
|
};
|
||||||
|
|
||||||
|
{{#authMethods}}
|
||||||
|
{{#isOAuth}}
|
||||||
|
// Authorization
|
||||||
|
if let Scopes::Some(ref scopes) = authorization.scopes {
|
||||||
|
let required_scopes: BTreeSet<String> = vec![
|
||||||
|
{{#scopes}}
|
||||||
|
"{{scope}}".to_string(), // {{description}}
|
||||||
|
{{/scopes}}
|
||||||
|
].into_iter().collect();
|
||||||
|
|
||||||
|
if !required_scopes.is_subset(scopes) {
|
||||||
|
let missing_scopes = required_scopes.difference(scopes);
|
||||||
|
return Box::new(future::ok(Response::new()
|
||||||
|
.with_status(StatusCode::Forbidden)
|
||||||
|
.with_body(missing_scopes.fold(
|
||||||
|
"Insufficient authorization, missing scopes".to_string(),
|
||||||
|
|s, scope| format!("{} {}", s, scope)
|
||||||
|
))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{{/isOAuth}}
|
||||||
|
{{/authMethods}}
|
||||||
|
}
|
||||||
|
{{/hasAuthMethods}}
|
||||||
|
|
||||||
|
{{#vendorExtensions}}{{#hasPathParams}}
|
||||||
|
// Path parameters
|
||||||
|
let path = uri.path().to_string();
|
||||||
|
let path_params =
|
||||||
|
paths::REGEX_{{PATH_ID}}
|
||||||
|
.captures(&path)
|
||||||
|
.unwrap_or_else(||
|
||||||
|
panic!("Path {} matched RE {{PATH_ID}} in set but failed match against \"{}\"", path, paths::REGEX_{{PATH_ID}}.as_str())
|
||||||
|
);
|
||||||
|
{{/hasPathParams}}{{/vendorExtensions}}
|
||||||
|
{{#pathParams}}
|
||||||
|
let param_{{paramName}} = match percent_encoding::percent_decode(path_params["{{baseName}}"].as_bytes()).decode_utf8() {
|
||||||
|
Ok(param_{{paramName}}) => match param_{{paramName}}.parse::<{{{dataType}}}>() {
|
||||||
|
Ok(param_{{paramName}}) => param_{{paramName}},
|
||||||
|
Err(e) => return Box::new(future::ok(Response::new().with_status(StatusCode::BadRequest).with_body(format!("Couldn't parse path parameter {{baseName}}: {}", e)))),
|
||||||
|
},
|
||||||
|
Err(_) => return Box::new(future::ok(Response::new().with_status(StatusCode::BadRequest).with_body(format!("Couldn't percent-decode path parameter as UTF-8: {}", &path_params["{{baseName}}"]))))
|
||||||
|
};
|
||||||
|
{{/pathParams}}
|
||||||
|
{{#headerParams}}{{#-first}}
|
||||||
|
// Header parameters
|
||||||
|
{{/-first}}
|
||||||
|
header! { (Request{{vendorExtensions.typeName}}, "{{baseName}}") => {{#isListContainer}}({{{baseType}}})*{{/isListContainer}}{{^isListContainer}}[{{{dataType}}}]{{/isListContainer}} }
|
||||||
|
{{#required}}
|
||||||
|
let param_{{paramName}} = match headers.get::<Request{{vendorExtensions.typeName}}>() {
|
||||||
|
Some(param_{{paramName}}) => param_{{paramName}}.0.clone(),
|
||||||
|
None => return Box::new(future::ok(Response::new().with_status(StatusCode::BadRequest).with_body("Missing or invalid required header {{baseName}}"))),
|
||||||
|
};
|
||||||
|
{{/required}}
|
||||||
|
{{^required}}
|
||||||
|
let param_{{paramName}} = headers.get::<Request{{vendorExtensions.typeName}}>().map(|header| header.0.clone());
|
||||||
|
{{/required}}{{/headerParams}}
|
||||||
|
|
||||||
|
{{#queryParams}}{{#-first}}
|
||||||
|
// Query parameters (note that non-required or collection query parameters will ignore garbage values, rather than causing a 400 response)
|
||||||
|
let query_params = form_urlencoded::parse(uri.query().unwrap_or_default().as_bytes()).collect::<Vec<_>>();
|
||||||
|
{{/-first}}
|
||||||
|
let param_{{paramName}} = query_params.iter().filter(|e| e.0 == "{{baseName}}").map(|e| e.1.to_owned())
|
||||||
|
{{#isListContainer}}
|
||||||
|
.filter_map(|param_{{paramName}}| param_{{paramName}}.parse::<{{{baseType}}}>().ok())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
{{^required}}
|
||||||
|
let param_{{paramName}} = if !param_{{paramName}}.is_empty() {
|
||||||
|
Some(param_{{paramName}})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
{{/required}}
|
||||||
|
{{/isListContainer}}{{^isListContainer}}
|
||||||
|
.nth(0);
|
||||||
|
{{#required}}
|
||||||
|
let param_{{paramName}} = match param_{{paramName}} {
|
||||||
|
Some(param_{{paramName}}) => match param_{{paramName}}.parse::<{{{dataType}}}>() {
|
||||||
|
Ok(param_{{paramName}}) => param_{{paramName}},
|
||||||
|
Err(e) => return Box::new(future::ok(Response::new().with_status(StatusCode::BadRequest).with_body(format!("Couldn't parse query parameter {{baseName}} - doesn't match schema: {}", e)))),
|
||||||
|
},
|
||||||
|
None => return Box::new(future::ok(Response::new().with_status(StatusCode::BadRequest).with_body("Missing required query parameter {{baseName}}"))),
|
||||||
|
};
|
||||||
|
{{/required}}{{^required}}
|
||||||
|
let param_{{paramName}} = param_{{paramName}}.and_then(|param_{{paramName}}| param_{{paramName}}.parse::<{{{baseType}}}>().ok());
|
||||||
|
{{/required}}
|
||||||
|
{{/isListContainer}}
|
||||||
|
{{/queryParams}}
|
||||||
|
|
||||||
|
{{#bodyParams}}{{#-first}}
|
||||||
|
// Body parameters (note that non-required body parameters will ignore garbage
|
||||||
|
// values, rather than causing a 400 response). Produce warning header and logs for
|
||||||
|
// any unused fields.
|
||||||
|
Box::new(body.concat2()
|
||||||
|
.then(move |result| -> Box<Future<Item=Response, Error=Error>> {
|
||||||
|
match result {
|
||||||
|
Ok(body) => {
|
||||||
|
{{#vendorExtensions}}{{^consumesPlainText}}
|
||||||
|
let mut unused_elements = Vec::new();
|
||||||
|
{{/consumesPlainText}}
|
||||||
|
let param_{{paramName}}: Option<{{{dataType}}}> = if !body.is_empty() {
|
||||||
|
{{#consumesXml}}
|
||||||
|
let deserializer = &mut serde_xml_rs::de::Deserializer::new_from_reader(&*body);
|
||||||
|
{{/consumesXml}}{{#consumesJson}}
|
||||||
|
let deserializer = &mut serde_json::Deserializer::from_slice(&*body);
|
||||||
|
{{/consumesJson}}{{^consumesPlainText}}
|
||||||
|
match serde_ignored::deserialize(deserializer, |path| {
|
||||||
|
warn!("Ignoring unknown field in body: {}", path);
|
||||||
|
unused_elements.push(path.to_string());
|
||||||
|
}) {
|
||||||
|
Ok(param_{{paramName}}) => param_{{paramName}},
|
||||||
|
{{#required}}
|
||||||
|
Err(e) => return Box::new(future::ok(Response::new().with_status(StatusCode::BadRequest).with_body(format!("Couldn't parse body parameter {{baseName}} - doesn't match schema: {}", e)))),
|
||||||
|
{{/required}}{{^required}}
|
||||||
|
Err(_) => None,
|
||||||
|
{{/required}}
|
||||||
|
}
|
||||||
|
{{/consumesPlainText}}{{#consumesPlainText}}
|
||||||
|
match String::from_utf8(body.to_vec()) {
|
||||||
|
Ok(param_{{paramName}}) => Some(param_{{paramName}}),
|
||||||
|
Err(e) => return Box::new(future::ok(Response::new().with_status(StatusCode::BadRequest).with_body(format!("Couldn't parse body parameter {{baseName}} - not valid UTF-8: {}", e)))),
|
||||||
|
}
|
||||||
|
{{/consumesPlainText}}{{/vendorExtensions}}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
{{#required}}
|
||||||
|
let param_{{paramName}} = match param_{{paramName}} {
|
||||||
|
Some(param_{{paramName}}) => param_{{paramName}},
|
||||||
|
None => return Box::new(future::ok(Response::new().with_status(StatusCode::BadRequest).with_body("Missing required body parameter {{baseName}}"))),
|
||||||
|
};
|
||||||
|
{{/required}}
|
||||||
|
{{/-first}}{{/bodyParams}}
|
||||||
|
{{^bodyParams}}{{#vendorExtensions}}{{#hasFile}}
|
||||||
|
let boundary = match multipart_boundary(&headers) {
|
||||||
|
Some(boundary) => boundary.to_string(),
|
||||||
|
None => return Box::new(future::ok(Response::new().with_status(StatusCode::BadRequest).with_body("Couldn't find valid multipart body"))),
|
||||||
|
};
|
||||||
|
|
||||||
|
Box::new(body.concat2()
|
||||||
|
.then(move |result| -> Box<Future<Item=Response, Error=Error>> {
|
||||||
|
match result {
|
||||||
|
Ok(body) => {
|
||||||
|
let mut entries = match Multipart::with_body(&body.to_vec()[..], boundary).save().temp() {
|
||||||
|
SaveResult::Full(entries) => {
|
||||||
|
entries
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Box::new(future::ok(Response::new().with_status(StatusCode::BadRequest).with_body(format!("Unable to process all message parts"))))
|
||||||
|
},
|
||||||
|
};
|
||||||
|
{{#formParams}}{{#-first}}
|
||||||
|
// Form parameters
|
||||||
|
{{/-first}}
|
||||||
|
let param_{{paramName}} = entries.fields.remove("{{paramName}}");
|
||||||
|
let param_{{paramName}} = match param_{{paramName}} {
|
||||||
|
Some(entry) =>
|
||||||
|
{{#isFile}}
|
||||||
|
{{^required}}Some({{/required}}Box::new(stream::once(Ok(entry.as_bytes().to_vec()))) as Box<Stream<Item=Vec<u8>, Error=io::Error> + Send>{{^required}}){{/required}},
|
||||||
|
{{/isFile}}{{^isFile}}
|
||||||
|
match entry.parse::<{{{dataType}}}>() {
|
||||||
|
Ok(entry) => {{^required}}Some({{/required}}entry{{^required}}){{/required}},
|
||||||
|
{{#required}}
|
||||||
|
Err(e) => return Box::new(future::ok(Response::new().with_status(StatusCode::BadRequest).with_body(format!("Couldn't parse form parameter {{baseName}} - doesn't match schema: {}", e)))),
|
||||||
|
{{/required}}{{^required}}
|
||||||
|
Err(_) => None,
|
||||||
|
{{/required}}
|
||||||
|
},
|
||||||
|
{{/isFile}}
|
||||||
|
{{#required}}
|
||||||
|
None => return Box::new(future::ok(Response::new().with_status(StatusCode::BadRequest).with_body(format!("Missing required form parameter {{paramName}}")))),
|
||||||
|
{{/required}}{{^required}}
|
||||||
|
None => None,
|
||||||
|
{{/required}}
|
||||||
|
};
|
||||||
|
{{^required}}{{#isFile}} let param_{{paramName}} = Box::new(future::ok(param_{{paramName}}));{{/isFile}}{{/required}}
|
||||||
|
{{/formParams}}
|
||||||
|
{{/hasFile}}{{^hasFile}}
|
||||||
|
Box::new(({
|
||||||
|
{{
|
||||||
|
{{#formParams}}{{#-first}}
|
||||||
|
// Form parameters
|
||||||
|
{{/-first}}
|
||||||
|
let param_{{paramName}} = {{^isContainer}}{{#vendorExtensions}}{{{example}}};{{/vendorExtensions}}{{/isContainer}}{{#isListContainer}}{{#required}}Vec::new();{{/required}}{{^required}}None;{{/required}}{{/isListContainer}}{{#isMapContainer}}None;{{/isMapContainer}}
|
||||||
|
{{/formParams}}
|
||||||
|
{{/hasFile}}{{/vendorExtensions}}{{/bodyParams}}
|
||||||
|
Box::new(api_impl.{{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}({{#allParams}}param_{{paramName}}{{#isListContainer}}.as_ref(){{/isListContainer}}, {{/allParams}}&context)
|
||||||
|
.then(move |result| {
|
||||||
|
let mut response = Response::new();
|
||||||
|
context.x_span_id.as_ref().map(|header| response.headers_mut().set(XSpanId(header.clone())));
|
||||||
|
{{#bodyParams}}{{#vendorExtensions}}{{^consumesPlainText}}
|
||||||
|
if !unused_elements.is_empty() {
|
||||||
|
response.headers_mut().set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
|
||||||
|
}
|
||||||
|
{{/consumesPlainText}}{{/vendorExtensions}}{{/bodyParams}}
|
||||||
|
match result {
|
||||||
|
Ok(rsp) => match rsp {
|
||||||
|
{{#responses}}
|
||||||
|
{{operationId}}Response::{{#vendorExtensions}}{{x-responseId}}{{/vendorExtensions}}
|
||||||
|
{{#dataType}}{{^headers}}
|
||||||
|
(body)
|
||||||
|
{{/headers}}{{#headers}}
|
||||||
|
{{#-first}}
|
||||||
|
{
|
||||||
|
body,
|
||||||
|
{{/-first}}
|
||||||
|
{{name}}{{^-last}}, {{/-last}}
|
||||||
|
{{#-last}}
|
||||||
|
}
|
||||||
|
{{/-last}}
|
||||||
|
{{/headers}}{{/dataType}}
|
||||||
|
{{^dataType}}{{#headers}}{{#-first}}
|
||||||
|
{
|
||||||
|
{{/-first}}
|
||||||
|
{{name}}{{^-last}}, {{/-last}}
|
||||||
|
{{#-last}}
|
||||||
|
}
|
||||||
|
{{/-last}}
|
||||||
|
{{/headers}}{{/dataType}}
|
||||||
|
=> {
|
||||||
|
{{^isFile}} response.set_status(StatusCode::try_from({{code}}).unwrap());
|
||||||
|
{{#headers}}
|
||||||
|
header! { (Response{{nameInCamelCase}}, "{{baseName}}") => [{{{datatype}}}] }
|
||||||
|
response.headers_mut().set(Response{{nameInCamelCase}}({{name}}));
|
||||||
|
{{/headers}}
|
||||||
|
{{#produces}}{{#-first}}{{#dataType}}
|
||||||
|
response.headers_mut().set(ContentType(mimetypes::responses::{{#vendorExtensions}}{{uppercase_operation_id}}_{{x-uppercaseResponseId}}{{/vendorExtensions}}.clone()));
|
||||||
|
{{/dataType}}{{/-first}}{{/produces}}
|
||||||
|
{{#dataType}}
|
||||||
|
{{#vendorExtensions}}{{#producesXml}}{{^has_namespace}}
|
||||||
|
let body = serde_xml_rs::to_string(&body).expect("impossible to fail to serialize");
|
||||||
|
{{/has_namespace}}{{#has_namespace}}
|
||||||
|
let mut namespaces = BTreeMap::new();
|
||||||
|
// An empty string is used to indicate a global namespace in xmltree.
|
||||||
|
namespaces.insert("".to_string(), models::namespaces::{{uppercase_data_type}}.clone());
|
||||||
|
let body = serde_xml_rs::to_string_with_namespaces(&body, namespaces).expect("impossible to fail to serialize");
|
||||||
|
{{/has_namespace}}{{/producesXml}}{{#producesJson}}
|
||||||
|
let body = serde_json::to_string(&body).expect("impossible to fail to serialize");
|
||||||
|
{{/producesJson}}{{/vendorExtensions}}
|
||||||
|
response.set_body(body);
|
||||||
|
{{/dataType}}{{/isFile}}{{#isFile}}
|
||||||
|
let body = body.fold(Vec::new(), | mut body, chunk| {
|
||||||
|
body.extend(chunk.iter());
|
||||||
|
future::ok::<Vec<u8>, io::Error>(body)
|
||||||
|
})
|
||||||
|
// Block whilst waiting for the stream to complete
|
||||||
|
.wait();
|
||||||
|
|
||||||
|
match body {
|
||||||
|
// If no error occurred then create successful hyper response
|
||||||
|
Ok(vec) => {
|
||||||
|
response.set_status(StatusCode::try_from({{code}}).unwrap());
|
||||||
|
|
||||||
|
{{#headers}}
|
||||||
|
header! { (Response{{nameInCamelCase}}, "{{baseName}}") => [{{{datatype}}}] }
|
||||||
|
response.headers_mut().set(Response{{nameInCamelCase}}({{name}}));
|
||||||
|
{{/headers}}{{#produces}}{{#-first}}{{#dataType}}
|
||||||
|
response.headers_mut().set(ContentType(mimetypes::responses::{{#vendorExtensions}}{{uppercase_operation_id}}_{{x-uppercaseResponseId}}{{/vendorExtensions}}.clone()));
|
||||||
|
{{/dataType}}{{/-first}}{{/produces}}
|
||||||
|
response.set_body(vec);
|
||||||
|
},
|
||||||
|
// It's possible that errors were received in the stream, if this is the case then we can't return a success response to the client and instead must return an internal error.
|
||||||
|
Err(e) => {
|
||||||
|
response.set_status(StatusCode::InternalServerError);
|
||||||
|
response.set_body("An internal error occurred");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{{/isFile}}
|
||||||
|
},
|
||||||
|
{{/responses}}
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
// Application code returned an error. This should not happen, as the implementation should
|
||||||
|
// return a valid response.
|
||||||
|
response.set_status(StatusCode::InternalServerError);
|
||||||
|
response.set_body("An internal error occurred");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
future::ok(response)
|
||||||
|
}
|
||||||
|
))
|
||||||
|
{{^bodyParams}}{{#vendorExtensions}}{{^hasFile}}
|
||||||
|
}}
|
||||||
|
})) as Box<Future<Item=Response, Error=Error>>
|
||||||
|
{{/hasFile}}{{#hasFile}}
|
||||||
|
as Box<Future<Item=Response, Error=Error>>
|
||||||
|
},
|
||||||
|
Err(e) => Box::new(future::ok(Response::new().with_status(StatusCode::BadRequest).with_body(format!("Couldn't read multipart body")))),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
{{/hasFile}}{{/vendorExtensions}}{{/bodyParams}}
|
||||||
|
{{#bodyParams}}{{#-first}}
|
||||||
|
},
|
||||||
|
Err(e) => Box::new(future::ok(Response::new().with_status(StatusCode::BadRequest).with_body(format!("Couldn't read body parameter {{baseName}}: {}", e)))),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
) as Box<Future<Item=Response, Error=Error>>
|
||||||
|
{{/-first}}{{/bodyParams}}
|
||||||
|
},
|
||||||
|
|
||||||
|
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
|
||||||
|
_ => Box::new(future::ok(Response::new().with_status(StatusCode::NotFound))) as Box<Future<Item=Response, Error=Error>>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{{#apiHasFile}}
|
||||||
|
/// Utility function to get the multipart boundary marker (if any) from the Headers.
|
||||||
|
fn multipart_boundary<'a>(headers: &'a Headers) -> Option<&'a str> {
|
||||||
|
headers.get::<ContentType>().and_then(|content_type| {
|
||||||
|
let ContentType(ref mime) = *content_type;
|
||||||
|
if mime.type_() == mime::MULTIPART && mime.subtype() == mime::FORM_DATA {
|
||||||
|
mime.get_param(mime::BOUNDARY).map(|x| x.as_str())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{{/apiHasFile}}
|
@ -1,340 +0,0 @@
|
|||||||
#![allow(unused_extern_crates)]
|
|
||||||
extern crate serde_ignored;
|
|
||||||
extern crate iron;
|
|
||||||
extern crate router;
|
|
||||||
extern crate bodyparser;
|
|
||||||
extern crate urlencoded;
|
|
||||||
extern crate uuid;
|
|
||||||
extern crate chrono;
|
|
||||||
{{#apiHasFile}}extern crate multipart;{{/apiHasFile}}
|
|
||||||
|
|
||||||
use futures::Future;
|
|
||||||
use futures::future;
|
|
||||||
use futures::{stream, Stream};
|
|
||||||
use hyper;
|
|
||||||
use hyper::header::{Headers, ContentType};
|
|
||||||
use self::iron::prelude::*;
|
|
||||||
use self::iron::{status, modifiers, BeforeMiddleware};
|
|
||||||
use self::iron::url::percent_encoding::percent_decode;
|
|
||||||
use self::router::Router;
|
|
||||||
use self::urlencoded::UrlEncodedQuery;
|
|
||||||
use mimetypes;
|
|
||||||
{{#apiHasFile}}use multipart::server::{Multipart, SaveResult};{{/apiHasFile}}
|
|
||||||
|
|
||||||
use serde_json;
|
|
||||||
{{#usesXml}}use serde_xml_rs;{{/usesXml}}
|
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use std::collections::{HashMap, BTreeMap};
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use swagger;
|
|
||||||
use std::io::Error;
|
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use std::collections::BTreeSet;
|
|
||||||
|
|
||||||
pub use swagger::auth::Authorization;
|
|
||||||
use swagger::auth::{AuthData, Scopes};
|
|
||||||
use swagger::{ApiError, Context, XSpanId};
|
|
||||||
|
|
||||||
use {Api{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}},
|
|
||||||
{{operationId}}Response{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
|
|
||||||
};
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use models;
|
|
||||||
|
|
||||||
header! { (Warning, "Warning") => [String] }
|
|
||||||
|
|
||||||
/// Create a new router for `Api`
|
|
||||||
pub fn router<T>(api: T) -> Router where T: Api + Send + Sync + Clone + 'static {
|
|
||||||
let mut router = Router::new();
|
|
||||||
add_routes(&mut router, api);
|
|
||||||
router
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add routes for `Api` to a provided router.
|
|
||||||
///
|
|
||||||
/// Note that these routes are added straight onto the router. This means that if the router
|
|
||||||
/// already has a route for an endpoint which clashes with those provided by this API, then the
|
|
||||||
/// old route will be lost.
|
|
||||||
///
|
|
||||||
/// It is generally a bad idea to add routes in this way to an existing router, which may have
|
|
||||||
/// routes on it for other APIs. Distinct APIs should be behind distinct paths to encourage
|
|
||||||
/// separation of interfaces, which this function does not enforce. APIs should not overlap.
|
|
||||||
///
|
|
||||||
/// Alternative approaches include:
|
|
||||||
///
|
|
||||||
/// - generate an `iron::middleware::Handler` (usually a `router::Router` or
|
|
||||||
/// `iron::middleware::chain`) for each interface, and add those handlers inside an existing
|
|
||||||
/// router, mounted at different paths - so the interfaces are separated by path
|
|
||||||
/// - use a different instance of `iron::Iron` for each interface - so the interfaces are
|
|
||||||
/// separated by the address/port they listen on
|
|
||||||
///
|
|
||||||
/// This function exists to allow legacy code, which doesn't separate its APIs properly, to make
|
|
||||||
/// use of this crate.
|
|
||||||
#[deprecated(note="APIs should not overlap - only for use in legacy code.")]
|
|
||||||
pub fn route<T>(router: &mut Router, api: T) where T: Api + Send + Sync + Clone + 'static {
|
|
||||||
add_routes(router, api)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add routes for `Api` to a provided router
|
|
||||||
fn add_routes<T>(router: &mut Router, api: T) where T: Api + Send + Sync + Clone + 'static {
|
|
||||||
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
|
|
||||||
let api_clone = api.clone();
|
|
||||||
router.{{#vendorExtensions}}{{httpmethod}}{{/vendorExtensions}}(
|
|
||||||
"{{#vendorExtensions}}{{basePathWithoutHost}}{{path}}{{/vendorExtensions}}",
|
|
||||||
move |req: &mut Request| {
|
|
||||||
let mut context = Context::default();
|
|
||||||
|
|
||||||
// Helper function to provide a code block to use `?` in (to be replaced by the `catch` block when it exists).
|
|
||||||
fn handle_request<T>(req: &mut Request, api: &T, context: &mut Context) -> Result<Response, Response> where T: Api {
|
|
||||||
|
|
||||||
context.x_span_id = Some(req.headers.get::<XSpanId>().map(XSpanId::to_string).unwrap_or_else(|| self::uuid::Uuid::new_v4().to_string()));
|
|
||||||
context.auth_data = req.extensions.remove::<AuthData>();
|
|
||||||
context.authorization = req.extensions.remove::<Authorization>();
|
|
||||||
|
|
||||||
{{#hasAuthMethods}}
|
|
||||||
let authorization = context.authorization.as_ref().ok_or_else(|| {
|
|
||||||
Response::with((
|
|
||||||
status::Forbidden,
|
|
||||||
"Unauthenticated".to_string()
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
{{#authMethods}}
|
|
||||||
{{#isOAuth}}
|
|
||||||
// Authorization
|
|
||||||
if let Scopes::Some(ref scopes) = authorization.scopes {
|
|
||||||
let required_scopes: BTreeSet<String> = vec![
|
|
||||||
{{#scopes}}
|
|
||||||
"{{scope}}".to_string(), // {{description}}
|
|
||||||
{{/scopes}}
|
|
||||||
].into_iter().collect();
|
|
||||||
|
|
||||||
if !required_scopes.is_subset(scopes) {
|
|
||||||
let missing_scopes = required_scopes.difference(scopes);
|
|
||||||
return Err(Response::with((
|
|
||||||
status::Forbidden,
|
|
||||||
missing_scopes.fold(
|
|
||||||
"Insufficient authorization, missing scopes".to_string(),
|
|
||||||
|s, scope| format!("{} {}", s, scope)
|
|
||||||
)
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{{/isOAuth}}
|
|
||||||
{{/authMethods}}
|
|
||||||
{{/hasAuthMethods}}
|
|
||||||
|
|
||||||
{{#pathParams}}{{#-first}}
|
|
||||||
// Path parameters
|
|
||||||
{{/-first}} let param_{{paramName}} = {
|
|
||||||
let param = req.extensions.get::<Router>().ok_or_else(|| Response::with((status::InternalServerError, "An internal error occurred".to_string())))?
|
|
||||||
.find("{{baseName}}").ok_or_else(|| Response::with((status::BadRequest, "Missing path parameter {{baseName}}".to_string())))?;
|
|
||||||
percent_decode(param.as_bytes()).decode_utf8()
|
|
||||||
.map_err(|_| Response::with((status::BadRequest, format!("Couldn't percent-decode path parameter as UTF-8: {}", param))))?
|
|
||||||
.parse().map_err(|e| Response::with((status::BadRequest, format!("Couldn't parse path parameter {{baseName}}: {}", e))))?
|
|
||||||
};
|
|
||||||
{{/pathParams}}
|
|
||||||
{{#headerParams}}{{#-first}}
|
|
||||||
// Header parameters
|
|
||||||
{{/-first}} header! { (Request{{vendorExtensions.typeName}}, "{{baseName}}") => {{#isListContainer}}({{{baseType}}})*{{/isListContainer}}{{^isListContainer}}[{{{dataType}}}]{{/isListContainer}} }
|
|
||||||
{{#required}} let param_{{paramName}} = req.headers.get::<Request{{vendorExtensions.typeName}}>().ok_or_else(|| Response::with((status::BadRequest, "Missing or invalid required header {{baseName}}".to_string())))?.0.clone();
|
|
||||||
{{/required}}{{^required}} let param_{{paramName}} = req.headers.get::<Request{{vendorExtensions.typeName}}>().map(|header| header.0.clone());
|
|
||||||
{{/required}}{{/headerParams}}
|
|
||||||
{{#queryParams}}{{#-first}}
|
|
||||||
// Query parameters (note that non-required or collection query parameters will ignore garbage values, rather than causing a 400 response)
|
|
||||||
let query_params = req.get::<UrlEncodedQuery>().unwrap_or_default();
|
|
||||||
{{/-first}} let param_{{paramName}} = query_params.get("{{baseName}}")
|
|
||||||
{{#required}} .ok_or_else(|| Response::with((status::BadRequest, "Missing required query parameter {{baseName}}".to_string())))?
|
|
||||||
{{#isListContainer}} .iter().flat_map(|x| x.parse::<{{{baseType}}}>()).collect::<Vec<_>>();
|
|
||||||
{{/isListContainer}}{{^isListContainer}} .first().ok_or_else(|| Response::with((status::BadRequest, "Required query parameter {{baseName}} was empty".to_string())))?
|
|
||||||
.parse::<{{{dataType}}}>().map_err(|e| Response::with((status::BadRequest, format!("Couldn't parse query parameter {{baseName}} - doesn't match schema: {}", e))))?;
|
|
||||||
{{/isListContainer}}{{/required}}{{^required}}{{#isListContainer}} .map(|list| list.iter().flat_map(|x| x.parse::<{{{baseType}}}>()).collect::<Vec<_>>());
|
|
||||||
{{/isListContainer}}{{^isListContainer}} .and_then(|list| list.first()).and_then(|x| x.parse::<{{{dataType}}}>().ok());
|
|
||||||
{{/isListContainer}}{{/required}}{{/queryParams}}
|
|
||||||
{{#bodyParams}}{{#-first}} // Body parameters (note that non-required body parameters will ignore garbage
|
|
||||||
// values, rather than causing a 400 response). Produce warning header and logs for
|
|
||||||
// any unused fields.
|
|
||||||
{{/-first}}{{#required}}
|
|
||||||
let param_{{paramName}} = req.get::<bodyparser::Raw>().map_err(|e| Response::with((status::BadRequest, format!("Couldn't parse body parameter {{baseName}} - not valid UTF-8: {}", e))))?;
|
|
||||||
{{/required}}{{^required}}
|
|
||||||
let param_{{paramName}} = req.get::<bodyparser::Raw>().unwrap_or(None);
|
|
||||||
{{/required}}{{#vendorExtensions}}{{^consumesPlainText}}
|
|
||||||
let mut unused_elements = Vec::new();
|
|
||||||
|
|
||||||
let param_{{paramName}} = if let Some(param_{{paramName}}_raw) = param_{{paramName}} { {{#consumesXml}}
|
|
||||||
let deserializer = &mut serde_xml_rs::de::Deserializer::new_from_reader(param_{{paramName}}_raw.as_bytes());{{/consumesXml}}{{#consumesJson}}
|
|
||||||
let deserializer = &mut serde_json::Deserializer::from_str(¶m_{{paramName}}_raw);{{/consumesJson}}
|
|
||||||
|
|
||||||
let param_{{paramName}}: Option<{{{dataType}}}> = serde_ignored::deserialize(deserializer, |path| {
|
|
||||||
warn!("Ignoring unknown field in body: {}", path);
|
|
||||||
unused_elements.push(path.to_string());
|
|
||||||
}){{#required}}.map_err(|e| Response::with((status::BadRequest, format!("Couldn't parse body parameter {{baseName}} - doesn't match schema: {}", e))))?{{/required}}{{^required}}.unwrap_or(None){{/required}};
|
|
||||||
|
|
||||||
param_{{paramName}}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};{{/consumesPlainText}}{{/vendorExtensions}}{{#required}}
|
|
||||||
let param_{{paramName}} = param_{{paramName}}.ok_or_else(|| Response::with((status::BadRequest, "Missing required body parameter {{baseName}}".to_string())))?{{/required}};
|
|
||||||
|
|
||||||
{{/bodyParams}}
|
|
||||||
{{#formParams}}
|
|
||||||
{{#-first}}
|
|
||||||
// Form parameters
|
|
||||||
{{/-first}}{{/formParams}}{{#vendorExtensions}}{{#hasFile}}
|
|
||||||
// Expecting a multipart form, extract and parse it now.
|
|
||||||
let mut entries = match Multipart::from_request(req) {
|
|
||||||
Ok(mut multipart) => {
|
|
||||||
|
|
||||||
match multipart.save().temp() {
|
|
||||||
SaveResult::Full(entries) => {
|
|
||||||
Ok(entries)
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
Err(Response::with((status::InternalServerError, format!("Unable to process all message parts"))))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
// Unable to parse as multipart
|
|
||||||
Err(Response::with((status::BadRequest, format!("Couldn't parse body as multipart"))))
|
|
||||||
}
|
|
||||||
}?;
|
|
||||||
|
|
||||||
{{/hasFile}}{{/vendorExtensions}}{{#allParams}}{{#isFormParam}}{{#isFile}}
|
|
||||||
|
|
||||||
let param_{{paramName}} = entries.fields.remove("{{paramName}}");
|
|
||||||
|
|
||||||
let param_{{paramName}} = match param_{{paramName}} {
|
|
||||||
Some(body) => {
|
|
||||||
Ok({let bytes = body.as_bytes();
|
|
||||||
{{^required}}Some({{/required}}
|
|
||||||
Box::new(stream::once(Ok(bytes.to_vec()))) as Box<Stream<Item=Vec<u8>, Error=Error> + Send>
|
|
||||||
{{^required}}){{/required}}}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
None => {Err(Response::with((status::BadRequest, format!("Body part not found!"))))}
|
|
||||||
}?;
|
|
||||||
{{/isFile}}
|
|
||||||
let param_{{paramName}} = {{#isFile}}{{^required}}Box::new(future::ok({{/required}}param_{{paramName}}{{^required}})){{/required}};{{/isFile}}{{^isFile}}{{^isContainer}}{{#vendorExtensions}}{{{example}}};{{/vendorExtensions}}{{/isContainer}}{{#isListContainer}}{{#required}}Vec::new();{{/required}}{{^required}}None;{{/required}}{{/isListContainer}}{{#isMapContainer}}None;{{/isMapContainer}}{{/isFile}}
|
|
||||||
{{/isFormParam}}
|
|
||||||
{{/allParams}}
|
|
||||||
|
|
||||||
match api.{{#vendorExtensions}}{{operation_id}}{{/vendorExtensions}}({{#allParams}}param_{{paramName}}{{#isListContainer}}.as_ref(){{/isListContainer}}, {{/allParams}}context).wait() {
|
|
||||||
Ok(rsp) => match rsp {
|
|
||||||
{{#responses}}
|
|
||||||
{{operationId}}Response::{{#vendorExtensions}}{{x-responseId}}{{/vendorExtensions}}{{#dataType}}{{^headers}}(body){{/headers}}{{#headers}}{{#-first}}{ body{{/-first}}{{/headers}}{{/dataType}}{{#headers}}{{#-first}}{{^dataType}}{ {{/dataType}}{{#dataType}}, {{/dataType}}{{/-first}}{{^-first}}, {{/-first}}{{name}}{{#-last}} }{{/-last}}{{/headers}} => {
|
|
||||||
{{^isFile}}
|
|
||||||
{{#dataType}}{{#vendorExtensions}}{{#producesPlainText}} let body_string = body;
|
|
||||||
{{/producesPlainText}}{{#producesXml}}
|
|
||||||
{{^has_namespace}} let body_string = serde_xml_rs::to_string(&body).expect("impossible to fail to serialize");{{/has_namespace}}{{#has_namespace}}
|
|
||||||
let mut namespaces = BTreeMap::new();
|
|
||||||
// An empty string is used to indicate a global namespace in xmltree.
|
|
||||||
namespaces.insert("".to_string(), models::namespaces::{{uppercase_data_type}}.clone());
|
|
||||||
let body_string = serde_xml_rs::to_string_with_namespaces(&body, namespaces).expect("impossible to fail to serialize");{{/has_namespace}}{{/producesXml}}{{#producesJson}}
|
|
||||||
let body_string = serde_json::to_string(&body).expect("impossible to fail to serialize");{{/producesJson}}{{/vendorExtensions}}{{/dataType}}
|
|
||||||
|
|
||||||
let mut response = Response::with((status::Status::from_u16({{code}}){{#dataType}}, body_string{{/dataType}}));{{/isFile}}{{#isFile}} body.fold(Vec::new(), |mut body, chunk| {
|
|
||||||
body.extend(chunk.iter());
|
|
||||||
future::ok::<Vec<u8>, Error>(body)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Block whilst waiting for the stream to complete
|
|
||||||
.wait()
|
|
||||||
|
|
||||||
// It's possible that errors were received in the stream, if this is the case then we can't return a success response to the client and instead must return an internal error.
|
|
||||||
.map_err(|_| Response::with((status::InternalServerError, "An internal error occurred".to_string())))
|
|
||||||
|
|
||||||
// Assuming no errors then create an Iron response.
|
|
||||||
.map(|rsp| {
|
|
||||||
let mut response = Response::new();
|
|
||||||
response.status = Some(status::Status::from_u16({{code}}));
|
|
||||||
response.body = Some(Box::new(rsp));
|
|
||||||
{{/isFile}}
|
|
||||||
{{#headers}}{{#isFile}} {{/isFile}} header! { (Response{{nameInCamelCase}}, "{{baseName}}") => [{{{datatype}}}] }
|
|
||||||
{{#isFile}} {{/isFile}} response.headers.set(Response{{nameInCamelCase}}({{name}}));
|
|
||||||
{{/headers}}
|
|
||||||
{{#produces}}{{#-first}}
|
|
||||||
{{#dataType}}{{#isFile}} {{/isFile}} response.headers.set(ContentType(mimetypes::responses::{{#vendorExtensions}}{{uppercase_operation_id}}_{{x-uppercaseResponseId}}{{/vendorExtensions}}.clone()));{{/dataType}}
|
|
||||||
{{/-first}}{{/produces}}
|
|
||||||
{{#isFile}} {{/isFile}} context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
|
|
||||||
{{#bodyParams}}{{#vendorExtensions}}{{^consumesPlainText}} if !unused_elements.is_empty() {
|
|
||||||
response.headers.set(Warning(format!("Ignoring unknown fields in body: {:?}", unused_elements)));
|
|
||||||
}{{/consumesPlainText}}{{/vendorExtensions}}{{/bodyParams}}
|
|
||||||
{{^isFile}}Ok(response){{/isFile}}{{#isFile}} response
|
|
||||||
}){{/isFile}}
|
|
||||||
},
|
|
||||||
{{/responses}}
|
|
||||||
},
|
|
||||||
Err(_) => {
|
|
||||||
// Application code returned an error. This should not happen, as the implementation should
|
|
||||||
// return a valid response.
|
|
||||||
Err(Response::with((status::InternalServerError, "An internal error occurred".to_string())))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_request(req, &api_clone, &mut context).or_else(|mut response| {
|
|
||||||
context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
|
|
||||||
Ok(response)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
"{{operationId}}");
|
|
||||||
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Middleware to extract authentication data from request
|
|
||||||
pub struct ExtractAuthData;
|
|
||||||
|
|
||||||
impl BeforeMiddleware for ExtractAuthData {
|
|
||||||
fn before(&self, req: &mut Request) -> IronResult<()> {
|
|
||||||
{{#authMethods}}
|
|
||||||
{{#isBasic}}
|
|
||||||
{
|
|
||||||
use hyper::header::{Authorization, Basic, Bearer};
|
|
||||||
use std::ops::Deref;
|
|
||||||
if let Some(basic) = req.headers.get::<Authorization<Basic>>() {
|
|
||||||
req.extensions.insert::<AuthData>(AuthData::Basic(basic.deref().clone()));
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{{/isBasic}}
|
|
||||||
{{#isOAuth}}
|
|
||||||
{
|
|
||||||
use hyper::header::{Authorization, Basic, Bearer};
|
|
||||||
use std::ops::Deref;
|
|
||||||
if let Some(bearer) = req.headers.get::<Authorization<Bearer>>() {
|
|
||||||
req.extensions.insert::<AuthData>(AuthData::Bearer(bearer.deref().clone()));
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{{/isOAuth}}
|
|
||||||
{{#isApiKey}}
|
|
||||||
{{#isKeyInHeader}}
|
|
||||||
{
|
|
||||||
header! { (ApiKey{{-index}}, "{{keyParamName}}") => [String] }
|
|
||||||
if let Some(header) = req.headers.get::<ApiKey{{-index}}>() {
|
|
||||||
req.extensions.insert::<AuthData>(AuthData::ApiKey(header.0.clone()));
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{{/isKeyInHeader}}
|
|
||||||
{{#isKeyInQuery}}
|
|
||||||
{
|
|
||||||
let header = match req.get_ref::<UrlEncodedQuery>() {
|
|
||||||
Ok(query) => query.get("{{keyParamName}}").map(|v| v[0].clone()),
|
|
||||||
_ => None
|
|
||||||
};
|
|
||||||
if let Some(key) = header {
|
|
||||||
req.extensions.insert::<AuthData>(AuthData::ApiKey(key));
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{{/isKeyInQuery}}
|
|
||||||
{{/isApiKey}}
|
|
||||||
{{/authMethods}}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +1 @@
|
|||||||
{{{openapi-yaml}}}
|
{{{swagger-yaml}}}
|
Loading…
x
Reference in New Issue
Block a user