From 1e1436c66949bf37916205fce30b174996e3bfe6 Mon Sep 17 00:00:00 2001 From: Benjamin Gill Date: Mon, 5 Nov 2018 16:14:23 +0000 Subject: [PATCH] Rust server html (#1329) Builds on #1180 by @colelawrence. This addition to the rust-server generator enables the use of text/html responses as plaintext. I've added an html endpoint to the sample to demonstrate that this works (and fixed the problem that that uncovered). --- .../codegen/languages/RustServerCodegen.java | 10 ++- .../2_0/rust-server/rust-server-test.yaml | 16 ++++ .../output/rust-server-test/README.md | 1 + .../output/rust-server-test/api/openapi.yaml | 16 ++++ .../rust-server-test/examples/client.rs | 9 ++- .../examples/server_lib/server.rs | 10 ++- .../output/rust-server-test/src/client/mod.rs | 74 ++++++++++++++++- .../output/rust-server-test/src/lib.rs | 17 ++++ .../output/rust-server-test/src/mimetypes.rs | 8 ++ .../output/rust-server-test/src/server/mod.rs | 81 ++++++++++++++++++- 10 files changed, 236 insertions(+), 6 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java index 85ea1893656c..01dc433229c6 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java @@ -474,6 +474,10 @@ boolean isMimetypePlainText(String mimetype) { return mimetype.toLowerCase(Locale.ROOT).startsWith("text/plain"); } + boolean isMimetypeHtmlText(String mimetype) { + return mimetype.toLowerCase(Locale.ROOT).startsWith("text/html"); + } + boolean isMimetypeWwwFormUrlEncoded(String mimetype) { return mimetype.toLowerCase(Locale.ROOT).startsWith("application/x-www-form-urlencoded"); } @@ -544,6 +548,8 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation consumesXml = true; } else if (isMimetypePlainText(mimeType)) { consumesPlainText = true; + } else if (isMimetypeHtmlText(mimeType)) { + consumesPlainText = true; } else if (isMimetypeWwwFormUrlEncoded(mimeType)) { additionalProperties.put("usesUrlEncodedForm", true); } @@ -570,6 +576,8 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation producesXml = true; } else if (isMimetypePlainText(mimeType)) { producesPlainText = true; + } else if (isMimetypeHtmlText(mimeType)) { + producesPlainText = true; } mediaType.put("mediaType", mimeType); @@ -662,7 +670,7 @@ public Map postProcessOperationsWithModels(Map o if (isMimetypeXml(mediaType)) { additionalProperties.put("usesXml", true); consumesXml = true; - } else if (isMimetypePlainText(mediaType)) { + } else if (isMimetypePlainText(mediaType) || isMimetypeHtmlText(mediaType)) { consumesPlainText = true; } else if (isMimetypeWwwFormUrlEncoded(mediaType)) { additionalProperties.put("usesUrlEncodedForm", true); diff --git a/modules/openapi-generator/src/test/resources/2_0/rust-server/rust-server-test.yaml b/modules/openapi-generator/src/test/resources/2_0/rust-server/rust-server-test.yaml index 85f7981b68cf..75f693b72073 100644 --- a/modules/openapi-generator/src/test/resources/2_0/rust-server/rust-server-test.yaml +++ b/modules/openapi-generator/src/test/resources/2_0/rust-server/rust-server-test.yaml @@ -12,6 +12,22 @@ paths: responses: '200': description: Success + /html: + post: + summary: Test HTML handling + consumes: [text/html] + produces: [text/html] + parameters: + - in: body + name: body + required: true + schema: + type: string + responses: + 200: + description: Success + schema: + type: string definitions: additionalPropertiesObject: description: An additionalPropertiesObject diff --git a/samples/server/petstore/rust-server/output/rust-server-test/README.md b/samples/server/petstore/rust-server/output/rust-server-test/README.md index efac790e2540..daaa9194d4c2 100644 --- a/samples/server/petstore/rust-server/output/rust-server-test/README.md +++ b/samples/server/petstore/rust-server/output/rust-server-test/README.md @@ -56,6 +56,7 @@ To run a client, follow one of the following simple steps: ``` cargo run --example client DummyGet +cargo run --example client HtmlPost ``` ### HTTPS diff --git a/samples/server/petstore/rust-server/output/rust-server-test/api/openapi.yaml b/samples/server/petstore/rust-server/output/rust-server-test/api/openapi.yaml index 0a96a7ce761b..468311e05a0c 100644 --- a/samples/server/petstore/rust-server/output/rust-server-test/api/openapi.yaml +++ b/samples/server/petstore/rust-server/output/rust-server-test/api/openapi.yaml @@ -13,6 +13,22 @@ paths: content: {} description: Success summary: A dummy endpoint to make the spec valid. + /html: + post: + requestBody: + content: + text/html: + schema: + type: string + required: true + responses: + 200: + content: + text/html: + schema: + type: string + description: Success + summary: Test HTML handling components: schemas: additionalPropertiesObject: diff --git a/samples/server/petstore/rust-server/output/rust-server-test/examples/client.rs b/samples/server/petstore/rust-server/output/rust-server-test/examples/client.rs index 51c9c627c0ab..7fa860193404 100644 --- a/samples/server/petstore/rust-server/output/rust-server-test/examples/client.rs +++ b/samples/server/petstore/rust-server/output/rust-server-test/examples/client.rs @@ -19,7 +19,8 @@ use tokio_core::reactor; #[allow(unused_imports)] use rust_server_test::{ApiNoContext, ContextWrapperExt, ApiError, - DummyGetResponse + DummyGetResponse, + HtmlPostResponse }; use clap::{App, Arg}; @@ -29,6 +30,7 @@ fn main() { .help("Sets the operation to run") .possible_values(&[ "DummyGet", + "HtmlPost", ]) .required(true) .index(1)) @@ -74,6 +76,11 @@ fn main() { println!("{:?} (X-Span-ID: {:?})", result, (client.context() as &Has).get().clone()); }, + Some("HtmlPost") => { + let result = core.run(client.html_post("body_example".to_string())); + println!("{:?} (X-Span-ID: {:?})", result, (client.context() as &Has).get().clone()); + }, + _ => { panic!("Invalid operation provided") } diff --git a/samples/server/petstore/rust-server/output/rust-server-test/examples/server_lib/server.rs b/samples/server/petstore/rust-server/output/rust-server-test/examples/server_lib/server.rs index e7b86f0313ec..e94645b09453 100644 --- a/samples/server/petstore/rust-server/output/rust-server-test/examples/server_lib/server.rs +++ b/samples/server/petstore/rust-server/output/rust-server-test/examples/server_lib/server.rs @@ -11,7 +11,8 @@ use swagger; use swagger::{Has, XSpanIdString}; use rust_server_test::{Api, ApiError, - DummyGetResponse + DummyGetResponse, + HtmlPostResponse }; use rust_server_test::models; @@ -35,4 +36,11 @@ impl Api for Server where C: Has{ Box::new(futures::failed("Generic failure".into())) } + /// Test HTML handling + fn html_post(&self, body: String, context: &C) -> Box> { + let context = context.clone(); + println!("html_post(\"{}\") - X-Span-ID: {:?}", body, context.get().0.clone()); + Box::new(futures::failed("Generic failure".into())) + } + } diff --git a/samples/server/petstore/rust-server/output/rust-server-test/src/client/mod.rs b/samples/server/petstore/rust-server/output/rust-server-test/src/client/mod.rs index 78aa49e0085f..5b771a2040a6 100644 --- a/samples/server/petstore/rust-server/output/rust-server-test/src/client/mod.rs +++ b/samples/server/petstore/rust-server/output/rust-server-test/src/client/mod.rs @@ -39,7 +39,8 @@ use swagger; use swagger::{ApiError, XSpanId, XSpanIdString, Has, AuthData}; use {Api, - DummyGetResponse + DummyGetResponse, + HtmlPostResponse }; use models; @@ -302,6 +303,77 @@ impl Api for Client where } + fn html_post(&self, param_body: String, context: &C) -> Box> { + + + let uri = format!( + "{}/html", + self.base_path + ); + + let uri = match Uri::from_str(&uri) { + Ok(uri) => uri, + Err(err) => return Box::new(futures::done(Err(ApiError(format!("Unable to build URI: {}", err))))), + }; + + let mut request = hyper::Request::new(hyper::Method::Post, uri); + + let body = param_body; + + + request.set_body(body.into_bytes()); + + + request.headers_mut().set(ContentType(mimetypes::requests::HTML_POST.clone())); + request.headers_mut().set(XSpanId((context as &Has).get().0.clone())); + + + Box::new(self.client_service.call(request) + .map_err(|e| ApiError(format!("No response received: {}", e))) + .and_then(|mut response| { + match response.status().as_u16() { + 200 => { + let body = response.body(); + Box::new( + body + .concat2() + .map_err(|e| ApiError(format!("Failed to read response: {}", e))) + .and_then(|body| str::from_utf8(&body) + .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e))) + .and_then(|body| + + Ok(body.to_string()) + + )) + .map(move |body| + HtmlPostResponse::Success(body) + ) + ) as Box> + }, + code => { + let headers = response.headers().clone(); + Box::new(response.body() + .take(100) + .concat2() + .then(move |body| + future::err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}", + code, + headers, + match body { + Ok(ref body) => match str::from_utf8(body) { + Ok(body) => Cow::from(body), + Err(e) => Cow::from(format!("", e)), + }, + Err(e) => Cow::from(format!("", e)), + }))) + ) + ) as Box> + } + } + })) + + } + } #[derive(Debug)] diff --git a/samples/server/petstore/rust-server/output/rust-server-test/src/lib.rs b/samples/server/petstore/rust-server/output/rust-server-test/src/lib.rs index 8cb366a2ffba..1cefa9c82789 100644 --- a/samples/server/petstore/rust-server/output/rust-server-test/src/lib.rs +++ b/samples/server/petstore/rust-server/output/rust-server-test/src/lib.rs @@ -45,6 +45,12 @@ pub enum DummyGetResponse { Success , } +#[derive(Debug, PartialEq)] +pub enum HtmlPostResponse { + /// Success + Success ( String ) , +} + /// API pub trait Api { @@ -52,6 +58,9 @@ pub trait Api { /// A dummy endpoint to make the spec valid. fn dummy_get(&self, context: &C) -> Box>; + /// Test HTML handling + fn html_post(&self, body: String, context: &C) -> Box>; + } /// API without a `Context` @@ -60,6 +69,9 @@ pub trait ApiNoContext { /// A dummy endpoint to make the spec valid. fn dummy_get(&self) -> Box>; + /// Test HTML handling + fn html_post(&self, body: String) -> Box>; + } /// Trait to extend an API to make it easy to bind it to a context. @@ -81,6 +93,11 @@ impl<'a, T: Api, C> ApiNoContext for ContextWrapper<'a, T, C> { self.api().dummy_get(&self.context()) } + /// Test HTML handling + fn html_post(&self, body: String) -> Box> { + self.api().html_post(body, &self.context()) + } + } #[cfg(feature = "client")] diff --git a/samples/server/petstore/rust-server/output/rust-server-test/src/mimetypes.rs b/samples/server/petstore/rust-server/output/rust-server-test/src/mimetypes.rs index 304c686b3e57..f2c9ff91715c 100644 --- a/samples/server/petstore/rust-server/output/rust-server-test/src/mimetypes.rs +++ b/samples/server/petstore/rust-server/output/rust-server-test/src/mimetypes.rs @@ -4,10 +4,18 @@ pub mod responses { use hyper::mime::*; // The macro is called per-operation to beat the recursion limit + /// Create Mime objects for the response content types for HtmlPost + lazy_static! { + pub static ref HTML_POST_SUCCESS: Mime = "text/html".parse().unwrap(); + } } pub mod requests { use hyper::mime::*; + /// Create Mime objects for the request content types for HtmlPost + lazy_static! { + pub static ref HTML_POST: Mime = "text/html".parse().unwrap(); + } } diff --git a/samples/server/petstore/rust-server/output/rust-server-test/src/server/mod.rs b/samples/server/petstore/rust-server/output/rust-server-test/src/server/mod.rs index 919e430b084d..2f796e35655b 100644 --- a/samples/server/petstore/rust-server/output/rust-server-test/src/server/mod.rs +++ b/samples/server/petstore/rust-server/output/rust-server-test/src/server/mod.rs @@ -37,7 +37,8 @@ use swagger::{ApiError, XSpanId, XSpanIdString, Has, RequestParser}; use swagger::auth::Scopes; use {Api, - DummyGetResponse + DummyGetResponse, + HtmlPostResponse }; #[allow(unused_imports)] use models; @@ -51,10 +52,12 @@ mod paths { lazy_static! { pub static ref GLOBAL_REGEX_SET: regex::RegexSet = regex::RegexSet::new(&[ - r"^/dummy$" + r"^/dummy$", + r"^/html$" ]).unwrap(); } pub static ID_DUMMY: usize = 0; + pub static ID_HTML: usize = 1; } pub struct NewService { @@ -166,6 +169,80 @@ where }, + // HtmlPost - POST /html + &hyper::Method::Post if path.matched(paths::ID_HTML) => { + + + + + + + // Body parameters (note that non-required body parameters will ignore garbage + // values, rather than causing a 400 response). Produce warning header and logs for + // any unused fields. + Box::new(body.concat2() + .then(move |result| -> Box> { + match result { + Ok(body) => { + let param_body: Option = if !body.is_empty() { + + match String::from_utf8(body.to_vec()) { + Ok(param_body) => Some(param_body), + Err(e) => return Box::new(future::ok(Response::new().with_status(StatusCode::BadRequest).with_body(format!("Couldn't parse body parameter body - not valid UTF-8: {}", e)))), + } + + } else { + None + }; + let param_body = match param_body { + Some(param_body) => param_body, + None => return Box::new(future::ok(Response::new().with_status(StatusCode::BadRequest).with_body("Missing required body parameter body"))), + }; + + + Box::new(api_impl.html_post(param_body, &context) + .then(move |result| { + let mut response = Response::new(); + response.headers_mut().set(XSpanId((&context as &Has).get().0.to_string())); + + match result { + Ok(rsp) => match rsp { + HtmlPostResponse::Success + + (body) + + + => { + response.set_status(StatusCode::try_from(200).unwrap()); + + response.headers_mut().set(ContentType(mimetypes::responses::HTML_POST_SUCCESS.clone())); + + + response.set_body(body); + }, + }, + Err(_) => { + // Application code returned an error. This should not happen, as the implementation should + // return a valid response. + response.set_status(StatusCode::InternalServerError); + response.set_body("An internal error occurred"); + }, + } + + future::ok(response) + } + )) + + + }, + Err(e) => Box::new(future::ok(Response::new().with_status(StatusCode::BadRequest).with_body(format!("Couldn't read body parameter body: {}", e)))), + } + }) + ) as Box> + + }, + + _ => Box::new(future::ok(Response::new().with_status(StatusCode::NotFound))) as Box>, } }