prax_codegen/lib.rs
1//! Procedural macros for the Prax ORM.
2//!
3//! This crate provides compile-time code generation for Prax, transforming
4//! schema definitions into type-safe Rust code.
5//!
6//! # Macros
7//!
8//! - [`prax_schema!`] - Generate models from a `.prax` schema file
9//! - [`Model`] - Derive macro for manual model definition
10//!
11//! # Plugins
12//!
13//! Code generation can be extended with plugins enabled via environment variables:
14//!
15//! ```bash
16//! # Enable debug information
17//! PRAX_PLUGIN_DEBUG=1 cargo build
18//!
19//! # Enable JSON Schema generation
20//! PRAX_PLUGIN_JSON_SCHEMA=1 cargo build
21//!
22//! # Enable GraphQL SDL generation
23//! PRAX_PLUGIN_GRAPHQL=1 cargo build
24//!
25//! # Enable custom serialization helpers
26//! PRAX_PLUGIN_SERDE=1 cargo build
27//!
28//! # Enable runtime validation
29//! PRAX_PLUGIN_VALIDATOR=1 cargo build
30//!
31//! # Enable all plugins
32//! PRAX_PLUGINS_ALL=1 cargo build
33//! ```
34//!
35//! # Example
36//!
37//! ```rust,ignore
38//! // Generate models from schema file
39//! prax::prax_schema!("schema.prax");
40//!
41//! // Or manually define with derive macro
42//! #[derive(prax::Model)]
43//! #[prax(table = "users")]
44//! struct User {
45//! #[prax(id, auto)]
46//! id: i32,
47//! #[prax(unique)]
48//! email: String,
49//! name: Option<String>,
50//! }
51//! ```
52
53use proc_macro::TokenStream;
54use quote::quote;
55use syn::{DeriveInput, LitStr, parse_macro_input};
56
57mod generators;
58mod plugins;
59mod schema_reader;
60mod types;
61
62use generators::{
63 generate_enum_module, generate_model_module_with_style, generate_type_module,
64 generate_view_module,
65};
66
67/// Generate models from a Prax schema file.
68///
69/// This macro reads a `.prax` schema file at compile time and generates
70/// type-safe Rust code for all models, enums, and types defined in the schema.
71///
72/// # Example
73///
74/// ```rust,ignore
75/// prax::prax_schema!("schema.prax");
76///
77/// // Now you can use the generated types:
78/// let user = client.user().find_unique(user::id::equals(1)).exec().await?;
79/// ```
80///
81/// # Generated Code
82///
83/// For each model in the schema, this macro generates:
84/// - A module with the model name (snake_case)
85/// - A `Data` struct representing a row from the database
86/// - A `CreateInput` struct for creating new records
87/// - A `UpdateInput` struct for updating records
88/// - Field modules with filter operations (`equals`, `contains`, `in_`, etc.)
89/// - A `WhereParam` enum for type-safe filtering
90/// - An `OrderByParam` enum for sorting
91/// - Select and Include builders for partial queries
92#[proc_macro]
93pub fn prax_schema(input: TokenStream) -> TokenStream {
94 let input = parse_macro_input!(input as LitStr);
95 let schema_path = input.value();
96
97 match generate_from_schema(&schema_path) {
98 Ok(tokens) => tokens.into(),
99 Err(err) => {
100 let err_msg = err.to_string();
101 quote! {
102 compile_error!(#err_msg);
103 }
104 .into()
105 }
106 }
107}
108
109/// Derive macro for defining Prax models manually.
110///
111/// This derive macro allows you to define models in Rust code instead of
112/// using a `.prax` schema file. It generates the same query builder methods
113/// and type-safe operations.
114///
115/// # Attributes
116///
117/// ## Struct-level
118/// - `#[prax(table = "table_name")]` - Map to a different table name
119/// - `#[prax(schema = "schema_name")]` - Specify database schema
120///
121/// ## Field-level
122/// - `#[prax(id)]` - Mark as primary key
123/// - `#[prax(auto)]` - Auto-increment field
124/// - `#[prax(unique)]` - Unique constraint
125/// - `#[prax(default = value)]` - Default value
126/// - `#[prax(column = "col_name")]` - Map to different column
127/// - `#[prax(relation(...))]` - Define relation
128///
129/// # Example
130///
131/// ```rust,ignore
132/// #[derive(prax::Model)]
133/// #[prax(table = "users")]
134/// struct User {
135/// #[prax(id, auto)]
136/// id: i32,
137///
138/// #[prax(unique)]
139/// email: String,
140///
141/// #[prax(column = "display_name")]
142/// name: Option<String>,
143///
144/// #[prax(default = "now()")]
145/// created_at: chrono::DateTime<chrono::Utc>,
146/// }
147/// ```
148#[proc_macro_derive(Model, attributes(prax))]
149pub fn derive_model(input: TokenStream) -> TokenStream {
150 let input = parse_macro_input!(input as DeriveInput);
151
152 match generators::derive_model_impl(&input) {
153 Ok(tokens) => tokens.into(),
154 Err(err) => err.to_compile_error().into(),
155 }
156}
157
158/// Internal function to generate code from a schema file.
159fn generate_from_schema(schema_path: &str) -> Result<proc_macro2::TokenStream, syn::Error> {
160 use plugins::{PluginConfig, PluginContext, PluginRegistry};
161 use schema_reader::read_schema_with_config;
162
163 // Read and parse the schema file along with prax.toml configuration
164 let schema_with_config = read_schema_with_config(schema_path).map_err(|e| {
165 syn::Error::new(
166 proc_macro2::Span::call_site(),
167 format!("Failed to parse schema: {}", e),
168 )
169 })?;
170
171 let schema = schema_with_config.schema;
172 let model_style = schema_with_config.model_style;
173
174 // Initialize plugin system with model_style from prax.toml
175 // This auto-enables graphql plugins when model_style is GraphQL
176 let plugin_config = PluginConfig::with_model_style(model_style);
177 let plugin_registry = PluginRegistry::with_builtins();
178 let plugin_ctx = PluginContext::new(&schema, &plugin_config);
179
180 let mut output = proc_macro2::TokenStream::new();
181
182 // Run plugin start hooks
183 let start_output = plugin_registry.run_start(&plugin_ctx);
184 output.extend(start_output.tokens);
185 output.extend(start_output.root_items);
186
187 // Generate enums first (models may reference them)
188 for (_, enum_def) in &schema.enums {
189 output.extend(generate_enum_module(enum_def)?);
190
191 // Run plugin enum hooks
192 let plugin_output = plugin_registry.run_enum(&plugin_ctx, enum_def);
193 if !plugin_output.is_empty() {
194 // Add plugin output to the enum module
195 output.extend(plugin_output.tokens);
196 }
197 }
198
199 // Generate composite types
200 for (_, type_def) in &schema.types {
201 output.extend(generate_type_module(type_def)?);
202
203 // Run plugin type hooks
204 let plugin_output = plugin_registry.run_type(&plugin_ctx, type_def);
205 if !plugin_output.is_empty() {
206 output.extend(plugin_output.tokens);
207 }
208 }
209
210 // Generate views
211 for (_, view_def) in &schema.views {
212 output.extend(generate_view_module(view_def)?);
213
214 // Run plugin view hooks
215 let plugin_output = plugin_registry.run_view(&plugin_ctx, view_def);
216 if !plugin_output.is_empty() {
217 output.extend(plugin_output.tokens);
218 }
219 }
220
221 // Generate models with the configured model style
222 for (_, model_def) in &schema.models {
223 output.extend(generate_model_module_with_style(
224 model_def,
225 &schema,
226 model_style,
227 )?);
228
229 // Run plugin model hooks
230 let plugin_output = plugin_registry.run_model(&plugin_ctx, model_def);
231 if !plugin_output.is_empty() {
232 output.extend(plugin_output.tokens);
233 }
234 }
235
236 // Run plugin finish hooks
237 let finish_output = plugin_registry.run_finish(&plugin_ctx);
238 output.extend(finish_output.tokens);
239 output.extend(finish_output.root_items);
240
241 // Generate plugin documentation
242 output.extend(plugins::generate_plugin_docs(&plugin_registry));
243
244 Ok(output)
245}