Skip to content

Commit

Permalink
feat: added test to verify serde_json::Value work as subscription ret…
Browse files Browse the repository at this point in the history
…urn type.
  • Loading branch information
chirino committed Aug 16, 2021
1 parent 2e8b326 commit fab7321
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 23 deletions.
76 changes: 66 additions & 10 deletions integration_tests/juniper_tests/src/codegen/subscription_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ fn schema<'q, C, Qry, Sub>(
query_root: Qry,
subscription_root: Sub,
) -> RootNode<'q, Qry, EmptyMutation<C>, Sub>
where
Qry: GraphQLType<DefaultScalarValue, Context = C, TypeInfo = ()> + 'q,
Sub: GraphQLType<DefaultScalarValue, Context = C, TypeInfo = ()> + 'q,
where
Qry: GraphQLType<DefaultScalarValue, Context=C, TypeInfo=()> + 'q,
Sub: GraphQLType<DefaultScalarValue, Context=C, TypeInfo=()> + 'q,
{
RootNode::new(query_root, EmptyMutation::<C>::new(), subscription_root)
}
Expand All @@ -35,15 +35,15 @@ fn schema_with_scalar<'q, S, C, Qry, Sub>(
query_root: Qry,
subscription_root: Sub,
) -> RootNode<'q, Qry, EmptyMutation<C>, Sub, S>
where
Qry: GraphQLType<S, Context = C, TypeInfo = ()> + 'q,
Sub: GraphQLType<S, Context = C, TypeInfo = ()> + 'q,
S: ScalarValue + 'q,
where
Qry: GraphQLType<S, Context=C, TypeInfo=()> + 'q,
Sub: GraphQLType<S, Context=C, TypeInfo=()> + 'q,
S: ScalarValue + 'q,
{
RootNode::new_with_scalar_value(query_root, EmptyMutation::<C>::new(), subscription_root)
}

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

mod trivial {
use super::*;
Expand Down Expand Up @@ -1698,8 +1698,8 @@ mod executor {
impl Human {
// TODO: Make work for `Stream<'e, &'e str>`.
async fn id<'e, S>(&self, executor: &'e Executor<'_, '_, (), S>) -> Stream<'static, String>
where
S: ScalarValue,
where
S: ScalarValue,
{
Box::pin(stream::once(future::ready(
executor.look_ahead().field_name().to_owned(),
Expand Down Expand Up @@ -1797,4 +1797,60 @@ mod executor {
)),
);
}


#[tokio::test]
async fn test_integration_json() {
use juniper::integrations::json::{TypedJsonInfo, TypedJson};
use serde_json::json;

struct Foo;
impl TypedJsonInfo for Foo
{
fn type_name() -> &'static str {
"Foo"
}
fn schema() -> &'static str {
r#"
type Foo {
message: [String]
}
"#
}
}

struct Query;
#[graphql_object()]
impl Query {
fn zero() -> FieldResult<i32> {
Ok(0)
}
}

struct Subscription;
#[graphql_subscription(scalar = S: ScalarValue)]
impl Subscription {
// TODO: Make work for `Stream<'e, &'e str>`.
async fn foo<'e, S>(&self, _executor: &'e Executor<'_, '_, (), S>) -> Stream<'static, TypedJson<Foo>>
where
S: ScalarValue,
{
let data = TypedJson::new(json!({"message": ["Hello World"] }));
Box::pin(stream::once(future::ready(data)))
}
}

let schema = juniper::RootNode::new(Query, EmptyMutation::new(), Subscription);

const DOC: &str = r#"subscription {
foo { message }
}"#;

assert_eq!(
resolve_into_stream(DOC, None, &schema, &Variables::new(), &())
.then(|s| extract_next(s))
.await,
Ok((graphql_value!({"foo":{"message": ["Hello World"] }}), vec![])),
);
}
}
75 changes: 62 additions & 13 deletions juniper/src/integrations/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,17 +339,35 @@ impl<S> GraphQLValueAsync<S> for Json
}
}

