Skip to content

Commit fab7321

Browse files
committed
feat: added test to verify serde_json::Value work as subscription return type.
1 parent 2e8b326 commit fab7321

File tree

2 files changed

+128
-23
lines changed

2 files changed

+128
-23
lines changed

integration_tests/juniper_tests/src/codegen/subscription_attr.rs

+66-10
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ fn schema<'q, C, Qry, Sub>(
2424
query_root: Qry,
2525
subscription_root: Sub,
2626
) -> RootNode<'q, Qry, EmptyMutation<C>, Sub>
27-
where
28-
Qry: GraphQLType<DefaultScalarValue, Context = C, TypeInfo = ()> + 'q,
29-
Sub: GraphQLType<DefaultScalarValue, Context = C, TypeInfo = ()> + 'q,
27+
where
28+
Qry: GraphQLType<DefaultScalarValue, Context=C, TypeInfo=()> + 'q,
29+
Sub: GraphQLType<DefaultScalarValue, Context=C, TypeInfo=()> + 'q,
3030
{
3131
RootNode::new(query_root, EmptyMutation::<C>::new(), subscription_root)
3232
}
@@ -35,15 +35,15 @@ fn schema_with_scalar<'q, S, C, Qry, Sub>(
3535
query_root: Qry,
3636
subscription_root: Sub,
3737
) -> RootNode<'q, Qry, EmptyMutation<C>, Sub, S>
38-
where
39-
Qry: GraphQLType<S, Context = C, TypeInfo = ()> + 'q,
40-
Sub: GraphQLType<S, Context = C, TypeInfo = ()> + 'q,
41-
S: ScalarValue + 'q,
38+
where
39+
Qry: GraphQLType<S, Context=C, TypeInfo=()> + 'q,
40+
Sub: GraphQLType<S, Context=C, TypeInfo=()> + 'q,
41+
S: ScalarValue + 'q,
4242
{
4343
RootNode::new_with_scalar_value(query_root, EmptyMutation::<C>::new(), subscription_root)
4444
}
4545

46-
type Stream<'a, I> = Pin<Box<dyn futures::Stream<Item = I> + Send + 'a>>;
46+
type Stream<'a, I> = Pin<Box<dyn futures::Stream<Item=I> + Send + 'a>>;
4747

4848
mod trivial {
4949
use super::*;
@@ -1698,8 +1698,8 @@ mod executor {
16981698
impl Human {
16991699
// TODO: Make work for `Stream<'e, &'e str>`.
17001700
async fn id<'e, S>(&self, executor: &'e Executor<'_, '_, (), S>) -> Stream<'static, String>
1701-
where
1702-
S: ScalarValue,
1701+
where
1702+
S: ScalarValue,
17031703
{
17041704
Box::pin(stream::once(future::ready(
17051705
executor.look_ahead().field_name().to_owned(),
@@ -1797,4 +1797,60 @@ mod executor {
17971797
)),
17981798
);
17991799
}
1800+
1801+
1802+
#[tokio::test]
1803+
async fn test_integration_json() {
1804+
use juniper::integrations::json::{TypedJsonInfo, TypedJson};
1805+
use serde_json::json;
1806+
1807+
struct Foo;
1808+
impl TypedJsonInfo for Foo
1809+
{
1810+
fn type_name() -> &'static str {
1811+
"Foo"
1812+
}
1813+
fn schema() -> &'static str {
1814+
r#"
1815+
type Foo {
1816+
message: [String]
1817+
}
1818+
"#
1819+
}
1820+
}
1821+
1822+
struct Query;
1823+
#[graphql_object()]
1824+
impl Query {
1825+
fn zero() -> FieldResult<i32> {
1826+
Ok(0)
1827+
}
1828+
}
1829+
1830+
struct Subscription;
1831+
#[graphql_subscription(scalar = S: ScalarValue)]
1832+
impl Subscription {
1833+
// TODO: Make work for `Stream<'e, &'e str>`.
1834+
async fn foo<'e, S>(&self, _executor: &'e Executor<'_, '_, (), S>) -> Stream<'static, TypedJson<Foo>>
1835+
where
1836+
S: ScalarValue,
1837+
{
1838+
let data = TypedJson::new(json!({"message": ["Hello World"] }));
1839+
Box::pin(stream::once(future::ready(data)))
1840+
}
1841+
}
1842+
1843+
let schema = juniper::RootNode::new(Query, EmptyMutation::new(), Subscription);
1844+
1845+
const DOC: &str = r#"subscription {
1846+
foo { message }
1847+
}"#;
1848+
1849+
assert_eq!(
1850+
resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
1851+
.then(|s| extract_next(s))
1852+
.await,
1853+
Ok((graphql_value!({"foo":{"message": ["Hello World"] }}), vec![])),
1854+
);
1855+
}
18001856
}

juniper/src/integrations/json.rs

+62-13
Original file line numberDiff line numberDiff line change
@@ -339,17 +339,35 @@ impl<S> GraphQLValueAsync<S> for Json
339339
}
340340
}
341341

