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//! - `enums_as_str`: An array of string literals containing the names of enum types from the GraphQL schema that should be represented as strings. Defaults to `[]`.
15//!   When `borrow` is true, the values are `std::borrow::Cow<str>`, otherwise they are `String`.
16//!
17//! #### Queries
18//! Within the module defining the schema definition, a submodule can be defined for any number of executable documents.
19//! This can be done by decorating the submodule with `#[query(...)]` where the argument follows the same convention as the positional argument of the macro.
20//! 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`.
21//! See [type path pattern](#type-path-pattern) for more information on how the path for a given type is determined.
22//!
23//! ### Example
24//! ```
25//! #[bluejay_typegen::typegen([
26//!   scalar UnsignedInt
27//!
28//!   enum Position {
29//!     WING
30//!     CENTRE
31//!     DEFENCE
32//!   }
33//!
34//!   type Skater {
35//!     name: String!
36//!     position: Position!
37//!     age: UnsignedInt!
38//!     stats: [SkaterStat!]!
39//!   }
40//!
41//!   type SkaterStat {
42//!     goals: UnsignedInt!
43//!   }
44//!
45//!   type Goalie {
46//!     name: String!
47//!     age: UnsignedInt!
48//!     stats: [GoalieStat!]!
49//!   }
50//!
51//!   type GoalieStat {
52//!     wins: UnsignedInt!
53//!   }
54//!
55//!   union Player = Skater | Goalie
56//!
57//!   type Query {
58//!     player: Player!
59//!   }
60//! ], borrow = true)]
61//! mod schema {
62//!     type UnsignedInt = u32;
63//!
64//!     #[query([
65//!       query Player {
66//!         player {
67//!           __typename
68//!           ...on Skater {
69//!             name
70//!             age
71//!             position
72//!             stats { goals }
73//!           }
74//!           ...on Goalie {
75//!             name
76//!             age
77//!             stats { wins }
78//!           }
79//!         }
80//!       }
81//!     ])]
82//!     pub mod query {}
83//! }
84//!
85//! let value = serde_json::json!({
86//!     "player": {
87//!         "__typename": "Skater",
88//!         "name": "Auston Matthews",
89//!         "age": 25,
90//!         "position": "CENTRE",
91//!         "stats": [
92//!             {
93//!                 "goals": 60
94//!             },
95//!         ],
96//!     },
97//! }).to_string();
98//!
99//! let result: schema::query::Player = serde_json::from_str(&value).expect("Error parsing value");
100//!
101//! assert_eq!(
102//!     schema::query::Player {
103//!         player: schema::query::player::Player::Skater(schema::query::player::player::Skater {
104//!             name: "Auston Matthews".into(),
105//!             age: 25,
106//!             position: schema::Position::Centre,
107//!             stats: vec![schema::query::player::player::skater::Stats { goals: 60 }],
108//!         }),
109//!     },
110//!     result,
111//! );
112//! ```
113//!
114//! ### Limitations
115//! - A query cannot contain a fragment definition with the same name as an operation definition
116//! - 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
117//! - Within the scope of an object type, the selection set must not contain any inline fragments and must either:
118//!     - Contain at least one field selection, or
119//!     - Contain exactly one fragment spread
120//! - Within the scope of an interface type, the selection set must not contain any inline fragments and must either:
121//!     - Contain at least one field selection, or
122//!     - 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
123//! - Within the scope of a union type, the selection set must:
124//!     - Contain an unaliased field selection of `__typename` as the first selection in the set, and no other field selections, and
125//!     - Not contain any fragment spreads, and
126//!     - Not contain multiple inline fragments targeting any object type in the union type, and
127//!     - Not contain any inline fragments targeting types that are not a member of the union type
128//!
129//! ### Type path pattern
130//! The path for a given type in the generated Rust types is determined by the following rules:
131//! - 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`.
132//! - 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`.
133//! - If the type is an anonymous operation root type, the path is `schema_module::query_module::Root`
134//! - 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`.
135//! - 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`.
136
137pub use bluejay_typegen_macro::typegen;
138
139pub use srd as serde;