trait TypedJsonInfo: Send + Sync {
/// Trait used to provide the type information for a
/// serde_json::Value
pub trait TypedJsonInfo: Send + Sync {
/// the GraphQL type name
fn type_name() -> &'static str;

/// schema returns the GrpahQL Schema Definition language that contains the type_name
fn schema() -> &'static str;
}

/// Wrapper generic type for serde_json::Value that associates
/// type information.
#[derive(Debug, Clone, PartialEq)]
struct TypedJson<T: TypedJsonInfo> {
value: serde_json::Value,
pub struct TypedJson<T: TypedJsonInfo> {
/// the wrapped json value
pub json: serde_json::Value,
phantom: PhantomData<T>,
}

impl<T: TypedJsonInfo> TypedJson<T> {
/// creates a new TypedJson from a serde_json::Value
pub fn new(v: serde_json::Value) -> TypedJson<T> {
TypedJson {
json: v,
phantom: PhantomData,
}
}
}

impl<T, S> IsOutputType<S> for TypedJson<T> where
S: ScalarValue,
T: TypedJsonInfo,
Expand All @@ -365,15 +383,10 @@ impl<T, S> FromInputValue<S> for TypedJson<T> where
T: TypedJsonInfo,
{
fn from_input_value(v: &InputValue<S>) -> Option<Self> {
<serde_json::Value as FromInputValue<S>>::from_input_value(v).map(|x| TypedJson { value: x, phantom: PhantomData })
<serde_json::Value as FromInputValue<S>>::from_input_value(v).map(|x| TypedJson::new(x))
}
}

impl<T, S> GraphQLValueAsync<S> for TypedJson<T> where
S: ScalarValue + Send + Sync,
T: TypedJsonInfo
{}

impl<T, S> GraphQLType<S> for TypedJson<T> where
S: ScalarValue,
T: TypedJsonInfo,
Expand Down Expand Up @@ -407,10 +420,44 @@ impl<T, S> GraphQLValue<S> for TypedJson<T>
_selection: Option<&[Selection<S>]>,
executor: &Executor<Self::Context, S>,
) -> ExecutionResult<S> {
executor.resolve(&TypeInfo { schema: None, name: T::type_name().to_string() }, &self.value)
executor.resolve(&TypeInfo { schema: None, name: T::type_name().to_string() }, &self.json)
}
}

impl<T, S> GraphQLValueAsync<S> for TypedJson<T>
where
Self::TypeInfo: Sync,
Self::Context: Sync,
S: ScalarValue + Send + Sync,
T: TypedJsonInfo,
{
fn resolve_async<'a>(
&'a self,
_info: &'a Self::TypeInfo,
selection_set: Option<&'a [Selection<S>]>,
executor: &'a Executor<Self::Context, S>,
) -> BoxFuture<'a, ExecutionResult<S>> {
Box::pin(async move {
let info = TypeInfo { schema: None, name: T::type_name().to_string() };
<Json as GraphQLValue<S>>::resolve(&self.json, &info, selection_set, executor)
})
}

fn resolve_field_async<'a>(
&'a self,
_info: &'a Self::TypeInfo,
field_name: &'a str,
arguments: &'a Arguments<S>,
executor: &'a Executor<Self::Context, S>,
) -> BoxFuture<'a, ExecutionResult<S>> {
Box::pin(async move {
let info = TypeInfo { schema: None, name: T::type_name().to_string() };
<Json as GraphQLValue<S>>::resolve_field(&self.json, &info, field_name, arguments, executor)
})
}
}


#[cfg(test)]
mod tests {
use std::marker::PhantomData;
Expand All @@ -420,8 +467,10 @@ mod tests {
use juniper::{
integrations::json::{TypedJson, TypedJsonInfo, TypeInfo},
EmptyMutation, EmptySubscription, execute_sync, FieldResult, graphql_object, graphql_value,
RootNode, ToInputValue, Variables,
RootNode, ToInputValue, Variables, graphql_subscription,
};
use crate::{Executor, ScalarValue, resolve_into_stream, Value, ValuesStream, ExecutionError, GraphQLError};
use std::pin::Pin;

#[test]
fn sdl_type_info() {
Expand Down Expand Up @@ -680,7 +729,7 @@ mod tests {
impl Query {
fn foo() -> FieldResult<TypedJson<Foo>> {
let data = json!({"message": ["Hello", "World"] });
Ok(TypedJson { value: data, phantom: PhantomData })
Ok(TypedJson::new(data))
}
}
let schema = juniper::RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
Expand Down Expand Up @@ -725,7 +774,7 @@ mod tests {
#[graphql_object()]
impl Query {
fn foo(value: TypedJson<Foo>) -> FieldResult<bool> {
Ok(value == TypedJson { value: json!({"message":["Hello", "World"]}), phantom: PhantomData })
Ok(value == TypedJson::new(json!({"message":["Hello", "World"]})))
}
}
let schema = juniper::RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
Expand Down

0 comments on commit fab7321

Please sign in to comment.