bluejay_typegen/lib.rs
1//! `bluejay-typegen` is a crate for generating Rust types from GraphQL schemas and queries.
2//!
3//! ### Usage
4//! The [`typegen`] macro generates Rust types from a GraphQL schema and any number of queries.
5//! The macro must decorate a module, and the module must contain a type alias for each custom scalar defined in the schema.
6//! All shared types for the schema (input object, enums) are generated within the module.
7//!
8//! #### Arguments
9//! The macro takes one positional argument, followed by a series of optional named arguments.
10//! The positional argument can be either a string literal pointing to a file containing the schema in SDL format (path relative to the `Cargo.toml` of the crate where the macro is used),
11//! or DSL code defining the schema directly in the macro invocation, enclosed within square brackets.
12//! The optional named arguments are:
13//! - `borrow`: A boolean indicating whether the generated types should borrow strings from the input JSON value instead of owning them. Defaults to `false`.
14//! - `codec`: A string literal specifying the codec to use for serializing and deserializing values.
15//! Must be one of `"serde"` or `"miniserde"`. Defaults to `"serde"` when the `serde` feature is enabled, otherwise `"miniserde"` when the `miniserde` feature is enabled.
16//! When `"miniserde"` is used, `borrow` must be `false` as `miniserde` does not support borrowing strings.
17//!
18//! #### Queries
19//! Within the module defining the schema definition, a submodule can be defined for any number of executable documents.
20//! This can be done by decorating the submodule with `#[query(...)]` where the argument follows the same convention as the positional argument of the macro.
21//! For each operation and fragment definition in the query document, a corresponding Rust type is generated. If an anonymous operation is defined, the type is named `Root`.
22//! See [type path pattern](#type-path-pattern) for more information on how the path for a given type is determined.
23//!
24//! ### Example
25//! ```
26//! #[bluejay_typegen::typegen([
27//! scalar UnsignedInt
28//!
29//! enum Position {
30//! WING
31//! CENTRE
32//! DEFENCE
33//! }
34//!
35//! type Skater {
36//! name: String!
37//! position: Position!
38//! age: UnsignedInt!
39//! stats: [SkaterStat!]!
40//! }
41//!
42//! type SkaterStat {
43//! goals: UnsignedInt!
44//! }
45//!
46//! type Goalie {
47//! name: String!
48//! age: UnsignedInt!
49//! stats: [GoalieStat!]!
50//! }
51//!
52//! type GoalieStat {
53//! wins: UnsignedInt!
54//! }
55//!
56//! union Player = Skater | Goalie
57//!
58//! type Query {
59//! player: Player!
60//! }
61//! ], borrow = true)]
62//! mod schema {
63//! type UnsignedInt = u32;
64//!
65//! #[query([
66//! query Player {
67//! player {
68//! __typename
69//! ...on Skater {
70//! name
71//! age
72//! position
73//! stats { goals }
74//! }
75//! ...on Goalie {
76//! name
77//! age
78//! stats { wins }
79//! }
80//! }
81//! }
82//! ])]
83//! pub mod query {}
84//! }
85//!
86//! let value = serde_json::json!({
87//! "player": {
88//! "__typename": "Skater",
89//! "name": "Auston Matthews",
90//! "age": 25,
91//! "position": "CENTRE",
92//! "stats": [
93//! {
94//! "goals": 60
95//! },
96//! ],
97//! },
98//! }).to_string();
99//!
100//! let result: schema::query::Player = serde_json::from_str(&value).expect("Error parsing value");
101//!
102//! assert_eq!(
103//! schema::query::Player {
104//! player: schema::query::player::Player::Skater {
105//! name: "Auston Matthews".into(),
106//! age: 25,
107//! position: schema::Position::Centre,
108//! stats: vec![schema::query::player::player::skater::Stats { goals: 60 }],
109//! },
110//! },
111//! result,
112//! );
113//! ```
114//!
115//! ### Limitations
116//! - A query cannot contain a fragment definition with the same name as an operation definition
117//! - A schema module must contain exactly one type alias for each custom scalar defined in the schema, so that the type alias can be used in the generated Rust types
118//! - Within the scope of an object type, the selection set must not contain any inline fragments and must either:
119//! - Contain at least one field selection, or
120//! - Contain exactly one fragment spread
121//! - Within the scope of an interface type, the selection set must not contain any inline fragments and must either:
122//! - Contain at least one field selection, or
123//! - Contain exactly one fragment spread, where the target type of the fragment spread is either the interface type itself or an interface that the interface type implements
124//! - Within the scope of a union type, the selection set must:
125//! - Contain an unaliased field selection of `__typename` as the first selection in the set, and no other field selections, and
126//! - Not contain any fragment spreads, and
127//! - Not contain multiple inline fragments targeting any object type in the union type, and
128//! - Not contain any inline fragments targeting types that are not a member of the union type
129//!
130//! ### Type path pattern
131//! The path for a given type in the generated Rust types is determined by the following rules:
132//! - If the type is a custom scalar, enum, or input type, the path is `schema_module::TypeName`. For example, the `Position` enum in the example above has the path `schema::Position`.
133//! - If the type is an operation root type, the path is `schema_module::query_module::OperationName`. For example, the `Player` type for the `Player` query root in the example above has the path `schema::query::Player`.
134//! - If the type is an anonymous operation root type, the path is `schema_module::query_module::Root`
135//! - If the type is a nested object type, the path is nested under the path of the parent object type, like `schema_module::query_module::operation_name::TypeName`. For example, the `Player` Rust enum type for the `player` field in the example above has the path `schema::query::player::Player`. And the `Stats` Rust struct type for the `stats` field in the `Skater` arm of the `Player` enum has the path `schema::query::player::player::skater::Stats`.
136//! - If the type is a fragment definition, the path is `schema_module::query_module::FragmentName`, with all nested types following the same pattern as operation types, e.g. at `schema_module::query_module::fragment_name::TypeName`.
137
138pub use bluejay_typegen_macro::typegen;
139
140#[cfg(feature = "serde")]
141pub use srd as serde;
142
143#[cfg(feature = "miniserde")]
144pub use mnsrd as miniserde;