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;