Skip to content

Commit 2e8b326

Browse files
committed
feat: added TypedJson wrapper type that makes it easier to define static type info for serde_json::Value
1 parent 38495b7 commit 2e8b326

File tree

1 file changed

+112
-94
lines changed

1 file changed

+112
-94
lines changed

juniper/src/integrations/json.rs

+112-94
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
//! GraphQL support for [`serde_json::Value`].
22
3+
use std::marker::PhantomData;
4+
35
use graphql_parser::{
46
parse_schema,
57
query::{Text, Type},
68
schema::{Definition, TypeDefinition},
79
};
8-
use juniper::{
9-
marker::{IsOutputType, IsInputType},
10-
meta::{Field, MetaType, Argument},
11-
types::base::resolve_selection_set_into,
12-
Arguments, ExecutionResult, Executor, FieldError, GraphQLType, GraphQLValue, Registry,
13-
ScalarValue, Selection, Value, GraphQLValueAsync, BoxFuture, FromInputValue, InputValue,
14-
};
1510
use serde_json::Value as Json;
1611

12+
use juniper::{
13+
Arguments,
14+
BoxFuture,
15+
ExecutionResult,
16+
Executor, FieldError, FromInputValue, GraphQLType, GraphQLValue, GraphQLValueAsync, InputValue,
17+
marker::{IsInputType, IsOutputType}, meta::{Argument, Field, MetaType}, Registry, ScalarValue, Selection, types::base::resolve_selection_set_into, Value,
18+
};
1719

1820
// Used to describe the graphql type of a `serde_json::Value` using the GraphQL
1921
// schema definition language.
@@ -337,19 +339,89 @@ impl<S> GraphQLValueAsync<S> for Json
337339
}
338340
}
339341

342+
trait TypedJsonInfo: Send + Sync {
343+
fn type_name() -> &'static str;
344+
fn schema() -> &'static str;
345+
}
346+
347+
#[derive(Debug, Clone, PartialEq)]
348+
struct TypedJson<T: TypedJsonInfo> {
349+
value: serde_json::Value,
350+
phantom: PhantomData<T>,
351+
}
352+
353+
impl<T, S> IsOutputType<S> for TypedJson<T> where
354+
S: ScalarValue,
355+
T: TypedJsonInfo,
356+
{}
357+
358+
impl<T, S> IsInputType<S> for TypedJson<T> where
359+
S: ScalarValue,
360+
T: TypedJsonInfo,
361+
{}
362+
363+
impl<T, S> FromInputValue<S> for TypedJson<T> where
364+
S: ScalarValue,
365+
T: TypedJsonInfo,
366+
{
367+
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 })
369+
}
370+
}
371+
372+
impl<T, S> GraphQLValueAsync<S> for TypedJson<T> where
373+
S: ScalarValue + Send + Sync,
374+
T: TypedJsonInfo
375+
{}
376+
377+
impl<T, S> GraphQLType<S> for TypedJson<T> where
378+
S: ScalarValue,
379+
T: TypedJsonInfo,
380+
{
381+
fn name(_info: &Self::TypeInfo) -> Option<&str> {
382+
Some(T::type_name())
383+
}
384+
fn meta<'r>(_info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S>
385+
where S: 'r,
386+
{
387+
TypeInfo
388+
{
389+
name: T::type_name().to_string(),
390+
schema: Some(T::schema().to_string()),
391+
}.meta(registry)
392+
}
393+
}
394+
395+
impl<T, S> GraphQLValue<S> for TypedJson<T>
396+
where S: ScalarValue,
397+
T: TypedJsonInfo,
398+
{
399+
type Context = ();
400+
type TypeInfo = ();
401+
fn type_name<'i>(&self, _info: &'i Self::TypeInfo) -> Option<&'i str> {
402+
Some(T::type_name())
403+
}
404+
fn resolve(
405+
&self,
406+
_info: &Self::TypeInfo,
407+
_selection: Option<&[Selection<S>]>,
408+
executor: &Executor<Self::Context, S>,
409+
) -> ExecutionResult<S> {
410+
executor.resolve(&TypeInfo { schema: None, name: T::type_name().to_string() }, &self.value)
411+
}
412+
}
340413

