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}