Skip to content

Commit

Permalink
Add support for GraphQL Schema Language
Browse files Browse the repository at this point in the history
Fixes #307.
  • Loading branch information
LegNeato committed Mar 2, 2019
1 parent 26f5abe commit 3ac03c6
Show file tree
Hide file tree
Showing 10 changed files with 461 additions and 8 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ see the [hyper][hyper_examples], [rocket][rocket_examples], [iron][iron_examples

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.
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
Expand Down Expand Up @@ -86,6 +85,7 @@ 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
[test_schema_rs]: https://github.com/graphql-rust/juniper/blob/master/juniper/src/tests/schema.rs
[tokio]: https://github.com/tokio-rs/tokio
[hyper_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_hyper/examples
Expand Down
20 changes: 20 additions & 0 deletions juniper/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@
The DirectiveLocation::InlineFragment had an invalid literal value,
which broke third party tools like apollo cli.
- Added GraphQL Playground integration
- Added the `schema-language` feature (on by default). This features enables converting
the schema to a `String` in the
[GraphQL Schema Language](https://graphql.org/learn/schema/#type-language)
format (usually called `schema.json`).

Example:

```rust
use juniper::{RootNode, EmptyMutation};

#[derive(GraphQLObject)]
struct Query{
foo: bool
};
let s = RootNode::new(Query, EmptyMutation::<()>::new());
println!("{}, s.as_schema_language());
```
Note: The `schema-language` feature brings in more dependencies.
If you don't use the schema language you may want to turn the feature off.
# [0.11.1] 2018-12-19
Expand Down
4 changes: 4 additions & 0 deletions juniper/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@ path = "benches/bench.rs"
[features]
nightly = []
expose-test-schema = []
schema-language = ["graphql-parser-integration"]
graphql-parser-integration = ["graphql-parser"]
default = [
"chrono",
"url",
"uuid",
"schema-language",
]

[dependencies]
Expand All @@ -42,6 +45,7 @@ chrono = { version = "0.4.0", optional = true }
serde_json = { version="1.0.2", optional = true }
url = { version = "1.5.1", optional = true }
uuid = { version = "0.7", optional = true }
graphql-parser = {version = "0.2.2", optional = true }

[dev-dependencies]
bencher = "0.1.2"
Expand Down
9 changes: 6 additions & 3 deletions juniper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,16 @@ extern crate fnv;

extern crate indexmap;

#[cfg(any(test, feature = "chrono"))]
#[cfg(feature = "graphql-parser-integration")]
extern crate graphql_parser;

#[cfg(feature = "chrono")]
extern crate chrono;

#[cfg(any(test, feature = "url"))]
#[cfg(feature = "url")]
extern crate url;

#[cfg(any(test, feature = "uuid"))]
#[cfg(feature = "uuid")]
extern crate uuid;

// Depend on juniper_codegen and re-export everything in it.
Expand Down
32 changes: 32 additions & 0 deletions juniper/src/schema/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,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> {
Expand All @@ -178,6 +186,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 {
Expand Down Expand Up @@ -364,6 +380,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 mutation marker
name == "_EmptyMutation"
}
} else {
false
}
}

pub(crate) fn fields<'b>(&self, schema: &'b SchemaType<S>) -> Option<Vec<&'b Field<'b, S>>> {
schema
.lookup_type(&self.as_type())
Expand Down
1 change: 1 addition & 0 deletions juniper/src/schema/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod meta;
pub mod model;
pub mod schema;
pub mod translate;
116 changes: 114 additions & 2 deletions juniper/src/schema/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ use fnv::FnvHashMap;

use ast::Type;
use executor::{Context, Registry};
#[cfg(feature = "graphql-parser-integration")]
use graphql_parser::schema::Document;
use schema::meta::{Argument, InterfaceMeta, MetaType, ObjectMeta, PlaceholderMeta, UnionMeta};
#[cfg(feature = "graphql-parser-integration")]
use schema::translate::graphql_parser::GraphQLParserAst;
#[cfg(feature = "graphql-parser-integration")]
use schema::translate::AstTranslator;

use types::base::GraphQLType;
use types::name::Name;
use value::{DefaultScalarValue, ScalarRefValue, ScalarValue};
Expand Down Expand Up @@ -35,8 +42,8 @@ where
#[derive(Debug)]
pub struct SchemaType<'a, S> {
pub(crate) types: FnvHashMap<Name, MetaType<'a, S>>,
query_type_name: String,
mutation_type_name: Option<String>,
pub(crate) query_type_name: String,
pub(crate) mutation_type_name: Option<String>,
directives: FnvHashMap<String, DirectiveType<'a, S>>,
}

Expand Down Expand Up @@ -88,6 +95,22 @@ where
{
RootNode::new_with_info(query_obj, mutation_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(&self) -> Document {
GraphQLParserAst::translate_schema(&self.schema)
}
}

impl<'a, S, QueryT, MutationT> RootNode<'a, QueryT, MutationT, S>
Expand Down Expand Up @@ -466,3 +489,92 @@ impl<'a, S> fmt::Display for TypeType<'a, S> {
}
}
}

#[cfg(test)]
mod test {

#[cfg(feature = "graphql-parser-integration")]
mod graphql_parser_integration {
use EmptyMutation;

#[test]
fn graphql_parser_doc() {
struct Query;
graphql_object!(Query: () |&self| {
field blah() -> bool {
true
}
});
let schema = crate::RootNode::new(Query, EmptyMutation::<()>::new());
let ast = graphql_parser::parse_schema(
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 EmptyMutation;

#[test]
fn schema_language() {
struct Query;
graphql_object!(Query: () |&self| {
field blah() -> bool {
true
}
/// This is whatever's description.
field whatever() -> String {
"foo".to_string()
}
field fizz(buzz: String) -> Option<&str> {
None
}
field arr(stuff: Vec<String>) -> Option<&str> {
None
}
#[deprecated]
field old() -> i32 {
42
}
#[deprecated(note="This field is deprecated, use another.")]
field really_old() -> f64 {
42.0
}
});
let schema = crate::RootNode::new(Query, EmptyMutation::<()>::new());
let ast = graphql_parser::parse_schema(
r#"
type Query {
blah: Boolean!
"This is whatever's description."
whatever: String!
fizz(buzz: String!): String
arr(stuff: [String!]!): String
old: Int! @deprecated
reallyOld: Float! @deprecated(reason: "This field is deprecated, use another.")
}
schema {
query: Query
}
"#,
)
.unwrap();
assert_eq!(format!("{}", ast), schema.as_schema_language());
}
}
}
Loading

0 comments on commit 3ac03c6

Please sign in to comment.