Add support for the canonical introspection query
Fixes https://github.com/graphql-rust/juniper/issues/307.
This commit is contained in:
parent
d5341e252a
commit
ba8cfbd105
13 changed files with 2825 additions and 21 deletions
|
@ -2,20 +2,22 @@
|
|||
- [Quickstart](quickstart.md)
|
||||
|
||||
- [Type System](types/index.md)
|
||||
- [Defining objects](types/objects/defining_objects.md)
|
||||
- [Complex fields](types/objects/complex_fields.md)
|
||||
- [Using contexts](types/objects/using_contexts.md)
|
||||
- [Error handling](types/objects/error_handling.md)
|
||||
- [Other types](types/other-index.md)
|
||||
- [Enums](types/enums.md)
|
||||
- [Interfaces](types/interfaces.md)
|
||||
- [Input objects](types/input_objects.md)
|
||||
- [Scalars](types/scalars.md)
|
||||
- [Unions](types/unions.md)
|
||||
|
||||
- [Defining objects](types/objects/defining_objects.md)
|
||||
- [Complex fields](types/objects/complex_fields.md)
|
||||
- [Using contexts](types/objects/using_contexts.md)
|
||||
- [Error handling](types/objects/error_handling.md)
|
||||
- [Other types](types/other-index.md)
|
||||
- [Enums](types/enums.md)
|
||||
- [Interfaces](types/interfaces.md)
|
||||
- [Input objects](types/input_objects.md)
|
||||
- [Scalars](types/scalars.md)
|
||||
- [Unions](types/unions.md)
|
||||
|
||||
- [Schemas and mutations](schema/schemas_and_mutations.md)
|
||||
|
||||
- [Adding A Server](servers/index.md)
|
||||
|
||||
- [Official Server Integrations](servers/official.md) - [Hyper](servers/hyper.md)
|
||||
- [Warp](servers/warp.md)
|
||||
- [Rocket](servers/rocket.md)
|
||||
|
@ -24,8 +26,12 @@
|
|||
- [Third Party Integrations](servers/third-party.md)
|
||||
|
||||
- [Advanced Topics](advanced/index.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)
|
||||
|
||||
- [Introspection](advanced/introspection.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]
|
||||
|
||||
# - [Dynamic type system]
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
The chapters below cover some more advanced scenarios.
|
||||
|
||||
- [Introspection](advanced/introspection.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)
|
||||
|
|
73
docs/book/content/advanced/introspection.md
Normal file
73
docs/book/content/advanced/introspection.md
Normal 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());
|
||||
}
|
||||
```
|
|
@ -33,8 +33,8 @@ use juniper::{FieldResult};
|
|||
# struct DatabasePool;
|
||||
# impl DatabasePool {
|
||||
# fn get_connection(&self) -> FieldResult<DatabasePool> { Ok(DatabasePool) }
|
||||
# fn find_human(&self, id: &str) -> FieldResult<Human> { Err("")? }
|
||||
# fn insert_human(&self, human: &NewHuman) -> FieldResult<Human> { Err("")? }
|
||||
# fn find_human(&self, _id: &str) -> FieldResult<Human> { Err("")? }
|
||||
# fn insert_human(&self, _human: &NewHuman) -> FieldResult<Human> { Err("")? }
|
||||
# }
|
||||
|
||||
#[derive(juniper::GraphQLEnum)]
|
||||
|
@ -114,12 +114,14 @@ juniper::graphql_object!(Mutation: Context |&self| {
|
|||
// Request queries can be executed against a RootNode.
|
||||
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!
|
||||
|
||||
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:
|
||||
|
||||
|
@ -129,6 +131,7 @@ You can invoke `juniper::execute` directly to run a GraphQL query:
|
|||
|
||||
```rust
|
||||
# // Only needed due to 2018 edition because the macro is not accessible.
|
||||
# #[macro_use]
|
||||
# extern crate juniper;
|
||||
use juniper::{FieldResult, Variables, EmptyMutation};
|
||||
|
||||
|
@ -172,7 +175,7 @@ fn main() {
|
|||
assert_eq!(
|
||||
res,
|
||||
graphql_value!({
|
||||
"favoriteEpisode": "NEW_HONE",
|
||||
"favoriteEpisode": "NEW_HOPE",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
extern crate skeptic;
|
||||
|
||||
fn main() {
|
||||
let files = skeptic::markdown_files_of_directory("../content/types");
|
||||
let files = skeptic::markdown_files_of_directory("../content/");
|
||||
skeptic::generate_doc_tests(&files);
|
||||
}
|
||||
|
|
|
@ -2,12 +2,14 @@
|
|||
|
||||
- The minimum required Rust version is now `1.30.0`.
|
||||
- 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
|
||||
The 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 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)
|
||||
|
||||
# [0.11.1] 2018-12-19
|
||||
|
|
19
juniper/src/introspection/mod.rs
Normal file
19
juniper/src/introspection/mod.rs
Normal 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
|
||||
}
|
||||
}
|
96
juniper/src/introspection/query.graphql
Normal file
96
juniper/src/introspection/query.graphql
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
91
juniper/src/introspection/query_without_descriptions.graphql
Normal file
91
juniper/src/introspection/query_without_descriptions.graphql
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -127,6 +127,7 @@ mod value;
|
|||
mod macros;
|
||||
mod ast;
|
||||
mod executor;
|
||||
mod introspection;
|
||||
pub mod parser;
|
||||
pub(crate) mod schema;
|
||||
mod types;
|
||||
|
@ -151,6 +152,7 @@ mod executor_tests;
|
|||
pub use util::to_camel_case;
|
||||
|
||||
use executor::execute_validated_query;
|
||||
use introspection::{INTROSPECTION_QUERY, INTROSPECTION_QUERY_WITHOUT_DESCRIPTIONS};
|
||||
use parser::{parse_document_source, ParseError, Spanning};
|
||||
use validation::{validate_input_values, visit_all_rules, ValidatorContext};
|
||||
|
||||
|
@ -162,6 +164,7 @@ pub use executor::{
|
|||
Context, ExecutionError, ExecutionResult, Executor, FieldError, FieldResult, FromContext,
|
||||
IntoFieldError, IntoResolvable, Registry, Variables,
|
||||
};
|
||||
pub use introspection::IntrospectionFormat;
|
||||
pub use schema::meta;
|
||||
pub use schema::model::RootNode;
|
||||
pub use types::base::{Arguments, GraphQLType, TypeKind};
|
||||
|
@ -219,6 +222,30 @@ where
|
|||
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> {
|
||||
fn from(f: Spanning<ParseError<'a>>) -> GraphQLError<'a> {
|
||||
GraphQLError::ParseError(f)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use super::schema_introspection::*;
|
||||
use executor::Variables;
|
||||
use introspection::IntrospectionFormat;
|
||||
use schema::model::RootNode;
|
||||
use tests::model::Database;
|
||||
use types::scalars::EmptyMutation;
|
||||
|
@ -229,3 +231,23 @@ fn test_introspection_possible_types() {
|
|||
|
||||
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![])));
|
||||
}
|
||||
|
|
|
@ -7,4 +7,6 @@ pub mod model;
|
|||
mod query_tests;
|
||||
mod schema;
|
||||
#[cfg(test)]
|
||||
mod schema_introspection;
|
||||
#[cfg(test)]
|
||||
mod type_info_tests;
|
||||
|
|
2462
juniper/src/tests/schema_introspection.rs
Normal file
2462
juniper/src/tests/schema_introspection.rs
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue