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;