Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic JSON body support in rust-server #1523

Merged
merged 2 commits into from
Dec 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ use rust_server_test::{ApiNoContext, ContextWrapperExt,
DummyGetResponse,
DummyPutResponse,
FileResponseGetResponse,
HtmlPostResponse
HtmlPostResponse,
RawJsonGetResponse
};
use clap::{App, Arg};

Expand All @@ -35,6 +36,7 @@ fn main() {
"DummyPut",
"FileResponseGet",
"HtmlPost",
"RawJsonGet",
])
.required(true)
.index(1))
Expand Down Expand Up @@ -95,6 +97,11 @@ fn main() {
println!("{:?} (X-Span-ID: {:?})", result, (client.context() as &Has<XSpanIdString>).get().clone());
},

Some("RawJsonGet") => {
let result = core.run(client.raw_json_get());
println!("{:?} (X-Span-ID: {:?})", result, (client.context() as &Has<XSpanIdString>).get().clone());
},

_ => {
panic!("Invalid operation provided")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ use rust_server_test::{Api, ApiError,
DummyGetResponse,
DummyPutResponse,
FileResponseGetResponse,
HtmlPostResponse
HtmlPostResponse,
RawJsonGetResponse
};
use rust_server_test::models;

Expand Down Expand Up @@ -59,4 +60,11 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString>{
Box::new(futures::failed("Generic failure".into()))
}

/// Get an arbitrary JSON blob.
fn raw_json_get(&self, context: &C) -> Box<Future<Item=RawJsonGetResponse, Error=ApiError>> {
let context = context.clone();
println!("raw_json_get() - X-Span-ID: {:?}", context.get().0.clone());
Box::new(futures::failed("Generic failure".into()))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ use {Api,
DummyGetResponse,
DummyPutResponse,
FileResponseGetResponse,
HtmlPostResponse
HtmlPostResponse,
RawJsonGetResponse
};
use models;

Expand Down Expand Up @@ -508,6 +509,73 @@ if let Some(body) = body {

}

fn raw_json_get(&self, context: &C) -> Box<Future<Item=RawJsonGetResponse, Error=ApiError>> {


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<XSpanIdString>).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::<serde_json::Value>(body)
.map_err(|e| e.into())

))
.map(move |body|
RawJsonGetResponse::Success(body)
)
) as Box<Future<Item=_, Error=_>>
},
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!("<Body was not UTF8: {:?}>", e)),
},
Err(e) => Cow::from(format!("<Failed to read body: {}>", e)),
})))
)
) as Box<Future<Item=_, Error=_>>
}
}
}))

}

}

#[derive(Debug)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ pub enum HtmlPostResponse {
Success ( String ) ,
}

#[derive(Debug, PartialEq)]
pub enum RawJsonGetResponse {
/// Success
Success ( serde_json::Value ) ,
}


/// API
pub trait Api<C> {
Expand All @@ -79,6 +85,9 @@ pub trait Api<C> {
/// Test HTML handling
fn html_post(&self, body: String, context: &C) -> Box<Future<Item=HtmlPostResponse, Error=ApiError>>;

/// Get an arbitrary JSON blob.
fn raw_json_get(&self, context: &C) -> Box<Future<Item=RawJsonGetResponse, Error=ApiError>>;

}

/// API without a `Context`
Expand All @@ -96,6 +105,9 @@ pub trait ApiNoContext {
/// Test HTML handling
fn html_post(&self, body: String) -> Box<Future<Item=HtmlPostResponse, Error=ApiError>>;

/// Get an arbitrary JSON blob.
fn raw_json_get(&self) -> Box<Future<Item=RawJsonGetResponse, Error=ApiError>>;

}

/// Trait to extend an API to make it easy to bind it to a context.
Expand Down Expand Up @@ -132,6 +144,11 @@ impl<'a, T: Api<C>, 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<Future<Item=RawJsonGetResponse, Error=ApiError>> {
self.api().raw_json_get(&self.context())
}

}

#[cfg(feature = "client")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ use {Api,
DummyGetResponse,
DummyPutResponse,
FileResponseGetResponse,
HtmlPostResponse
HtmlPostResponse,
RawJsonGetResponse
};
#[allow(unused_imports)]
use models;
Expand All @@ -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<T, C> {
Expand Down Expand Up @@ -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<XSpanIdString>).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<Future<Item=Response, Error=Error>>


},


_ => Box::new(future::ok(Response::new().with_status(StatusCode::NotFound))) as Box<Future<Item=Response, Error=Error>>,
}
}
Expand Down Expand Up @@ -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(()),
}
}
Expand Down