Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add integration of serde_json #975

Draft
wants to merge 22 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0a803df
feat: add integration of serde_json
chirino Aug 14, 2021
86a60c1
Codestyle corrections
tyranron Aug 15, 2021
d2bdcd9
feat: add GraphQLValueAsync impl for serde_json
chirino Aug 15, 2021
38495b7
feat: support using serde_json::Value as input/output fields
chirino Aug 15, 2021
2e8b326
feat: added TypedJson wrapper type that makes it easier to define sta…
chirino Aug 16, 2021
be3a32d
feat: added test to verify serde_json::Value work as subscription ret…
chirino Aug 16, 2021
365658b
feat: added section to the book to show examples of how to use the se…
chirino Aug 22, 2021
f3e42f5
Merge branch 'master' into json
LegNeato Aug 26, 2021
7ac73b8
Merge branch 'master' into json
LegNeato Sep 9, 2021
af6e4aa
Merge branch 'master' into json
tyranron Sep 16, 2021
48fea85
Merge remote-tracking branch 'chirino/json' into json
tyranron Sep 16, 2021
9ff9879
Reimpl serde_json::Value, vol.1
tyranron Sep 17, 2021
6222be4
Reimpl serde_json::Value, vol.2
tyranron Oct 4, 2021
568d1ec
Impl Json<T> wrapper, vol.1
tyranron Oct 4, 2021
cd3398b
Impl Json<T> wrapper, vol.2
tyranron Oct 5, 2021
94f755d
Impl dynamic TypeInfo, vol.1
tyranron Oct 5, 2021
b807c43
Impl dynamic TypeInfo, vol.2
tyranron Oct 7, 2021
9bb2d04
Impl dynamic TypeInfo, vol.3
tyranron Oct 7, 2021
9ce0717
Bikeshed serde + ScalarValue stuff, vol.1
tyranron Oct 8, 2021
3306cf1
Merge branch 'master' into json
tyranron Apr 14, 2022
0451540
Restore and refactor, vol.1
tyranron Apr 15, 2022
a1e2ffe
Restore and refactor, vol.2 [skip ci]
tyranron Apr 15, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ jobs:
- { feature: chrono-tz, crate: juniper }
- { feature: expose-test-schema, crate: juniper }
- { feature: graphql-parser, crate: juniper }
- { feature: json, crate: juniper }
- { feature: schema-language, crate: juniper }
- { feature: serde_json, crate: juniper }
- { feature: time, crate: juniper }
- { feature: url, crate: juniper }
- { feature: uuid, crate: juniper }
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ your Schemas automatically.
- [chrono-tz][chrono-tz]
- [time][time]
- [bson][bson]
- [serde_json][serde_json]

### Web Frameworks

Expand Down Expand Up @@ -122,3 +123,4 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected.
[time]: https://crates.io/crates/time
[bson]: https://crates.io/crates/bson
[juniper-from-schema]: https://github.com/davidpdrsn/juniper-from-schema
[serde_json]: https://crates.io/crates/serde_json
1 change: 1 addition & 0 deletions book/src/advanced/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ The chapters below cover some more advanced scenarios.
- [Multiple operations per request](multiple_ops_per_request.md)
- [Dataloaders](dataloaders.md)
- [Subscriptions](subscriptions.md)
- [Dynamic JSON value](serde_json.md)
151 changes: 151 additions & 0 deletions book/src/advanced/serde_json.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
Dynamic JSON value
==================

The following example shows you how to run a GraphQL query against JSON data held in a `serde_json::Value`. To make this work you have to construct the `RootNode` using the `new_with_info` method so that you can describe the GraphQL schema of the JSON data.

```rust
use serde_json::json;
use juniper::{
integrations::serde_json::TypeInfo,
graphql_value, RootNode, EmptyMutation, EmptySubscription, Variables,
};

fn main() {
// Use SDL to define the structure of the JSON data.
let type_info = TypeInfo {
name: "Query".to_string(),
schema: Some(r#"
type Query {
bar: Bar
}
type Bar {
name: String
capacity: Int
open: Boolean!
}
"#.to_string()),
};

let data = json!({
"bar": {
"name": "Cheers",
"capacity": 80,
"open": true,
},
});


let schema = RootNode::new_with_info(
data,
EmptyMutation::new(),
EmptySubscription::new(),
type_info,
(),
(),
);

let (res, _errors) = juniper::execute_sync(
"query { bar { name} }",
None,
&schema,
&Variables::new(),
&(),
).unwrap();

// Ensure the value matches.
assert_eq!(
res,
graphql_value!({
"bar": { "name": "Cheers"},
})
);
}
```

## Using Json Fields in a GraphQL Object

If you know the schema definition at compile time for a value that your want to hold as a json
field of normal juniper graphql object, you can use the `TypedJson` wrapper struct to provide the
type information of the wrapped `serde_json::Value`.

```rust
use serde_json::json;
use juniper::{
graphql_object, EmptyMutation, EmptySubscription, FieldResult,
Variables, graphql_value,
integrations::serde_json::{TypedJsonInfo,TypedJson},
};

// Defines schema for the Person Graphql Type
struct Person;
impl TypedJsonInfo for Person {
fn type_name() -> &'static str {
"Person"
}
fn schema() -> &'static str {
r#"
type Person {
name: String
age: Int
}
"#
}
}

// You can also define graphql input types this way
struct DetailInput;
impl TypedJsonInfo for DetailInput {
fn type_name() -> &'static str {
"DetailInput"
}
fn schema() -> &'static str {
r#"
input DetailInput {
ever: Boolean!
}
"#
}
}

struct Query;

#[graphql_object]
impl Query {
// define a field that uses both Json input type and output type.
pub fn favorite_person(details: TypedJson<DetailInput>) -> FieldResult<TypedJson<Person>> {
let ever = details.json.get("ever").unwrap().as_bool().unwrap();
let data = if ever {
json!({"name": "Destiny", "age":29})
} else {
json!({"name": "David", "age":45})
};
Ok(TypedJson::new(data))
}
}

fn main() {

let root_node = &juniper::RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());

// Run the executor.
let (res, _errors) = juniper::execute_sync(r#"
query {
favoritePerson( details: { ever: true }) {
name, age
}
}"#,
None,
root_node,
&Variables::new(),
&(),
).unwrap();

// Ensure the value matches.
assert_eq!(
res,
graphql_value!({
"favoritePerson": {"name": "Destiny", "age":29},
}),
);
}
```
4 changes: 4 additions & 0 deletions juniper/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
- Reworked [`chrono` crate] integration GraphQL scalars according to [graphql-scalars.dev] specs: ([#1010])
- Disabled `chrono` [Cargo feature] by default.
- Removed `scalar-naivetime` [Cargo feature].
- Renamed `graphql-parser-integration` [Cargo feature] as `graphql-parser`. ([#1043])
- Renamed `serde_json` [Cargo feature] as `json`. ([#975])

### Added

Expand Down Expand Up @@ -74,6 +76,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
[#965]: /../../pull/965
[#966]: /../../pull/966
[#971]: /../../pull/971
[#975]: /../../pull/975
[#979]: /../../pull/979
[#985]: /../../pull/985
[#987]: /../../pull/987
Expand All @@ -90,6 +93,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
[#1017]: /../../pull/1017
[#1025]: /../../pull/1025
[#1026]: /../../pull/1026
[#1043]: /../../pull/1043
[#1051]: /../../issues/1051
[#1054]: /../../pull/1054

Expand Down
2 changes: 2 additions & 0 deletions juniper/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ default = [
]
chrono-clock = ["chrono", "chrono/clock"]
expose-test-schema = ["anyhow", "serde_json"]
json = ["ref-cast", "serde_json", "smartstring/serde"]
schema-language = ["graphql-parser"]

[dependencies]
Expand All @@ -46,6 +47,7 @@ futures-enum = { version = "0.1.12", default-features = false }
graphql-parser = { version = "0.4", optional = true }
indexmap = { version = "1.0", features = ["serde-1"] }
juniper_codegen = { version = "0.16.0-dev", path = "../juniper_codegen" }
ref-cast = { version = "1.0.6", optional = true }
serde = { version = "1.0.8", features = ["derive"], default-features = false }
serde_json = { version = "1.0.2", default-features = false, optional = true }
smartstring = "1.0"
Expand Down
1 change: 1 addition & 0 deletions juniper/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ As an exception to other [GraphQL] libraries for other languages, [Juniper] buil
- [`bson`]
- [`chrono`] (feature gated)
- [`chrono-tz`] (feature gated)
- [`serde_json`] (`json` feature gated)
- [`time`] (feature gated)
- [`url`]
- [`uuid`]
Expand Down
21 changes: 8 additions & 13 deletions juniper/src/executor/look_ahead.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,26 +38,21 @@ where
{
fn from_input_value(input_value: &'a InputValue<S>, vars: &'a Variables<S>) -> Self {
match *input_value {
InputValue::Null => LookAheadValue::Null,
InputValue::Scalar(ref s) => LookAheadValue::Scalar(s),
InputValue::Enum(ref e) => LookAheadValue::Enum(e),
InputValue::Null => Self::Null,
InputValue::Scalar(ref s) => Self::Scalar(s),
InputValue::Enum(ref e) => Self::Enum(e),
InputValue::Variable(ref name) => vars
.get(name)
.map(|v| Self::from_input_value(v, vars))
.unwrap_or(LookAheadValue::Null),
InputValue::List(ref l) => LookAheadValue::List(
.unwrap_or(Self::Null),
InputValue::List(ref l) => Self::List(
l.iter()
.map(|i| LookAheadValue::from_input_value(&i.item, vars))
.map(|i| Self::from_input_value(&i.item, vars))
.collect(),
),
InputValue::Object(ref o) => LookAheadValue::Object(
InputValue::Object(ref o) => Self::Object(
o.iter()
.map(|&(ref n, ref i)| {
(
&n.item as &str,
LookAheadValue::from_input_value(&i.item, vars),
)
})
.map(|&(ref n, ref i)| (&n.item as &str, Self::from_input_value(&i.item, vars)))
.collect(),
),
}
Expand Down
43 changes: 37 additions & 6 deletions juniper/src/executor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,34 @@ where
}
}

// This hack is required as Juniper doesn't allow at the
// moment for custom defined types to tweak into executor.
// TODO: Redesign executor layer to allow such things.
#[cfg(feature = "json")]
#[doc(hidden)]
pub(crate) fn field_with_parent_type_sub_executor<'s>(
&'s self,
field_alias: &'a str,
location: SourcePosition,
selection_set: Option<&'s [Selection<'a, S>]>,
) -> Executor<'s, 'a, CtxT, S> {
Executor {
fragments: self.fragments,
variables: self.variables,
current_selection_set: selection_set,
parent_selection_set: self.current_selection_set,
current_type: self.current_type.clone(),
schema: self.schema,
context: self.context,
errors: self.errors,
field_path: Arc::new(FieldPath::Field(
field_alias,
location,
Arc::clone(&self.field_path),
)),
}
}

#[doc(hidden)]
pub fn type_sub_executor<'s>(
&'s self,
Expand Down Expand Up @@ -826,6 +854,9 @@ where
QueryT: GraphQLType<S>,
MutationT: GraphQLType<S, Context = QueryT::Context>,
SubscriptionT: GraphQLType<S, Context = QueryT::Context>,
QueryT::TypeInfo: Sized,
MutationT::TypeInfo: Sized,
SubscriptionT::TypeInfo: Sized,
{
if operation.item.operation_type == OperationType::Subscription {
return Err(GraphQLError::IsSubscription);
Expand Down Expand Up @@ -917,12 +948,12 @@ pub async fn execute_validated_query_async<'a, 'b, QueryT, MutationT, Subscripti
) -> Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError<'a>>
where
QueryT: GraphQLTypeAsync<S>,
QueryT::TypeInfo: Sync,
QueryT::TypeInfo: Sync + Sized,
QueryT::Context: Sync,
MutationT: GraphQLTypeAsync<S, Context = QueryT::Context>,
MutationT::TypeInfo: Sync,
MutationT::TypeInfo: Sync + Sized,
SubscriptionT: GraphQLType<S, Context = QueryT::Context> + Sync,
SubscriptionT::TypeInfo: Sync,
SubscriptionT::TypeInfo: Sync + Sized,
S: ScalarValue + Send + Sync,
{
if operation.item.operation_type == OperationType::Subscription {
Expand Down Expand Up @@ -1064,12 +1095,12 @@ where
'd: 'r,
'op: 'd,
QueryT: GraphQLTypeAsync<S>,
QueryT::TypeInfo: Sync,
QueryT::TypeInfo: Sync + Sized,
QueryT::Context: Sync + 'r,
MutationT: GraphQLTypeAsync<S, Context = QueryT::Context>,
MutationT::TypeInfo: Sync,
MutationT::TypeInfo: Sync + Sized,
SubscriptionT: GraphQLSubscriptionType<S, Context = QueryT::Context>,
SubscriptionT::TypeInfo: Sync,
SubscriptionT::TypeInfo: Sync + Sized,
S: ScalarValue + Send + Sync,
{
if operation.item.operation_type != OperationType::Subscription {
Expand Down
Loading