[Core, Rust Server] Support multipart/related requests (#5015)

* [Core, Rust] Support multipart/related requests

* Treat multipart/related as a type of form data
This commit is contained in:
Richard Whitehouse
2020-01-27 01:39:43 +00:00
committed by Jim Schubert
parent 52e09e2ffa
commit 84b6804d8f
28 changed files with 855 additions and 161 deletions

View File

@@ -23,7 +23,7 @@ public class CodegenParameter implements IJsonSchemaValidationProperties {
public boolean isFormParam, isQueryParam, isPathParam, isHeaderParam,
isCookieParam, isBodyParam, hasMore, isContainer,
secondaryParam, isCollectionFormatMulti, isPrimitiveType, isModel, isExplode;
public String baseName, paramName, dataType, datatypeWithEnum, dataFormat,
public String baseName, paramName, dataType, datatypeWithEnum, dataFormat, contentType,
collectionFormat, description, unescapedDescription, baseType, defaultValue, enumName, style;
public String nameInLowerCase; // property name in lower case
@@ -178,13 +178,14 @@ public class CodegenParameter implements IJsonSchemaValidationProperties {
output.isMapContainer = this.isMapContainer;
output.isExplode = this.isExplode;
output.style = this.style;
output.contentType = this.contentType;
return output;
}
@Override
public int hashCode() {
return Objects.hash(isFormParam, isQueryParam, isPathParam, isHeaderParam, isCookieParam, isBodyParam, hasMore, isContainer, secondaryParam, isCollectionFormatMulti, isPrimitiveType, isModel, isExplode, baseName, paramName, dataType, datatypeWithEnum, dataFormat, collectionFormat, description, unescapedDescription, baseType, defaultValue, enumName, style, example, jsonSchema, isString, isNumeric, isInteger, isLong, isNumber, isFloat, isDouble, isByteArray, isBinary, isBoolean, isDate, isDateTime, isUuid, isUri, isEmail, isFreeFormObject, isListContainer, isMapContainer, isFile, isEnum, _enum, allowableValues, items, mostInnerItems, vendorExtensions, hasValidation, getMaxProperties(), getMinProperties(), isNullable, required, getMaximum(), getExclusiveMaximum(), getMinimum(), getExclusiveMinimum(), getMaxLength(), getMinLength(), getPattern(), getMaxItems(), getMinItems(), getUniqueItems(), multipleOf);
return Objects.hash(isFormParam, isQueryParam, isPathParam, isHeaderParam, isCookieParam, isBodyParam, hasMore, isContainer, secondaryParam, isCollectionFormatMulti, isPrimitiveType, isModel, isExplode, baseName, paramName, dataType, datatypeWithEnum, dataFormat, collectionFormat, description, unescapedDescription, baseType, defaultValue, enumName, style, example, jsonSchema, isString, isNumeric, isInteger, isLong, isNumber, isFloat, isDouble, isByteArray, isBinary, isBoolean, isDate, isDateTime, isUuid, isUri, isEmail, isFreeFormObject, isListContainer, isMapContainer, isFile, isEnum, _enum, allowableValues, items, mostInnerItems, vendorExtensions, hasValidation, getMaxProperties(), getMinProperties(), isNullable, required, getMaximum(), getExclusiveMaximum(), getMinimum(), getExclusiveMinimum(), getMaxLength(), getMinLength(), getPattern(), getMaxItems(), getMinItems(), getUniqueItems(), contentType, multipleOf);
}
@Override
@@ -259,6 +260,7 @@ public class CodegenParameter implements IJsonSchemaValidationProperties {
Objects.equals(getPattern(), that.getPattern()) &&
Objects.equals(getMaxItems(), that.getMaxItems()) &&
Objects.equals(getMinItems(), that.getMinItems()) &&
Objects.equals(contentType, that.contentType) &&
Objects.equals(multipleOf, that.multipleOf);
}
@@ -332,6 +334,7 @@ public class CodegenParameter implements IJsonSchemaValidationProperties {
sb.append(", maxItems=").append(maxItems);
sb.append(", minItems=").append(minItems);
sb.append(", uniqueItems=").append(uniqueItems);
sb.append(", contentType=").append(contentType);
sb.append(", multipleOf=").append(multipleOf);
sb.append('}');
return sb.toString();

View File

@@ -1511,6 +1511,23 @@ public class DefaultCodegen implements CodegenConfig {
setParameterExampleValue(codegenParameter);
}
/**
* Sets the content type of the parameter based on the encoding specified in the request body.
*
* @param codegenParameter Codegen parameter
* @param mediaType MediaType from the request body
*/
public void setParameterContentType(CodegenParameter codegenParameter, MediaType mediaType) {
if (mediaType != null && mediaType.getEncoding() != null) {
Encoding encoding = mediaType.getEncoding().get(codegenParameter.baseName);
if (encoding != null) {
codegenParameter.contentType = encoding.getContentType();
} else {
LOGGER.debug("encoding not specified for " + codegenParameter.baseName);
}
}
}
/**
* Return the example value of the property
*
@@ -2879,13 +2896,17 @@ public class DefaultCodegen implements CodegenConfig {
RequestBody requestBody = operation.getRequestBody();
if (requestBody != null) {
String contentType = getContentType(requestBody);
if (contentType != null) {
contentType = contentType.toLowerCase(Locale.ROOT);
}
if (contentType != null &&
(contentType.toLowerCase(Locale.ROOT).startsWith("application/x-www-form-urlencoded") ||
contentType.toLowerCase(Locale.ROOT).startsWith("multipart"))) {
(contentType.startsWith("application/x-www-form-urlencoded") ||
contentType.startsWith("multipart"))) {
// process form parameters
formParams = fromRequestBodyToFormParameters(requestBody, imports);
op.isMultipart = contentType.toLowerCase(Locale.ROOT).startsWith("multipart");
op.isMultipart = contentType.startsWith("multipart");
for (CodegenParameter cp : formParams) {
setParameterContentType(cp, requestBody.getContent().get(contentType));
postProcessParameter(cp);
}
// add form parameters to the beginning of all parameter list

View File

@@ -544,6 +544,10 @@ public class RustServerCodegen extends DefaultCodegen implements CodegenConfig {
return mimetype.toLowerCase(Locale.ROOT).startsWith(octetMimeType);
}
private boolean isMimetypeMultipartRelated(String mimetype) {
return mimetype.toLowerCase(Locale.ROOT).startsWith("multipart/related");
}
private boolean isMimetypePlain(String mimetype) {
return isMimetypePlainText(mimetype) || isMimetypeHtmlText(mimetype) || isMimetypeOctetStream(mimetype);
}
@@ -847,6 +851,11 @@ public class RustServerCodegen extends DefaultCodegen implements CodegenConfig {
additionalProperties.put("usesUrlEncodedForm", true);
} else if (isMimetypeMultipartFormData(mediaType)) {
op.vendorExtensions.put("consumesMultipart", true);
additionalProperties.put("apiUsesMultipartFormData", true);
additionalProperties.put("apiUsesMultipart", true);
} else if (isMimetypeMultipartRelated(mediaType)) {
op.vendorExtensions.put("consumesMultipartRelated", true);
additionalProperties.put("apiUsesMultipartRelated", true);
additionalProperties.put("apiUsesMultipart", true);
}
}

View File

@@ -11,8 +11,14 @@ license = "Unlicense"
default = ["client", "server"]
client = [
{{#apiUsesMultipart}}
"multipart", "multipart/client", "swagger/multipart",
"mime_0_2",
{{/apiUsesMultipart}}
{{#apiUsesMultipartFormData}}
"multipart", "multipart/client", "swagger/multipart",
{{/apiUsesMultipartFormData}}
{{#apiUsesMultipartRelated}}
"hyper_0_10", "mime_multipart",
{{/apiUsesMultipartRelated}}
{{#usesUrlEncodedForm}}
"serde_urlencoded",
{{/usesUrlEncodedForm}}
@@ -20,8 +26,14 @@ client = [
]
server = [
{{#apiUsesMultipart}}
"multipart", "multipart/server",
"mime_0_2",
{{/apiUsesMultipart}}
{{#apiUsesMultipartFormData}}
"multipart", "multipart/server",
{{/apiUsesMultipartFormData}}
{{#apiUsesMultipartRelated}}
"hyper_0_10", "mime_multipart",
{{/apiUsesMultipartRelated}}
"serde_json", "serde_ignored", "hyper", "native-tls", "openssl", "tokio", "tokio-tls", "regex", "percent-encoding", "url", "lazy_static"
]
conversion = ["frunk", "frunk_derives", "frunk_core", "frunk-enum-core", "frunk-enum-derive"]
@@ -44,9 +56,11 @@ serde_derive = "1.0"
serde-xml-rs = {git = "git://github.com/Metaswitch/serde-xml-rs.git" , branch = "master"}
{{/usesXml}}
{{#apiUsesMultipart}}
mime_0_2 = { package = "mime", version = "0.2.6" }
multipart = { version = "0.16", default-features = false, optional = true }
mime_0_2 = { package = "mime", version = "0.2.6", optional = true }
{{/apiUsesMultipart}}
{{#apiUsesMultipartFormData}}
multipart = { version = "0.16", default-features = false, optional = true }
{{/apiUsesMultipartFormData}}
{{#apiUsesUuid}}
uuid = {version = "0.7", features = ["serde", "v4"]}
{{/apiUsesUuid}}
@@ -54,6 +68,10 @@ uuid = {version = "0.7", features = ["serde", "v4"]}
# Common between server and client features
hyper = {version = "0.12", optional = true}
hyper-tls = {version = "0.2.1", optional = true}
{{#apiUsesMultipartRelated}}
mime_multipart = {version = "0.5", optional = true}
hyper_0_10 = {package = "hyper", version = "0.10", default-features = false, optional=true}
{{/apiUsesMultipartRelated}}
native-tls = {version = "0.1.4", optional = true}
openssl = {version = "0.9.14", optional = true}
serde_json = {version = "1.0", optional = true}

View File

@@ -25,11 +25,16 @@ use swagger;
use swagger::{ApiError, XSpanIdString, Has, AuthData};
use swagger::client::Service;
{{#apiUsesMultipart}}
{{#apiUsesMultipartFormData}}
use mime::Mime;
use std::io::Cursor;
use multipart::client::lazy::Multipart;
{{/apiUsesMultipart}}
{{/apiUsesMultipartFormData}}
{{#apiUsesMultipartRelated}}
use hyper_0_10::header::{Headers, ContentType};
header! { (ContentId, "Content-ID") => [String] }
use mime_multipart::{Node, Part, generate_boundary, write_multipart};
{{/apiUsesMultipartRelated}}
{{#apiUsesUuid}}
use uuid;
{{/apiUsesUuid}}
@@ -253,7 +258,7 @@ impl<C, F> Api<C> for Client<F> where
{{#vendorExtensions}}
{{#consumesMultipart}}
let mut multipart = Multipart::new();
let mut multipart = Multipart::new();
{{#vendorExtensions}}
{{#formParams}}
@@ -274,7 +279,7 @@ impl<C, F> Api<C> for Client<F> where
let {{{paramName}}}_cursor = Cursor::new({{{paramName}}}_vec);
multipart.add_stream("{{{paramName}}}", {{{paramName}}}_cursor, None as Option<&str>, Some({{{paramName}}}_mime));
multipart.add_stream("{{{paramName}}}", {{{paramName}}}_cursor, None as Option<&str>, Some({{{paramName}}}_mime));
{{/jsonSchema}}
{{/isByteArray}}
{{#isByteArray}}
@@ -314,11 +319,10 @@ impl<C, F> Api<C> for Client<F> where
Err(e) => return Box::new(future::err(ApiError(format!("Unable to create header: {} - {}", multipart_header, e))))
});
{{/consumesMultipart}}
{{/vendorExtensions}}
{{#vendorExtensions}}
{{^consumesMultipart}}
{{#vendorExtensions}}
{{#formParams}}
{{^consumesMultipartRelated}}
{{#formParams}}
{{#-first}}
let params = &[
{{/-first}}
@@ -334,7 +338,70 @@ impl<C, F> Api<C> for Client<F> where
});
*request.body_mut() = Body::from(body.into_bytes());
{{/-last}}
{{/formParams}}
{{/formParams}}
{{/consumesMultipartRelated}}
{{#consumesMultipartRelated}}
{{#formParams}}
{{#-first}}
// Construct the Body for a multipart/related request. The mime 0.2.6 library
// does not parse quoted-string parameters correctly. The boundary doesn't
// need to be a quoted string if it does not contain a '/', hence ensure
// no such boundary is used.
let mut boundary = generate_boundary();
for b in boundary.iter_mut() {
if b == &('/' as u8) {
*b = '=' as u8;
}
}
let mut body_parts = vec![];
{{/-first}}
{{^required}}
if let Some({{{paramName}}}) = param_{{{paramName}}} {
{{/required}}
let part = Node::Part(Part {
headers: {
let mut h = Headers::new();
h.set(ContentType("{{{contentType}}}".parse().unwrap()));
h.set(ContentId("{{{baseName}}}".parse().unwrap()));
h
},
{{#isBinary}}
body: {{#required}}param_{{/required}}{{{paramName}}}.0,
{{/isBinary}}
{{^isBinary}}
body: serde_json::to_string(&{{{paramName}}})
.expect("Impossible to fail to serialize")
.into_bytes(),
{{/isBinary}}
});
body_parts.push(part);
{{^required}}
}
{{/required}}
{{#-last}}
// Write the body into a vec.
let mut body: Vec<u8> = vec![];
write_multipart(&mut body, &boundary, &body_parts)
.expect("Failed to write multipart body");
// Add the message body to the request object.
*request.body_mut() = Body::from(body);
let header = &mimetypes::requests::{{#vendorExtensions}}{{{uppercase_operation_id}}}{{/vendorExtensions}};
request.headers_mut().insert(CONTENT_TYPE,
match HeaderValue::from_bytes(
&[header.as_bytes(), "; boundary=".as_bytes(), &boundary, "; type=\"application/json\"".as_bytes()].concat()
) {
Ok(h) => h,
Err(e) => return Box::new(future::err(ApiError(format!("Unable to create header: {} - {}", header, e))))
});
{{/-last}}
{{/formParams}}
{{/consumesMultipartRelated}}
{{/vendorExtensions}}
{{#bodyParam}}
{{#-first}}

View File

@@ -12,6 +12,11 @@ extern crate lazy_static;
extern crate url;
#[macro_use]
extern crate log;
{{#apiUsesMultipartRelated}}
#[cfg(any(feature = "client", feature = "server"))]
#[macro_use]
extern crate hyper_0_10;
{{/apiUsesMultipartRelated}}
// Crates for conversion support
#[cfg(feature = "conversion")]
@@ -35,6 +40,14 @@ extern crate hyper;
extern crate hyper_tls;
#[cfg(any(feature = "client", feature = "server"))]
extern crate openssl;
{{#apiUsesMultipart}}
#[cfg(any(feature = "client", feature = "server"))]
extern crate mime_0_2;
{{/apiUsesMultipart}}
{{#apiUsesMultipartRelated}}
#[cfg(any(feature = "client", feature = "server"))]
extern crate mime_multipart;
{{/apiUsesMultipartRelated}}
#[cfg(any(feature = "client", feature = "server"))]
extern crate native_tls;
#[cfg(feature = "server")]
@@ -51,11 +64,10 @@ extern crate tokio;
{{#usesXml}}extern crate serde_xml_rs;{{/usesXml}}
{{#apiUsesMultipart}}
{{#apiUsesMultipartFormData}}
#[cfg(any(feature = "client", feature = "server"))]
extern crate multipart;
extern crate mime_0_2;
{{/apiUsesMultipart}}
{{/apiUsesMultipartFormData}}
#[cfg(any(feature = "client", feature = "server"))]
{{#usesUrlEncodedForm}}extern crate serde_urlencoded;{{/usesUrlEncodedForm}}

View File

@@ -15,5 +15,8 @@ pub mod requests {
{{/bodyParam}}{{^bodyParam}}{{#vendorExtensions}}{{#formParams}}{{#-first}}
/// Create &str objects for the request content types for {{{operationId}}}
pub static {{{uppercase_operation_id}}}: &str = "{{#consumes}}{{#-first}}{{{mediaType}}}{{/-first}}{{/consumes}}{{^consumes}}application/x-www-form-urlencoded{{/consumes}}";
{{/-first}}{{/formParams}}{{/vendorExtensions}}{{/bodyParam}}{{/vendorExtensions.consumesMultipart}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
{{/-first}}{{/formParams}}{{#relatedParams}}{{#-first}}
/// Create &str objects for the request content types for {{{operationId}}}
pub static {{{uppercase_operation_id}}}: &str = "{{#consumes}}{{#-first}}{{{mediaType}}}{{/-first}}{{/consumes}}{{^consumes}}multipart/related{{/consumes}}";
{{/-first}}{{/relatedParams}}{{/vendorExtensions}}{{/bodyParam}}{{/vendorExtensions.consumesMultipart}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
}

View File

@@ -13,10 +13,16 @@ use swagger::{ApiError, XSpanIdString, Has, RequestParser};
use swagger::auth::Scopes;
use swagger::context::ContextualPayload;
use url::form_urlencoded;
{{#apiUsesMultipart}}
{{#apiUsesMultipartRelated}}
use hyper_0_10::header::{Headers, ContentType};
header! { (ContentId, "Content-ID") => [String] }
use mime_0_2::{TopLevel, SubLevel, Mime as Mime2};
use mime_multipart::{read_multipart_body, Node, Part};
{{/apiUsesMultipartRelated}}
{{#apiUsesMultipartFormData}}
use multipart::server::Multipart;
use multipart::server::save::SaveResult;
{{/apiUsesMultipart}}
{{/apiUsesMultipartFormData}}
{{#usesXml}}
use serde_xml_rs;
{{/usesXml}}
@@ -175,7 +181,7 @@ where
}
{{/hasAuthMethods}}
{{#vendorExtensions}}
{{#consumesMultipart}}
{{#consumesMultipart}}
let boundary = match swagger::multipart::boundary(&headers) {
Some(boundary) => boundary.to_string(),
None => return Box::new(future::ok(Response::builder()
@@ -364,7 +370,7 @@ where
.expect("Unable to create Bad Request response due to failure to process all message")))
},
};
{{#formParams}}{{#-first}}{{/-first}}
{{#formParams}}
let field_{{{paramName}}} = entries.fields.remove("{{{paramName}}}");
let param_{{{paramName}}} = match field_{{{paramName}}} {
Some(field) => {
@@ -415,25 +421,131 @@ where
{{/vendorExtensions}}
{{/bodyParams}}
{{/consumesMultipart}}
{{^consumesMultipart}}
{{^bodyParams}}
{{#vendorExtensions}}
{{^consumesMultipartRelated}}
{{^consumesMultipart}}
{{^bodyParams}}
{{#vendorExtensions}}
Box::new({
{{
{{#formParams}}
{{#-first}}
{{#formParams}}
{{#-first}}
// Form parameters
{{/-first}}
let param_{{{paramName}}} =
{{/-first}}
let param_{{{paramName}}} =
{{^isContainer}}{{#vendorExtensions}}{{{example}}};{{/vendorExtensions}}{{/isContainer}}
{{#isListContainer}}{{#required}}Vec::new();{{/required}}{{^required}}None;{{/required}}{{/isListContainer}}
{{#isMapContainer}}None;{{/isMapContainer}}
{{/formParams}}
{{/vendorExtensions}}
{{/bodyParams}}
{{/consumesMultipart}}
{{/vendorExtensions}}
{{/formParams}}
{{/vendorExtensions}}
{{/bodyParams}}
{{/consumesMultipart}}
{{/consumesMultipartRelated}}
{{#consumesMultipartRelated}}
// 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| -> Self::Future {
match result {
Ok(body) => {
let mut unused_elements: Vec<String> = vec![];
// Get multipart chunks.
// Extract the top-level content type header.
let content_type_mime = headers
.get(CONTENT_TYPE)
.ok_or("Missing content-type header".to_string())
.and_then(|v| v.to_str().map_err(|e| format!("Couldn't read content-type header value for {{operationId}}: {}", e)))
.and_then(|v| v.parse::<Mime2>().map_err(|_e| format!("Couldn't parse content-type header value for {{operationId}}")));
// Insert top-level content type header into a Headers object.
let mut multi_part_headers = Headers::new();
match content_type_mime {
Ok(content_type_mime) => {
multi_part_headers.set(ContentType(content_type_mime));
},
Err(e) => {
return Box::new(future::ok(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Body::from(e))
.expect("Unable to create Bad Request response due to unable to read content-type header for {{operationId}}")));
}
}
// &*body expresses the body as a byteslice, &mut provides a
// mutable reference to that byteslice.
let nodes = match read_multipart_body(&mut&*body, &multi_part_headers, false) {
Ok(nodes) => nodes,
Err(e) => {
return Box::new(future::ok(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Body::from(format!("Could not read multipart body for {{operationId}}: {}", e)))
.expect("Unable to create Bad Request response due to unable to read multipart body for {{operationId}}")));
}
};
{{#formParams}}
let mut param_{{{paramName}}} = None;
{{/formParams}}
for node in nodes {
if let Node::Part(part) = node {
let content_type = part.content_type().map(|x| format!("{}",x));
match content_type.as_ref().map(|x| x.as_str()) {
{{#formParams}}
{{^isBinary}}
Some("{{{contentType}}}") => {
// Extract JSON part.
let deserializer = &mut serde_json::Deserializer::from_slice(part.body.as_slice());
let json_data: {{dataType}} = match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in JSON part: {}", path);
unused_elements.push(path.to_string());
}) {
Ok(json_data) => json_data,
Err(e) => return Box::new(future::ok(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Body::from(format!("Couldn't parse body parameter {{dataType}} - doesn't match schema: {}", e)))
.expect("Unable to create Bad Request response for invalid body parameter {{dataType}} due to schema")))
};
// Push JSON part to return object.
param_{{{paramName}}}.get_or_insert(json_data);
},
{{/isBinary}}
{{#isBinary}}
Some("{{{contentType}}}") => {
param_{{{paramName}}}.get_or_insert(swagger::ByteArray(part.body));
},
{{/isBinary}}
{{/formParams}}
Some(content_type) => {
warn!("Ignoring unknown content type: {}", content_type);
unused_elements.push(content_type.to_string());
},
None => {
warn!("Missing content type");
},
}
} else {
unimplemented!("No support for handling unexpected parts");
// unused_elements.push();
}
}
// Check that the required multipart chunks are present.
{{#formParams}}
{{#required}}
let param_{{{paramName}}} = match param_required_binary_field {
Some(x) => x,
None => return Box::new(future::ok(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Body::from(format!("Missing required multipart/related parameter {{{paramName}}}")))
.expect("Unable to create Bad Request response for missing multipart/related parameter {{{paramName}}} due to schema")))
};
{{/required}}
{{/formParams}}
{{/consumesMultipartRelated}}
{{/vendorExtensions}}
Box::new(
api_impl.{{#vendorExtensions}}{{{operation_id}}}{{/vendorExtensions}}(
{{#allParams}}
@@ -537,8 +649,23 @@ where
{{#vendorExtensions}}
{{^consumesMultipart}}
{{^bodyParams}}
{{#vendorExtensions}}
{{#consumesMultipartRelated}}
},
Err(e) => Box::new(future::ok(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Body::from(format!("Couldn't read body parameter {{{baseName}}}: {}", e)))
.expect("Unable to create Bad Request response due to unable to read body parameter {{{baseName}}}"))),
}
})
) as Self::Future
{{/consumesMultipartRelated}}
{{^consumesMultipartRelated}}
}}
}) as Self::Future
{{/consumesMultipartRelated}}
{{/vendorExtensions}}
{{/bodyParams}}
{{/consumesMultipart}}
{{/vendorExtensions}}

View File

@@ -23,6 +23,33 @@ paths:
responses:
'201':
description: 'OK'
/multipart_related_request:
post:
requestBody:
required: true
content:
multipart/related: # message with binary body part(s)
schema:
$ref: '#/components/schemas/multipart_related_request'
encoding:
object_field:
contentType: application/json
optional_binary_field:
contentType: application/zip
headers:
Content-Id:
schema:
type: string
required_binary_field:
contentType: image/png
headers:
Content-Id:
schema:
type: string
responses:
'201':
description: 'OK'
components:
schemas:
multipart_request:
@@ -49,3 +76,25 @@ components:
binary_field:
type: string
format: byte
multipart_related_request:
type: object
required:
- required_binary_field
properties:
object_field:
type: object
required:
- field_a
properties:
field_a:
type: string
field_b:
type: array
items:
type: string
optional_binary_field:
type: string
format: binary
required_binary_field:
type: string
format: binary

View File

@@ -8,11 +8,15 @@ license = "Unlicense"
[features]
default = ["client", "server"]
client = [
"mime_0_2",
"multipart", "multipart/client", "swagger/multipart",
"hyper_0_10", "mime_multipart",
"serde_json", "serde_ignored", "hyper", "hyper-tls", "native-tls", "openssl", "tokio", "url"
]
server = [
"mime_0_2",
"multipart", "multipart/server",
"hyper_0_10", "mime_multipart",
"serde_json", "serde_ignored", "hyper", "native-tls", "openssl", "tokio", "tokio-tls", "regex", "percent-encoding", "url", "lazy_static"
]
conversion = ["frunk", "frunk_derives", "frunk_core", "frunk-enum-core", "frunk-enum-derive"]
@@ -29,12 +33,14 @@ serde = "1.0"
serde_derive = "1.0"
# Crates included if required by the API definition
mime_0_2 = { package = "mime", version = "0.2.6" }
mime_0_2 = { package = "mime", version = "0.2.6", optional = true }
multipart = { version = "0.16", default-features = false, optional = true }
# Common between server and client features
hyper = {version = "0.12", optional = true}
hyper-tls = {version = "0.2.1", optional = true}
mime_multipart = {version = "0.5", optional = true}
hyper_0_10 = {package = "hyper", version = "0.10", default-features = false, optional=true}
native-tls = {version = "0.1.4", optional = true}
openssl = {version = "0.9.14", optional = true}
serde_json = {version = "1.0", optional = true}

View File

@@ -61,6 +61,7 @@ cargo run --example server
To run a client, follow one of the following simple steps:
```
cargo run --example client MultipartRelatedRequestPost
cargo run --example client MultipartRequestPost
```
@@ -95,11 +96,13 @@ All URIs are relative to *http://localhost*
Method | HTTP request | Description
------------- | ------------- | -------------
[****](docs/default_api.md#) | **POST** /multipart_related_request |
[****](docs/default_api.md#) | **POST** /multipart_request |
## Documentation For Models
- [MultipartRelatedRequest](docs/MultipartRelatedRequest.md)
- [MultipartRequest](docs/MultipartRequest.md)
- [MultipartRequestObjectField](docs/MultipartRequestObjectField.md)

View File

@@ -17,6 +17,39 @@ paths:
responses:
"201":
description: OK
/multipart_related_request:
post:
requestBody:
content:
multipart/related:
encoding:
object_field:
contentType: application/json
style: form
optional_binary_field:
contentType: application/zip
headers:
Content-Id:
explode: false
schema:
type: string
style: simple
style: form
required_binary_field:
contentType: image/png
headers:
Content-Id:
explode: false
schema:
type: string
style: simple
style: form
schema:
$ref: '#/components/schemas/multipart_related_request'
required: true
responses:
"201":
description: OK
components:
schemas:
multipart_request:
@@ -34,6 +67,19 @@ components:
- binary_field
- string_field
type: object
multipart_related_request:
properties:
object_field:
$ref: '#/components/schemas/multipart_request_object_field'
optional_binary_field:
format: binary
type: string
required_binary_field:
format: binary
type: string
required:
- required_binary_field
type: object
multipart_request_object_field:
properties:
field_a:

View File

@@ -0,0 +1,12 @@
# MultipartRelatedRequest
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**object_field** | [***models::MultipartRequestObjectField**](multipart_request_object_field.md) | | [optional] [default to None]
**optional_binary_field** | [***swagger::ByteArray**](file.md) | | [optional] [default to None]
**required_binary_field** | [***swagger::ByteArray**](file.md) | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -4,9 +4,45 @@ All URIs are relative to *http://localhost*
Method | HTTP request | Description
------------- | ------------- | -------------
****](default_api.md#) | **POST** /multipart_related_request |
****](default_api.md#) | **POST** /multipart_request |
# ****
> (required_binary_field, optional)
### Required Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**required_binary_field** | **swagger::ByteArray**| |
**optional** | **map[string]interface{}** | optional parameters | nil if no parameters
### Optional Parameters
Optional parameters are passed through a map[string]interface{}.
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**required_binary_field** | **swagger::ByteArray**| |
**object_field** | [**multipart_request_object_field**](multipart_request_object_field.md)| |
**optional_binary_field** | **swagger::ByteArray**| |
### Return type
(empty response body)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: multipart/related
- **Accept**: Not defined
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# ****
> (string_field, binary_field, optional)

View File

@@ -14,6 +14,7 @@ use futures::{Future, future, Stream, stream};
#[allow(unused_imports)]
use multipart_v3::{Api, ApiNoContext, Client, ContextWrapperExt,
ApiError,
MultipartRelatedRequestPostResponse,
MultipartRequestPostResponse
};
use clap::{App, Arg};
@@ -24,6 +25,8 @@ fn main() {
.help("Sets the operation to run")
.possible_values(&[
"MultipartRelatedRequestPost",
"MultipartRequestPost",
])
@@ -70,6 +73,16 @@ fn main() {
match matches.value_of("operation") {
Some("MultipartRelatedRequestPost") => {
let mut rt = tokio::runtime::Runtime::new().unwrap();
let result = rt.block_on(client.multipart_related_request_post(
swagger::ByteArray(Vec::from("BINARY_DATA_HERE")),
None,
Some(swagger::ByteArray(Vec::from("BINARY_DATA_HERE")))
));
println!("{:?} (X-Span-ID: {:?})", result, (client.context() as &dyn Has<XSpanIdString>).get().clone());
},
Some("MultipartRequestPost") => {
let mut rt = tokio::runtime::Runtime::new().unwrap();
let result = rt.block_on(client.multipart_request_post(

View File

@@ -17,6 +17,7 @@ use swagger;
use swagger::{Has, XSpanIdString};
use multipart_v3::{Api, ApiError,
MultipartRelatedRequestPostResponse,
MultipartRequestPostResponse
};
use multipart_v3::models;
@@ -35,6 +36,13 @@ impl<C> Server<C> {
impl<C> Api<C> for Server<C> where C: Has<XSpanIdString>{
fn multipart_related_request_post(&self, required_binary_field: swagger::ByteArray, object_field: Option<models::MultipartRequestObjectField>, optional_binary_field: Option<swagger::ByteArray>, context: &C) -> Box<Future<Item=MultipartRelatedRequestPostResponse, Error=ApiError> + Send> {
let context = context.clone();
println!("multipart_related_request_post({:?}, {:?}, {:?}) - X-Span-ID: {:?}", required_binary_field, object_field, optional_binary_field, context.get().0.clone());
Box::new(futures::failed("Generic failure".into()))
}
fn multipart_request_post(&self, string_field: String, binary_field: swagger::ByteArray, optional_string_field: Option<String>, object_field: Option<models::MultipartRequestObjectField>, context: &C) -> Box<Future<Item=MultipartRequestPostResponse, Error=ApiError> + Send> {
let context = context.clone();
println!("multipart_request_post(\"{}\", {:?}, {:?}, {:?}) - X-Span-ID: {:?}", string_field, binary_field, optional_string_field, object_field, context.get().0.clone());

View File

@@ -28,8 +28,12 @@ use swagger::client::Service;
use mime::Mime;
use std::io::Cursor;
use multipart::client::lazy::Multipart;
use hyper_0_10::header::{Headers, ContentType};
header! { (ContentId, "Content-ID") => [String] }
use mime_multipart::{Node, Part, generate_boundary, write_multipart};
use {Api,
MultipartRelatedRequestPostResponse,
MultipartRequestPostResponse
};
@@ -196,6 +200,146 @@ impl<C, F> Api<C> for Client<F> where
F: Future<Item=Response<Body>, Error=hyper::Error> + Send + 'static
{
fn multipart_related_request_post(&self, param_required_binary_field: swagger::ByteArray, param_object_field: Option<models::MultipartRequestObjectField>, param_optional_binary_field: Option<swagger::ByteArray>, context: &C) -> Box<dyn Future<Item=MultipartRelatedRequestPostResponse, Error=ApiError> + Send> {
let mut uri = format!(
"{}/multipart_related_request",
self.base_path
);
// Query parameters
let mut query_string = url::form_urlencoded::Serializer::new("".to_owned());
let query_string_str = query_string.finish();
if !query_string_str.is_empty() {
uri += "?";
uri += &query_string_str;
}
let uri = match Uri::from_str(&uri) {
Ok(uri) => uri,
Err(err) => return Box::new(future::err(ApiError(format!("Unable to build URI: {}", err)))),
};
let mut request = match hyper::Request::builder()
.method("POST")
.uri(uri)
.body(Body::empty()) {
Ok(req) => req,
Err(e) => return Box::new(future::err(ApiError(format!("Unable to create request: {}", e))))
};
// Construct the Body for a multipart/related request. The mime 0.2.6 library
// does not parse quoted-string parameters correctly. The boundary doesn't
// need to be a quoted string if it does not contain a '/', hence ensure
// no such boundary is used.
let mut boundary = generate_boundary();
for b in boundary.iter_mut() {
if b == &('/' as u8) {
*b = '=' as u8;
}
}
let mut body_parts = vec![];
if let Some(object_field) = param_object_field {
let part = Node::Part(Part {
headers: {
let mut h = Headers::new();
h.set(ContentType("application/json".parse().unwrap()));
h.set(ContentId("object_field".parse().unwrap()));
h
},
body: serde_json::to_string(&object_field)
.expect("Impossible to fail to serialize")
.into_bytes(),
});
body_parts.push(part);
}
if let Some(optional_binary_field) = param_optional_binary_field {
let part = Node::Part(Part {
headers: {
let mut h = Headers::new();
h.set(ContentType("application/zip".parse().unwrap()));
h.set(ContentId("optional_binary_field".parse().unwrap()));
h
},
body: optional_binary_field.0,
});
body_parts.push(part);
}
let part = Node::Part(Part {
headers: {
let mut h = Headers::new();
h.set(ContentType("image/png".parse().unwrap()));
h.set(ContentId("required_binary_field".parse().unwrap()));
h
},
body: param_required_binary_field.0,
});
body_parts.push(part);
// Write the body into a vec.
let mut body: Vec<u8> = vec![];
write_multipart(&mut body, &boundary, &body_parts)
.expect("Failed to write multipart body");
// Add the message body to the request object.
*request.body_mut() = Body::from(body);
let header = &mimetypes::requests::MULTIPART_RELATED_REQUEST_POST;
request.headers_mut().insert(CONTENT_TYPE,
match HeaderValue::from_bytes(
&[header.as_bytes(), "; boundary=".as_bytes(), &boundary, "; type=\"application/json\"".as_bytes()].concat()
) {
Ok(h) => h,
Err(e) => return Box::new(future::err(ApiError(format!("Unable to create header: {} - {}", header, e))))
});
let header = HeaderValue::from_str((context as &dyn Has<XSpanIdString>).get().0.clone().to_string().as_str());
request.headers_mut().insert(HeaderName::from_static("x-span-id"), match header {
Ok(h) => h,
Err(e) => return Box::new(future::err(ApiError(format!("Unable to create X-Span ID header value: {}", e))))
});
Box::new(self.client_service.request(request)
.map_err(|e| ApiError(format!("No response received: {}", e)))
.and_then(|mut response| {
match response.status().as_u16() {
201 => {
let body = response.into_body();
Box::new(
future::ok(
MultipartRelatedRequestPostResponse::OK
)
) as Box<dyn Future<Item=_, Error=_> + Send>
},
code => {
let headers = response.headers().clone();
Box::new(response.into_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<dyn Future<Item=_, Error=_> + Send>
}
}
}))
}
fn multipart_request_post(&self, param_string_field: String, param_binary_field: swagger::ByteArray, param_optional_string_field: Option<String>, param_object_field: Option<models::MultipartRequestObjectField>, context: &C) -> Box<dyn Future<Item=MultipartRequestPostResponse, Error=ApiError> + Send> {
let mut uri = format!(
"{}/multipart_request",
@@ -223,7 +367,7 @@ impl<C, F> Api<C> for Client<F> where
Err(e) => return Box::new(future::err(ApiError(format!("Unable to create request: {}", e))))
};
let mut multipart = Multipart::new();
let mut multipart = Multipart::new();
// For each parameter, encode as appropriate and add to the multipart body as a stream.
@@ -238,7 +382,7 @@ impl<C, F> Api<C> for Client<F> where
let string_field_cursor = Cursor::new(string_field_vec);
multipart.add_stream("string_field", string_field_cursor, None as Option<&str>, Some(string_field_mime));
multipart.add_stream("string_field", string_field_cursor, None as Option<&str>, Some(string_field_mime));
let optional_string_field_str = match serde_json::to_string(&param_optional_string_field) {
Ok(str) => str,
@@ -251,7 +395,7 @@ impl<C, F> Api<C> for Client<F> where
let optional_string_field_cursor = Cursor::new(optional_string_field_vec);
multipart.add_stream("optional_string_field", optional_string_field_cursor, None as Option<&str>, Some(optional_string_field_mime));
multipart.add_stream("optional_string_field", optional_string_field_cursor, None as Option<&str>, Some(optional_string_field_mime));
let object_field_str = match serde_json::to_string(&param_object_field) {
Ok(str) => str,
@@ -264,7 +408,7 @@ impl<C, F> Api<C> for Client<F> where
let object_field_cursor = Cursor::new(object_field_vec);
multipart.add_stream("object_field", object_field_cursor, None as Option<&str>, Some(object_field_mime));
multipart.add_stream("object_field", object_field_cursor, None as Option<&str>, Some(object_field_mime));
let binary_field_vec = param_binary_field.to_vec();

View File

@@ -12,6 +12,9 @@ extern crate lazy_static;
extern crate url;
#[macro_use]
extern crate log;
#[cfg(any(feature = "client", feature = "server"))]
#[macro_use]
extern crate hyper_0_10;
// Crates for conversion support
#[cfg(feature = "conversion")]
@@ -36,6 +39,10 @@ extern crate hyper_tls;
#[cfg(any(feature = "client", feature = "server"))]
extern crate openssl;
#[cfg(any(feature = "client", feature = "server"))]
extern crate mime_0_2;
#[cfg(any(feature = "client", feature = "server"))]
extern crate mime_multipart;
#[cfg(any(feature = "client", feature = "server"))]
extern crate native_tls;
#[cfg(feature = "server")]
extern crate percent_encoding;
@@ -53,7 +60,6 @@ extern crate tokio;
#[cfg(any(feature = "client", feature = "server"))]
extern crate multipart;
extern crate mime_0_2;
#[cfg(any(feature = "client", feature = "server"))]
@@ -77,6 +83,12 @@ pub const BASE_PATH: &'static str = "";
pub const API_VERSION: &'static str = "1.0.7";
#[derive(Debug, PartialEq)]
pub enum MultipartRelatedRequestPostResponse {
/// OK
OK
}
#[derive(Debug, PartialEq)]
pub enum MultipartRequestPostResponse {
/// OK
@@ -88,6 +100,9 @@ pub enum MultipartRequestPostResponse {
pub trait Api<C> {
fn multipart_related_request_post(&self, required_binary_field: swagger::ByteArray, object_field: Option<models::MultipartRequestObjectField>, optional_binary_field: Option<swagger::ByteArray>, context: &C) -> Box<dyn Future<Item=MultipartRelatedRequestPostResponse, Error=ApiError> + Send>;
fn multipart_request_post(&self, string_field: String, binary_field: swagger::ByteArray, optional_string_field: Option<String>, object_field: Option<models::MultipartRequestObjectField>, context: &C) -> Box<dyn Future<Item=MultipartRequestPostResponse, Error=ApiError> + Send>;
}
@@ -96,6 +111,9 @@ pub trait Api<C> {
pub trait ApiNoContext {
fn multipart_related_request_post(&self, required_binary_field: swagger::ByteArray, object_field: Option<models::MultipartRequestObjectField>, optional_binary_field: Option<swagger::ByteArray>) -> Box<dyn Future<Item=MultipartRelatedRequestPostResponse, Error=ApiError> + Send>;
fn multipart_request_post(&self, string_field: String, binary_field: swagger::ByteArray, optional_string_field: Option<String>, object_field: Option<models::MultipartRequestObjectField>) -> Box<dyn Future<Item=MultipartRequestPostResponse, Error=ApiError> + Send>;
}
@@ -115,6 +133,11 @@ impl<'a, T: Api<C> + Sized, C> ContextWrapperExt<'a, C> for T {
impl<'a, T: Api<C>, C> ApiNoContext for ContextWrapper<'a, T, C> {
fn multipart_related_request_post(&self, required_binary_field: swagger::ByteArray, object_field: Option<models::MultipartRequestObjectField>, optional_binary_field: Option<swagger::ByteArray>) -> Box<dyn Future<Item=MultipartRelatedRequestPostResponse, Error=ApiError> + Send> {
self.api().multipart_related_request_post(required_binary_field, object_field, optional_binary_field, &self.context())
}
fn multipart_request_post(&self, string_field: String, binary_field: swagger::ByteArray, optional_string_field: Option<String>, object_field: Option<models::MultipartRequestObjectField>) -> Box<dyn Future<Item=MultipartRequestPostResponse, Error=ApiError> + Send> {
self.api().multipart_request_post(string_field, binary_field, optional_string_field, object_field, &self.context())
}

View File

@@ -7,4 +7,7 @@ pub mod responses {
pub mod requests {
/// Create &str objects for the request content types for MultipartRelatedRequestPost
pub static MULTIPART_RELATED_REQUEST_POST: &str = "multipart/related";
}

View File

@@ -8,6 +8,33 @@ use swagger;
use std::string::ParseError;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "conversion", derive(LabelledGeneric))]
pub struct MultipartRelatedRequest {
#[serde(rename = "object_field")]
#[serde(skip_serializing_if="Option::is_none")]
pub object_field: Option<models::MultipartRequestObjectField>,
#[serde(rename = "optional_binary_field")]
#[serde(skip_serializing_if="Option::is_none")]
pub optional_binary_field: Option<swagger::ByteArray>,
#[serde(rename = "required_binary_field")]
pub required_binary_field: swagger::ByteArray,
}
impl MultipartRelatedRequest {
pub fn new(required_binary_field: swagger::ByteArray, ) -> MultipartRelatedRequest {
MultipartRelatedRequest {
object_field: None,
optional_binary_field: None,
required_binary_field: required_binary_field,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "conversion", derive(LabelledGeneric))]
pub struct MultipartRequest {

View File

@@ -13,6 +13,10 @@ use swagger::{ApiError, XSpanIdString, Has, RequestParser};
use swagger::auth::Scopes;
use swagger::context::ContextualPayload;
use url::form_urlencoded;
use hyper_0_10::header::{Headers, ContentType};
header! { (ContentId, "Content-ID") => [String] }
use mime_0_2::{TopLevel, SubLevel, Mime as Mime2};
use mime_multipart::{read_multipart_body, Node, Part};
use multipart::server::Multipart;
use multipart::server::save::SaveResult;
@@ -21,6 +25,7 @@ use mimetypes;
pub use swagger::auth::Authorization;
use {Api,
MultipartRelatedRequestPostResponse,
MultipartRequestPostResponse
};
@@ -34,11 +39,13 @@ mod paths {
lazy_static! {
pub static ref GLOBAL_REGEX_SET: regex::RegexSet = regex::RegexSet::new(vec![
r"^/multipart_related_request$",
r"^/multipart_request$"
])
.expect("Unable to create global regex set");
}
pub static ID_MULTIPART_REQUEST: usize = 0;
pub static ID_MULTIPART_RELATED_REQUEST: usize = 0;
pub static ID_MULTIPART_REQUEST: usize = 1;
}
pub struct MakeService<T, RC> {
@@ -117,6 +124,150 @@ where
// Please update both places if changing how this code is autogenerated.
match &method {
// MultipartRelatedRequestPost - POST /multipart_related_request
&hyper::Method::POST if path.matched(paths::ID_MULTIPART_RELATED_REQUEST) => {
// 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| -> Self::Future {
match result {
Ok(body) => {
let mut unused_elements: Vec<String> = vec![];
// Get multipart chunks.
// Extract the top-level content type header.
let content_type_mime = headers
.get(CONTENT_TYPE)
.ok_or("Missing content-type header".to_string())
.and_then(|v| v.to_str().map_err(|e| format!("Couldn't read content-type header value for MultipartRelatedRequestPost: {}", e)))
.and_then(|v| v.parse::<Mime2>().map_err(|_e| format!("Couldn't parse content-type header value for MultipartRelatedRequestPost")));
// Insert top-level content type header into a Headers object.
let mut multi_part_headers = Headers::new();
match content_type_mime {
Ok(content_type_mime) => {
multi_part_headers.set(ContentType(content_type_mime));
},
Err(e) => {
return Box::new(future::ok(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Body::from(e))
.expect("Unable to create Bad Request response due to unable to read content-type header for MultipartRelatedRequestPost")));
}
}
// &*body expresses the body as a byteslice, &mut provides a
// mutable reference to that byteslice.
let nodes = match read_multipart_body(&mut&*body, &multi_part_headers, false) {
Ok(nodes) => nodes,
Err(e) => {
return Box::new(future::ok(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Body::from(format!("Could not read multipart body for MultipartRelatedRequestPost: {}", e)))
.expect("Unable to create Bad Request response due to unable to read multipart body for MultipartRelatedRequestPost")));
}
};
let mut param_object_field = None;
let mut param_optional_binary_field = None;
let mut param_required_binary_field = None;
for node in nodes {
if let Node::Part(part) = node {
let content_type = part.content_type().map(|x| format!("{}",x));
match content_type.as_ref().map(|x| x.as_str()) {
Some("application/json") => {
// Extract JSON part.
let deserializer = &mut serde_json::Deserializer::from_slice(part.body.as_slice());
let json_data: models::MultipartRequestObjectField = match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in JSON part: {}", path);
unused_elements.push(path.to_string());
}) {
Ok(json_data) => json_data,
Err(e) => return Box::new(future::ok(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Body::from(format!("Couldn't parse body parameter models::MultipartRequestObjectField - doesn't match schema: {}", e)))
.expect("Unable to create Bad Request response for invalid body parameter models::MultipartRequestObjectField due to schema")))
};
// Push JSON part to return object.
param_object_field.get_or_insert(json_data);
},
Some("application/zip") => {
param_optional_binary_field.get_or_insert(swagger::ByteArray(part.body));
},
Some("image/png") => {
param_required_binary_field.get_or_insert(swagger::ByteArray(part.body));
},
Some(content_type) => {
warn!("Ignoring unknown content type: {}", content_type);
unused_elements.push(content_type.to_string());
},
None => {
warn!("Missing content type");
},
}
} else {
unimplemented!("No support for handling unexpected parts");
// unused_elements.push();
}
}
// Check that the required multipart chunks are present.
let param_required_binary_field = match param_required_binary_field {
Some(x) => x,
None => return Box::new(future::ok(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Body::from(format!("Missing required multipart/related parameter required_binary_field")))
.expect("Unable to create Bad Request response for missing multipart/related parameter required_binary_field due to schema")))
};
Box::new(
api_impl.multipart_related_request_post(
param_required_binary_field,
param_object_field,
param_optional_binary_field,
&context
).then(move |result| {
let mut response = Response::new(Body::empty());
response.headers_mut().insert(
HeaderName::from_static("x-span-id"),
HeaderValue::from_str((&context as &dyn Has<XSpanIdString>).get().0.clone().to_string().as_str())
.expect("Unable to create X-Span-ID header value"));
match result {
Ok(rsp) => match rsp {
MultipartRelatedRequestPostResponse::OK
=> {
*response.status_mut() = StatusCode::from_u16(201).expect("Unable to turn 201 into a StatusCode");
},
},
Err(_) => {
// Application code returned an error. This should not happen, as the implementation should
// return a valid response.
*response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
*response.body_mut() = Body::from("An internal error occurred");
},
}
future::ok(response)
}
))
},
Err(e) => Box::new(future::ok(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Body::from(format!("Couldn't read body parameter Default: {}", e)))
.expect("Unable to create Bad Request response due to unable to read body parameter Default"))),
}
})
) as Self::Future
},
// MultipartRequestPost - POST /multipart_request
&hyper::Method::POST if path.matched(paths::ID_MULTIPART_REQUEST) => {
let boundary = match swagger::multipart::boundary(&headers) {
@@ -147,7 +298,6 @@ where
.expect("Unable to create Bad Request response due to failure to process all message")))
},
};
let field_string_field = entries.fields.remove("string_field");
let param_string_field = match field_string_field {
Some(field) => {
@@ -174,7 +324,6 @@ where
.expect("Unable to create Bad Request due to missing required form parameter string_field")))
}
};
let field_optional_string_field = entries.fields.remove("optional_string_field");
let param_optional_string_field = match field_optional_string_field {
Some(field) => {
@@ -199,7 +348,6 @@ where
None
}
};
let field_object_field = entries.fields.remove("object_field");
let param_object_field = match field_object_field {
Some(field) => {
@@ -224,7 +372,6 @@ where
None
}
};
let field_binary_field = entries.fields.remove("binary_field");
let param_binary_field = match field_binary_field {
Some(field) => {
@@ -241,7 +388,6 @@ where
.expect("Unable to create Bad Request due to missing required form parameter binary_field")))
}
};
Box::new(
api_impl.multipart_request_post(
param_string_field,
@@ -315,6 +461,9 @@ impl<T> RequestParser<T> for ApiRequestParser {
let path = paths::GLOBAL_REGEX_SET.matches(request.uri().path());
match request.method() {
// MultipartRelatedRequestPost - POST /multipart_related_request
&hyper::Method::POST if path.matched(paths::ID_MULTIPART_RELATED_REQUEST) => Ok("MultipartRelatedRequestPost"),
// MultipartRequestPost - POST /multipart_request
&hyper::Method::POST if path.matched(paths::ID_MULTIPART_REQUEST) => Ok("MultipartRequestPost"),
_ => Err(()),

View File

@@ -147,7 +147,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_MULTIGET) => {
Box::new({
{{
Box::new(
api_impl.multiget_get(
&context
@@ -322,7 +321,6 @@ where
}
Box::new({
{{
Box::new(
api_impl.multiple_auth_scheme_get(
&context
@@ -391,7 +389,6 @@ where
}
Box::new({
{{
Box::new(
api_impl.readonly_auth_scheme_get(
&context
@@ -449,7 +446,6 @@ where
.body(Body::from("Missing required body parameter body"))
.expect("Unable to create Bad Request response for missing body parameter body"))),
};
Box::new(
api_impl.required_octet_stream_put(
param_body,
@@ -497,7 +493,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_RESPONSES_WITH_HEADERS) => {
Box::new({
{{
Box::new(
api_impl.responses_with_headers_get(
&context
@@ -574,7 +569,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_UUID) => {
Box::new({
{{
Box::new(
api_impl.uuid_get(
&context
@@ -642,7 +636,6 @@ where
} else {
None
};
Box::new(
api_impl.xml_extra_post(
param_duplicate_xml_object,
@@ -722,7 +715,6 @@ where
} else {
None
};
Box::new(
api_impl.xml_other_post(
param_another_xml_object,
@@ -802,7 +794,6 @@ where
} else {
None
};
Box::new(
api_impl.xml_other_put(
param_string,
@@ -882,7 +873,6 @@ where
} else {
None
};
Box::new(
api_impl.xml_post(
param_string,
@@ -962,7 +952,6 @@ where
} else {
None
};
Box::new(
api_impl.xml_put(
param_xml_object,

View File

@@ -227,7 +227,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP10) => {
Box::new({
{{
Box::new(
api_impl.op10_get(
&context
@@ -268,7 +267,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP11) => {
Box::new({
{{
Box::new(
api_impl.op11_get(
&context
@@ -309,7 +307,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP12) => {
Box::new({
{{
Box::new(
api_impl.op12_get(
&context
@@ -350,7 +347,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP13) => {
Box::new({
{{
Box::new(
api_impl.op13_get(
&context
@@ -391,7 +387,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP14) => {
Box::new({
{{
Box::new(
api_impl.op14_get(
&context
@@ -432,7 +427,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP15) => {
Box::new({
{{
Box::new(
api_impl.op15_get(
&context
@@ -473,7 +467,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP16) => {
Box::new({
{{
Box::new(
api_impl.op16_get(
&context
@@ -514,7 +507,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP17) => {
Box::new({
{{
Box::new(
api_impl.op17_get(
&context
@@ -555,7 +547,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP18) => {
Box::new({
{{
Box::new(
api_impl.op18_get(
&context
@@ -596,7 +587,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP19) => {
Box::new({
{{
Box::new(
api_impl.op19_get(
&context
@@ -637,7 +627,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP1) => {
Box::new({
{{
Box::new(
api_impl.op1_get(
&context
@@ -678,7 +667,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP20) => {
Box::new({
{{
Box::new(
api_impl.op20_get(
&context
@@ -719,7 +707,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP21) => {
Box::new({
{{
Box::new(
api_impl.op21_get(
&context
@@ -760,7 +747,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP22) => {
Box::new({
{{
Box::new(
api_impl.op22_get(
&context
@@ -801,7 +787,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP23) => {
Box::new({
{{
Box::new(
api_impl.op23_get(
&context
@@ -842,7 +827,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP24) => {
Box::new({
{{
Box::new(
api_impl.op24_get(
&context
@@ -883,7 +867,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP25) => {
Box::new({
{{
Box::new(
api_impl.op25_get(
&context
@@ -924,7 +907,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP26) => {
Box::new({
{{
Box::new(
api_impl.op26_get(
&context
@@ -965,7 +947,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP27) => {
Box::new({
{{
Box::new(
api_impl.op27_get(
&context
@@ -1006,7 +987,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP28) => {
Box::new({
{{
Box::new(
api_impl.op28_get(
&context
@@ -1047,7 +1027,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP29) => {
Box::new({
{{
Box::new(
api_impl.op29_get(
&context
@@ -1088,7 +1067,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP2) => {
Box::new({
{{
Box::new(
api_impl.op2_get(
&context
@@ -1129,7 +1107,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP30) => {
Box::new({
{{
Box::new(
api_impl.op30_get(
&context
@@ -1170,7 +1147,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP31) => {
Box::new({
{{
Box::new(
api_impl.op31_get(
&context
@@ -1211,7 +1187,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP32) => {
Box::new({
{{
Box::new(
api_impl.op32_get(
&context
@@ -1252,7 +1227,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP33) => {
Box::new({
{{
Box::new(
api_impl.op33_get(
&context
@@ -1293,7 +1267,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP34) => {
Box::new({
{{
Box::new(
api_impl.op34_get(
&context
@@ -1334,7 +1307,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP35) => {
Box::new({
{{
Box::new(
api_impl.op35_get(
&context
@@ -1375,7 +1347,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP36) => {
Box::new({
{{
Box::new(
api_impl.op36_get(
&context
@@ -1416,7 +1387,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP37) => {
Box::new({
{{
Box::new(
api_impl.op37_get(
&context
@@ -1457,7 +1427,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP3) => {
Box::new({
{{
Box::new(
api_impl.op3_get(
&context
@@ -1498,7 +1467,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP4) => {
Box::new({
{{
Box::new(
api_impl.op4_get(
&context
@@ -1539,7 +1507,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP5) => {
Box::new({
{{
Box::new(
api_impl.op5_get(
&context
@@ -1580,7 +1547,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP6) => {
Box::new({
{{
Box::new(
api_impl.op6_get(
&context
@@ -1621,7 +1587,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP7) => {
Box::new({
{{
Box::new(
api_impl.op7_get(
&context
@@ -1662,7 +1627,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP8) => {
Box::new({
{{
Box::new(
api_impl.op8_get(
&context
@@ -1703,7 +1667,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_OP9) => {
Box::new({
{{
Box::new(
api_impl.op9_get(
&context

View File

@@ -8,11 +8,13 @@ license = "Unlicense"
[features]
default = ["client", "server"]
client = [
"mime_0_2",
"multipart", "multipart/client", "swagger/multipart",
"serde_urlencoded",
"serde_json", "serde_ignored", "hyper", "hyper-tls", "native-tls", "openssl", "tokio", "url"
]
server = [
"mime_0_2",
"multipart", "multipart/server",
"serde_json", "serde_ignored", "hyper", "native-tls", "openssl", "tokio", "tokio-tls", "regex", "percent-encoding", "url", "lazy_static"
]
@@ -33,7 +35,7 @@ serde_derive = "1.0"
# TODO: this should be updated to point at the official crate once
# https://github.com/RReverser/serde-xml-rs/pull/45 is accepted upstream
serde-xml-rs = {git = "git://github.com/Metaswitch/serde-xml-rs.git" , branch = "master"}
mime_0_2 = { package = "mime", version = "0.2.6" }
mime_0_2 = { package = "mime", version = "0.2.6", optional = true }
multipart = { version = "0.16", default-features = false, optional = true }
uuid = {version = "0.7", features = ["serde", "v4"]}

View File

@@ -2194,7 +2194,7 @@ impl<C, F> Api<C> for Client<F> where
Err(e) => return Box::new(future::err(ApiError(format!("Unable to create request: {}", e))))
};
let mut multipart = Multipart::new();
let mut multipart = Multipart::new();
// For each parameter, encode as appropriate and add to the multipart body as a stream.
@@ -2209,7 +2209,7 @@ impl<C, F> Api<C> for Client<F> where
let additional_metadata_cursor = Cursor::new(additional_metadata_vec);
multipart.add_stream("additional_metadata", additional_metadata_cursor, None as Option<&str>, Some(additional_metadata_mime));
multipart.add_stream("additional_metadata", additional_metadata_cursor, None as Option<&str>, Some(additional_metadata_mime));
let file_str = match serde_json::to_string(&param_file) {
Ok(str) => str,
@@ -2222,7 +2222,7 @@ impl<C, F> Api<C> for Client<F> where
let file_cursor = Cursor::new(file_vec);
multipart.add_stream("file", file_cursor, None as Option<&str>, Some(file_mime));
multipart.add_stream("file", file_cursor, None as Option<&str>, Some(file_mime));
let mut fields = match multipart.prepare() {
Ok(fields) => fields,

View File

@@ -36,6 +36,8 @@ extern crate hyper_tls;
#[cfg(any(feature = "client", feature = "server"))]
extern crate openssl;
#[cfg(any(feature = "client", feature = "server"))]
extern crate mime_0_2;
#[cfg(any(feature = "client", feature = "server"))]
extern crate native_tls;
#[cfg(feature = "server")]
extern crate percent_encoding;
@@ -53,7 +55,6 @@ extern crate serde_xml_rs;
#[cfg(any(feature = "client", feature = "server"))]
extern crate multipart;
extern crate mime_0_2;
#[cfg(any(feature = "client", feature = "server"))]
extern crate serde_urlencoded;

View File

@@ -256,7 +256,6 @@ where
.body(Body::from("Missing required body parameter body"))
.expect("Unable to create Bad Request response for missing body parameter body"))),
};
Box::new(
api_impl.test_special_tags(
param_body,
@@ -338,7 +337,6 @@ where
} else {
None
};
Box::new(
api_impl.fake_outer_boolean_serialize(
param_body,
@@ -420,7 +418,6 @@ where
} else {
None
};
Box::new(
api_impl.fake_outer_composite_serialize(
param_body,
@@ -502,7 +499,6 @@ where
} else {
None
};
Box::new(
api_impl.fake_outer_number_serialize(
param_body,
@@ -584,7 +580,6 @@ where
} else {
None
};
Box::new(
api_impl.fake_outer_string_serialize(
param_body,
@@ -669,7 +664,6 @@ where
};
Box::new({
{{
Box::new(
api_impl.hyphen_param(
param_hyphen_param,
@@ -757,7 +751,6 @@ where
.body(Body::from("Missing required body parameter body"))
.expect("Unable to create Bad Request response for missing body parameter body"))),
};
Box::new(
api_impl.test_body_with_query_params(
param_query,
@@ -841,7 +834,6 @@ where
.body(Body::from("Missing required body parameter body"))
.expect("Unable to create Bad Request response for missing body parameter body"))),
};
Box::new(
api_impl.test_client_model(
param_body,
@@ -916,63 +908,62 @@ where
Box::new({
{{
// Form parameters
let param_integer =
let param_integer =
Some(56);
let param_int32 =
let param_int32 =
Some(56);
let param_int64 =
let param_int64 =
Some(789);
let param_number =
let param_number =
8.14;
let param_float =
let param_float =
Some(3.4);
let param_double =
let param_double =
1.2;
let param_string =
let param_string =
Some("string_example".to_string());
let param_pattern_without_delimiter =
let param_pattern_without_delimiter =
"pattern_without_delimiter_example".to_string();
let param_byte =
let param_byte =
swagger::ByteArray(Vec::from("BYTE_ARRAY_DATA_HERE"));
let param_binary =
let param_binary =
Some(swagger::ByteArray(Vec::from("BINARY_DATA_HERE")));
let param_date =
let param_date =
None;
let param_date_time =
let param_date_time =
None;
let param_password =
let param_password =
Some("password_example".to_string());
let param_callback =
let param_callback =
Some("callback_example".to_string());
Box::new(
api_impl.test_endpoint_parameters(
param_number,
@@ -1070,11 +1061,10 @@ Some("callback_example".to_string());
Box::new({
{{
// Form parameters
let param_enum_form_string =
let param_enum_form_string =
Some("enum_form_string_example".to_string());
Box::new(
api_impl.test_enum_parameters(
param_enum_header_string_array.as_ref(),
@@ -1157,7 +1147,6 @@ Some("enum_form_string_example".to_string());
.body(Body::from("Missing required body parameter param"))
.expect("Unable to create Bad Request response for missing body parameter param"))),
};
Box::new(
api_impl.test_inline_additional_properties(
param_param,
@@ -1213,15 +1202,14 @@ Some("enum_form_string_example".to_string());
Box::new({
{{
// Form parameters
let param_param =
let param_param =
"param_example".to_string();
let param_param2 =
let param_param2 =
"param2_example".to_string();
Box::new(
api_impl.test_json_form_data(
param_param,
@@ -1302,7 +1290,6 @@ Some("enum_form_string_example".to_string());
.body(Body::from("Missing required body parameter body"))
.expect("Unable to create Bad Request response for missing body parameter body"))),
};
Box::new(
api_impl.test_classname(
param_body,
@@ -1423,7 +1410,6 @@ Some("enum_form_string_example".to_string());
.body(Body::from("Missing required body parameter body"))
.expect("Unable to create Bad Request response for missing body parameter body"))),
};
Box::new(
api_impl.add_pet(
param_body,
@@ -1535,7 +1521,6 @@ Some("enum_form_string_example".to_string());
});
Box::new({
{{
Box::new(
api_impl.delete_pet(
param_pet_id,
@@ -1613,7 +1598,6 @@ Some("enum_form_string_example".to_string());
.collect::<Vec<_>>();
Box::new({
{{
Box::new(
api_impl.find_pets_by_status(
param_status.as_ref(),
@@ -1706,7 +1690,6 @@ Some("enum_form_string_example".to_string());
.collect::<Vec<_>>();
Box::new({
{{
Box::new(
api_impl.find_pets_by_tags(
param_tags.as_ref(),
@@ -1795,7 +1778,6 @@ Some("enum_form_string_example".to_string());
};
Box::new({
{{
Box::new(
api_impl.get_pet_by_id(
param_pet_id,
@@ -1917,7 +1899,6 @@ Some("enum_form_string_example".to_string());
.body(Body::from("Missing required body parameter body"))
.expect("Unable to create Bad Request response for missing body parameter body"))),
};
Box::new(
api_impl.update_pet(
param_body,
@@ -2037,15 +2018,14 @@ Some("enum_form_string_example".to_string());
Box::new({
{{
// Form parameters
let param_name =
let param_name =
Some("name_example".to_string());
let param_status =
let param_status =
Some("status_example".to_string());
Box::new(
api_impl.update_pet_with_form(
param_pet_id,
@@ -2165,7 +2145,6 @@ Some("status_example".to_string());
.expect("Unable to create Bad Request response due to failure to process all message")))
},
};
let field_additional_metadata = entries.fields.remove("additional_metadata");
let param_additional_metadata = match field_additional_metadata {
Some(field) => {
@@ -2190,7 +2169,6 @@ Some("status_example".to_string());
None
}
};
let field_file = entries.fields.remove("file");
let param_file = match field_file {
Some(field) => {
@@ -2215,7 +2193,6 @@ Some("status_example".to_string());
None
}
};
Box::new(
api_impl.upload_file(
param_pet_id,
@@ -2296,7 +2273,6 @@ Some("status_example".to_string());
};
Box::new({
{{
Box::new(
api_impl.delete_order(
param_order_id,
@@ -2355,7 +2331,6 @@ Some("status_example".to_string());
}
Box::new({
{{
Box::new(
api_impl.get_inventory(
&context
@@ -2426,7 +2401,6 @@ Some("status_example".to_string());
};
Box::new({
{{
Box::new(
api_impl.get_order_by_id(
param_order_id,
@@ -2519,7 +2493,6 @@ Some("status_example".to_string());
.body(Body::from("Missing required body parameter body"))
.expect("Unable to create Bad Request response for missing body parameter body"))),
};
Box::new(
api_impl.place_order(
param_body,
@@ -2618,7 +2591,6 @@ Some("status_example".to_string());
.body(Body::from("Missing required body parameter body"))
.expect("Unable to create Bad Request response for missing body parameter body"))),
};
Box::new(
api_impl.create_user(
param_body,
@@ -2701,7 +2673,6 @@ Some("status_example".to_string());
.body(Body::from("Missing required body parameter body"))
.expect("Unable to create Bad Request response for missing body parameter body"))),
};
Box::new(
api_impl.create_users_with_array_input(
param_body.as_ref(),
@@ -2784,7 +2755,6 @@ Some("status_example".to_string());
.body(Body::from("Missing required body parameter body"))
.expect("Unable to create Bad Request response for missing body parameter body"))),
};
Box::new(
api_impl.create_users_with_list_input(
param_body.as_ref(),
@@ -2860,7 +2830,6 @@ Some("status_example".to_string());
};
Box::new({
{{
Box::new(
api_impl.delete_user(
param_username,
@@ -2930,7 +2899,6 @@ Some("status_example".to_string());
};
Box::new({
{{
Box::new(
api_impl.get_user_by_name(
param_username,
@@ -3028,7 +2996,6 @@ Some("status_example".to_string());
};
Box::new({
{{
Box::new(
api_impl.login_user(
param_username,
@@ -3100,7 +3067,6 @@ Some("status_example".to_string());
&hyper::Method::GET if path.matched(paths::ID_USER_LOGOUT) => {
Box::new({
{{
Box::new(
api_impl.logout_user(
&context
@@ -3190,7 +3156,6 @@ Some("status_example".to_string());
.body(Body::from("Missing required body parameter body"))
.expect("Unable to create Bad Request response for missing body parameter body"))),
};
Box::new(
api_impl.update_user(
param_username,

View File

@@ -129,7 +129,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_DUMMY) => {
Box::new({
{{
Box::new(
api_impl.dummy_get(
&context
@@ -198,7 +197,6 @@ where
.body(Body::from("Missing required body parameter nested_response"))
.expect("Unable to create Bad Request response for missing body parameter nested_response"))),
};
Box::new(
api_impl.dummy_put(
param_nested_response,
@@ -253,7 +251,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_FILE_RESPONSE) => {
Box::new({
{{
Box::new(
api_impl.file_response_get(
&context
@@ -326,7 +323,6 @@ where
.body(Body::from("Missing required body parameter body"))
.expect("Unable to create Bad Request response for missing body parameter body"))),
};
Box::new(
api_impl.html_post(
param_body,
@@ -383,7 +379,6 @@ where
&hyper::Method::GET if path.matched(paths::ID_RAW_JSON) => {
Box::new({
{{
Box::new(
api_impl.raw_json_get(
&context