[Rust Server] Support multiple identical MIME types (#5562)

* Support multiple identical MIME types in a multipart request
* Add test for multiple identical MIME types
* Update samples
This commit is contained in:
Richard Whitehouse
2020-03-22 15:30:37 +00:00
committed by GitHub
parent 058d1d2aa0
commit b0520a346d
13 changed files with 528 additions and 11 deletions

View File

@@ -368,7 +368,7 @@
match content_type.as_ref().map(|x| x.as_str()) {
{{#formParams}}
{{^isBinary}}
Some("{{{contentType}}}") => {
Some("{{{contentType}}}") if param_{{{paramName}}}.is_none() => {
// 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| {
@@ -386,13 +386,13 @@
},
{{/isBinary}}
{{#isBinary}}
Some("{{{contentType}}}") => {
Some("{{{contentType}}}") if param_{{{paramName}}}.is_none() => {
param_{{{paramName}}}.get_or_insert(swagger::ByteArray(part.body));
},
{{/isBinary}}
{{/formParams}}
Some(content_type) => {
warn!("Ignoring unknown content type: {}", content_type);
warn!("Ignoring unexpected content type: {}", content_type);
unused_elements.push(content_type.to_string());
},
None => {

View File

@@ -50,6 +50,29 @@ paths:
responses:
'201':
description: 'OK'
/multiple-identical-mime-types:
post:
requestBody:
required: true
content:
multipart/related:
schema:
type: object
properties:
binary1:
type: string
format: binary
binary2:
type: string
format: binary
encoding:
binary1:
contentType: application/octet-stream
binary2:
contentType: application/octet-stream
responses:
200:
description: OK
components:
schemas:
multipart_request:

View File

@@ -63,6 +63,7 @@ To run a client, follow one of the following simple steps:
```
cargo run --example client MultipartRelatedRequestPost
cargo run --example client MultipartRequestPost
cargo run --example client MultipleIdenticalMimeTypesPost
```
### HTTPS
@@ -98,10 +99,12 @@ Method | HTTP request | Description
------------- | ------------- | -------------
[****](docs/default_api.md#) | **POST** /multipart_related_request |
[****](docs/default_api.md#) | **POST** /multipart_request |
[****](docs/default_api.md#) | **POST** /multiple-identical-mime-types |
## Documentation For Models
- [InlineObject](docs/InlineObject.md)
- [MultipartRelatedRequest](docs/MultipartRelatedRequest.md)
- [MultipartRequest](docs/MultipartRequest.md)
- [MultipartRequestObjectField](docs/MultipartRequestObjectField.md)

View File

@@ -50,7 +50,40 @@ paths:
responses:
"201":
description: OK
/multiple-identical-mime-types:
post:
requestBody:
$ref: '#/components/requestBodies/inline_object'
content:
multipart/related:
encoding:
binary1:
contentType: application/octet-stream
style: form
binary2:
contentType: application/octet-stream
style: form
schema:
properties:
binary1:
format: binary
type: string
binary2:
format: binary
type: string
type: object
required: true
responses:
"200":
description: OK
components:
requestBodies:
inline_object:
content:
multipart/related:
schema:
$ref: '#/components/schemas/inline_object'
required: true
schemas:
multipart_request:
properties:
@@ -80,6 +113,15 @@ components:
required:
- required_binary_field
type: object
inline_object:
properties:
binary1:
format: binary
type: string
binary2:
format: binary
type: string
type: object
multipart_request_object_field:
properties:
field_a:

View File

@@ -0,0 +1,11 @@
# InlineObject
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**binary1** | [***swagger::ByteArray**](file.md) | | [optional] [default to None]
**binary2** | [***swagger::ByteArray**](file.md) | | [optional] [default to None]
[[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

@@ -6,6 +6,7 @@ Method | HTTP request | Description
------------- | ------------- | -------------
****](default_api.md#) | **POST** /multipart_related_request |
****](default_api.md#) | **POST** /multipart_request |
****](default_api.md#) | **POST** /multiple-identical-mime-types |
# ****
@@ -80,3 +81,36 @@ No authorization required
[[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)
# ****
> (optional)
### Required Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**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
------------- | ------------- | ------------- | -------------
**binary1** | **swagger::ByteArray**| |
**binary2** | **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)

View File

@@ -15,7 +15,8 @@ use futures::{Future, future, Stream, stream};
use multipart_v3::{Api, ApiNoContext, Client, ContextWrapperExt,
ApiError,
MultipartRelatedRequestPostResponse,
MultipartRequestPostResponse
MultipartRequestPostResponse,
MultipleIdenticalMimeTypesPostResponse
};
use clap::{App, Arg};
use swagger::{ContextBuilder, EmptyContext, XSpanIdString, Has, Push, AuthData};
@@ -29,6 +30,7 @@ fn main() {
.possible_values(&[
"MultipartRelatedRequestPost",
"MultipartRequestPost",
"MultipleIdenticalMimeTypesPost",
])
.required(true)
.index(1))
@@ -89,6 +91,13 @@ fn main() {
));
info!("{:?} (X-Span-ID: {:?})", result, (client.context() as &Has<XSpanIdString>).get().clone());
},
Some("MultipleIdenticalMimeTypesPost") => {
let result = rt.block_on(client.multiple_identical_mime_types_post(
Some(swagger::ByteArray(Vec::from("BINARY_DATA_HERE"))),
Some(swagger::ByteArray(Vec::from("BINARY_DATA_HERE")))
));
info!("{:?} (X-Span-ID: {:?})", result, (client.context() as &Has<XSpanIdString>).get().clone());
},
_ => {
panic!("Invalid operation provided")
}

View File

@@ -108,6 +108,7 @@ use multipart_v3::{
ApiError,
MultipartRelatedRequestPostResponse,
MultipartRequestPostResponse,
MultipleIdenticalMimeTypesPostResponse,
};
use multipart_v3::server::MakeService;
@@ -137,4 +138,15 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString>{
Box::new(future::err("Generic failure".into()))
}
fn multiple_identical_mime_types_post(
&self,
binary1: Option<swagger::ByteArray>,
binary2: Option<swagger::ByteArray>,
context: &C) -> Box<Future<Item=MultipleIdenticalMimeTypesPostResponse, Error=ApiError> + Send>
{
let context = context.clone();
info!("multiple_identical_mime_types_post({:?}, {:?}) - X-Span-ID: {:?}", binary1, binary2, context.get().0.clone());
Box::new(future::err("Generic failure".into()))
}
}

View File

@@ -43,7 +43,8 @@ define_encode_set! {
use {Api,
MultipartRelatedRequestPostResponse,
MultipartRequestPostResponse
MultipartRequestPostResponse,
MultipleIdenticalMimeTypesPostResponse
};
/// Convert input into a base path, e.g. "http://example:123". Also checks the scheme as it goes.
@@ -554,4 +555,133 @@ impl<C, F> Api<C> for Client<F> where
}))
}
fn multiple_identical_mime_types_post(
&self,
param_binary1: Option<swagger::ByteArray>,
param_binary2: Option<swagger::ByteArray>,
context: &C) -> Box<dyn Future<Item=MultipleIdenticalMimeTypesPostResponse, Error=ApiError> + Send>
{
let mut uri = format!(
"{}/multiple-identical-mime-types",
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(binary1) = param_binary1 {
let part = Node::Part(Part {
headers: {
let mut h = Headers::new();
h.set(ContentType("application/octet-stream".parse().unwrap()));
h.set(ContentId("binary1".parse().unwrap()));
h
},
body: binary1.0,
});
body_parts.push(part);
}
if let Some(binary2) = param_binary2 {
let part = Node::Part(Part {
headers: {
let mut h = Headers::new();
h.set(ContentType("application/octet-stream".parse().unwrap()));
h.set(ContentId("binary2".parse().unwrap()));
h
},
body: binary2.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::MULTIPLE_IDENTICAL_MIME_TYPES_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() {
200 => {
let body = response.into_body();
Box::new(
future::ok(
MultipleIdenticalMimeTypesPostResponse::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>
}
}
}))
}
}

View File

@@ -91,6 +91,12 @@ pub enum MultipartRequestPostResponse {
OK
}
#[derive(Debug, PartialEq)]
pub enum MultipleIdenticalMimeTypesPostResponse {
/// OK
OK
}
/// API
pub trait Api<C> {
fn multipart_related_request_post(
@@ -108,6 +114,12 @@ pub trait Api<C> {
object_field: Option<models::MultipartRequestObjectField>,
context: &C) -> Box<dyn Future<Item=MultipartRequestPostResponse, Error=ApiError> + Send>;
fn multiple_identical_mime_types_post(
&self,
binary1: Option<swagger::ByteArray>,
binary2: Option<swagger::ByteArray>,
context: &C) -> Box<dyn Future<Item=MultipleIdenticalMimeTypesPostResponse, Error=ApiError> + Send>;
}
/// API without a `Context`
@@ -127,6 +139,12 @@ pub trait ApiNoContext {
object_field: Option<models::MultipartRequestObjectField>,
) -> Box<dyn Future<Item=MultipartRequestPostResponse, Error=ApiError> + Send>;
fn multiple_identical_mime_types_post(
&self,
binary1: Option<swagger::ByteArray>,
binary2: Option<swagger::ByteArray>,
) -> Box<dyn Future<Item=MultipleIdenticalMimeTypesPostResponse, Error=ApiError> + Send>;
}
/// Trait to extend an API to make it easy to bind it to a context.
@@ -163,6 +181,15 @@ impl<'a, T: Api<C>, C> ApiNoContext for ContextWrapper<'a, T, C> {
self.api().multipart_request_post(string_field, binary_field, optional_string_field, object_field, &self.context())
}
fn multiple_identical_mime_types_post(
&self,
binary1: Option<swagger::ByteArray>,
binary2: Option<swagger::ByteArray>,
) -> Box<dyn Future<Item=MultipleIdenticalMimeTypesPostResponse, Error=ApiError> + Send>
{
self.api().multiple_identical_mime_types_post(binary1, binary2, &self.context())
}
}
#[cfg(feature = "client")]

View File

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

View File

@@ -11,6 +11,107 @@ use std::str::FromStr;
use header::IntoHeaderValue;
// Methods for converting between IntoHeaderValue<InlineObject> and HeaderValue
impl From<IntoHeaderValue<InlineObject>> for HeaderValue {
fn from(hdr_value: IntoHeaderValue<InlineObject>) -> Self {
HeaderValue::from_str(&hdr_value.to_string()).unwrap()
}
}
impl From<HeaderValue> for IntoHeaderValue<InlineObject> {
fn from(hdr_value: HeaderValue) -> Self {
IntoHeaderValue(InlineObject::from_str(hdr_value.to_str().unwrap()).unwrap())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "conversion", derive(LabelledGeneric))]
pub struct InlineObject {
#[serde(rename = "binary1")]
#[serde(skip_serializing_if="Option::is_none")]
pub binary1: Option<swagger::ByteArray>,
#[serde(rename = "binary2")]
#[serde(skip_serializing_if="Option::is_none")]
pub binary2: Option<swagger::ByteArray>,
}
impl InlineObject {
pub fn new() -> InlineObject {
InlineObject {
binary1: None,
binary2: None,
}
}
}
/// Converts the InlineObject value to the Query Parameters representation (style=form, explode=false)
/// specified in https://swagger.io/docs/specification/serialization/
/// Should be implemented in a serde serializer
impl ::std::string::ToString for InlineObject {
fn to_string(&self) -> String {
let mut params: Vec<String> = vec![];
// Skipping binary1 in query parameter serialization
// Skipping binary1 in query parameter serialization
// Skipping binary2 in query parameter serialization
// Skipping binary2 in query parameter serialization
params.join(",").to_string()
}
}
/// Converts Query Parameters representation (style=form, explode=false) to a InlineObject value
/// as specified in https://swagger.io/docs/specification/serialization/
/// Should be implemented in a serde deserializer
impl ::std::str::FromStr for InlineObject {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Default)]
// An intermediate representation of the struct to use for parsing.
struct IntermediateRep {
pub binary1: Vec<swagger::ByteArray>,
pub binary2: Vec<swagger::ByteArray>,
}
let mut intermediate_rep = IntermediateRep::default();
// Parse into intermediate representation
let mut string_iter = s.split(',').into_iter();
let mut key_result = string_iter.next();
while key_result.is_some() {
let val = match string_iter.next() {
Some(x) => x,
None => return Err("Missing value while parsing InlineObject".to_string())
};
if let Some(key) = key_result {
match key {
"binary1" => return Err("Parsing binary data in this style is not supported in InlineObject".to_string()),
"binary2" => return Err("Parsing binary data in this style is not supported in InlineObject".to_string()),
_ => return Err("Unexpected key while parsing InlineObject".to_string())
}
}
// Get the next key
key_result = string_iter.next();
}
// Use the intermediate representation to return the struct
Ok(InlineObject {
binary1: intermediate_rep.binary1.into_iter().next(),
binary2: intermediate_rep.binary2.into_iter().next(),
})
}
}
// Methods for converting between IntoHeaderValue<MultipartRelatedRequest> and HeaderValue
impl From<IntoHeaderValue<MultipartRelatedRequest>> for HeaderValue {

View File

@@ -30,7 +30,8 @@ pub use crate::context;
use {Api,
MultipartRelatedRequestPostResponse,
MultipartRequestPostResponse
MultipartRequestPostResponse,
MultipleIdenticalMimeTypesPostResponse
};
mod paths {
@@ -39,12 +40,14 @@ mod paths {
lazy_static! {
pub static ref GLOBAL_REGEX_SET: regex::RegexSet = regex::RegexSet::new(vec![
r"^/multipart_related_request$",
r"^/multipart_request$"
r"^/multipart_request$",
r"^/multiple-identical-mime-types$"
])
.expect("Unable to create global regex set");
}
pub static ID_MULTIPART_RELATED_REQUEST: usize = 0;
pub static ID_MULTIPART_REQUEST: usize = 1;
pub static ID_MULTIPLE_IDENTICAL_MIME_TYPES: usize = 2;
}
pub struct MakeService<T, RC> {
@@ -185,7 +188,7 @@ where
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") => {
Some("application/json") if param_object_field.is_none() => {
// 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| {
@@ -201,14 +204,14 @@ where
// Push JSON part to return object.
param_object_field.get_or_insert(json_data);
},
Some("application/zip") => {
Some("application/zip") if param_optional_binary_field.is_none() => {
param_optional_binary_field.get_or_insert(swagger::ByteArray(part.body));
},
Some("image/png") => {
Some("image/png") if param_required_binary_field.is_none() => {
param_required_binary_field.get_or_insert(swagger::ByteArray(part.body));
},
Some(content_type) => {
warn!("Ignoring unknown content type: {}", content_type);
warn!("Ignoring unexpected content type: {}", content_type);
unused_elements.push(content_type.to_string());
},
None => {
@@ -435,8 +438,124 @@ where
)
},
// MultipleIdenticalMimeTypesPost - POST /multiple-identical-mime-types
&hyper::Method::POST if path.matched(paths::ID_MULTIPLE_IDENTICAL_MIME_TYPES) => {
// 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 MultipleIdenticalMimeTypesPost: {}", e)))
.and_then(|v| v.parse::<Mime2>().map_err(|_e| format!("Couldn't parse content-type header value for MultipleIdenticalMimeTypesPost")));
// 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 MultipleIdenticalMimeTypesPost")));
}
}
// &*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 MultipleIdenticalMimeTypesPost: {}", e)))
.expect("Unable to create Bad Request response due to unable to read multipart body for MultipleIdenticalMimeTypesPost")));
}
};
let mut param_binary1 = None;
let mut param_binary2 = 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/octet-stream") if param_binary1.is_none() => {
param_binary1.get_or_insert(swagger::ByteArray(part.body));
},
Some("application/octet-stream") if param_binary2.is_none() => {
param_binary2.get_or_insert(swagger::ByteArray(part.body));
},
Some(content_type) => {
warn!("Ignoring unexpected 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.
Box::new(
api_impl.multiple_identical_mime_types_post(
param_binary1,
param_binary2,
&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 {
MultipleIdenticalMimeTypesPostResponse::OK
=> {
*response.status_mut() = StatusCode::from_u16(200).expect("Unable to turn 200 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
},
_ if path.matched(paths::ID_MULTIPART_RELATED_REQUEST) => method_not_allowed(),
_ if path.matched(paths::ID_MULTIPART_REQUEST) => method_not_allowed(),
_ if path.matched(paths::ID_MULTIPLE_IDENTICAL_MIME_TYPES) => method_not_allowed(),
_ => Box::new(future::ok(
Response::builder().status(StatusCode::NOT_FOUND)
.body(Body::empty())
@@ -466,6 +585,8 @@ impl<T> RequestParser<T> for ApiRequestParser {
&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"),
// MultipleIdenticalMimeTypesPost - POST /multiple-identical-mime-types
&hyper::Method::POST if path.matched(paths::ID_MULTIPLE_IDENTICAL_MIME_TYPES) => Ok("MultipleIdenticalMimeTypesPost"),
_ => Err(()),
}
}