Skip to content

Commit 2bed836

Browse files
committed
Add support for GraphQL Schema Language
1 parent a3699ff commit 2bed836

File tree

9 files changed

+456
-7
lines changed

9 files changed

+456
-7
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ see the [hyper][hyper_examples], [rocket][rocket_examples], [iron][iron_examples
4646

4747
Juniper supports the full GraphQL query language according to the
4848
[specification][graphql_spec], including interfaces, unions, schema
49-
introspection, and validations.
50-
It does not, however, support the schema language.
49+
introspection, and validations. It can also output the schema in the [GraphQL Schema Language][schema_language].
5150

5251
As an exception to other GraphQL libraries for other languages, Juniper builds
5352
non-null types by default. A field of type `Vec<Episode>` will be converted into
@@ -86,6 +85,7 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected.
8685
[playground]: https://github.com/prisma/graphql-playground
8786
[iron]: http://ironframework.io
8887
[graphql_spec]: http://facebook.github.io/graphql
88+
[schema_language]: https://graphql.org/learn/schema/#type-language
8989
[test_schema_rs]: https://github.com/graphql-rust/juniper/blob/master/juniper/src/tests/schema.rs
9090
[tokio]: https://github.com/tokio-rs/tokio
9191
[hyper_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_hyper/examples

juniper/CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,26 @@
66
The DirectiveLocation::InlineFragment had an invalid literal value,
77
which broke third party tools like apollo cli.
88
- Added GraphQL Playground integration
9+
- Added the `schema-language` feature (on by default). This feature enables converting
10+
the schema to a `String` in the
11+
[GraphQL Schema Language](https://graphql.org/learn/schema/#type-language)
12+
format (usually written to a file called `schema.graphql`).
13+
14+
Example:
15+
16+
```rust
17+
use juniper::{RootNode, EmptyMutation};
18+
19+
#[derive(GraphQLObject)]
20+
struct Query{
21+
foo: bool
22+
};
23+
let s = RootNode::new(Query, EmptyMutation::<()>::new());
24+
println!("{}, s.as_schema_language());
25+
```
26+
27+
Note: The `schema-language` feature brings in more dependencies.
28+
If you don't use the schema language you may want to turn the feature off.
929
1030
# [0.11.1] 2018-12-19
1131

juniper/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,13 @@ path = "benches/bench.rs"
2424
[features]
2525
nightly = []
2626
expose-test-schema = []
27+
schema-language = ["graphql-parser-integration"]
28+
graphql-parser-integration = ["graphql-parser"]
2729
default = [
2830
"chrono",
2931
"url",
3032
"uuid",
33+
"schema-language",
3134
]
3235

3336
[dependencies]
@@ -42,6 +45,7 @@ chrono = { version = "0.4.0", optional = true }
4245
serde_json = { version="1.0.2", optional = true }
4346
url = { version = "1.5.1", optional = true }
4447
uuid = { version = "0.7", optional = true }
48+
graphql-parser = {version = "0.2.2", optional = true }
4549

4650
[dev-dependencies]
4751
bencher = "0.1.2"

juniper/src/lib.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,16 @@ extern crate fnv;
103103

104104
extern crate indexmap;
105105

106-
#[cfg(any(test, feature = "chrono"))]
106+
#[cfg(feature = "graphql-parser-integration")]
107+
extern crate graphql_parser;
108+
109+
#[cfg(feature = "chrono")]
107110
extern crate chrono;
108111

109-
#[cfg(any(test, feature = "url"))]
112+
#[cfg(feature = "url")]
110113
extern crate url;
111114

112-
#[cfg(any(test, feature = "uuid"))]
115+
#[cfg(feature = "uuid")]
113116
extern crate uuid;
114117

115118
// Depend on juniper_codegen and re-export everything in it.

juniper/src/schema/meta.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,14 @@ pub struct Field<'a, S> {
165165
pub deprecation_status: DeprecationStatus,
166166
}
167167

168+
impl<'a, S> Field<'a, S> {
169+
/// Returns true if the type is built-in to GraphQL.
170+
pub fn is_builtin(&self) -> bool {
171+
// "used exclusively by GraphQL’s introspection system"
172+
self.name.starts_with("__")
173+
}
174+
}
175+
168176
/// Metadata for an argument to a field
169177
#[derive(Debug, Clone)]
170178
pub struct Argument<'a, S> {
@@ -178,6 +186,14 @@ pub struct Argument<'a, S> {
178186
pub default_value: Option<InputValue<S>>,
179187
}
180188

189+
impl<'a, S> Argument<'a, S> {
190+
/// Returns true if the type is built-in to GraphQL.
191+
pub fn is_builtin(&self) -> bool {
192+
// "used exclusively by GraphQL’s introspection system"
193+
self.name.starts_with("__")
194+
}
195+
}
196+
181197
/// Metadata for a single value in an enum
182198
#[derive(Debug, Clone)]
183199
pub struct EnumValue {
@@ -364,6 +380,22 @@ impl<'a, S> MetaType<'a, S> {
364380
}
365381
}
366382

383+
/// Returns true if the type is built-in to GraphQL.
384+
pub fn is_builtin(&self) -> bool {
385+
if let Some(name) = self.name() {
386+
// "used exclusively by GraphQL’s introspection system"
387+
{
388+
name.starts_with("__") ||
389+
// <https://facebook.github.io/graphql/draft/#sec-Scalars>
390+
name == "Boolean" || name == "String" || name == "Int" || name == "Float" || name == "ID" ||
391+
// Our custom empty mutation marker
392+
name == "_EmptyMutation"
393+
}
394+
} else {
395+
false
396+
}
397+
}
398+
367399
pub(crate) fn fields<'b>(&self, schema: &'b SchemaType<S>) -> Option<Vec<&'b Field<'b, S>>> {
368400
schema
369401
.lookup_type(&self.as_type())

juniper/src/schema/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pub mod meta;
22
pub mod model;
33
pub mod schema;
4+
pub mod translate;

juniper/src/schema/model.rs

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@ use fnv::FnvHashMap;
44

55
use ast::Type;
66
use executor::{Context, Registry};
7+
#[cfg(feature = "graphql-parser-integration")]
8+
use graphql_parser::schema::Document;
79
use schema::meta::{Argument, InterfaceMeta, MetaType, ObjectMeta, PlaceholderMeta, UnionMeta};
10+
#[cfg(feature = "graphql-parser-integration")]
11+
use schema::translate::graphql_parser::GraphQLParserTranslator;
12+
#[cfg(feature = "graphql-parser-integration")]
13+
use schema::translate::SchemaTranslator;
14+
815
use types::base::GraphQLType;
916
use types::name::Name;
1017
use value::{DefaultScalarValue, ScalarRefValue, ScalarValue};
@@ -35,8 +42,8 @@ where
3542
#[derive(Debug)]
3643
pub struct SchemaType<'a, S> {
3744
pub(crate) types: FnvHashMap<Name, MetaType<'a, S>>,
38-
query_type_name: String,
39-
mutation_type_name: Option<String>,
45+
pub(crate) query_type_name: String,
46+
pub(crate) mutation_type_name: Option<String>,
4047
directives: FnvHashMap<String, DirectiveType<'a, S>>,
4148
}
4249

