Query Fragments
QueryFragments are the main tool for building queries & mutations in cynic.
Cynic builds up a GraphQL query document from the fields on a QueryFragment
and any QueryFragments
nested inside it. And after executing an operation it
deserializes the result into the QueryFragment
struct.
Generally you'll use a derive to create query fragments, like this:
#![allow(unused)] fn main() { #[derive(cynic::QueryFragment, Debug)] struct Film { title: Option<String>, director: Option<String>, } }
When this struct is used in a query it will select the title
& director
fields of the Film
type, which are both optional strings. QueryFragments can
be nested inside each other, like so:
#![allow(unused)] fn main() { #[derive(cynic::QueryFragment, Debug)] struct FilmsConnection { films: Vec<Film>, } }
If the above QueryFragment was used in a query, it would result in GraphQL that looked like:
films {
title
director
}
QueryFragments are compile time checked against the provided GraphQL schema.
You cannot nest a Film
QueryFragment into a field that was expecting an
Actor
for example. Similarly, nullable fields must be an Option
and lists
must be a Vec
.
Making a Query with QueryFragments
QueryFragments that apply to the Query type (otherwise known as the Root type)
of a schema can be used to build a cynic::Operation
. This Operation
is the
type that should be serialized and sent to the server.
If we wanted to use our FilmConnection to get all the films from the star wars API we need a QueryFragment like this:
#![allow(unused)] fn main() { #[derive(cynic::QueryFragment, Debug)] #[cynic(graphql_type = "Root")] struct AllFilmsQuery { all_films: Option<FilmConnection>, } }
An Operation
can be created from this QueryFragment
:
#![allow(unused)] fn main() { use cynic::QueryBuilder; let operation = AllFilmsQuery::build(()); }
This particular query has no arguments so we provide the unit type ()
in place
of actual arguments.
This Operation
can be serialized into JSON using serde
, sent to a server,
and then then a cynic::GraphQlResponse<AllFilmsQuery>
can be deserialized
from the response. An example of this is in the Quickstart.
Passing Arguments
GraphQL allows a server to define arguments that a field can accept. Cynic
provides support for passing in these arguments via its arguments
attribute.
Here, we define a query that fetches a film by a particular ID:
#![allow(unused)] fn main() { #[derive(cynic::QueryFragment, Debug)] #[cynic(graphql_type = "Root")] struct FilmQuery { #[arguments(id: "ZmlsbXM6MQ==")] film: Option<Film>, } }
Note the #[arguments: id: "ZmlsbXM6MQ=="]
attribute on the film
field. The
GraphQL generated for this query will provide a hard coded id
argument to the
film
field, like this:
film(id: "ZmlsbXM6MQ==") {
title
director
}
The syntax of the inside of arguments is very similar to the syntax expected for arguments in GraphQL itself. Some examples:
GraphQL | Cynic |
---|---|
input: { filters: "ACTIVE" } | input: { filters: "ACTIVE" } |
values: ["Hello"] | values: ["Hello"] |
values: ["Hello"] | values: ["Hello"] |
arg1: "Foo", arg2: "Bar" | arg1: "Foo", arg2: "Bar" |
arg1: null | arg1: null |
Variables
If you don't want to hard code the value of an argument, you can parameterise your query with some variables. These variables must be defined on a struct:
#![allow(unused)] fn main() { #[derive(cynic::QueryVariables)] struct FilmQueryVariables { id: Option<cynic::Id>, } }
The fields of this struct can be any Enum
, InputObject
, or Scalar
.
To use this struct you need to tell your QueryFragment
that it takes variables
using the variables
parameter to to the cynic
attribute, and then you can
use variables much like you would in GraphQL.
Here, we update our FilmQuery
struct to make use of our FilmQueryVariables
to provide the id
argument.
#![allow(unused)] fn main() { #[derive(cynic::QueryFragment, Debug)] #[cynic( graphql_type = "Root", variables = "FilmQueryVariables" )] struct FilmQuery { #[arguments(id: $id)] film: Option<Film>, } }
Any field of the variables struct may be used by prefixing the name of the
field with $
.
This can be converted into a query in a similar way we just need to provide
some FilmArguments
:
#![allow(unused)] fn main() { use cynic::QueryBuilder; let operation = FilmQuery::build( FilmArguments{ id: Some("ZmlsbXM6MQ==".into()), } ); }
See query variables for more details.
Nested Variables
The example above showed how to pass variables to the top level of a query. If
you want to pass variables to a nested QueryFragment then all it's parent
QueryFragment
s must specify the same variables
in their cynic
attribute. This is necessary so that the QueryVariables
struct gets passed
down to that level of a query.
If no nested QueryFragments
require arguments, you can omit the
variables
attr from those QueryFragments
Mutations
Mutations are also constructed using QueryFragments in a very similar way to
queries. Instead of selecting query fields you select a mutation, and pass in
any arguments in exactly the same way. Mutations use the MutationBuilder
rather than QueryBulder
:
#![allow(unused)] fn main() { use cynic::MutationBuilder; let operation = SomeMutation::build(SomeArguments { ... }); }
This operation
can then be used in exactly the same way as with queries.
Struct Attributes
A QueryFragment can be configured with several attributes on the struct itself:
graphql_type = "AType"
tells cynic which object in the GraphQL schema this struct represents. The name of the struct is used if it is omitted.variables
defines theQueryVariables
struct that is available toarguments
attributes on fields of the given struct.schema
tells cynic which schema to use to validate your InlineFragments. The schema you provide should have been registered in yourbuild.rs
. This is optional if you're using the schema that was registered as default, or if you're usingschema_path
instead.schema_path
sets a path to the GraphQL schema. This is only required if you're using a schema that wasn't registered inbuild.rs
.schema_module
tells cynic where to find your schema module. This is optional and should only be needed if your schema module is not in scope or namedschema
.
Field Attributes
Each field can also have it's own attributes:
rename = "someGraphqlName"
can be provided if you want the rust field name to differ from the GraphQL field name. You should provide the name as it is in the GraphQL schema (although due to implementation details a snake case form of the name may work as well)alias
can be provided if you have a renamed field and want to explicitly request a GraphQL alias in the resulting query output.recurse = "5"
tells cynic that this field is recursive and should be fetched to a maximum depth of 5. See Recursive Queries for more info.- The
flatten
attr can be used to "flatten" out excessive Options from lists. As GraphQL is used in languages with implicit nulls, it's not uncommon to see a type[Int]
- which in Rust maps toOption<Vec<Option<i32>>
. This isn't a very nice type to work with - applying theflatten
attribute lets you represent this as aVec<i32>
in your QueryFragment. Any outer nulls become an empty list and inner nulls are dropped. - The
spread
attr can be used to spread anotherQueryFragment
s into the currentQueryFragment
, if each of theQueryFragment
s point at the same GraphQL type. - The
feature
attribute can be used to feature flag parts of a query, allowing cynic to support different versions of a schema with the sameQueryFragments
. See feature flagging queries for more details.
Related
- QueryVariables are used to provide variables to a QueryFragment.
- Recursive queries are supported by QueryFragments.