Skip to content

Commit

Permalink
[Rust Server] Refactor Mustache templates for request/response body h…
Browse files Browse the repository at this point in the history
…andling (#19347)

* [Rust Server] Refactor Mustache templates for request/response body handling

* Update samples
  • Loading branch information
richardwhiuk authored Aug 14, 2024
1 parent 8f7354a commit daf5222
Show file tree
Hide file tree
Showing 27 changed files with 1,644 additions and 1,143 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -821,12 +821,15 @@ private void postProcessOperationWithModels(CodegenOperation op, List<ModelMap>
} else if (isMimetypePlain(mediaType)) {
consumesPlainText = true;
} else if (isMimetypeWwwFormUrlEncoded(mediaType)) {
op.vendorExtensions.put("x-consumes-form", true);
additionalProperties.put("usesUrlEncodedForm", true);
} else if (isMimetypeMultipartFormData(mediaType)) {
op.vendorExtensions.put("x-consumes-multipart", true);
op.vendorExtensions.put("x-consumes-multipart-form", true);
additionalProperties.put("apiUsesMultipartFormData", true);
additionalProperties.put("apiUsesMultipart", true);
} else if (isMimetypeMultipartRelated(mediaType)) {
op.vendorExtensions.put("x-consumes-multipart", true);
op.vendorExtensions.put("x-consumes-multipart-related", true);
additionalProperties.put("apiUsesMultipartRelated", true);
additionalProperties.put("apiUsesMultipart", true);
Expand All @@ -835,15 +838,23 @@ private void postProcessOperationWithModels(CodegenOperation op, List<ModelMap>
}
}

if (op.bodyParams.size() > 0 || op.formParams.size() > 0){
op.vendorExtensions.put("x-has-request-body", true);
}

String underscoredOperationId = underscore(op.operationId).toUpperCase(Locale.ROOT);

if (op.bodyParam != null) {
// Default to consuming json
op.bodyParam.vendorExtensions.put("x-uppercase-operation-id", underscoredOperationId);
if (consumesXml) {
op.vendorExtensions.put("x-consumes-basic", true);
op.bodyParam.vendorExtensions.put("x-consumes-xml", true);
} else if (consumesPlainText) {
op.vendorExtensions.put("x-consumes-basic", true);
op.bodyParam.vendorExtensions.put("x-consumes-plain-text", true);
} else {
op.vendorExtensions.put("x-consumes-basic", true);
op.bodyParam.vendorExtensions.put("x-consumes-json", true);
}
}
Expand All @@ -855,10 +866,13 @@ private void postProcessOperationWithModels(CodegenOperation op, List<ModelMap>

// Default to producing json if nothing else is specified
if (consumesXml) {
op.vendorExtensions.put("x-consumes-basic", true);
param.vendorExtensions.put("x-consumes-xml", true);
} else if (consumesPlainText) {
op.vendorExtensions.put("x-consumes-basic", true);
param.vendorExtensions.put("x-consumes-plain-text", true);
} else {
op.vendorExtensions.put("x-consumes-basic", true);
param.vendorExtensions.put("x-consumes-json", true);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,217 +85,7 @@
Ok(req) => req,
Err(e) => return Err(ApiError(format!("Unable to create request: {}", e)))
};

{{#vendorExtensions}}
{{#x-consumes-multipart}}
let (body_string, multipart_header) = {
let mut multipart = Multipart::new();
{{#vendorExtensions}}
{{#formParams}}
{{#-first}}
// For each parameter, encode as appropriate and add to the multipart body as a stream.
{{/-first}}

{{^isByteArray}}
{{#jsonSchema}}
let {{{paramName}}}_str = match serde_json::to_string(&param_{{{paramName}}}) {
Ok(str) => str,
Err(e) => return Err(ApiError(format!("Unable to serialize {{{paramName}}} to string: {}", e))),
};

let {{{paramName}}}_vec = {{{paramName}}}_str.as_bytes().to_vec();
let {{{paramName}}}_mime = mime_0_2::Mime::from_str("application/json").expect("impossible to fail to parse");
let {{{paramName}}}_cursor = Cursor::new({{{paramName}}}_vec);

multipart.add_stream("{{{paramName}}}", {{{paramName}}}_cursor, None as Option<&str>, Some({{{paramName}}}_mime));
{{/jsonSchema}}
{{/isByteArray}}

{{#isByteArray}}
let {{{paramName}}}_vec = param_{{{paramName}}}.to_vec();

let {{{paramName}}}_mime = match mime_0_2::Mime::from_str("application/octet-stream") {
Ok(mime) => mime,
Err(err) => return Err(ApiError(format!("Unable to get mime type: {:?}", err))),
};

let {{{paramName}}}_cursor = Cursor::new({{{paramName}}}_vec);

let filename = None as Option<&str> ;
multipart.add_stream("{{{paramName}}}", {{{paramName}}}_cursor, filename, Some({{{paramName}}}_mime));
{{/isByteArray}}
{{/formParams}}
{{/vendorExtensions}}

let mut fields = match multipart.prepare() {
Ok(fields) => fields,
Err(err) => return Err(ApiError(format!("Unable to build request: {}", err))),
};

let mut body_string = String::new();

match fields.read_to_string(&mut body_string) {
Ok(_) => (),
Err(err) => return Err(ApiError(format!("Unable to build body: {}", err))),
}

let boundary = fields.boundary();

let multipart_header = format!("multipart/form-data;boundary={}", boundary);

(body_string, multipart_header)
};

*request.body_mut() = Body::from(body_string);

request.headers_mut().insert(CONTENT_TYPE, match HeaderValue::from_str(&multipart_header) {
Ok(h) => h,
Err(e) => return Err(ApiError(format!("Unable to create header: {} - {}", multipart_header, e)))
});

{{/x-consumes-multipart}}
{{^x-consumes-multipart}}
{{#vendorExtensions}}
{{^x-consumes-multipart-related}}
{{#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");

let header = "{{#consumes}}{{#-first}}{{{mediaType}}}{{/-first}}{{/consumes}}{{^consumes}}application/json{{/consumes}}";
request.headers_mut().insert(CONTENT_TYPE, match HeaderValue::from_str(header) {
Ok(h) => h,
Err(e) => return Err(ApiError(format!("Unable to create header: {} - {}", header, e)))
});
*request.body_mut() = Body::from(body.into_bytes());
{{/-last}}
{{/formParams}}
{{/x-consumes-multipart-related}}
{{#x-consumes-multipart-related}}
{{#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 == &(b'/') {
*b = b'=';
}
}

let mut body_parts = vec![];
{{/-first}}

{{#required}}
{
{{/required}}
{{^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_raw("Content-ID", vec![b"{{{baseName}}}".to_vec()]);
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);
}
{{#-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 = "{{#consumes}}{{#-first}}{{{mediaType}}}{{/-first}}{{/consumes}}{{^consumes}}application/json{{/consumes}}";
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 Err(ApiError(format!("Unable to create header: {} - {}", header, e)))
});

{{/-last}}
{{/formParams}}
{{/x-consumes-multipart-related}}
{{/vendorExtensions}}
{{#bodyParam}}
{{#-first}}
// Body parameter
{{/-first}}
{{#vendorExtensions}}
{{#x-consumes-plain-text}}
{{#isByteArray}}
let body = param_{{{paramName}}}.0;
{{/isByteArray}}
{{^isByteArray}}
let body = param_{{{paramName}}};
{{/isByteArray}}
{{/x-consumes-plain-text}}
{{#required}}
{{#x-consumes-xml}}
let body = param_{{{paramName}}}.as_xml();
{{/x-consumes-xml}}
{{#x-consumes-json}}
let body = serde_json::to_string(&param_{{{paramName}}}).expect("impossible to fail to serialize");
{{/x-consumes-json}}
{{/required}}
{{^required}}
let body = param_{{{paramName}}}.map(|ref body| {
{{#x-consumes-xml}}
body.as_xml()
{{/x-consumes-xml}}
{{#x-consumes-json}}
serde_json::to_string(body).expect("impossible to fail to serialize")
{{/x-consumes-json}}
});
{{/required}}
{{/vendorExtensions}}
{{#-last}}

{{/-last}}
{{/bodyParam}}
{{#bodyParam}}
{{^required}}
if let Some(body) = body {
{{/required}}
*request.body_mut() = Body::from(body);
{{^required}}
}
{{/required}}

let header = "{{#consumes}}{{#-first}}{{{mediaType}}}{{/-first}}{{/consumes}}{{^consumes}}application/json{{/consumes}}";
request.headers_mut().insert(CONTENT_TYPE, match HeaderValue::from_str(header) {
Ok(h) => h,
Err(e) => return Err(ApiError(format!("Unable to create header: {} - {}", header, e)))
});
{{#-last}}

{{/-last}}
{{/bodyParam}}
{{/x-consumes-multipart}}
{{/vendorExtensions}}
{{>client-request-body-instance}}
let header = HeaderValue::from_str(Has::<XSpanIdString>::get(context).0.as_str());
request.headers_mut().insert(HeaderName::from_static("x-span-id"), match header {
Ok(h) => h,
Expand Down Expand Up @@ -421,29 +211,9 @@
let body = body
.into_raw()
.map_err(|e| ApiError(format!("Failed to read response: {}", e))).await?;
{{#vendorExtensions}}
{{#x-produces-bytes}}
let body = swagger::ByteArray(body.to_vec());
{{/x-produces-bytes}}
{{^x-produces-bytes}}
let body = str::from_utf8(&body)
.map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;
{{#x-produces-xml}}
// 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}}}>(body)
.map_err(|e| ApiError(format!("Response body did not match the schema: {}", e)))?;
{{/x-produces-xml}}
{{#x-produces-json}}
let body = serde_json::from_str::<{{{dataType}}}>(body).map_err(|e| {
ApiError(format!("Response body did not match the schema: {}", e))
})?;
{{/x-produces-json}}
{{#x-produces-plain-text}}
let body = body.to_string();
{{/x-produces-plain-text}}
{{/x-produces-bytes}}
{{/vendorExtensions}}

{{>client-response-body-instance}}

Ok({{{operationId}}}Response::{{#vendorExtensions}}{{x-response-id}}{{/vendorExtensions}}
{{^headers}}
(body)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
{{#vendorExtensions}}
{{#x-consumes-multipart-form}}

// Consumes multipart/form body
{{>client-request-body-multipart-form}}
{{/x-consumes-multipart-form}}
{{#x-consumes-multipart-related}}

// Consumes multipart/related body
{{#formParams}}
{{>generate-multipart-related}}
{{/formParams}}

let header = "multipart/related";
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 Err(ApiError(format!("Unable to create header: {} - {}", header, e)))
});

// Add the message body to the request object.
*request.body_mut() = Body::from(body);
{{/x-consumes-multipart-related}}
{{#x-consumes-form}}

// Consumes form body
{{#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.body_mut() = Body::from(body.into_bytes());

let header = "{{#consumes}}{{#-first}}{{{mediaType}}}{{/-first}}{{/consumes}}{{^consumes}}application/json{{/consumes}}";
request.headers_mut().insert(CONTENT_TYPE, match HeaderValue::from_str(header) {
Ok(h) => h,
Err(e) => return Err(ApiError(format!("Unable to create header: {} - {}", header, e)))
});
{{/-last}}
{{/formParams}}
{{/x-consumes-form}}
{{#x-consumes-basic}}

// Consumes basic body
{{#bodyParam}}
// Body parameter
{{^required}}
if let Some(param_{{{paramName}}}) = param_{{{paramName}}} {
{{/required}}
{{#vendorExtensions}}
{{#x-consumes-plain-text}}
{{#isByteArray}}
let body = param_{{{paramName}}}.0;
{{/isByteArray}}
{{^isByteArray}}
let body = param_{{{paramName}}};
{{/isByteArray}}
{{/x-consumes-plain-text}}
{{#x-consumes-xml}}
let body = param_{{{paramName}}}.as_xml();
{{/x-consumes-xml}}
{{#x-consumes-json}}
let body = serde_json::to_string(&param_{{{paramName}}}).expect("impossible to fail to serialize");
{{/x-consumes-json}}
{{/vendorExtensions}}
*request.body_mut() = Body::from(body);
{{^required}}
}
{{/required}}

let header = "{{#consumes}}{{#-first}}{{{mediaType}}}{{/-first}}{{/consumes}}{{^consumes}}application/json{{/consumes}}";
request.headers_mut().insert(CONTENT_TYPE, match HeaderValue::from_str(header) {
Ok(h) => h,
Err(e) => return Err(ApiError(format!("Unable to create header: {} - {}", header, e)))
});
{{/bodyParam}}
{{/x-consumes-basic}}
{{/vendorExtensions}}
Loading

0 comments on commit daf5222

Please sign in to comment.