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

Add support for GraphQL Schema Language #324

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
26 changes: 22 additions & 4 deletions juniper/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,31 @@

- The minimum required Rust version is now `1.30.0`.
- The `ScalarValue` custom derive has been renamed to `GraphQLScalarValue`.
- Fix introspection query validity
The DirectiveLocation::InlineFragment had an invalid literal value,
- Fix introspection query validity.
`DirectiveLocation::InlineFragment` had an invalid literal value,
which broke third party tools like apollo cli.
- Added GraphQL Playground integration
The DirectiveLocation::InlineFragment had an invalid literal value,
which broke third party tools like apollo cli.
- The return type of `value::object::Object::iter/iter_mut` has changed to `impl Iter` [#312](https://github.com/graphql-rust/juniper/pull/312)
- Added the `schema-language` feature (on by default). This feature enables converting
the schema to a `String` in the
[GraphQL Schema Language](https://graphql.org/learn/schema/#type-language)
format (usually written to a file called `schema.graphql`).

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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a bit hacky, __ is not reserved so users could add their own objects/ fields/arguments/...

We could add a builtin: bool flag to the relevant schema types.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From https://github.com/graphql/graphql-spec/blob/master/spec/Section%203%20--%20Type%20System.md#schema:

"All types and directives defined within a schema must not have a name which begins with {"__"} (two underscores), as this is 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;
188 changes: 186 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::GraphQLParserTranslator;
#[cfg(feature = "graphql-parser-integration")]
use schema::translate::SchemaTranslator;

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 {
GraphQLParserTranslator::translate_schema(&self.schema)
}
}

impl<'a, S, QueryT, MutationT> RootNode<'a, QueryT, MutationT, S>
Expand Down Expand Up @@ -466,3 +489,164 @@ 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 crate as juniper;
use EmptyMutation;

#[test]
fn schema_language() {
#[derive(GraphQLObject, Default)]
struct Cake {
fresh: bool,
};
#[derive(GraphQLObject, Default)]
struct IceCream {
cold: bool,
};
enum Sweet {
Cake(Cake),
IceCream(IceCream),
}
enum GlutenFree {
Cake(Cake),
IceCream(IceCream),
}
graphql_interface!(Sweet: () where Scalar = <S> |&self| {
field is_brownie() -> bool { false }
instance_resolvers: |_| {
&Cake => match *self { Sweet::Cake(ref x) => Some(x), _ => None },
&IceCream => match *self { Sweet::IceCream(ref x) => Some(x), _ => None },
}
});
graphql_union!(GlutenFree: () where Scalar = <S> |&self| {
instance_resolvers: |_| {
&Cake => match *self { GlutenFree::Cake(ref x) => Some(x), _ => None },
&IceCream => match *self { GlutenFree::IceCream(ref x) => Some(x), _ => None },
}
});
#[derive(GraphQLEnum)]
enum Fruit {
Apple,
Orange,
}
#[derive(GraphQLInputObject)]
struct Coordinate {
latitude: f64,
longitude: f64,
}
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 as "Buzz description") -> Option<Sweet> {
if buzz == "whatever" {
Some(Sweet::Cake(Cake::default()))
} else {
Some(Sweet::IceCream(IceCream::default()))
}
}
field arr(stuff: Vec<Coordinate>) -> Option<&str> {
None
}
field fruit() -> Fruit {
Fruit::Apple
}
field gluten_free(people = 1: i32 ) -> GlutenFree {
if people > 1 {
GlutenFree::Cake(Cake::default())
} else {
GlutenFree::IceCream(IceCream::default())
} }
#[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#"
union GlutenFree = Cake | IceCream
enum Fruit {
APPLE
ORANGE
}
interface Sweet {
isBrownie: Boolean!
}
type Cake {
fresh: Boolean!
}
type IceCream {
cold: Boolean!
}
type Query {
blah: Boolean!
"This is whatever's description."
whatever: String!
fizz("Buzz description" buzz: String!): Sweet
arr(stuff: [Coordinate!]!): String
fruit: Fruit!
glutenFree(people: Int = 1): 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());
}
}
}
Loading