Document not found (404)
+This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +diff --git a/current/.nojekyll b/current/.nojekyll index 86312159..f1731109 100644 --- a/current/.nojekyll +++ b/current/.nojekyll @@ -1 +1 @@ -This file makes sure that Github Pages doesn't process mdBook's output. \ No newline at end of file +This file makes sure that Github Pages doesn't process mdBook's output. diff --git a/current/404.html b/current/404.html new file mode 100644 index 00000000..36d0c13b --- /dev/null +++ b/current/404.html @@ -0,0 +1,222 @@ + + +
+ + +This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +The chapters below cover some more advanced scenarios.
- - -GraphQL defines a special built-in top-level field called __schema
. Querying
-for this field allows one to introspect the schema
-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:
-{
- __schema {
- types {
- name
- }
- }
-}
-
-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:
-- -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::object( - Context = Context, -)] -impl Query { - fn example(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()); -} -
The GraphQL standard generally assumes there will be one server request for each client operation you want to perform (such as a query or mutation). This is conceptually simple but has the potential to be inefficent.
-Some client libraries such as apollo-link-batch-http have added the ability to batch operations in a single HTTP request to save network round-trips and potentially increase performance. There are some tradeoffs that should be considered before batching requests.
-Juniper's server integration crates support multiple operations in a single HTTP request using JSON arrays. This makes them compatible with client libraries that support batch operations without any special configuration.
-Server integration crates maintained by others are not required to support batch requests. Batch requests aren't part of the official GraphQL specification.
-Assuming an integration supports batch requests, for the following GraphQL query:
-{
- hero {
- name
- }
-}
-
-The json data to POST to the server for an individual request would be:
-{
- "query": "{hero{name}}"
-}
-
-And the response would be of the form:
-{
- "data": {
- "hero": {
- "name": "R2-D2"
- }
- }
-}
-
-If you wanted to run the same query twice in a single HTTP request, the batched json data to POST to the server would be:
-[
- {
- "query": "{hero{name}}"
- },
- {
- "query": "{hero{name}}"
- }
-]
-
-And the response would be of the form:
-[
- {
- "data": {
- "hero": {
- "name": "R2-D2"
- }
- }
- },
- {
- "data": {
- "hero": {
- "name": "R2-D2"
- }
- }
- }
-]
-
-
- Up until now, we've only looked at mapping structs to GraphQL objects. However, -any Rust type can be mapped into a GraphQL object. In this chapter, we'll look -at enums, but traits will work too - they don't have to be mapped into GraphQL -interfaces.
-Using Result
-like enums can be a useful way of reporting e.g. validation
-errors from a mutation:
-# #[derive(juniper::GraphQLObject)] struct User { name: String } - -#[derive(juniper::GraphQLObject)] -struct ValidationError { - field: String, - message: String, -} - -# #[allow(dead_code)] -enum SignUpResult { - Ok(User), - Error(Vec<ValidationError>), -} - -#[juniper::object] -impl SignUpResult { - fn user(&self) -> Option<&User> { - match *self { - SignUpResult::Ok(ref user) => Some(user), - SignUpResult::Error(_) => None, - } - } - - fn error(&self) -> Option<&Vec<ValidationError>> { - match *self { - SignUpResult::Ok(_) => None, - SignUpResult::Error(ref errors) => Some(errors) - } - } -} - -# fn main() {} -
Here, we use an enum to decide whether a user's input data was valid or not, and -it could be used as the result of e.g. a sign up mutation.
-While this is an example of how you could use something other than a struct to -represent a GraphQL object, it's also an example on how you could implement -error handling for "expected" errors - errors like validation errors. There are -no hard rules on how to represent errors in GraphQL, but there are -some -comments -from one of the authors of GraphQL on how they intended "hard" field errors to -be used, and how to model expected errors.
- -Yet another point where GraphQL and Rust differs is in how generics work. In -Rust, almost any type could be generic - that is, take type parameters. In -GraphQL, there are only two generic types: lists and non-nullables.
-This poses a restriction on what you can expose in GraphQL from Rust: no generic
-structs can be exposed - all type parameters must be bound. For example, you can
-not make e.g. Result<T, E>
into a GraphQL type, but you can make e.g.
-Result<User, String>
into a GraphQL type.
Let's make a slightly more compact but generic implementation of the last -chapter:
--# #[derive(juniper::GraphQLObject)] struct User { name: String } -# #[derive(juniper::GraphQLObject)] struct ForumPost { title: String } - -#[derive(juniper::GraphQLObject)] -struct ValidationError { - field: String, - message: String, -} - -# #[allow(dead_code)] -struct MutationResult<T>(Result<T, Vec<ValidationError>>); - -#[juniper::object( - name = "UserResult", -)] -impl MutationResult<User> { - fn user(&self) -> Option<&User> { - self.0.as_ref().ok() - } - - fn error(&self) -> Option<&Vec<ValidationError>> { - self.0.as_ref().err() - } -} - -#[juniper::object( - name = "ForumPostResult", -)] -impl MutationResult<ForumPost> { - fn forum_post(&self) -> Option<&ForumPost> { - self.0.as_ref().ok() - } - - fn error(&self) -> Option<&Vec<ValidationError>> { - self.0.as_ref().err() - } -} - -# fn main() {} -
Here, we've made a wrapper around Result
and exposed some concrete
-instantiations of Result<T, E>
as distinct GraphQL objects. The reason we
-needed the wrapper is of Rust's rules for when you can derive a trait - in this
-case, both Result
and Juniper's internal GraphQL trait are from third-party
-sources.
Because we're using generics, we also need to specify a name for our
-instantiated types. Even if Juniper could figure out the name,
-MutationResult<User>
wouldn't be a valid GraphQL type name.