graphql_codegen_rust/cli.rs
1use clap::{Parser, Subcommand};
2use std::path::PathBuf;
3
4#[derive(Parser)]
5#[command(name = "graphql-codegen-rust")]
6#[command(version = env!("CARGO_PKG_VERSION"))]
7#[command(about = "Generate Rust ORM code from GraphQL schemas")]
8pub struct Cli {
9 #[command(subcommand)]
10 pub command: Option<Commands>,
11
12 /// Increase verbosity level (-v, -vv, -vvv)
13 #[arg(short, long, action = clap::ArgAction::Count)]
14 pub verbose: u8,
15}
16
17#[derive(Subcommand)]
18pub enum Commands {
19 /// Initialize a new GraphQL codegen project
20 Init {
21 /// GraphQL endpoint URL
22 #[arg(short, long)]
23 url: String,
24
25 /// ORM to generate code for
26 #[arg(short, long, value_enum, default_value = "diesel")]
27 orm: OrmType,
28
29 /// Database backend
30 #[arg(short, long, value_enum, default_value = "sqlite")]
31 db: DatabaseType,
32
33 /// Output directory for generated code
34 #[arg(long, default_value = "./generated")]
35 output: PathBuf,
36
37 /// Additional headers for GraphQL requests (key:value pairs)
38 #[arg(short = 'H', long, value_parser = parse_header)]
39 headers: Vec<(String, String)>,
40 },
41
42 /// Generate code from existing configuration
43 Generate {
44 /// Config file path (auto-detects codegen.yml or TOML)
45 #[arg(short, long)]
46 config: Option<PathBuf>,
47
48 /// Output directory (overrides config)
49 #[arg(short, long)]
50 output: Option<PathBuf>,
51 },
52}
53
54/// Supported ORM frameworks for code generation.
55///
56/// Each ORM generates different code structures optimized for their respective ecosystems:
57/// - **Diesel**: Mature, compile-time SQL safety, macro-heavy approach
58/// - **Sea-ORM**: Async-first, runtime SQL building, entity relationships
59#[derive(
60 Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, clap::ValueEnum, Default,
61)]
62pub enum OrmType {
63 /// Generates Diesel table schemas, Queryable structs, and Insertable structs.
64 /// Best for applications needing maximum compile-time safety and performance.
65 #[default]
66 Diesel,
67
68 /// Generates Sea-ORM Entity models, ActiveModel structs, and migration files.
69 /// Best for async applications with complex relationships and runtime flexibility.
70 SeaOrm,
71}
72
73/// Supported database backends.
74///
75/// Each database has different capabilities and type mappings:
76/// - **SQLite**: File-based, simple deployment, limited concurrent writes
77/// - **PostgreSQL**: Advanced features, JSON support, excellent concurrency
78/// - **MySQL**: High performance, wide adoption, good for large datasets
79#[derive(
80 Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, clap::ValueEnum, Default,
81)]
82pub enum DatabaseType {
83 /// SQLite database - file-based, ACID compliant, no server required.
84 /// Uses INTEGER for IDs, TEXT for strings, REAL for floats.
85 #[default]
86 Sqlite,
87
88 /// PostgreSQL database - advanced open-source RDBMS.
89 /// Uses UUID for IDs, native JSON/JSONB, full-text search, advanced indexing.
90 Postgres,
91
92 /// MySQL database - high-performance, widely adopted RDBMS.
93 /// Uses INT/UNSIGNED for IDs, VARCHAR/TEXT for strings, various numeric types.
94 Mysql,
95}
96
97/// Parses a header string in "key:value" format for CLI arguments.
98///
99/// Used internally by clap to validate and parse header arguments.
100/// Headers must be provided as `key:value` pairs with no spaces around the colon.
101///
102/// # Examples
103/// ```rust
104/// # fn test_parse_header() {
105/// let result = graphql_codegen_rust::cli::parse_header("Authorization:Bearer token123");
106/// assert_eq!(result.unwrap(), ("Authorization".to_string(), "Bearer token123".to_string()));
107/// # }
108/// ```
109///
110/// # Errors
111/// Returns an error if the string doesn't contain exactly one colon separator,
112/// or if the key or value would be empty after trimming.
113pub fn parse_header(s: &str) -> Result<(String, String), String> {
114 let parts: Vec<&str> = s.splitn(2, ':').collect();
115 if parts.len() != 2 {
116 return Err(format!(
117 "Invalid header format '{}'. Headers must be in 'key:value' format.\nExample: --header 'Authorization:Bearer token123'",
118 s
119 ));
120 }
121 let key = parts[0].trim();
122 let value = parts[1].trim();
123
124 if key.is_empty() {
125 return Err("Header key cannot be empty. Format: 'key:value'".to_string());
126 }
127 if value.is_empty() {
128 return Err("Header value cannot be empty. Format: 'key:value'".to_string());
129 }
130
131 Ok((key.to_string(), value.to_string()))
132}