graphql_codegen_rust/lib.rs
1//! # GraphQL Code Generator for Rust ORMs
2//!
3//! Transform GraphQL schemas into production-ready Rust code for Diesel and Sea-ORM.
4//! Automatically generates entities, migrations, and schema definitions from your
5//! GraphQL API's introspection.
6//!
7//! ## Key Features
8//!
9//! - **Dual ORM Support**: Generate code for both [Diesel](https://diesel.rs) and [Sea-ORM](https://www.sea-ql.org/SeaORM)
10//! - **Database Agnostic**: Support for SQLite, PostgreSQL, and MySQL
11//! - **Type Safety**: Compile-time guarantees with full GraphQL type mapping
12//! - **Migration Ready**: Automatic database migration generation
13//! - **Introspection Powered**: Works with any GraphQL API that supports introspection
14//! - **Flexible Configuration**: TOML and YAML config support (with feature flag)
15//!
16//! ## Quick Start
17//!
18//! ### 1. Install the CLI
19//!
20//! ```bash
21//! cargo install graphql-codegen-rust
22//! ```
23//!
24//! ### 2. Initialize your project
25//!
26//! ```bash
27//! graphql-codegen-rust init --url https://api.example.com/graphql
28//! ```
29//!
30//! ### 3. Generate code
31//!
32//! ```bash
33//! graphql-codegen-rust generate
34//! ```
35//!
36//! ## Library Usage
37//!
38//! For programmatic use in your Rust applications:
39//!
40//! ```rust,no_run
41//! use graphql_codegen_rust::{CodeGenerator, Config};
42//! use graphql_codegen_rust::cli::{OrmType, DatabaseType};
43//!
44//! # async fn example() -> anyhow::Result<()> {
45//! // Create configuration programmatically
46//! let config = Config {
47//! url: "https://api.example.com/graphql".to_string(),
48//! orm: OrmType::Diesel,
49//! db: DatabaseType::Postgres,
50//! output_dir: "./generated".into(),
51//! headers: std::collections::HashMap::new(),
52//! type_mappings: std::collections::HashMap::new(),
53//! scalar_mappings: std::collections::HashMap::new(),
54//! table_naming: Default::default(),
55//! generate_migrations: true,
56//! generate_entities: true,
57//! };
58//!
59//! // Generate code
60//! let generator = CodeGenerator::new(&config.orm);
61//! generator.generate_from_config(&config).await?;
62//! # Ok(())
63//! # }
64//! ```
65//!
66//! ## Configuration
67//!
68//! ### TOML Configuration (`graphql-codegen-rust.toml`)
69//!
70//! ```toml
71//! url = "https://api.example.com/graphql"
72//! orm = "Diesel"
73//! db = "Postgres"
74//! output_dir = "./generated"
75//!
76//! [headers]
77//! Authorization = "Bearer your-token-here"
78//!
79//! [type_mappings]
80//! # Custom type mappings if needed
81//! ```
82//!
83//! ### YAML Configuration (`codegen.yml`) - *Requires `yaml-codegen-config` feature*
84//!
85//! ```yaml
86//! schema:
87//! url: https://api.example.com/graphql
88//! headers:
89//! Authorization: Bearer your-token-here
90//!
91//! rust_codegen:
92//! orm: Diesel
93//! db: Postgres
94//! output_dir: ./generated
95//! ```
96//!
97//! ## Generated Code Structure
98//!
99//! ```text
100//! generated/
101//! ├── src/
102//! │ ├── schema.rs # Diesel schema definitions
103//! │ ├── entities/
104//! │ │ ├── user.rs # Entity structs and implementations
105//! │ │ └── post.rs
106//! │ └── mod.rs # Sea-ORM module definitions
107//! └── migrations/
108//! └── 0001_create_users_table/
109//! ├── up.sql
110//! └── down.sql
111//! ```
112//!
113//! ## Error Handling
114//!
115//! The library uses [`anyhow`](https://docs.rs/anyhow) for error handling, providing
116//! detailed error messages with context. Common error scenarios:
117//!
118//! - **Network errors**: GraphQL endpoint unreachable or authentication failures
119//! - **Schema errors**: Invalid GraphQL schema or introspection disabled
120//! - **Configuration errors**: Missing or invalid configuration files
121//! - **Generation errors**: Unsupported GraphQL types or ORM constraints
122//!
123//! ## Feature Flags
124//!
125//! - `yaml-codegen-config`: Enable YAML configuration file support
126//! - Default features include TOML support and both Diesel and Sea-ORM generators
127//!
128//! ## Requirements
129//!
130//! - Rust 1.86+
131//! - A GraphQL API that supports introspection
132//! - Appropriate database dependencies based on your ORM choice
133
134pub mod cli;
135pub mod config;
136pub mod generator;
137pub mod introspection;
138pub mod parser;
139
140pub use config::Config;
141pub use generator::create_generator;
142
143use std::path::Path;
144
145use fs_err as fs;
146
147/// High-level interface for generating Rust ORM code from GraphQL schemas.
148///
149/// The `CodeGenerator` provides a unified API for generating code regardless of the
150/// underlying ORM (Diesel or Sea-ORM). It handles the complete code generation
151/// pipeline: schema introspection, parsing, and code emission.
152///
153/// ## ORM Support
154///
155/// - **Diesel**: Generates table schemas, entity structs, and database migrations
156/// - **Sea-ORM**: Generates entity models, active records, and migration files
157///
158/// ## Example
159///
160/// ```rust,no_run
161/// use graphql_codegen_rust::{CodeGenerator, Config};
162/// use graphql_codegen_rust::cli::{OrmType, DatabaseType};
163///
164/// # async fn example() -> anyhow::Result<()> {
165/// let config = Config {
166/// url: "https://api.example.com/graphql".to_string(),
167/// orm: OrmType::Diesel,
168/// db: DatabaseType::Postgres,
169/// output_dir: "./generated".into(),
170/// ..Default::default()
171/// };
172///
173/// let generator = CodeGenerator::new(&config.orm);
174/// generator.generate_from_config(&config).await?;
175/// # Ok(())
176/// # }
177/// ```
178pub struct CodeGenerator {
179 inner: Box<dyn generator::CodeGenerator>,
180}
181
182impl CodeGenerator {
183 /// Creates a new code generator for the specified ORM type.
184 ///
185 /// # Parameters
186 /// - `orm`: The ORM to generate code for (Diesel or Sea-ORM)
187 ///
188 /// # Returns
189 /// A configured `CodeGenerator` ready to produce ORM code.
190 ///
191 /// # Example
192 /// ```rust
193 /// use graphql_codegen_rust::{CodeGenerator, cli::OrmType};
194 ///
195 /// let generator = CodeGenerator::new(&OrmType::Diesel);
196 /// ```
197 pub fn new(orm: &cli::OrmType) -> Self {
198 Self {
199 inner: generator::create_generator(orm),
200 }
201 }
202
203 /// Generates complete ORM code from a GraphQL configuration.
204 ///
205 /// This method orchestrates the full code generation pipeline:
206 /// 1. Introspects the GraphQL schema from the configured endpoint
207 /// 2. Parses the schema into an internal representation
208 /// 3. Generates ORM-specific code (entities, migrations, schemas)
209 /// 4. Writes generated files to the configured output directory
210 ///
211 /// # Parameters
212 /// - `config`: Complete configuration including GraphQL endpoint, ORM type,
213 /// database settings, and output preferences
214 ///
215 /// # Returns
216 /// - `Ok(())` on successful code generation
217 /// - `Err(anyhow::Error)` with detailed context on failure
218 ///
219 /// # Errors
220 /// This method can fail due to:
221 /// - Network issues when accessing the GraphQL endpoint
222 /// - Authentication failures (invalid headers)
223 /// - Schema parsing errors (invalid GraphQL schema)
224 /// - File system errors (permission issues, disk space)
225 /// - Code generation constraints (unsupported GraphQL types)
226 ///
227 /// # Example
228 /// ```rust,no_run
229 /// use graphql_codegen_rust::{CodeGenerator, Config};
230 /// use graphql_codegen_rust::cli::{OrmType, DatabaseType};
231 ///
232 /// # async fn example() -> anyhow::Result<()> {
233 /// let config = Config {
234 /// url: "https://api.example.com/graphql".to_string(),
235 /// orm: OrmType::Diesel,
236 /// db: DatabaseType::Postgres,
237 /// output_dir: "./generated".into(),
238 /// ..Default::default()
239 /// };
240 ///
241 /// let generator = CodeGenerator::new(&config.orm);
242 /// generator.generate_from_config(&config).await?;
243 /// # Ok(())
244 /// # }
245 /// ```
246 pub async fn generate_from_config(&self, config: &Config) -> anyhow::Result<()> {
247 // Fetch and parse schema
248 let parser = parser::GraphQLParser::new();
249 let schema = parser
250 .parse_from_introspection(&config.url, &config.headers)
251 .await?;
252
253 // Generate all code
254 generate_all_code(&schema, config, &*self.inner).await
255 }
256}
257
258/// Generates ORM code directly from a configuration file path.
259///
260/// This is a convenience function that combines configuration loading and code generation
261/// into a single call. It automatically detects the configuration format (TOML or YAML)
262/// and creates the appropriate code generator.
263///
264/// # Supported Configuration Formats
265///
266/// - **TOML**: `graphql-codegen-rust.toml` or any `.toml` file
267/// - **YAML**: `codegen.yml`, `codegen.yaml` (requires `yaml-codegen-config` feature)
268///
269/// # Parameters
270/// - `config_path`: Path to the configuration file. Can be any type that converts to `Path`.
271///
272/// # Returns
273/// - `Ok(())` on successful code generation
274/// - `Err(anyhow::Error)` with context about what failed
275///
276/// # Errors
277/// This function can fail due to:
278/// - Configuration file not found or unreadable
279/// - Invalid configuration format or content
280/// - Network issues during GraphQL introspection
281/// - Code generation failures
282///
283/// # Example
284/// ```rust,no_run
285/// use graphql_codegen_rust::generate_from_config_file;
286///
287/// # async fn example() -> anyhow::Result<()> {
288/// // Generate from TOML config
289/// generate_from_config_file("graphql-codegen-rust.toml").await?;
290///
291/// // Generate from YAML config (if feature enabled)
292/// generate_from_config_file("codegen.yml").await?;
293/// # Ok(())
294/// # }
295/// ```
296pub async fn generate_from_config_file<P: AsRef<Path>>(config_path: P) -> anyhow::Result<()> {
297 let path_buf = config_path.as_ref().to_path_buf();
298 let config = Config::from_file(&path_buf)?;
299 let generator = CodeGenerator::new(&config.orm);
300 generator.generate_from_config(&config).await
301}
302
303pub async fn generate_all_code(
304 schema: &parser::ParsedSchema,
305 config: &Config,
306 generator: &dyn generator::CodeGenerator,
307) -> anyhow::Result<()> {
308 // Create output directory structure
309 fs::create_dir_all(&config.output_dir)?;
310 let src_dir = config.output_dir.join("src");
311 fs::create_dir_all(&src_dir)?;
312
313 // Generate schema file
314 let schema_code = generator.generate_schema(schema, config)?;
315 if config.orm == cli::OrmType::Diesel {
316 let schema_path = src_dir.join("schema.rs");
317 fs::write(schema_path, schema_code)?;
318 } else if config.orm == cli::OrmType::SeaOrm {
319 // Sea-ORM generates a mod.rs file at the root
320 let mod_path = config.output_dir.join("mod.rs");
321 fs::write(mod_path, schema_code)?;
322 }
323
324 // Generate entity files
325 let entities = generator.generate_entities(schema, config)?;
326 let entities_dir = src_dir.join("entities");
327 fs::create_dir_all(&entities_dir)?;
328
329 for (filename, code) in entities {
330 let entity_path = entities_dir.join(filename);
331 fs::write(entity_path, code)?;
332 }
333
334 // Generate migrations
335 let migrations = generator.generate_migrations(schema, config)?;
336 let migrations_dir = config.output_dir.join("migrations");
337 fs::create_dir_all(&migrations_dir)?;
338
339 for migration in migrations {
340 let migration_dir = migrations_dir.join(&migration.name);
341 fs::create_dir_all(&migration_dir)?;
342
343 let up_path = migration_dir.join("up.sql");
344 let down_path = migration_dir.join("down.sql");
345
346 fs::write(up_path, migration.up_sql)?;
347 fs::write(down_path, migration.down_sql)?;
348 }
349
350 Ok(())
351}