Skip to content

Commit

Permalink
Make juniper_hyper generic over hyper::body::Body (#1263, #1102)
Browse files Browse the repository at this point in the history
Co-authored-by: Christian Legnitto <[email protected]>
Co-authored-by: Kai Ren <[email protected]>
  • Loading branch information
3 people authored Sep 26, 2024
1 parent 86297a4 commit 257bc69
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 53 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,17 +153,17 @@ jobs:
- juniper_graphql_ws
- juniper_actix
- juniper_axum
- juniper_hyper
#- juniper_hyper
- juniper_rocket
- juniper_warp
os:
- ubuntu
- macOS
- windows
#include:
# - { msrv: "1.75.0", crate: "juniper_actix", os: "ubuntu" }
# - { msrv: "1.75.0", crate: "juniper_actix", os: "macOS" }
# - { msrv: "1.75.0", crate: "juniper_actix", os: "windows" }
include:
- { msrv: "1.79.0", crate: "juniper_hyper", os: "ubuntu" }
- { msrv: "1.79.0", crate: "juniper_hyper", os: "macOS" }
- { msrv: "1.79.0", crate: "juniper_hyper", os: "windows" }
runs-on: ${{ matrix.os }}-latest
steps:
- uses: actions/checkout@v4
Expand Down
6 changes: 4 additions & 2 deletions juniper_hyper/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ All user visible changes to `juniper_hyper` crate will be documented in this fil

### BC Breaks

- Bumped up [MSRV] to 1.75. ([#1272])
- Bumped up [MSRV] to 1.79. ([#1263])
- Made `hyper::Request` in `graphql()` and `graphql_sync()` functions generic over `B: hyper::body::Body`. ([#1263], [#1102])

[#1272]: /../../pull/1272
[#1102]: /../../issues/1102
[#1263]: /../../pull/1263



Expand Down
2 changes: 1 addition & 1 deletion juniper_hyper/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "juniper_hyper"
version = "0.9.0"
edition = "2021"
rust-version = "1.75"
rust-version = "1.79"
description = "`juniper` GraphQL integration with `hyper`."
license = "BSD-2-Clause"
authors = [
Expand Down
2 changes: 1 addition & 1 deletion juniper_hyper/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![Crates.io](https://img.shields.io/crates/v/juniper_hyper.svg?maxAge=2592000)](https://crates.io/crates/juniper_hyper)
[![Documentation](https://docs.rs/juniper_hyper/badge.svg)](https://docs.rs/juniper_hyper)
[![CI](https://github.com/graphql-rust/juniper/workflows/CI/badge.svg?branch=master "CI")](https://github.com/graphql-rust/juniper/actions?query=workflow%3ACI+branch%3Amaster)
[![Rust 1.75+](https://img.shields.io/badge/rustc-1.75+-lightgray.svg "Rust 1.75+")](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html)
[![Rust 1.79+](https://img.shields.io/badge/rustc-1.79+-lightgray.svg "Rust 1.79+")](https://blog.rust-lang.org/2024/06/13/Rust-1.79.0.html)

- [Changelog](https://github.com/graphql-rust/juniper/blob/juniper_hyper-v0.9.0/juniper_hyper/CHANGELOG.md)

Expand Down
163 changes: 119 additions & 44 deletions juniper_hyper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::{error::Error, fmt, string::FromUtf8Error, sync::Arc};

use http_body_util::BodyExt as _;
use hyper::{
body,
body::Body,
header::{self, HeaderValue},
Method, Request, Response, StatusCode,
};
Expand All @@ -15,10 +15,10 @@ use juniper::{
use serde_json::error::Error as SerdeError;
use url::form_urlencoded;

pub async fn graphql_sync<CtxT, QueryT, MutationT, SubscriptionT, S>(
pub async fn graphql_sync<CtxT, QueryT, MutationT, SubscriptionT, S, B>(
root_node: Arc<RootNode<'static, QueryT, MutationT, SubscriptionT, S>>,
context: Arc<CtxT>,
req: Request<body::Incoming>,
req: Request<B>,
) -> Response<String>
where
QueryT: GraphQLType<S, Context = CtxT>,
Expand All @@ -29,17 +29,18 @@ where
SubscriptionT::TypeInfo: Sync,
CtxT: Sync,
S: ScalarValue + Send + Sync,
B: Body<Error: fmt::Display>,
{
match parse_req(req).await {
Ok(req) => execute_request_sync(root_node, context, req).await,
Err(resp) => resp,
}
}

pub async fn graphql<CtxT, QueryT, MutationT, SubscriptionT, S>(
pub async fn graphql<CtxT, QueryT, MutationT, SubscriptionT, S, B>(
root_node: Arc<RootNode<'static, QueryT, MutationT, SubscriptionT, S>>,
context: Arc<CtxT>,
req: Request<body::Incoming>,
req: Request<B>,
) -> Response<String>
where
QueryT: GraphQLTypeAsync<S, Context = CtxT>,
Expand All @@ -50,16 +51,19 @@ where
SubscriptionT::TypeInfo: Sync,
CtxT: Sync,
S: ScalarValue + Send + Sync,
B: Body<Error: fmt::Display>,
{
match parse_req(req).await {
Ok(req) => execute_request(root_node, context, req).await,
Err(resp) => resp,
}
}

async fn parse_req<S: ScalarValue>(
req: Request<body::Incoming>,
) -> Result<GraphQLBatchRequest<S>, Response<String>> {
async fn parse_req<S, B>(req: Request<B>) -> Result<GraphQLBatchRequest<S>, Response<String>>
where
S: ScalarValue,
B: Body<Error: fmt::Display>,
{
match *req.method() {
Method::GET => parse_get_req(req),
Method::POST => {
Expand All @@ -78,9 +82,11 @@ async fn parse_req<S: ScalarValue>(
.map_err(render_error)
}

fn parse_get_req<S: ScalarValue>(
req: Request<body::Incoming>,
) -> Result<GraphQLBatchRequest<S>, GraphQLRequestError> {
fn parse_get_req<S, B>(req: Request<B>) -> Result<GraphQLBatchRequest<S>, GraphQLRequestError<B>>
where
S: ScalarValue,
B: Body,
{
req.uri()
.query()
.map(|q| gql_request_from_get(q).map(GraphQLBatchRequest::Single))
Expand All @@ -91,9 +97,13 @@ fn parse_get_req<S: ScalarValue>(
})
}

async fn parse_post_json_req<S: ScalarValue>(
body: body::Incoming,
) -> Result<GraphQLBatchRequest<S>, GraphQLRequestError> {
async fn parse_post_json_req<S, B>(
body: B,
) -> Result<GraphQLBatchRequest<S>, GraphQLRequestError<B>>
where
S: ScalarValue,
B: Body,
{
let chunk = body
.collect()
.await
Expand All @@ -106,9 +116,13 @@ async fn parse_post_json_req<S: ScalarValue>(
.map_err(GraphQLRequestError::BodyJSONError)
}

async fn parse_post_graphql_req<S: ScalarValue>(
body: body::Incoming,
) -> Result<GraphQLBatchRequest<S>, GraphQLRequestError> {
async fn parse_post_graphql_req<S, B>(
body: B,
) -> Result<GraphQLBatchRequest<S>, GraphQLRequestError<B>>
where
S: ScalarValue,
B: Body,
{
let chunk = body
.collect()
.await
Expand Down Expand Up @@ -143,7 +157,10 @@ pub async fn playground(
resp
}

fn render_error(err: GraphQLRequestError) -> Response<String> {
fn render_error<B>(err: GraphQLRequestError<B>) -> Response<String>
where
B: Body<Error: fmt::Display>,
{
let mut resp = new_response(StatusCode::BAD_REQUEST);
*resp.body_mut() = err.to_string();
resp
Expand Down Expand Up @@ -211,9 +228,12 @@ where
resp
}

fn gql_request_from_get<S>(input: &str) -> Result<JuniperGraphQLRequest<S>, GraphQLRequestError>
fn gql_request_from_get<S, B>(
input: &str,
) -> Result<JuniperGraphQLRequest<S>, GraphQLRequestError<B>>
where
S: ScalarValue,
B: Body,
{
let mut query = None;
let mut operation_name = None;
Expand Down Expand Up @@ -254,7 +274,7 @@ where
}
}

fn invalid_err(parameter_name: &str) -> GraphQLRequestError {
fn invalid_err<B: Body>(parameter_name: &str) -> GraphQLRequestError<B> {
GraphQLRequestError::Invalid(format!(
"`{parameter_name}` parameter is specified multiple times",
))
Expand All @@ -275,35 +295,57 @@ fn new_html_response(code: StatusCode) -> Response<String> {
resp
}

#[derive(Debug)]
enum GraphQLRequestError {
BodyHyper(hyper::Error),
enum GraphQLRequestError<B: Body> {
BodyHyper(B::Error),
BodyUtf8(FromUtf8Error),
BodyJSONError(SerdeError),
Variables(SerdeError),
Invalid(String),
}

impl fmt::Display for GraphQLRequestError {
// NOTE: Manual implementation instead of `#[derive(Debug)]` is used to omit imposing unnecessary
// `B: Debug` bound on the implementation.
impl<B> fmt::Debug for GraphQLRequestError<B>
where
B: Body<Error: fmt::Debug>,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
GraphQLRequestError::BodyHyper(err) => fmt::Display::fmt(err, f),
GraphQLRequestError::BodyUtf8(err) => fmt::Display::fmt(err, f),
GraphQLRequestError::BodyJSONError(err) => fmt::Display::fmt(err, f),
GraphQLRequestError::Variables(err) => fmt::Display::fmt(err, f),
GraphQLRequestError::Invalid(err) => fmt::Display::fmt(err, f),
Self::BodyHyper(e) => fmt::Debug::fmt(e, f),
Self::BodyUtf8(e) => fmt::Debug::fmt(e, f),
Self::BodyJSONError(e) => fmt::Debug::fmt(e, f),
Self::Variables(e) => fmt::Debug::fmt(e, f),
Self::Invalid(e) => fmt::Debug::fmt(e, f),
}
}
}

impl Error for GraphQLRequestError {
impl<B> fmt::Display for GraphQLRequestError<B>
where
B: Body<Error: fmt::Display>,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::BodyHyper(e) => fmt::Display::fmt(e, f),
Self::BodyUtf8(e) => fmt::Display::fmt(e, f),
Self::BodyJSONError(e) => fmt::Display::fmt(e, f),
Self::Variables(e) => fmt::Display::fmt(e, f),
Self::Invalid(e) => fmt::Display::fmt(e, f),
}
}
}

impl<B> Error for GraphQLRequestError<B>
where
B: Body<Error: Error + 'static>,
{
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
GraphQLRequestError::BodyHyper(err) => Some(err),
GraphQLRequestError::BodyUtf8(err) => Some(err),
GraphQLRequestError::BodyJSONError(err) => Some(err),
GraphQLRequestError::Variables(err) => Some(err),
GraphQLRequestError::Invalid(_) => None,
Self::BodyHyper(e) => Some(e),
Self::BodyUtf8(e) => Some(e),
Self::BodyJSONError(e) => Some(e),
Self::Variables(e) => Some(e),
Self::Invalid(_) => None,
}
}
}
Expand All @@ -314,7 +356,11 @@ mod tests {
convert::Infallible, error::Error, net::SocketAddr, panic, sync::Arc, time::Duration,
};

use hyper::{server::conn::http1, service::service_fn, Method, Response, StatusCode};
use http_body_util::BodyExt as _;
use hyper::{
body::Incoming, server::conn::http1, service::service_fn, Method, Request, Response,
StatusCode,
};
use hyper_util::rt::TokioIo;
use juniper::{
http::tests as http_tests,
Expand Down Expand Up @@ -376,8 +422,7 @@ mod tests {
}
}

async fn run_hyper_integration(is_sync: bool) {
let port = if is_sync { 3002 } else { 3001 };
async fn run_hyper_integration(port: u16, is_sync: bool, is_custom_type: bool) {
let addr = SocketAddr::from(([127, 0, 0, 1], port));

let db = Arc::new(Database::new());
Expand Down Expand Up @@ -405,7 +450,7 @@ mod tests {
if let Err(e) = http1::Builder::new()
.serve_connection(
io,
service_fn(move |req| {
service_fn(move |req: Request<Incoming>| {
let root_node = root_node.clone();
let db = db.clone();
let matches = {
Expand All @@ -419,10 +464,30 @@ mod tests {
};
async move {
Ok::<_, Infallible>(if matches {
if is_sync {
super::graphql_sync(root_node, db, req).await
if is_custom_type {
let (parts, mut body) = req.into_parts();
let body = {
let mut buf = String::new();
if let Some(Ok(frame)) = body.frame().await {
if let Ok(bytes) = frame.into_data() {
buf = String::from_utf8_lossy(&bytes)
.to_string();
}
}
buf
};
let req = Request::from_parts(parts, body);
if is_sync {
super::graphql_sync(root_node, db, req).await
} else {
super::graphql(root_node, db, req).await
}
} else {
super::graphql(root_node, db, req).await
if is_sync {
super::graphql_sync(root_node, db, req).await
} else {
super::graphql(root_node, db, req).await
}
}
} else {
let mut resp = Response::new(String::new());
Expand Down Expand Up @@ -460,11 +525,21 @@ mod tests {

#[tokio::test]
async fn test_hyper_integration() {
run_hyper_integration(false).await
run_hyper_integration(3000, false, false).await
}

#[tokio::test]
async fn test_sync_hyper_integration() {
run_hyper_integration(true).await
run_hyper_integration(3001, true, false).await
}

#[tokio::test]
async fn test_custom_request_hyper_integration() {
run_hyper_integration(3002, false, false).await
}

#[tokio::test]
async fn test_custom_request_sync_hyper_integration() {
run_hyper_integration(3003, true, true).await
}
}

0 comments on commit 257bc69

Please sign in to comment.