341414
#[cfg(test)]
342415
mod tests {
343-
use juniper::{
344-
marker::{IsOutputType, IsInputType},
345-
meta::MetaType,
346-
integrations::json::TypeInfo,
347-
execute_sync, graphql_object, graphql_value, EmptyMutation, EmptySubscription, RootNode, Variables,
348-
ScalarValue, GraphQLValue, GraphQLType, Selection, Executor, ExecutionResult, FieldResult,
349-
GraphQLValueAsync, Registry, ToInputValue, FromInputValue, InputValue,
350-
};
416+
use std::marker::PhantomData;
417+
351418
use serde_json::json;
352419

420+
use juniper::{
421+
integrations::json::{TypedJson, TypedJsonInfo, TypeInfo},
422+
EmptyMutation, EmptySubscription, execute_sync, FieldResult, graphql_object, graphql_value,
423+
RootNode, ToInputValue, Variables,
424+
};
353425

354426
#[test]
355427
fn sdl_type_info() {
@@ -588,51 +660,27 @@ mod tests {
588660
#[test]
589661
fn test_as_field_of_output_type() {
590662
// We need a Foo wrapper associate a static SDL to the Foo type which
591-
// wraps the serde_json::Value. Would be nice if a macro could code gen this.
592-
struct Foo(serde_json::Value);
593-
impl<S> IsOutputType<S> for Foo where S: ScalarValue {}
594-
impl<S> GraphQLValueAsync<S> for Foo where S: ScalarValue + Send + Sync {}
595-
impl<S> GraphQLType<S> for Foo where S: ScalarValue
596-
{
597-
fn name(_info: &Self::TypeInfo) -> Option<&str> {
598-
Some("Foo")
599-
}
600-
fn meta<'r>(_info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S>
601-
where S: 'r,
602-
{
603-
TypeInfo {
604-
name: "Foo".to_string(),
605-
schema: Some(r#"
606-
type Foo {
607-
message: [String]
608-
}
609-
"#.to_string()),
610-
}.meta(registry)
611-
}
612-
}
613-
impl<S> GraphQLValue<S> for Foo where S: ScalarValue
663+
struct Foo;
664+
impl TypedJsonInfo for Foo
614665
{
615-
type Context = ();
616-
type TypeInfo = ();
617-
fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> {
618-
<Self as GraphQLType>::name(info)
666+
fn type_name() -> &'static str {
667+
"Foo"
619668
}
620-
fn resolve(
621-
&self,
622-
_info: &Self::TypeInfo,
623-
_selection: Option<&[Selection<S>]>,
624-
executor: &Executor<Self::Context, S>,
625-
) -> ExecutionResult<S> {
626-
executor.resolve(&TypeInfo { schema: None, name: "Foo".to_string() }, &self.0)
669+
fn schema() -> &'static str {
670+
r#"
671+
type Foo {
672+
message: [String]
673+
}
674+
"#
627675
}
628676
}
629677

630678
struct Query;
631679
#[graphql_object()]
632680
impl Query {
633-
fn foo() -> FieldResult<Foo> {
681+
fn foo() -> FieldResult<TypedJson<Foo>> {
634682
let data = json!({"message": ["Hello", "World"] });
635-
Ok(Foo(data))
683+
Ok(TypedJson { value: data, phantom: PhantomData })
636684
}
637685
}
638686
let schema = juniper::RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
@@ -657,58 +705,27 @@ mod tests {
657705

658706
#[test]
659707
fn test_as_field_of_input_type() {
660-
// We need a Foo wrapper associate a static SDL to the Foo type which
661-
// wraps the serde_json::Value. Would be nice if a macro could code gen this.
662-
663708
#[derive(Debug, Clone, PartialEq)]
664-
struct Foo(serde_json::Value);
665-
impl<S> IsInputType<S> for Foo where S: ScalarValue {}
666-
impl<S> GraphQLValueAsync<S> for Foo where S: ScalarValue + Send + Sync {}
667-
impl<S> FromInputValue<S> for Foo where S: ScalarValue {
668-
fn from_input_value(v: &InputValue<S>) -> Option<Self> {
669-
<serde_json::Value as FromInputValue<S>>::from_input_value(v).map(|x| Foo(x))
670-
}
671-
}
672-
impl<S> GraphQLType<S> for Foo where S: ScalarValue
709+
struct Foo;
710+
impl TypedJsonInfo for Foo
673711
{
674-
fn name(_info: &Self::TypeInfo) -> Option<&str> {
675-
Some("Foo")
712+
fn type_name() -> &'static str {
713+
"Foo"
676714
}
677-
fn meta<'r>(_info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S>
678-
where S: 'r,
679-
{
680-
TypeInfo {
681-
name: "Foo".to_string(),
682-
schema: Some(r#"
683-
input Foo {
684-
message: [String]
685-
}
686-
"#.to_string()),
687-
}.meta(registry)
688-
}
689-
}
690-
impl<S> GraphQLValue<S> for Foo where S: ScalarValue
691-
{
692-
type Context = ();
693-
type TypeInfo = ();
694-
fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> {
695-
<Self as GraphQLType>::name(info)
696-
}
697-
fn resolve(
698-
&self,
699-
_info: &Self::TypeInfo,
700-
_selection: Option<&[Selection<S>]>,
701-
executor: &Executor<Self::Context, S>,
702-
) -> ExecutionResult<S> {
703-
executor.resolve(&TypeInfo { schema: None, name: "Foo".to_string() }, &self.0)
715+
fn schema() -> &'static str {
716+
r#"
717+
input Foo {
718+
message: [String]
719+
}
720+
"#
704721
}
705722
}
706723

707724
struct Query;
708725
#[graphql_object()]
709726
impl Query {
710-
fn foo(value: Foo) -> FieldResult<bool> {
711-
Ok(value == Foo(json!({"message":["Hello", "World"]})))
727+
fn foo(value: TypedJson<Foo>) -> FieldResult<bool> {
728+
Ok(value == TypedJson { value: json!({"message":["Hello", "World"]}), phantom: PhantomData })
712729
}
713730
}
714731
let schema = juniper::RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
@@ -738,3 +755,4 @@ mod tests {
738755
}
739756
}
740757

758+

0 commit comments

Comments
 (0)