Add support for GraphQL Schema Language (#676)
Co-authored-by: Alexander Lyon <arlyon@me.com>
This commit is contained in:
parent
40ad17c540
commit
9167654a73
10 changed files with 592 additions and 5 deletions
|
@ -48,14 +48,15 @@ see the [actix][actix_examples], [hyper][hyper_examples], [rocket][rocket_exampl
|
||||||
|
|
||||||
Juniper supports the full GraphQL query language according to the
|
Juniper supports the full GraphQL query language according to the
|
||||||
[specification][graphql_spec], including interfaces, unions, schema
|
[specification][graphql_spec], including interfaces, unions, schema
|
||||||
introspection, and validations.
|
introspection, and validations. It can also output the schema in the [GraphQL Schema Language][schema_language].
|
||||||
It does not, however, support the schema language. Consider using [juniper-from-schema][] for generating code from a schema file.
|
|
||||||
|
|
||||||
As an exception to other GraphQL libraries for other languages, Juniper builds
|
As an exception to other GraphQL libraries for other languages, Juniper builds
|
||||||
non-null types by default. A field of type `Vec<Episode>` will be converted into
|
non-null types by default. A field of type `Vec<Episode>` will be converted into
|
||||||
`[Episode!]!`. The corresponding Rust type for e.g. `[Episode]` would be
|
`[Episode!]!`. The corresponding Rust type for e.g. `[Episode]` would be
|
||||||
`Option<Vec<Option<Episode>>>`.
|
`Option<Vec<Option<Episode>>>`.
|
||||||
|
|
||||||
|
Juniper follows a [code-first approach][schema_approach] to defining GraphQL schemas. If you would like to use a [schema-first approach][schema_approach] instead, consider [juniper-from-schema][] for generating code from a schema file.
|
||||||
|
|
||||||
## Integrations
|
## Integrations
|
||||||
|
|
||||||
### Data types
|
### Data types
|
||||||
|
@ -91,6 +92,8 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected.
|
||||||
[playground]: https://github.com/prisma/graphql-playground
|
[playground]: https://github.com/prisma/graphql-playground
|
||||||
[iron]: http://ironframework.io
|
[iron]: http://ironframework.io
|
||||||
[graphql_spec]: http://facebook.github.io/graphql
|
[graphql_spec]: http://facebook.github.io/graphql
|
||||||
|
[schema_language]: https://graphql.org/learn/schema/#type-language
|
||||||
|
[schema_approach]: https://blog.logrocket.com/code-first-vs-schema-first-development-graphql/
|
||||||
[test_schema_rs]: https://github.com/graphql-rust/juniper/blob/master/juniper/src/tests/schema.rs
|
[test_schema_rs]: https://github.com/graphql-rust/juniper/blob/master/juniper/src/tests/schema.rs
|
||||||
[tokio]: https://github.com/tokio-rs/tokio
|
[tokio]: https://github.com/tokio-rs/tokio
|
||||||
[actix_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_actix/examples
|
[actix_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_actix/examples
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
This page will give you a short introduction to the concepts in Juniper.
|
This page will give you a short introduction to the concepts in Juniper.
|
||||||
|
|
||||||
|
Juniper follows a [code-first approach][schema_approach] to defining GraphQL schemas. If you would like to use a [schema-first approach][schema_approach] instead, consider [juniper-from-schema][] for generating code from a schema file.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
!FILENAME Cargo.toml
|
!FILENAME Cargo.toml
|
||||||
|
@ -193,6 +195,8 @@ fn main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[juniper-from-schema]: https://github.com/davidpdrsn/juniper-from-schema
|
||||||
|
[schema_approach]: https://blog.logrocket.com/code-first-vs-schema-first-development-graphql/
|
||||||
[hyper]: servers/hyper.md
|
[hyper]: servers/hyper.md
|
||||||
[warp]: servers/warp.md
|
[warp]: servers/warp.md
|
||||||
[rocket]: servers/rocket.md
|
[rocket]: servers/rocket.md
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
# Schemas
|
# Schemas
|
||||||
|
|
||||||
|
Juniper follows a [code-first approach][schema_approach] to defining GraphQL schemas. If you would like to use a [schema-first approach][schema_approach] instead, consider [juniper-from-schema][] for generating code from a schema file.
|
||||||
|
|
||||||
A schema consists of three types: a query object, a mutation object, and a subscription object.
|
A schema consists of three types: a query object, a mutation object, and a subscription object.
|
||||||
These three define the root query fields, mutations and subscriptions of the schema, respectively.
|
These three define the root query fields, mutations and subscriptions of the schema, respectively.
|
||||||
|
|
||||||
|
@ -60,6 +62,55 @@ impl Mutations {
|
||||||
# fn main() { }
|
# fn main() { }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# Outputting schemas in the [GraphQL Schema Language][schema_language]
|
||||||
|
|
||||||
|
Many tools in the GraphQL ecosystem require the schema to be defined in the [GraphQL Schema Language][schema_language]. You can generate a [GraphQL Schema Language][schema_language] representation of your schema defined in Rust using the `schema-language` feature (on by default):
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# // Only needed due to 2018 edition because the macro is not accessible.
|
||||||
|
# #[macro_use] extern crate juniper;
|
||||||
|
use juniper::{FieldResult, EmptyMutation, EmptySubscription, RootNode};
|
||||||
|
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
#[juniper::graphql_object]
|
||||||
|
impl Query {
|
||||||
|
fn hello(&self) -> FieldResult<&str> {
|
||||||
|
Ok("hello world")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Define our schema in Rust.
|
||||||
|
let schema = RootNode::new(
|
||||||
|
Query,
|
||||||
|
EmptyMutation::<()>::new(),
|
||||||
|
EmptySubscription::<()>::new(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Convert the Rust schema into the GraphQL Schema Language.
|
||||||
|
let result = schema.as_schema_language();
|
||||||
|
|
||||||
|
let expected = "\
|
||||||
|
type Query {
|
||||||
|
hello: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
schema {
|
||||||
|
query: Query
|
||||||
|
}
|
||||||
|
";
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note the `schema-language` feature may be turned off if you do not need this functionality to reduce dependencies and speed up
|
||||||
|
compile times.
|
||||||
|
|
||||||
|
|
||||||
|
[schema_language]: https://graphql.org/learn/schema/#type-language
|
||||||
|
[juniper-from-schema]: https://github.com/davidpdrsn/juniper-from-schema
|
||||||
|
[schema_approach]: https://blog.logrocket.com/code-first-vs-schema-first-development-graphql/
|
||||||
[section]: ../advanced/subscriptions.md
|
[section]: ../advanced/subscriptions.md
|
||||||
[EmptyMutation]: https://docs.rs/juniper/0.14.2/juniper/struct.EmptyMutation.html
|
[EmptyMutation]: https://docs.rs/juniper/0.14.2/juniper/struct.EmptyMutation.html
|
||||||
<!--TODO: Fix This URL when the EmptySubscription become available in the Documentation -->
|
<!--TODO: Fix This URL when the EmptySubscription become available in the Documentation -->
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
- Added support for outputting the Rust schema in the [GraphQL Schema Language](https://graphql.org/learn/schema/#type-language). ([#676](https://github.com/graphql-rust/juniper/pull/676))
|
||||||
|
- This is controlled by the `schema-language` feature and is on by default. It may be turned off if you do not need this functionality to reduce dependencies and speed up compile times.
|
||||||
|
|
||||||
- Normalization for the subscriptions_endpoint_url in the `graphiql_source`.
|
- Normalization for the subscriptions_endpoint_url in the `graphiql_source`.
|
||||||
(See [#628](https://github.com/graphql-rust/juniper/pull/628) for more details)
|
(See [#628](https://github.com/graphql-rust/juniper/pull/628) for more details)
|
||||||
|
|
||||||
|
|
|
@ -25,11 +25,14 @@ path = "benches/bench.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
expose-test-schema = ["serde_json"]
|
expose-test-schema = ["serde_json"]
|
||||||
|
schema-language = ["graphql-parser-integration"]
|
||||||
|
graphql-parser-integration = ["graphql-parser"]
|
||||||
default = [
|
default = [
|
||||||
"bson",
|
"bson",
|
||||||
"chrono",
|
"chrono",
|
||||||
"url",
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
"schema-language",
|
||||||
]
|
]
|
||||||
scalar-naivetime = []
|
scalar-naivetime = []
|
||||||
|
|
||||||
|
@ -46,6 +49,7 @@ serde_json = { version="1.0.2", optional = true }
|
||||||
static_assertions = "1.1"
|
static_assertions = "1.1"
|
||||||
url = { version = "2", optional = true }
|
url = { version = "2", optional = true }
|
||||||
uuid = { version = "0.8", optional = true }
|
uuid = { version = "0.8", optional = true }
|
||||||
|
graphql-parser = {version = "0.3.0", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
bencher = "0.1.2"
|
bencher = "0.1.2"
|
||||||
|
|
|
@ -169,6 +169,14 @@ pub struct Field<'a, S> {
|
||||||
pub deprecation_status: DeprecationStatus,
|
pub deprecation_status: DeprecationStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, S> Field<'a, S> {
|
||||||
|
/// Returns true if the type is built-in to GraphQL.
|
||||||
|
pub fn is_builtin(&self) -> bool {
|
||||||
|
// "used exclusively by GraphQL’s introspection system"
|
||||||
|
self.name.starts_with("__")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Metadata for an argument to a field
|
/// Metadata for an argument to a field
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Argument<'a, S> {
|
pub struct Argument<'a, S> {
|
||||||
|
@ -182,6 +190,14 @@ pub struct Argument<'a, S> {
|
||||||
pub default_value: Option<InputValue<S>>,
|
pub default_value: Option<InputValue<S>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, S> Argument<'a, S> {
|
||||||
|
/// Returns true if the type is built-in to GraphQL.
|
||||||
|
pub fn is_builtin(&self) -> bool {
|
||||||
|
// "used exclusively by GraphQL’s introspection system"
|
||||||
|
self.name.starts_with("__")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Metadata for a single value in an enum
|
/// Metadata for a single value in an enum
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct EnumValue {
|
pub struct EnumValue {
|
||||||
|
@ -368,6 +384,22 @@ impl<'a, S> MetaType<'a, S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the type is built-in to GraphQL.
|
||||||
|
pub fn is_builtin(&self) -> bool {
|
||||||
|
if let Some(name) = self.name() {
|
||||||
|
// "used exclusively by GraphQL’s introspection system"
|
||||||
|
{
|
||||||
|
name.starts_with("__") ||
|
||||||
|
// <https://facebook.github.io/graphql/draft/#sec-Scalars>
|
||||||
|
name == "Boolean" || name == "String" || name == "Int" || name == "Float" || name == "ID" ||
|
||||||
|
// Our custom empty markers
|
||||||
|
name == "_EmptyMutation" || name == "_EmptySubscription"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn fields<'b>(&self, schema: &'b SchemaType<S>) -> Option<Vec<&'b Field<'b, S>>> {
|
pub(crate) fn fields<'b>(&self, schema: &'b SchemaType<S>) -> Option<Vec<&'b Field<'b, S>>> {
|
||||||
schema
|
schema
|
||||||
.lookup_type(&self.as_type())
|
.lookup_type(&self.as_type())
|
||||||
|
|
|
@ -3,3 +3,4 @@
|
||||||
pub mod meta;
|
pub mod meta;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
pub mod schema;
|
pub mod schema;
|
||||||
|
pub mod translate;
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use fnv::FnvHashMap;
|
use fnv::FnvHashMap;
|
||||||
|
#[cfg(feature = "graphql-parser-integration")]
|
||||||
|
use graphql_parser::schema::Document;
|
||||||
|
|
||||||
use juniper_codegen::GraphQLEnumInternal as GraphQLEnum;
|
use juniper_codegen::GraphQLEnumInternal as GraphQLEnum;
|
||||||
|
|
||||||
|
@ -12,6 +14,9 @@ use crate::{
|
||||||
value::{DefaultScalarValue, ScalarValue},
|
value::{DefaultScalarValue, ScalarValue},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "graphql-parser-integration")]
|
||||||
|
use crate::schema::translate::{graphql_parser::GraphQLParserTranslator, SchemaTranslator};
|
||||||
|
|
||||||
/// Root query node of a schema
|
/// Root query node of a schema
|
||||||
///
|
///
|
||||||
/// This brings the mutation, subscription and query types together,
|
/// This brings the mutation, subscription and query types together,
|
||||||
|
@ -46,9 +51,9 @@ pub struct RootNode<
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SchemaType<'a, S> {
|
pub struct SchemaType<'a, S> {
|
||||||
pub(crate) types: FnvHashMap<Name, MetaType<'a, S>>,
|
pub(crate) types: FnvHashMap<Name, MetaType<'a, S>>,
|
||||||
query_type_name: String,
|
pub(crate) query_type_name: String,
|
||||||
mutation_type_name: Option<String>,
|
pub(crate) mutation_type_name: Option<String>,
|
||||||
subscription_type_name: Option<String>,
|
pub(crate) subscription_type_name: Option<String>,
|
||||||
directives: FnvHashMap<String, DirectiveType<'a, S>>,
|
directives: FnvHashMap<String, DirectiveType<'a, S>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,6 +107,22 @@ where
|
||||||
) -> Self {
|
) -> Self {
|
||||||
RootNode::new_with_info(query_obj, mutation_obj, subscription_obj, (), (), ())
|
RootNode::new_with_info(query_obj, mutation_obj, subscription_obj, (), (), ())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "schema-language")]
|
||||||
|
/// The schema definition as a `String` in the
|
||||||
|
/// [GraphQL Schema Language](https://graphql.org/learn/schema/#type-language)
|
||||||
|
/// format.
|
||||||
|
pub fn as_schema_language(&self) -> String {
|
||||||
|
let doc = self.as_parser_document();
|
||||||
|
format!("{}", doc)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "graphql-parser-integration")]
|
||||||
|
/// The schema definition as a [`graphql_parser`](https://crates.io/crates/graphql-parser)
|
||||||
|
/// [`Document`](https://docs.rs/graphql-parser/latest/graphql_parser/schema/struct.Document.html).
|
||||||
|
pub fn as_parser_document(&'a self) -> Document<'a, &'a str> {
|
||||||
|
GraphQLParserTranslator::translate_schema(&self.schema)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, S, QueryT, MutationT, SubscriptionT> RootNode<'a, QueryT, MutationT, SubscriptionT, S>
|
impl<'a, S, QueryT, MutationT, SubscriptionT> RootNode<'a, QueryT, MutationT, SubscriptionT, S>
|
||||||
|
@ -534,3 +555,157 @@ impl<'a, S> fmt::Display for TypeType<'a, S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
|
||||||
|
#[cfg(feature = "graphql-parser-integration")]
|
||||||
|
mod graphql_parser_integration {
|
||||||
|
use crate as juniper;
|
||||||
|
use crate::{EmptyMutation, EmptySubscription};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn graphql_parser_doc() {
|
||||||
|
struct Query;
|
||||||
|
#[juniper::graphql_object]
|
||||||
|
impl Query {
|
||||||
|
fn blah() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let schema = crate::RootNode::new(
|
||||||
|
Query,
|
||||||
|
EmptyMutation::<()>::new(),
|
||||||
|
EmptySubscription::<()>::new(),
|
||||||
|
);
|
||||||
|
let ast = graphql_parser::parse_schema::<&str>(
|
||||||
|
r#"
|
||||||
|
type Query {
|
||||||
|
blah: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
|
schema {
|
||||||
|
query: Query
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
format!("{}", ast),
|
||||||
|
format!("{}", schema.as_parser_document()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "schema-language")]
|
||||||
|
mod schema_language {
|
||||||
|
use crate as juniper;
|
||||||
|
use crate::{
|
||||||
|
EmptyMutation, EmptySubscription, GraphQLEnum, GraphQLInputObject, GraphQLObject,
|
||||||
|
GraphQLUnionInternal as GraphQLUnion,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn schema_language() {
|
||||||
|
#[derive(GraphQLObject, Default)]
|
||||||
|
struct Cake {
|
||||||
|
fresh: bool,
|
||||||
|
};
|
||||||
|
#[derive(GraphQLObject, Default)]
|
||||||
|
struct IceCream {
|
||||||
|
cold: bool,
|
||||||
|
};
|
||||||
|
#[derive(GraphQLUnion)]
|
||||||
|
enum GlutenFree {
|
||||||
|
Cake(Cake),
|
||||||
|
IceCream(IceCream),
|
||||||
|
}
|
||||||
|
#[derive(GraphQLEnum)]
|
||||||
|
enum Fruit {
|
||||||
|
Apple,
|
||||||
|
Orange,
|
||||||
|
}
|
||||||
|
#[derive(GraphQLInputObject)]
|
||||||
|
struct Coordinate {
|
||||||
|
latitude: f64,
|
||||||
|
longitude: f64,
|
||||||
|
}
|
||||||
|
struct Query;
|
||||||
|
#[juniper::graphql_object]
|
||||||
|
impl Query {
|
||||||
|
fn blah() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
/// This is whatever's description.
|
||||||
|
fn whatever() -> String {
|
||||||
|
"foo".to_string()
|
||||||
|
}
|
||||||
|
fn arr(stuff: Vec<Coordinate>) -> Option<&str> {
|
||||||
|
if stuff.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some("stuff")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn fruit() -> Fruit {
|
||||||
|
Fruit::Apple
|
||||||
|
}
|
||||||
|
fn gluten_free(flavor: String) -> GlutenFree {
|
||||||
|
if flavor == "savory" {
|
||||||
|
GlutenFree::Cake(Cake::default())
|
||||||
|
} else {
|
||||||
|
GlutenFree::IceCream(IceCream::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[deprecated]
|
||||||
|
fn old() -> i32 {
|
||||||
|
42
|
||||||
|
}
|
||||||
|
#[deprecated(note = "This field is deprecated, use another.")]
|
||||||
|
fn really_old() -> f64 {
|
||||||
|
42.0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let schema = crate::RootNode::new(
|
||||||
|
Query,
|
||||||
|
EmptyMutation::<()>::new(),
|
||||||
|
EmptySubscription::<()>::new(),
|
||||||
|
);
|
||||||
|
let ast = graphql_parser::parse_schema::<&str>(
|
||||||
|
r#"
|
||||||
|
union GlutenFree = Cake | IceCream
|
||||||
|
enum Fruit {
|
||||||
|
APPLE
|
||||||
|
ORANGE
|
||||||
|
}
|
||||||
|
type Cake {
|
||||||
|
fresh: Boolean!
|
||||||
|
}
|
||||||
|
type IceCream {
|
||||||
|
cold: Boolean!
|
||||||
|
}
|
||||||
|
type Query {
|
||||||
|
blah: Boolean!
|
||||||
|
"This is whatever's description."
|
||||||
|
whatever: String!
|
||||||
|
arr(stuff: [Coordinate!]!): String
|
||||||
|
fruit: Fruit!
|
||||||
|
glutenFree(flavor: String!): GlutenFree!
|
||||||
|
old: Int! @deprecated
|
||||||
|
reallyOld: Float! @deprecated(reason: "This field is deprecated, use another.")
|
||||||
|
}
|
||||||
|
input Coordinate {
|
||||||
|
latitude: Float!
|
||||||
|
longitude: Float!
|
||||||
|
}
|
||||||
|
schema {
|
||||||
|
query: Query
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(format!("{}", ast), schema.as_schema_language());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
306
juniper/src/schema/translate/graphql_parser.rs
Normal file
306
juniper/src/schema/translate/graphql_parser.rs
Normal file
|
@ -0,0 +1,306 @@
|
||||||
|
use std::boxed::Box;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use graphql_parser::query::{
|
||||||
|
Directive as ExternalDirective, Number as ExternalNumber, Type as ExternalType,
|
||||||
|
};
|
||||||
|
use graphql_parser::schema::{Definition, Document, SchemaDefinition, Text};
|
||||||
|
use graphql_parser::schema::{
|
||||||
|
EnumType as ExternalEnum, EnumValue as ExternalEnumValue, Field as ExternalField,
|
||||||
|
InputObjectType as ExternalInputObjectType, InputValue as ExternalInputValue,
|
||||||
|
InterfaceType as ExternalInterfaceType, ObjectType as ExternalObjectType,
|
||||||
|
ScalarType as ExternalScalarType, TypeDefinition as ExternalTypeDefinition,
|
||||||
|
UnionType as ExternalUnionType, Value as ExternalValue,
|
||||||
|
};
|
||||||
|
use graphql_parser::Pos;
|
||||||
|
|
||||||
|
use crate::ast::{InputValue, Type};
|
||||||
|
use crate::schema::meta::DeprecationStatus;
|
||||||
|
use crate::schema::meta::{Argument, EnumValue, Field, MetaType};
|
||||||
|
use crate::schema::model::SchemaType;
|
||||||
|
use crate::schema::translate::SchemaTranslator;
|
||||||
|
use crate::value::ScalarValue;
|
||||||
|
|
||||||
|
pub struct GraphQLParserTranslator;
|
||||||
|
|
||||||
|
impl<'a, S: 'a, T> From<&'a SchemaType<'a, S>> for Document<'a, T>
|
||||||
|
where
|
||||||
|
S: ScalarValue,
|
||||||
|
T: Text<'a> + Default,
|
||||||
|
{
|
||||||
|
fn from(input: &'a SchemaType<'a, S>) -> Document<'a, T> {
|
||||||
|
GraphQLParserTranslator::translate_schema(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> SchemaTranslator<'a, graphql_parser::schema::Document<'a, T>>
|
||||||
|
for GraphQLParserTranslator
|
||||||
|
where
|
||||||
|
T: Text<'a> + Default,
|
||||||
|
{
|
||||||
|
fn translate_schema<S: 'a>(input: &'a SchemaType<S>) -> graphql_parser::schema::Document<'a, T>
|
||||||
|
where
|
||||||
|
S: ScalarValue,
|
||||||
|
{
|
||||||
|
let mut doc = Document::default();
|
||||||
|
|
||||||
|
// Translate type defs.
|
||||||
|
let mut types = input
|
||||||
|
.types
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, meta)| !meta.is_builtin())
|
||||||
|
.map(|(_, meta)| GraphQLParserTranslator::translate_meta(meta))
|
||||||
|
.map(Definition::TypeDefinition)
|
||||||
|
.collect();
|
||||||
|
doc.definitions.append(&mut types);
|
||||||
|
|
||||||
|
doc.definitions
|
||||||
|
.push(Definition::SchemaDefinition(SchemaDefinition {
|
||||||
|
position: Pos::default(),
|
||||||
|
directives: vec![],
|
||||||
|
query: Some(From::from(input.query_type_name.as_str())),
|
||||||
|
mutation: input
|
||||||
|
.mutation_type_name
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| From::from(s.as_str())),
|
||||||
|
subscription: input
|
||||||
|
.subscription_type_name
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| From::from(s.as_str())),
|
||||||
|
}));
|
||||||
|
|
||||||
|
doc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphQLParserTranslator {
|
||||||
|
fn translate_argument<'a, S, T>(input: &'a Argument<S>) -> ExternalInputValue<'a, T>
|
||||||
|
where
|
||||||
|
S: ScalarValue,
|
||||||
|
T: Text<'a>,
|
||||||
|
{
|
||||||
|
ExternalInputValue {
|
||||||
|
position: Pos::default(),
|
||||||
|
description: input.description.as_ref().map(From::from),
|
||||||
|
name: From::from(input.name.as_str()),
|
||||||
|
value_type: GraphQLParserTranslator::translate_type(&input.arg_type),
|
||||||
|
default_value: input
|
||||||
|
.default_value
|
||||||
|
.as_ref()
|
||||||
|
.map(|x| GraphQLParserTranslator::translate_value(x)),
|
||||||
|
directives: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn translate_value<'a, S: 'a, T>(input: &'a InputValue<S>) -> ExternalValue<'a, T>
|
||||||
|
where
|
||||||
|
S: ScalarValue,
|
||||||
|
T: Text<'a>,
|
||||||
|
{
|
||||||
|
match input {
|
||||||
|
InputValue::Null => ExternalValue::Null,
|
||||||
|
InputValue::Scalar(x) => {
|
||||||
|
if let Some(v) = x.as_string() {
|
||||||
|
ExternalValue::String(v)
|
||||||
|
} else if let Some(v) = x.as_int() {
|
||||||
|
ExternalValue::Int(ExternalNumber::from(v))
|
||||||
|
} else if let Some(v) = x.as_float() {
|
||||||
|
ExternalValue::Float(v)
|
||||||
|
} else if let Some(v) = x.as_boolean() {
|
||||||
|
ExternalValue::Boolean(v)
|
||||||
|
} else {
|
||||||
|
panic!("unknown argument type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InputValue::Enum(x) => ExternalValue::Enum(From::from(x.as_str())),
|
||||||
|
InputValue::Variable(x) => ExternalValue::Variable(From::from(x.as_str())),
|
||||||
|
InputValue::List(x) => ExternalValue::List(
|
||||||
|
x.iter()
|
||||||
|
.map(|s| GraphQLParserTranslator::translate_value(&s.item))
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
InputValue::Object(x) => {
|
||||||
|
let mut fields = BTreeMap::new();
|
||||||
|
x.iter().for_each(|(name_span, value_span)| {
|
||||||
|
fields.insert(
|
||||||
|
From::from(name_span.item.as_str()),
|
||||||
|
GraphQLParserTranslator::translate_value(&value_span.item),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
ExternalValue::Object(fields)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn translate_type<'a, T>(input: &'a Type<'a>) -> ExternalType<'a, T>
|
||||||
|
where
|
||||||
|
T: Text<'a>,
|
||||||
|
{
|
||||||
|
match input {
|
||||||
|
Type::Named(x) => ExternalType::NamedType(From::from(x.as_ref())),
|
||||||
|
Type::List(x) => {
|
||||||
|
ExternalType::ListType(GraphQLParserTranslator::translate_type(x).into())
|
||||||
|
}
|
||||||
|
Type::NonNullNamed(x) => {
|
||||||
|
ExternalType::NonNullType(Box::new(ExternalType::NamedType(From::from(x.as_ref()))))
|
||||||
|
}
|
||||||
|
Type::NonNullList(x) => ExternalType::NonNullType(Box::new(ExternalType::ListType(
|
||||||
|
Box::new(GraphQLParserTranslator::translate_type(x)),
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn translate_meta<'a, S, T>(input: &'a MetaType<S>) -> ExternalTypeDefinition<'a, T>
|
||||||
|
where
|
||||||
|
S: ScalarValue,
|
||||||
|
T: Text<'a>,
|
||||||
|
{
|
||||||
|
match input {
|
||||||
|
MetaType::Scalar(x) => ExternalTypeDefinition::Scalar(ExternalScalarType {
|
||||||
|
position: Pos::default(),
|
||||||
|
description: x.description.as_ref().map(From::from),
|
||||||
|
name: From::from(x.name.as_ref()),
|
||||||
|
directives: vec![],
|
||||||
|
}),
|
||||||
|
MetaType::Enum(x) => ExternalTypeDefinition::Enum(ExternalEnum {
|
||||||
|
position: Pos::default(),
|
||||||
|
description: x.description.as_ref().map(|s| From::from(s.as_str())),
|
||||||
|
name: From::from(x.name.as_ref()),
|
||||||
|
directives: vec![],
|
||||||
|
values: x
|
||||||
|
.values
|
||||||
|
.iter()
|
||||||
|
.map(GraphQLParserTranslator::translate_enum_value)
|
||||||
|
.collect(),
|
||||||
|
}),
|
||||||
|
MetaType::Union(x) => ExternalTypeDefinition::Union(ExternalUnionType {
|
||||||
|
position: Pos::default(),
|
||||||
|
description: x.description.as_ref().map(|s| From::from(s.as_str())),
|
||||||
|
name: From::from(x.name.as_ref()),
|
||||||
|
directives: vec![],
|
||||||
|
types: x
|
||||||
|
.of_type_names
|
||||||
|
.iter()
|
||||||
|
.map(|s| From::from(s.as_str()))
|
||||||
|
.collect(),
|
||||||
|
}),
|
||||||
|
MetaType::Interface(x) => ExternalTypeDefinition::Interface(ExternalInterfaceType {
|
||||||
|
position: Pos::default(),
|
||||||
|
description: x.description.as_ref().map(|s| From::from(s.as_str())),
|
||||||
|
name: From::from(x.name.as_ref()),
|
||||||
|
directives: vec![],
|
||||||
|
fields: x
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.filter(|x| !x.is_builtin())
|
||||||
|
.map(GraphQLParserTranslator::translate_field)
|
||||||
|
.collect(),
|
||||||
|
}),
|
||||||
|
MetaType::InputObject(x) => {
|
||||||
|
ExternalTypeDefinition::InputObject(ExternalInputObjectType {
|
||||||
|
position: Pos::default(),
|
||||||
|
description: x.description.as_ref().map(|s| From::from(s.as_str())),
|
||||||
|
name: From::from(x.name.as_ref()),
|
||||||
|
directives: vec![],
|
||||||
|
fields: x
|
||||||
|
.input_fields
|
||||||
|
.iter()
|
||||||
|
.filter(|x| !x.is_builtin())
|
||||||
|
.map(GraphQLParserTranslator::translate_argument)
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
MetaType::Object(x) => ExternalTypeDefinition::Object(ExternalObjectType {
|
||||||
|
position: Pos::default(),
|
||||||
|
description: x.description.as_ref().map(|s| From::from(s.as_str())),
|
||||||
|
name: From::from(x.name.as_ref()),
|
||||||
|
directives: vec![],
|
||||||
|
fields: x
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.filter(|x| !x.is_builtin())
|
||||||
|
.map(GraphQLParserTranslator::translate_field)
|
||||||
|
.collect(),
|
||||||
|
implements_interfaces: x
|
||||||
|
.interface_names
|
||||||
|
.iter()
|
||||||
|
.map(|s| From::from(s.as_str()))
|
||||||
|
.collect(),
|
||||||
|
}),
|
||||||
|
_ => panic!("unknown meta type when translating"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn translate_enum_value<'a, T>(input: &'a EnumValue) -> ExternalEnumValue<'a, T>
|
||||||
|
where
|
||||||
|
T: Text<'a>,
|
||||||
|
{
|
||||||
|
ExternalEnumValue {
|
||||||
|
position: Pos::default(),
|
||||||
|
name: From::from(input.name.as_ref()),
|
||||||
|
description: input.description.as_ref().map(|s| From::from(s.as_str())),
|
||||||
|
directives: generate_directives(&input.deprecation_status),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn translate_field<'a, S: 'a, T>(input: &'a Field<S>) -> ExternalField<'a, T>
|
||||||
|
where
|
||||||
|
S: ScalarValue,
|
||||||
|
T: Text<'a>,
|
||||||
|
{
|
||||||
|
let arguments = input
|
||||||
|
.arguments
|
||||||
|
.as_ref()
|
||||||
|
.map(|a| {
|
||||||
|
a.iter()
|
||||||
|
.filter(|x| !x.is_builtin())
|
||||||
|
.map(|x| GraphQLParserTranslator::translate_argument(&x))
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| Vec::new());
|
||||||
|
|
||||||
|
ExternalField {
|
||||||
|
position: Pos::default(),
|
||||||
|
name: From::from(input.name.as_str()),
|
||||||
|
description: input.description.as_ref().map(|s| From::from(s.as_str())),
|
||||||
|
directives: generate_directives(&input.deprecation_status),
|
||||||
|
field_type: GraphQLParserTranslator::translate_type(&input.field_type),
|
||||||
|
arguments,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deprecation_to_directive<'a, T>(status: &DeprecationStatus) -> Option<ExternalDirective<'a, T>>
|
||||||
|
where
|
||||||
|
T: Text<'a>,
|
||||||
|
{
|
||||||
|
match status {
|
||||||
|
DeprecationStatus::Current => None,
|
||||||
|
DeprecationStatus::Deprecated(reason) => Some(ExternalDirective {
|
||||||
|
position: Pos::default(),
|
||||||
|
name: From::from("deprecated"),
|
||||||
|
arguments: if let Some(reason) = reason {
|
||||||
|
vec![(
|
||||||
|
From::from("reason"),
|
||||||
|
ExternalValue::String(reason.to_string()),
|
||||||
|
)]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Right now the only directive supported is `@deprecated`. `@skip` and `@include`
|
||||||
|
// are dealt with elsewhere.
|
||||||
|
// <https://facebook.github.io/graphql/draft/#sec-Type-System.Directives>
|
||||||
|
fn generate_directives<'a, T>(status: &DeprecationStatus) -> Vec<ExternalDirective<'a, T>>
|
||||||
|
where
|
||||||
|
T: Text<'a>,
|
||||||
|
{
|
||||||
|
if let Some(d) = deprecation_to_directive(&status) {
|
||||||
|
vec![d]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
8
juniper/src/schema/translate/mod.rs
Normal file
8
juniper/src/schema/translate/mod.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
use crate::{ScalarValue, SchemaType};
|
||||||
|
|
||||||
|
pub trait SchemaTranslator<'a, T> {
|
||||||
|
fn translate_schema<S: 'a + ScalarValue>(s: &'a SchemaType<S>) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "graphql-parser-integration")]
|
||||||
|
pub mod graphql_parser;
|
Loading…
Reference in a new issue