@@ -88,6 +95,22 @@ where
8895
{
8996
RootNode::new_with_info(query_obj, mutation_obj, (), ())
9097
}
98+
99+
#[cfg(feature = "schema-language")]
100+
/// The schema definition as a `String` in the
101+
/// [GraphQL Schema Language](https://graphql.org/learn/schema/#type-language)
102+
/// format.
103+
pub fn as_schema_language(&self) -> String {
104+
let doc = self.as_parser_document();
105+
format!("{}", doc)
106+
}
107+
108+
#[cfg(feature = "graphql-parser-integration")]
109+
/// The schema definition as a [`graphql_parser`](https://crates.io/crates/graphql-parser)
110+
/// [`Document`](https://docs.rs/graphql-parser/latest/graphql_parser/schema/struct.Document.html).
111+
pub fn as_parser_document(&self) -> Document {
112+
GraphQLParserTranslator::translate_schema(&self.schema)
113+
}
91114
}
92115

93116
impl<'a, S, QueryT, MutationT> RootNode<'a, QueryT, MutationT, S>
@@ -466,3 +489,92 @@ impl<'a, S> fmt::Display for TypeType<'a, S> {
466489
}
467490
}
468491
}
492+
493+
#[cfg(test)]
494+
mod test {
495+
496+
#[cfg(feature = "graphql-parser-integration")]
497+
mod graphql_parser_integration {
498+
use EmptyMutation;
499+
500+
#[test]
501+
fn graphql_parser_doc() {
502+
struct Query;
503+
graphql_object!(Query: () |&self| {
504+
field blah() -> bool {
505+
true
506+
}
507+
});
508+
let schema = crate::RootNode::new(Query, EmptyMutation::<()>::new());
509+
let ast = graphql_parser::parse_schema(
510+
r#"
511+
type Query {
512+
blah: Boolean!
513+
}
514+
515+
schema {
516+
query: Query
517+
}
518+
"#,
519+
)
520+
.unwrap();
521+
assert_eq!(
522+
format!("{}", ast),
523+
format!("{}", schema.as_parser_document()),
524+
);
525+
}
526+
}
527+
528+
#[cfg(feature = "schema-language")]
529+
mod schema_language {
530+
use EmptyMutation;
531+
532+
#[test]
533+
fn schema_language() {
534+
struct Query;
535+
graphql_object!(Query: () |&self| {
536+
field blah() -> bool {
537+
true
538+
}
539+
/// This is whatever's description.
540+
field whatever() -> String {
541+
"foo".to_string()
542+
}
543+
field fizz(buzz: String) -> Option<&str> {
544+
None
545+
}
546+
field arr(stuff: Vec<String>) -> Option<&str> {
547+
None
548+
}
549+
#[deprecated]
550+
field old() -> i32 {
551+
42
552+
}
553+
#[deprecated(note="This field is deprecated, use another.")]
554+
field really_old() -> f64 {
555+
42.0
556+
}
557+
});
558+
let schema = crate::RootNode::new(Query, EmptyMutation::<()>::new());
559+
let ast = graphql_parser::parse_schema(
560+
r#"
561+
type Query {
562+
blah: Boolean!
563+
"This is whatever's description."
564+
whatever: String!
565+
fizz(buzz: String!): String
566+
arr(stuff: [String!]!): String
567+
old: Int! @deprecated
568+
reallyOld: Float! @deprecated(reason: "This field is deprecated, use another.")
569+
}
570+
571+
schema {
572+
query: Query
573+
}
574+
"#,
575+
)
576+
.unwrap();
577+
assert_eq!(format!("{}", ast), schema.as_schema_language());
578+
}
579+
}
580+
}

0 commit comments

Comments
 (0)