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
|
||||
[specification][graphql_spec], including interfaces, unions, schema
|
||||
introspection, and validations.
|
||||
It does not, however, support the schema language. Consider using [juniper-from-schema][] for generating code from a schema file.
|
||||
introspection, and validations. It can also output the schema in the [GraphQL Schema Language][schema_language].
|
||||
|
||||
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
|
||||
`[Episode!]!`. The corresponding Rust type for e.g. `[Episode]` would be
|
||||
`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
|
||||
|
||||
### 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
|
||||
[iron]: http://ironframework.io
|
||||
[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
|
||||
[tokio]: https://github.com/tokio-rs/tokio
|
||||
[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.
|
||||
|
||||
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
|
||||
|
||||
!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
|
||||
[warp]: servers/warp.md
|
||||
[rocket]: servers/rocket.md
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# 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.
|
||||
These three define the root query fields, mutations and subscriptions of the schema, respectively.
|
||||
|
||||
|
@ -60,6 +62,55 @@ impl Mutations {
|
|||
# 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
|
||||
[EmptyMutation]: https://docs.rs/juniper/0.14.2/juniper/struct.EmptyMutation.html
|
||||
<!--TODO: Fix This URL when the EmptySubscription become available in the Documentation -->
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
## 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`.
|
||||
(See [#628](https://github.com/graphql-rust/juniper/pull/628) for more details)
|
||||
|
||||
|
|
|
@ -25,11 +25,14 @@ path = "benches/bench.rs"
|
|||
|
||||
[features]
|
||||
expose-test-schema = ["serde_json"]
|
||||
schema-language = ["graphql-parser-integration"]
|
||||
graphql-parser-integration = ["graphql-parser"]
|
||||
default = [
|
||||
"bson",
|
||||
"chrono",
|
||||
"url",
|
||||
"uuid",
|
||||
"schema-language",
|
||||
]
|
||||
scalar-naivetime = []
|
||||
|
||||
|
@ -46,6 +49,7 @@ serde_json = { version="1.0.2", optional = true }
|
|||
static_assertions = "1.1"
|
||||
url = { version = "2", optional = true }
|
||||
uuid = { version = "0.8", optional = true }
|
||||
graphql-parser = {version = "0.3.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bencher = "0.1.2"
|
||||
|
|
|
@ -169,6 +169,14 @@ pub struct Field<'a, S> {
|
|||
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
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Argument<'a, S> {
|
||||
|
@ -182,6 +190,14 @@ pub struct Argument<'a, 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
|
||||
#[derive(Debug, Clone)]
|
||||
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>>> {
|
||||
schema
|
||||
.lookup_type(&self.as_type())
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
pub mod meta;
|
||||
pub mod model;
|
||||
pub mod schema;
|
||||
pub mod translate;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use std::fmt;
|
||||
|
||||
use fnv::FnvHashMap;
|
||||
#[cfg(feature = "graphql-parser-integration")]
|
||||
use graphql_parser::schema::Document;
|
||||
|
||||
use juniper_codegen::GraphQLEnumInternal as GraphQLEnum;
|
||||
|
||||
|
@ -12,6 +14,9 @@ use crate::{
|
|||
value::{DefaultScalarValue, ScalarValue},
|
||||
};
|
||||
|
||||
#[cfg(feature = "graphql-parser-integration")]
|
||||
use crate::schema::translate::{graphql_parser::GraphQLParserTranslator, SchemaTranslator};
|
||||
|
||||
/// Root query node of a schema
|
||||
///
|
||||
/// This brings the mutation, subscription and query types together,
|
||||
|
@ -46,9 +51,9 @@ pub struct RootNode<
|
|||
#[derive(Debug)]
|
||||
pub struct SchemaType<'a, S> {
|
||||
pub(crate) types: FnvHashMap<Name, MetaType<'a, S>>,
|
||||
query_type_name: String,
|
||||
mutation_type_name: Option<String>,
|
||||
subscription_type_name: Option<String>,
|
||||
pub(crate) query_type_name: String,
|
||||
pub(crate) mutation_type_name: Option<String>,
|
||||
pub(crate) subscription_type_name: Option<String>,
|
||||
directives: FnvHashMap<String, DirectiveType<'a, S>>,
|
||||
}
|
||||
|
||||
|
@ -102,6 +107,22 @@ where
|
|||
) -> Self {
|
||||
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>
|
||||
|
@ -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