Merge pull request #784 from dungeonfog/actix-cbor-support
Actix integration: cbor response support + error handling improvements
This commit is contained in:
commit
e5141b0f64
|
@ -24,7 +24,13 @@ serde_json = "1.0.64"
|
||||||
serde_urlencoded = "0.7.0"
|
serde_urlencoded = "0.7.0"
|
||||||
futures-channel = "0.3.13"
|
futures-channel = "0.3.13"
|
||||||
thiserror = "1.0.30"
|
thiserror = "1.0.30"
|
||||||
|
serde_cbor = { version = "0.11.2", optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
cbor = ["serde_cbor"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.2.0"
|
actix-rt = "2.2.0"
|
||||||
async-mutex = "1.4.0"
|
async-mutex = "1.4.0"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use actix_http::body::BoxBody;
|
use actix_http::body::BoxBody;
|
||||||
|
use actix_web::error::JsonPayloadError;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::io::{self, ErrorKind};
|
use std::io::{self, ErrorKind};
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
@ -153,20 +154,62 @@ impl From<async_graphql::BatchResponse> for GraphQLResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cbor")]
|
||||||
|
mod cbor {
|
||||||
|
use actix_web::{http::StatusCode, ResponseError};
|
||||||
|
use core::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Error(pub serde_cbor::Error);
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ResponseError for Error {
|
||||||
|
fn status_code(&self) -> StatusCode {
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Responder for GraphQLResponse {
|
impl Responder for GraphQLResponse {
|
||||||
type Body = BoxBody;
|
type Body = BoxBody;
|
||||||
|
|
||||||
fn respond_to(self, _req: &HttpRequest) -> HttpResponse {
|
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
|
||||||
let mut res = HttpResponse::build(StatusCode::OK);
|
let mut res = HttpResponse::build(StatusCode::OK);
|
||||||
res.content_type("application/json");
|
|
||||||
if self.0.is_ok() {
|
if self.0.is_ok() {
|
||||||
if let Some(cache_control) = self.0.cache_control().value() {
|
if let Some(cache_control) = self.0.cache_control().value() {
|
||||||
res.append_header(("cache-control", cache_control));
|
res.append_header((http::header::CACHE_CONTROL, cache_control));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (name, value) in self.0.http_headers() {
|
for (name, value) in self.0.http_headers() {
|
||||||
res.append_header((name, value));
|
res.append_header((name, value));
|
||||||
}
|
}
|
||||||
res.body(serde_json::to_string(&self.0).unwrap())
|
let accept = req
|
||||||
|
.headers()
|
||||||
|
.get(http::header::ACCEPT)
|
||||||
|
.and_then(|val| val.to_str().ok());
|
||||||
|
let (ct, body) = match accept {
|
||||||
|
// optional cbor support
|
||||||
|
#[cfg(feature = "cbor")]
|
||||||
|
// this avoids copy-pasting the mime type
|
||||||
|
Some(ct @ "application/cbor") => (
|
||||||
|
ct,
|
||||||
|
match serde_cbor::to_vec(&self.0) {
|
||||||
|
Ok(body) => body,
|
||||||
|
Err(e) => return HttpResponse::from_error(cbor::Error(e)),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_ => (
|
||||||
|
"application/json",
|
||||||
|
match serde_json::to_vec(&self.0) {
|
||||||
|
Ok(body) => body,
|
||||||
|
Err(e) => return HttpResponse::from_error(JsonPayloadError::Serialize(e)),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
};
|
||||||
|
res.content_type(ct);
|
||||||
|
res.body(body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -220,3 +220,51 @@ async fn test_count() {
|
||||||
.into_bytes()
|
.into_bytes()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cbor")]
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_cbor() {
|
||||||
|
let srv = test::init_service(
|
||||||
|
App::new()
|
||||||
|
.app_data(Data::new(Schema::new(
|
||||||
|
AddQueryRoot,
|
||||||
|
EmptyMutation,
|
||||||
|
EmptySubscription,
|
||||||
|
)))
|
||||||
|
.service(
|
||||||
|
web::resource("/")
|
||||||
|
.guard(guard::Post())
|
||||||
|
.to(gql_handle_schema::<AddQueryRoot, EmptyMutation, EmptySubscription>),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let response = srv
|
||||||
|
.call(
|
||||||
|
test::TestRequest::with_uri("/")
|
||||||
|
.method(Method::POST)
|
||||||
|
.set_payload(r#"{"query":"{ add(a: 10, b: 20) }"}"#)
|
||||||
|
.insert_header((actix_http::header::ACCEPT, "application/cbor"))
|
||||||
|
.to_request(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
#[derive(Debug, serde::Deserialize, PartialEq)]
|
||||||
|
struct Response {
|
||||||
|
data: ResponseInner,
|
||||||
|
}
|
||||||
|
#[derive(Debug, serde::Deserialize, PartialEq)]
|
||||||
|
struct ResponseInner {
|
||||||
|
add: i32,
|
||||||
|
}
|
||||||
|
let body = actix_web::body::to_bytes(response.into_body())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let response: Response = serde_cbor::from_slice(&body).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
response,
|
||||||
|
Response {
|
||||||
|
data: ResponseInner { add: 30 }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user