Add support for the canonical introspection query

Fixes https://github.com/graphql-rust/juniper/issues/307.
This commit is contained in:
Christian Legnitto 2019-04-02 23:36:10 -07:00 committed by theduke
parent d5341e252a
commit ba8cfbd105
13 changed files with 2825 additions and 21 deletions

View file

@ -2,20 +2,22 @@
- [Quickstart](quickstart.md) - [Quickstart](quickstart.md)
- [Type System](types/index.md) - [Type System](types/index.md)
- [Defining objects](types/objects/defining_objects.md)
- [Complex fields](types/objects/complex_fields.md) - [Defining objects](types/objects/defining_objects.md)
- [Using contexts](types/objects/using_contexts.md) - [Complex fields](types/objects/complex_fields.md)
- [Error handling](types/objects/error_handling.md) - [Using contexts](types/objects/using_contexts.md)
- [Other types](types/other-index.md) - [Error handling](types/objects/error_handling.md)
- [Enums](types/enums.md) - [Other types](types/other-index.md)
- [Interfaces](types/interfaces.md) - [Enums](types/enums.md)
- [Input objects](types/input_objects.md) - [Interfaces](types/interfaces.md)
- [Scalars](types/scalars.md) - [Input objects](types/input_objects.md)
- [Unions](types/unions.md) - [Scalars](types/scalars.md)
- [Unions](types/unions.md)
- [Schemas and mutations](schema/schemas_and_mutations.md) - [Schemas and mutations](schema/schemas_and_mutations.md)
- [Adding A Server](servers/index.md) - [Adding A Server](servers/index.md)
- [Official Server Integrations](servers/official.md) - [Hyper](servers/hyper.md) - [Official Server Integrations](servers/official.md) - [Hyper](servers/hyper.md)
- [Warp](servers/warp.md) - [Warp](servers/warp.md)
- [Rocket](servers/rocket.md) - [Rocket](servers/rocket.md)
@ -24,8 +26,12 @@
- [Third Party Integrations](servers/third-party.md) - [Third Party Integrations](servers/third-party.md)
- [Advanced Topics](advanced/index.md) - [Advanced Topics](advanced/index.md)
- [Non-struct objects](advanced/non_struct_objects.md)
- [Objects and generics](advanced/objects_and_generics.md) - [Introspection](advanced/introspection.md)
- [Multiple operations per request](advanced/multiple_ops_per_request.md) - [Non-struct objects](advanced/non_struct_objects.md)
- [Objects and generics](advanced/objects_and_generics.md)
- [Multiple operations per request](advanced/multiple_ops_per_request.md)
# - [Context switching] # - [Context switching]
# - [Dynamic type system] # - [Dynamic type system]

View file

@ -2,6 +2,7 @@
The chapters below cover some more advanced scenarios. The chapters below cover some more advanced scenarios.
- [Introspection](advanced/introspection.md)
- [Non-struct objects](advanced/non_struct_objects.md) - [Non-struct objects](advanced/non_struct_objects.md)
- [Objects and generics](advanced/objects_and_generics.md) - [Objects and generics](advanced/objects_and_generics.md)
- [Multiple operations per request](advanced/multiple_ops_per_request.md) - [Multiple operations per request](advanced/multiple_ops_per_request.md)

View file

@ -0,0 +1,73 @@
# Introspection
GraphQL defines a special built-in top-level field called `__schema`. Querying
for this field allows one to [introspect the schema](https://graphql.org/learn/introspection/)
at runtime to see what queries and mutations the GraphQL server supports.
Because introspection queries are just regular GraphQL queries, Juniper supports
them natively. For example, to get all the names of the types supported one
could execute the following query against Juniper:
```graphql
{
__schema {
types {
name
}
}
}
```
## Schema introspection output as JSON
Many client libraries and tools in the GraphQL ecosystem require a complete
representation of the server schema. Often this representation is in JSON and
referred to as `schema.json`. A complete representation of the schema can be
produced by issuing a specially crafted introspection query.
Juniper provides a convenience function to introspect the entire schema. The
result can then be converted to JSON for use with tools and libraries such as
[graphql-client](https://github.com/graphql-rust/graphql-client):
```rust
# // Only needed due to 2018 edition because the macro is not accessible.
# extern crate juniper;
# extern crate serde_json;
use juniper::{EmptyMutation, FieldResult, IntrospectionFormat};
// Define our schema.
#[derive(juniper::GraphQLObject)]
struct Example {
id: String,
}
struct Context;
impl juniper::Context for Context {}
struct Query;
juniper::graphql_object!(Query: Context |&self| {
field example(&executor, id: String) -> FieldResult<Example> {
unimplemented!()
}
});
type Schema = juniper::RootNode<'static, Query, EmptyMutation<Context>>;
fn main() {
// Create a context object.
let ctx = Context{};
// Run the built-in introspection query.
let (res, _errors) = juniper::introspect(
&Schema::new(Query, EmptyMutation::new()),
&ctx,
IntrospectionFormat::default(),
).unwrap();
// Convert introspection result to json.
let json_result = serde_json::to_string_pretty(&res);
assert!(json_result.is_ok());
}
```

View file

@ -33,8 +33,8 @@ use juniper::{FieldResult};
# struct DatabasePool; # struct DatabasePool;
# impl DatabasePool { # impl DatabasePool {
# fn get_connection(&self) -> FieldResult<DatabasePool> { Ok(DatabasePool) } # fn get_connection(&self) -> FieldResult<DatabasePool> { Ok(DatabasePool) }
# fn find_human(&self, id: &str) -> FieldResult<Human> { Err("")? } # fn find_human(&self, _id: &str) -> FieldResult<Human> { Err("")? }
# fn insert_human(&self, human: &NewHuman) -> FieldResult<Human> { Err("")? } # fn insert_human(&self, _human: &NewHuman) -> FieldResult<Human> { Err("")? }
# } # }
#[derive(juniper::GraphQLEnum)] #[derive(juniper::GraphQLEnum)]
@ -114,12 +114,14 @@ juniper::graphql_object!(Mutation: Context |&self| {
// Request queries can be executed against a RootNode. // Request queries can be executed against a RootNode.
type Schema = juniper::RootNode<'static, Query, Mutation>; type Schema = juniper::RootNode<'static, Query, Mutation>;
# fn main() { } # fn main() {
# let _ = Schema::new(Query, Mutation{});
# }
``` ```
We now have a very simple but functional schema for a GraphQL server! We now have a very simple but functional schema for a GraphQL server!
To actually serve the schema, see the guides for our various [server integrations](./servers/index.md). To actually serve the schema, see the guides for our various [server integrations](./servers/index.md).
You can also invoke the executor directly to get a result for a query: You can also invoke the executor directly to get a result for a query:
@ -129,6 +131,7 @@ You can invoke `juniper::execute` directly to run a GraphQL query:
```rust ```rust
# // Only needed due to 2018 edition because the macro is not accessible. # // Only needed due to 2018 edition because the macro is not accessible.
# #[macro_use]
# extern crate juniper; # extern crate juniper;
use juniper::{FieldResult, Variables, EmptyMutation}; use juniper::{FieldResult, Variables, EmptyMutation};
@ -172,7 +175,7 @@ fn main() {
assert_eq!( assert_eq!(
res, res,
graphql_value!({ graphql_value!({
"favoriteEpisode": "NEW_HONE", "favoriteEpisode": "NEW_HOPE",
}) })
); );
} }

View file

@ -1,6 +1,6 @@
extern crate skeptic; extern crate skeptic;
fn main() { fn main() {
let files = skeptic::markdown_files_of_directory("../content/types"); let files = skeptic::markdown_files_of_directory("../content/");
skeptic::generate_doc_tests(&files); skeptic::generate_doc_tests(&files);
} }

View file

@ -2,12 +2,14 @@
- The minimum required Rust version is now `1.30.0`. - The minimum required Rust version is now `1.30.0`.
- The `ScalarValue` custom derive has been renamed to `GraphQLScalarValue`. - The `ScalarValue` custom derive has been renamed to `GraphQLScalarValue`.
- Added built-in support for the canonical schema introspection query via
`juniper::introspect()`. [#307](https://github.com/graphql-rust/juniper/issues/307)
- Fix introspection query validity - Fix introspection query validity
The DirectiveLocation::InlineFragment had an invalid literal value, The DirectiveLocation::InlineFragment had an invalid literal value,
which broke third party tools like apollo cli. which broke third party tools like apollo cli.
- Added GraphQL Playground integration - Added GraphQL Playground integration
The DirectiveLocation::InlineFragment had an invalid literal value, The DirectiveLocation::InlineFragment had an invalid literal value,
which broke third party tools like apollo cli. 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) - The return type of `value::object::Object::iter/iter_mut` has changed to `impl Iter` [#312](https://github.com/graphql-rust/juniper/pull/312)
# [0.11.1] 2018-12-19 # [0.11.1] 2018-12-19

View file

@ -0,0 +1,19 @@
/// From <https://github.com/graphql/graphql-js/blob/8c96dc8276f2de27b8af9ffbd71a4597d483523f/src/utilities/introspectionQuery.js#L21>s
pub(crate) const INTROSPECTION_QUERY: &str = include_str!("./query.graphql");
pub(crate) const INTROSPECTION_QUERY_WITHOUT_DESCRIPTIONS: &str =
include_str!("./query_without_descriptions.graphql");
/// The desired GraphQL introspection format for the canonical query
/// (<https://github.com/graphql/graphql-js/blob/8c96dc8276f2de27b8af9ffbd71a4597d483523f/src/utilities/introspectionQuery.js#L21>)
pub enum IntrospectionFormat {
/// The canonical GraphQL introspection query.
All,
/// The canonical GraphQL introspection query without descriptions.
WithoutDescriptions,
}
impl Default for IntrospectionFormat {
fn default() -> Self {
IntrospectionFormat::All
}
}

View file

@ -0,0 +1,96 @@
query IntrospectionQuery {
__schema {
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
types {
...FullType
}
directives {
name
description
locations
args {
...InputValue
}
}
}
}
fragment FullType on __Type {
kind
name
description
fields(includeDeprecated: true) {
name
description
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}
fragment InputValue on __InputValue {
name
description
type {
...TypeRef
}
defaultValue
}
fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
}
}

View file

@ -0,0 +1,91 @@
query IntrospectionQuery {
__schema {
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
types {
...FullType
}
directives {
name
locations
args {
...InputValue
}
}
}
}
fragment FullType on __Type {
kind
name
fields(includeDeprecated: true) {
name
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}
fragment InputValue on __InputValue {
name
type {
...TypeRef
}
defaultValue
}
fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
}
}

View file

@ -127,6 +127,7 @@ mod value;
mod macros; mod macros;
mod ast; mod ast;
mod executor; mod executor;
mod introspection;
pub mod parser; pub mod parser;
pub(crate) mod schema; pub(crate) mod schema;
mod types; mod types;
@ -151,6 +152,7 @@ mod executor_tests;
pub use util::to_camel_case; pub use util::to_camel_case;
use executor::execute_validated_query; use executor::execute_validated_query;
use introspection::{INTROSPECTION_QUERY, INTROSPECTION_QUERY_WITHOUT_DESCRIPTIONS};
use parser::{parse_document_source, ParseError, Spanning}; use parser::{parse_document_source, ParseError, Spanning};
use validation::{validate_input_values, visit_all_rules, ValidatorContext}; use validation::{validate_input_values, visit_all_rules, ValidatorContext};
@ -162,6 +164,7 @@ pub use executor::{
Context, ExecutionError, ExecutionResult, Executor, FieldError, FieldResult, FromContext, Context, ExecutionError, ExecutionResult, Executor, FieldError, FieldResult, FromContext,
IntoFieldError, IntoResolvable, Registry, Variables, IntoFieldError, IntoResolvable, Registry, Variables,
}; };
pub use introspection::IntrospectionFormat;
pub use schema::meta; pub use schema::meta;
pub use schema::model::RootNode; pub use schema::model::RootNode;
pub use types::base::{Arguments, GraphQLType, TypeKind}; pub use types::base::{Arguments, GraphQLType, TypeKind};
@ -219,6 +222,30 @@ where
execute_validated_query(document, operation_name, root_node, variables, context) execute_validated_query(document, operation_name, root_node, variables, context)
} }
/// Execute the reference introspection query in the provided schema
pub fn introspect<'a, S, CtxT, QueryT, MutationT>(
root_node: &'a RootNode<QueryT, MutationT, S>,
context: &CtxT,
format: IntrospectionFormat,
) -> Result<(Value<S>, Vec<ExecutionError<S>>), GraphQLError<'a>>
where
S: ScalarValue,
for<'b> &'b S: ScalarRefValue<'b>,
QueryT: GraphQLType<S, Context = CtxT>,
MutationT: GraphQLType<S, Context = CtxT>,
{
execute(
match format {
IntrospectionFormat::All => INTROSPECTION_QUERY,
IntrospectionFormat::WithoutDescriptions => INTROSPECTION_QUERY_WITHOUT_DESCRIPTIONS,
},
None,
root_node,
&Variables::new(),
context,
)
}
impl<'a> From<Spanning<ParseError<'a>>> for GraphQLError<'a> { impl<'a> From<Spanning<ParseError<'a>>> for GraphQLError<'a> {
fn from(f: Spanning<ParseError<'a>>) -> GraphQLError<'a> { fn from(f: Spanning<ParseError<'a>>) -> GraphQLError<'a> {
GraphQLError::ParseError(f) GraphQLError::ParseError(f)

View file

@ -1,6 +1,8 @@
use std::collections::HashSet; use std::collections::HashSet;
use super::schema_introspection::*;
use executor::Variables; use executor::Variables;
use introspection::IntrospectionFormat;
use schema::model::RootNode; use schema::model::RootNode;
use tests::model::Database; use tests::model::Database;
use types::scalars::EmptyMutation; use types::scalars::EmptyMutation;
@ -229,3 +231,23 @@ fn test_introspection_possible_types() {
assert_eq!(possible_types, vec!["Human", "Droid"].into_iter().collect()); assert_eq!(possible_types, vec!["Human", "Droid"].into_iter().collect());
} }
#[test]
fn test_builtin_introspection_query() {
let database = Database::new();
let schema = RootNode::new(&database, EmptyMutation::<Database>::new());
let result = ::introspect(&schema, &database, IntrospectionFormat::default());
let expected = schema_introspection_result();
assert_eq!(result, Ok((expected, vec![])));
}
#[test]
fn test_builtin_introspection_query_without_descriptions() {
let database = Database::new();
let schema = RootNode::new(&database, EmptyMutation::<Database>::new());
let result = ::introspect(&schema, &database, IntrospectionFormat::WithoutDescriptions);
let expected = schema_introspection_result_without_descriptions();
assert_eq!(result, Ok((expected, vec![])));
}

View file

@ -7,4 +7,6 @@ pub mod model;
mod query_tests; mod query_tests;
mod schema; mod schema;
#[cfg(test)] #[cfg(test)]
mod schema_introspection;
#[cfg(test)]
mod type_info_tests; mod type_info_tests;

File diff suppressed because it is too large Load diff