Co-authored-by: Christian Legnitto <LegNeato@users.noreply.github.com> Co-authored-by: Kai Ren <tyranron@gmail.com>
This commit is contained in:
parent
86297a42c6
commit
257bc69dde
5 changed files with 130 additions and 53 deletions
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
|
@ -153,17 +153,17 @@ jobs:
|
||||||
- juniper_graphql_ws
|
- juniper_graphql_ws
|
||||||
- juniper_actix
|
- juniper_actix
|
||||||
- juniper_axum
|
- juniper_axum
|
||||||
- juniper_hyper
|
#- juniper_hyper
|
||||||
- juniper_rocket
|
- juniper_rocket
|
||||||
- juniper_warp
|
- juniper_warp
|
||||||
os:
|
os:
|
||||||
- ubuntu
|
- ubuntu
|
||||||
- macOS
|
- macOS
|
||||||
- windows
|
- windows
|
||||||
#include:
|
include:
|
||||||
# - { msrv: "1.75.0", crate: "juniper_actix", os: "ubuntu" }
|
- { msrv: "1.79.0", crate: "juniper_hyper", os: "ubuntu" }
|
||||||
# - { msrv: "1.75.0", crate: "juniper_actix", os: "macOS" }
|
- { msrv: "1.79.0", crate: "juniper_hyper", os: "macOS" }
|
||||||
# - { msrv: "1.75.0", crate: "juniper_actix", os: "windows" }
|
- { msrv: "1.79.0", crate: "juniper_hyper", os: "windows" }
|
||||||
runs-on: ${{ matrix.os }}-latest
|
runs-on: ${{ matrix.os }}-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
|
@ -10,9 +10,11 @@ All user visible changes to `juniper_hyper` crate will be documented in this fil
|
||||||
|
|
||||||
### BC Breaks
|
### 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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
name = "juniper_hyper"
|
name = "juniper_hyper"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.75"
|
rust-version = "1.79"
|
||||||
description = "`juniper` GraphQL integration with `hyper`."
|
description = "`juniper` GraphQL integration with `hyper`."
|
||||||
license = "BSD-2-Clause"
|
license = "BSD-2-Clause"
|
||||||
authors = [
|
authors = [
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
[![Crates.io](https://img.shields.io/crates/v/juniper_hyper.svg?maxAge=2592000)](https://crates.io/crates/juniper_hyper)
|
[![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)
|
[![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)
|
[![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)
|
- [Changelog](https://github.com/graphql-rust/juniper/blob/juniper_hyper-v0.9.0/juniper_hyper/CHANGELOG.md)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::{error::Error, fmt, string::FromUtf8Error, sync::Arc};
|
||||||
|
|
||||||
use http_body_util::BodyExt as _;
|
use http_body_util::BodyExt as _;
|
||||||
use hyper::{
|
use hyper::{
|
||||||
body,
|
body::Body,
|
||||||
header::{self, HeaderValue},
|
header::{self, HeaderValue},
|
||||||
Method, Request, Response, StatusCode,
|
Method, Request, Response, StatusCode,
|
||||||
};
|
};
|
||||||
|
@ -15,10 +15,10 @@ use juniper::{
|
||||||
use serde_json::error::Error as SerdeError;
|
use serde_json::error::Error as SerdeError;
|
||||||
use url::form_urlencoded;
|
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>>,
|
root_node: Arc<RootNode<'static, QueryT, MutationT, SubscriptionT, S>>,
|
||||||
context: Arc<CtxT>,
|
context: Arc<CtxT>,
|
||||||
req: Request<body::Incoming>,
|
req: Request<B>,
|
||||||
) -> Response<String>
|
) -> Response<String>
|
||||||
where
|
where
|
||||||
QueryT: GraphQLType<S, Context = CtxT>,
|
QueryT: GraphQLType<S, Context = CtxT>,
|
||||||
|
@ -29,6 +29,7 @@ where
|
||||||
SubscriptionT::TypeInfo: Sync,
|
SubscriptionT::TypeInfo: Sync,
|
||||||
CtxT: Sync,
|
CtxT: Sync,
|
||||||
S: ScalarValue + Send + Sync,
|
S: ScalarValue + Send + Sync,
|
||||||
|
B: Body<Error: fmt::Display>,
|
||||||
{
|
{
|
||||||
match parse_req(req).await {
|
match parse_req(req).await {
|
||||||
Ok(req) => execute_request_sync(root_node, context, req).await,
|
Ok(req) => execute_request_sync(root_node, context, req).await,
|
||||||
|
@ -36,10 +37,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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>>,
|
root_node: Arc<RootNode<'static, QueryT, MutationT, SubscriptionT, S>>,
|
||||||
context: Arc<CtxT>,
|
context: Arc<CtxT>,
|
||||||
req: Request<body::Incoming>,
|
req: Request<B>,
|
||||||
) -> Response<String>
|
) -> Response<String>
|
||||||
where
|
where
|
||||||
QueryT: GraphQLTypeAsync<S, Context = CtxT>,
|
QueryT: GraphQLTypeAsync<S, Context = CtxT>,
|
||||||
|
@ -50,6 +51,7 @@ where
|
||||||
SubscriptionT::TypeInfo: Sync,
|
SubscriptionT::TypeInfo: Sync,
|
||||||
CtxT: Sync,
|
CtxT: Sync,
|
||||||
S: ScalarValue + Send + Sync,
|
S: ScalarValue + Send + Sync,
|
||||||
|
B: Body<Error: fmt::Display>,
|
||||||
{
|
{
|
||||||
match parse_req(req).await {
|
match parse_req(req).await {
|
||||||
Ok(req) => execute_request(root_node, context, req).await,
|
Ok(req) => execute_request(root_node, context, req).await,
|
||||||
|
@ -57,9 +59,11 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn parse_req<S: ScalarValue>(
|
async fn parse_req<S, B>(req: Request<B>) -> Result<GraphQLBatchRequest<S>, Response<String>>
|
||||||
req: Request<body::Incoming>,
|
where
|
||||||
) -> Result<GraphQLBatchRequest<S>, Response<String>> {
|
S: ScalarValue,
|
||||||
|
B: Body<Error: fmt::Display>,
|
||||||
|
{
|
||||||
match *req.method() {
|
match *req.method() {
|
||||||
Method::GET => parse_get_req(req),
|
Method::GET => parse_get_req(req),
|
||||||
Method::POST => {
|
Method::POST => {
|
||||||
|
@ -78,9 +82,11 @@ async fn parse_req<S: ScalarValue>(
|
||||||
.map_err(render_error)
|
.map_err(render_error)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_get_req<S: ScalarValue>(
|
fn parse_get_req<S, B>(req: Request<B>) -> Result<GraphQLBatchRequest<S>, GraphQLRequestError<B>>
|
||||||
req: Request<body::Incoming>,
|
where
|
||||||
) -> Result<GraphQLBatchRequest<S>, GraphQLRequestError> {
|
S: ScalarValue,
|
||||||
|
B: Body,
|
||||||
|
{
|
||||||
req.uri()
|
req.uri()
|
||||||
.query()
|
.query()
|
||||||
.map(|q| gql_request_from_get(q).map(GraphQLBatchRequest::Single))
|
.map(|q| gql_request_from_get(q).map(GraphQLBatchRequest::Single))
|
||||||
|
@ -91,9 +97,13 @@ fn parse_get_req<S: ScalarValue>(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn parse_post_json_req<S: ScalarValue>(
|
async fn parse_post_json_req<S, B>(
|
||||||
body: body::Incoming,
|
body: B,
|
||||||
) -> Result<GraphQLBatchRequest<S>, GraphQLRequestError> {
|
) -> Result<GraphQLBatchRequest<S>, GraphQLRequestError<B>>
|
||||||
|
where
|
||||||
|
S: ScalarValue,
|
||||||
|
B: Body,
|
||||||
|
{
|
||||||
let chunk = body
|
let chunk = body
|
||||||
.collect()
|
.collect()
|
||||||
.await
|
.await
|
||||||
|
@ -106,9 +116,13 @@ async fn parse_post_json_req<S: ScalarValue>(
|
||||||
.map_err(GraphQLRequestError::BodyJSONError)
|
.map_err(GraphQLRequestError::BodyJSONError)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn parse_post_graphql_req<S: ScalarValue>(
|
async fn parse_post_graphql_req<S, B>(
|
||||||
body: body::Incoming,
|
body: B,
|
||||||
) -> Result<GraphQLBatchRequest<S>, GraphQLRequestError> {
|
) -> Result<GraphQLBatchRequest<S>, GraphQLRequestError<B>>
|
||||||
|
where
|
||||||
|
S: ScalarValue,
|
||||||
|
B: Body,
|
||||||
|
{
|
||||||
let chunk = body
|
let chunk = body
|
||||||
.collect()
|
.collect()
|
||||||
.await
|
.await
|
||||||
|
@ -143,7 +157,10 @@ pub async fn playground(
|
||||||
resp
|
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);
|
let mut resp = new_response(StatusCode::BAD_REQUEST);
|
||||||
*resp.body_mut() = err.to_string();
|
*resp.body_mut() = err.to_string();
|
||||||
resp
|
resp
|
||||||
|
@ -211,9 +228,12 @@ where
|
||||||
resp
|
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
|
where
|
||||||
S: ScalarValue,
|
S: ScalarValue,
|
||||||
|
B: Body,
|
||||||
{
|
{
|
||||||
let mut query = None;
|
let mut query = None;
|
||||||
let mut operation_name = None;
|
let mut operation_name = None;
|
||||||
|
@ -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!(
|
GraphQLRequestError::Invalid(format!(
|
||||||
"`{parameter_name}` parameter is specified multiple times",
|
"`{parameter_name}` parameter is specified multiple times",
|
||||||
))
|
))
|
||||||
|
@ -275,35 +295,57 @@ fn new_html_response(code: StatusCode) -> Response<String> {
|
||||||
resp
|
resp
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
enum GraphQLRequestError<B: Body> {
|
||||||
enum GraphQLRequestError {
|
BodyHyper(B::Error),
|
||||||
BodyHyper(hyper::Error),
|
|
||||||
BodyUtf8(FromUtf8Error),
|
BodyUtf8(FromUtf8Error),
|
||||||
BodyJSONError(SerdeError),
|
BodyJSONError(SerdeError),
|
||||||
Variables(SerdeError),
|
Variables(SerdeError),
|
||||||
Invalid(String),
|
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 {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
GraphQLRequestError::BodyHyper(err) => fmt::Display::fmt(err, f),
|
Self::BodyHyper(e) => fmt::Debug::fmt(e, f),
|
||||||
GraphQLRequestError::BodyUtf8(err) => fmt::Display::fmt(err, f),
|
Self::BodyUtf8(e) => fmt::Debug::fmt(e, f),
|
||||||
GraphQLRequestError::BodyJSONError(err) => fmt::Display::fmt(err, f),
|
Self::BodyJSONError(e) => fmt::Debug::fmt(e, f),
|
||||||
GraphQLRequestError::Variables(err) => fmt::Display::fmt(err, f),
|
Self::Variables(e) => fmt::Debug::fmt(e, f),
|
||||||
GraphQLRequestError::Invalid(err) => fmt::Display::fmt(err, 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)> {
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||||
match self {
|
match self {
|
||||||
GraphQLRequestError::BodyHyper(err) => Some(err),
|
Self::BodyHyper(e) => Some(e),
|
||||||
GraphQLRequestError::BodyUtf8(err) => Some(err),
|
Self::BodyUtf8(e) => Some(e),
|
||||||
GraphQLRequestError::BodyJSONError(err) => Some(err),
|
Self::BodyJSONError(e) => Some(e),
|
||||||
GraphQLRequestError::Variables(err) => Some(err),
|
Self::Variables(e) => Some(e),
|
||||||
GraphQLRequestError::Invalid(_) => None,
|
Self::Invalid(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -314,7 +356,11 @@ mod tests {
|
||||||
convert::Infallible, error::Error, net::SocketAddr, panic, sync::Arc, time::Duration,
|
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 hyper_util::rt::TokioIo;
|
||||||
use juniper::{
|
use juniper::{
|
||||||
http::tests as http_tests,
|
http::tests as http_tests,
|
||||||
|
@ -376,8 +422,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_hyper_integration(is_sync: bool) {
|
async fn run_hyper_integration(port: u16, is_sync: bool, is_custom_type: bool) {
|
||||||
let port = if is_sync { 3002 } else { 3001 };
|
|
||||||
let addr = SocketAddr::from(([127, 0, 0, 1], port));
|
let addr = SocketAddr::from(([127, 0, 0, 1], port));
|
||||||
|
|
||||||
let db = Arc::new(Database::new());
|
let db = Arc::new(Database::new());
|
||||||
|
@ -405,7 +450,7 @@ mod tests {
|
||||||
if let Err(e) = http1::Builder::new()
|
if let Err(e) = http1::Builder::new()
|
||||||
.serve_connection(
|
.serve_connection(
|
||||||
io,
|
io,
|
||||||
service_fn(move |req| {
|
service_fn(move |req: Request<Incoming>| {
|
||||||
let root_node = root_node.clone();
|
let root_node = root_node.clone();
|
||||||
let db = db.clone();
|
let db = db.clone();
|
||||||
let matches = {
|
let matches = {
|
||||||
|
@ -419,10 +464,30 @@ mod tests {
|
||||||
};
|
};
|
||||||
async move {
|
async move {
|
||||||
Ok::<_, Infallible>(if matches {
|
Ok::<_, Infallible>(if matches {
|
||||||
if is_sync {
|
if is_custom_type {
|
||||||
super::graphql_sync(root_node, db, req).await
|
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 {
|
} 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 {
|
} else {
|
||||||
let mut resp = Response::new(String::new());
|
let mut resp = Response::new(String::new());
|
||||||
|
@ -460,11 +525,21 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_hyper_integration() {
|
async fn test_hyper_integration() {
|
||||||
run_hyper_integration(false).await
|
run_hyper_integration(3000, false, false).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_sync_hyper_integration() {
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue