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 01dc433229c6..445850a01ddb 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 @@ -621,6 +621,12 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation rsp.vendorExtensions.put("producesPlainText", true); } else { rsp.vendorExtensions.put("producesJson", true); + // If the data type is just "object", then ensure that the Rust data type + // is "serde_json::Value". This allows us to define APIs that + // can return arbitrary JSON bodies. + if (rsp.dataType.equals("object")) { + rsp.dataType = "serde_json::Value"; + } } Schema response = (Schema) rsp.schema; 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 5642c8bc113f..e8065080038f 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 @@ -37,11 +37,38 @@ paths: /file_response: get: summary: Get a file + produces: + - application/json responses: 200: description: Success schema: type: file + /raw_json: + get: + summary: Get an arbitrary JSON blob. + responses: + 200: + description: Success + schema: + type: object + # Requests with arbitrary JSON currently fail. + # post: + # summary: Send an arbitrary JSON blob + # consumes: + # - application/json + # parameters: + # - in: body + # name: body + # required: true + # schema: + # type: object + # responses: + # 200: + # description: Success + # schema: + # type: string + parameters: nested_response: name: nested_response 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 3d3469d47e0f..49af89f0718f 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 @@ -59,6 +59,7 @@ cargo run --example client DummyGet cargo run --example client DummyPut cargo run --example client FileResponseGet cargo run --example client HtmlPost +cargo run --example client RawJsonGet ``` ### 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 9e97e1f3d2a9..608408baae27 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 @@ -53,12 +53,22 @@ paths: responses: 200: content: - '*/*': + application/json: schema: format: binary type: string description: Success summary: Get a file + /raw_json: + get: + responses: + 200: + content: + '*/*': + schema: + type: object + description: Success + summary: Get an arbitrary JSON blob. components: requestBodies: nested_response: 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 3f877af3e098..6f189534fbb3 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 @@ -22,7 +22,8 @@ use rust_server_test::{ApiNoContext, ContextWrapperExt, DummyGetResponse, DummyPutResponse, FileResponseGetResponse, - HtmlPostResponse + HtmlPostResponse, + RawJsonGetResponse }; use clap::{App, Arg}; @@ -35,6 +36,7 @@ fn main() { "DummyPut", "FileResponseGet", "HtmlPost", + "RawJsonGet", ]) .required(true) .index(1)) @@ -95,6 +97,11 @@ fn main() { println!("{:?} (X-Span-ID: {:?})", result, (client.context() as &Has).get().clone()); }, + Some("RawJsonGet") => { + let result = core.run(client.raw_json_get()); + 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 ca0d0ff2ac56..a1067282c4b3 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 @@ -14,7 +14,8 @@ use rust_server_test::{Api, ApiError, DummyGetResponse, DummyPutResponse, FileResponseGetResponse, - HtmlPostResponse + HtmlPostResponse, + RawJsonGetResponse }; use rust_server_test::models; @@ -59,4 +60,11 @@ impl Api for Server where C: Has{ Box::new(futures::failed("Generic failure".into())) } + /// Get an arbitrary JSON blob. + fn raw_json_get(&self, context: &C) -> Box> { + let context = context.clone(); + println!("raw_json_get() - X-Span-ID: {:?}", 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 113aa2b01c9b..a487915c49eb 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 @@ -42,7 +42,8 @@ use {Api, DummyGetResponse, DummyPutResponse, FileResponseGetResponse, - HtmlPostResponse + HtmlPostResponse, + RawJsonGetResponse }; use models; @@ -508,6 +509,73 @@ if let Some(body) = body { } + fn raw_json_get(&self, context: &C) -> Box> { + + + let uri = format!( + "{}/raw_json", + 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::Get, uri); + + + + 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| + + serde_json::from_str::(body) + .map_err(|e| e.into()) + + )) + .map(move |body| + RawJsonGetResponse::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 277bf41c40cd..b319918858d2 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 @@ -63,6 +63,12 @@ pub enum HtmlPostResponse { Success ( String ) , } +#[derive(Debug, PartialEq)] +pub enum RawJsonGetResponse { + /// Success + Success ( serde_json::Value ) , +} + /// API pub trait Api { @@ -79,6 +85,9 @@ pub trait Api { /// Test HTML handling fn html_post(&self, body: String, context: &C) -> Box>; + /// Get an arbitrary JSON blob. + fn raw_json_get(&self, context: &C) -> Box>; + } /// API without a `Context` @@ -96,6 +105,9 @@ pub trait ApiNoContext { /// Test HTML handling fn html_post(&self, body: String) -> Box>; + /// Get an arbitrary JSON blob. + fn raw_json_get(&self) -> Box>; + } /// Trait to extend an API to make it easy to bind it to a context. @@ -132,6 +144,11 @@ impl<'a, T: Api, C> ApiNoContext for ContextWrapper<'a, T, C> { self.api().html_post(body, &self.context()) } + /// Get an arbitrary JSON blob. + fn raw_json_get(&self) -> Box> { + self.api().raw_json_get(&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 679f683a0bfa..01fdce1e9025 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 @@ -6,12 +6,16 @@ pub mod responses { // The macro is called per-operation to beat the recursion limit /// Create Mime objects for the response content types for FileResponseGet lazy_static! { - pub static ref FILE_RESPONSE_GET_SUCCESS: Mime = "*/*".parse().unwrap(); + pub static ref FILE_RESPONSE_GET_SUCCESS: Mime = "application/json".parse().unwrap(); } /// Create Mime objects for the response content types for HtmlPost lazy_static! { pub static ref HTML_POST_SUCCESS: Mime = "text/html".parse().unwrap(); } + /// Create Mime objects for the response content types for RawJsonGet + lazy_static! { + pub static ref RAW_JSON_GET_SUCCESS: Mime = "*/*".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 28666bec2559..852da4aab3b8 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 @@ -40,7 +40,8 @@ use {Api, DummyGetResponse, DummyPutResponse, FileResponseGetResponse, - HtmlPostResponse + HtmlPostResponse, + RawJsonGetResponse }; #[allow(unused_imports)] use models; @@ -56,12 +57,14 @@ mod paths { pub static ref GLOBAL_REGEX_SET: regex::RegexSet = regex::RegexSet::new(&[ r"^/dummy$", r"^/file_response$", - r"^/html$" + r"^/html$", + r"^/raw_json$" ]).unwrap(); } pub static ID_DUMMY: usize = 0; pub static ID_FILE_RESPONSE: usize = 1; pub static ID_HTML: usize = 2; + pub static ID_RAW_JSON: usize = 3; } pub struct NewService { @@ -377,6 +380,60 @@ where }, + // RawJsonGet - GET /raw_json + &hyper::Method::Get if path.matched(paths::ID_RAW_JSON) => { + + + + + + + + Box::new({ + {{ + + Box::new(api_impl.raw_json_get(&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 { + RawJsonGetResponse::Success + + (body) + + + => { + response.set_status(StatusCode::try_from(200).unwrap()); + + response.headers_mut().set(ContentType(mimetypes::responses::RAW_JSON_GET_SUCCESS.clone())); + + + let body = serde_json::to_string(&body).expect("impossible to fail to serialize"); + + 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) + } + )) + + }} + }) as Box> + + + }, + + _ => Box::new(future::ok(Response::new().with_status(StatusCode::NotFound))) as Box>, } } @@ -410,6 +467,9 @@ impl RequestParser for ApiRequestParser { // HtmlPost - POST /html &hyper::Method::Post if path.matched(paths::ID_HTML) => Ok("HtmlPost"), + + // RawJsonGet - GET /raw_json + &hyper::Method::Get if path.matched(paths::ID_RAW_JSON) => Ok("RawJsonGet"), _ => Err(()), } }