342-
trait TypedJsonInfo: Send + Sync {
342+
/// Trait used to provide the type information for a
343+
/// serde_json::Value
344+
pub trait TypedJsonInfo: Send + Sync {
345+
/// the GraphQL type name
343346
fn type_name() -> &'static str;
347+
348+
/// schema returns the GrpahQL Schema Definition language that contains the type_name
344349
fn schema() -> &'static str;
345350
}
346351

352+
/// Wrapper generic type for serde_json::Value that associates
353+
/// type information.
347354
#[derive(Debug, Clone, PartialEq)]
348-
struct TypedJson<T: TypedJsonInfo> {
349-
value: serde_json::Value,
355+
pub struct TypedJson<T: TypedJsonInfo> {
356+
/// the wrapped json value
357+
pub json: serde_json::Value,
350358
phantom: PhantomData<T>,
351359
}
352360

361+
impl<T: TypedJsonInfo> TypedJson<T> {
362+
/// creates a new TypedJson from a serde_json::Value
363+
pub fn new(v: serde_json::Value) -> TypedJson<T> {
364+
TypedJson {
365+
json: v,
366+
phantom: PhantomData,
367+
}
368+
}
369+
}
370+
353371
impl<T, S> IsOutputType<S> for TypedJson<T> where
354372
S: ScalarValue,
355373
T: TypedJsonInfo,
@@ -365,15 +383,10 @@ impl<T, S> FromInputValue<S> for TypedJson<T> where
365383
T: TypedJsonInfo,
366384
{
367385
fn from_input_value(v: &InputValue<S>) -> Option<Self> {
368-
<serde_json::Value as FromInputValue<S>>::from_input_value(v).map(|x| TypedJson { value: x, phantom: PhantomData })
386+
<serde_json::Value as FromInputValue<S>>::from_input_value(v).map(|x| TypedJson::new(x))
369387
}
370388
}
371389

372-
impl<T, S> GraphQLValueAsync<S> for TypedJson<T> where
373-
S: ScalarValue + Send + Sync,
374-
T: TypedJsonInfo
375-
{}
376-
377390
impl<T, S> GraphQLType<S> for TypedJson<T> where
378391
S: ScalarValue,
379392
T: TypedJsonInfo,
@@ -407,10 +420,44 @@ impl<T, S> GraphQLValue<S> for TypedJson<T>
407420
_selection: Option<&[Selection<S>]>,
408421
executor: &Executor<Self::Context, S>,
409422
) -> ExecutionResult<S> {
410-
executor.resolve(&TypeInfo { schema: None, name: T::type_name().to_string() }, &self.value)
423+
executor.resolve(&TypeInfo { schema: None, name: T::type_name().to_string() }, &self.json)
411424
}
412425
}
413426

427+
impl<T, S> GraphQLValueAsync<S> for TypedJson<T>
428+
where
429+
Self::TypeInfo: Sync,
430+
Self::Context: Sync,
431+
S: ScalarValue + Send + Sync,
432+
T: TypedJsonInfo,
433+
{
434+
fn resolve_async<'a>(
435+
&'a self,
436+
_info: &'a Self::TypeInfo,
437+
selection_set: Option<&'a [Selection<S>]>,
438+
executor: &'a Executor<Self::Context, S>,
439+
) -> BoxFuture<'a, ExecutionResult<S>> {
440+
Box::pin(async move {
441+
let info = TypeInfo { schema: None, name: T::type_name().to_string() };
442+
<Json as GraphQLValue<S>>::resolve(&self.json, &info, selection_set, executor)
443+
})
444+
}
445+
446+
fn resolve_field_async<'a>(
447+
&'a self,
448+
_info: &'a Self::TypeInfo,
449+
field_name: &'a str,
450+
arguments: &'a Arguments<S>,
451+
executor: &'a Executor<Self::Context, S>,
452+
) -> BoxFuture<'a, ExecutionResult<S>> {
453+
Box::pin(async move {
454+
let info = TypeInfo { schema: None, name: T::type_name().to_string() };
455+
<Json as GraphQLValue<S>>::resolve_field(&self.json, &info, field_name, arguments, executor)
456+
})
457+
}
458+
}
459+
460+
414461
#[cfg(test)]
415462
mod tests {
416463
use std::marker::PhantomData;
@@ -420,8 +467,10 @@ mod tests {
420467
use juniper::{
421468
integrations::json::{TypedJson, TypedJsonInfo, TypeInfo},
422469
EmptyMutation, EmptySubscription, execute_sync, FieldResult, graphql_object, graphql_value,
423-
RootNode, ToInputValue, Variables,
470+
RootNode, ToInputValue, Variables, graphql_subscription,
424471
};
472+
use crate::{Executor, ScalarValue, resolve_into_stream, Value, ValuesStream, ExecutionError, GraphQLError};
473+
use std::pin::Pin;
425474

426475
#[test]
427476
fn sdl_type_info() {
@@ -680,7 +729,7 @@ mod tests {
680729
impl Query {
681730
fn foo() -> FieldResult<TypedJson<Foo>> {
682731
let data = json!({"message": ["Hello", "World"] });
683-
Ok(TypedJson { value: data, phantom: PhantomData })
732+
Ok(TypedJson::new(data))
684733
}
685734
}
686735
let schema = juniper::RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
@@ -725,7 +774,7 @@ mod tests {
725774
#[graphql_object()]
726775
impl Query {
727776
fn foo(value: TypedJson<Foo>) -> FieldResult<bool> {
728-
Ok(value == TypedJson { value: json!({"message":["Hello", "World"]}), phantom: PhantomData })
777+
Ok(value == TypedJson::new(json!({"message":["Hello", "World"]})))
729778
}
730779
}
731780
let schema = juniper::RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());

0 commit comments

Comments
 (0)