rango_orm_derive/lib.rs
1//! `#[derive(Model)]` and `#[derive(ModelMixin)]` for [Rango ORM](https://crates.io/crates/rango-orm).
2//!
3//! Internal crate: depend on `rango-orm`, which re-exports the derives.
4#![forbid(unsafe_code)]
5use proc_macro::TokenStream;
6use proc_macro2::TokenStream as TokenStream2;
7use quote::quote;
8use syn::{DeriveInput, parse_macro_input};
9
10mod implicit_pk;
11mod mixin;
12mod model;
13mod partial;
14mod utils;
15
16/// Returns the path to rango_orm_core: through the `rango-orm` facade, or directly.
17pub(crate) fn rango_core_path() -> TokenStream2 {
18 match proc_macro_crate::crate_name("rango-orm") {
19 Ok(proc_macro_crate::FoundCrate::Itself) => quote! { ::rango_orm_core },
20 Ok(proc_macro_crate::FoundCrate::Name(name)) => {
21 // The facade's lib is named `rango`; proc-macro-crate returns the
22 // dependency key, which is `rango_orm` unless the user renamed it.
23 let name = if name == "rango_orm" {
24 "rango"
25 } else {
26 name.as_str()
27 };
28 let ident = proc_macro2::Ident::new(name, proc_macro2::Span::call_site());
29 quote! { ::#ident::rango_orm_core }
30 }
31 // No facade: the user depends on rango-orm-core directly.
32 Err(_) => quote! { ::rango_orm_core },
33 }
34}
35
36/// Derive macro for Rango models.
37///
38/// Generates:
39/// - `impl Model for MyStruct` with `table_name()` and `schema()`
40/// - Table name defaults to snake_case of the struct name (e.g. `UserProfile` → `user_profile`)
41///
42/// # Example
43/// ```rust,ignore
44/// #[derive(Model)]
45/// struct Article {
46/// id: FieldUuid,
47/// title: FieldVarchar<1, 255>,
48/// body: Option<FieldText>,
49/// #[field(index)]
50/// slug: FieldVarchar<1, 100>,
51/// #[field(auto_now_add)]
52/// created_at: FieldDateTime,
53/// }
54/// ```
55#[proc_macro_derive(Model, attributes(model, field))]
56pub fn derive_model(input: TokenStream) -> TokenStream {
57 let input = parse_macro_input!(input as DeriveInput);
58 model::expand(input)
59 .unwrap_or_else(|e| e.to_compile_error())
60 .into()
61}
62
63/// Attribute macro: adds an implicit primary key, then derives [`Model`].
64///
65/// - no PK declared: injects `pub id: FieldUuid` as the first field
66/// - `#[rango::model(pk = "bigint")]`: injects `id: FieldAutoBigInt` (database-generated);
67/// `"int"` gives `FieldAutoInt`, `"uuid"` is the default
68/// - a PK already exists (`id` field or `#[field(primary_key)]`): nothing is injected
69/// - `#[derive(Model)]` is added unless the struct already has it
70///
71/// The default is not read from `rango.toml`: cargo does not track that file,
72/// so builds would go stale when it changes.
73///
74/// ```rust,ignore
75/// #[rango::model]
76/// struct Article { title: FieldVarchar<1, 200> }
77/// ```
78#[proc_macro_attribute]
79pub fn model(args: TokenStream, input: TokenStream) -> TokenStream {
80 implicit_pk::expand(args.into(), input.into())
81 .unwrap_or_else(|e| e.to_compile_error())
82 .into()
83}
84
85/// Marker derive for abstract model mixins (never creates a table).
86///
87/// # Example
88/// ```rust,ignore
89/// #[derive(ModelMixin)]
90/// struct Timestamps {
91/// #[field(auto_now_add)]
92/// created_at: FieldDateTime,
93/// #[field(auto_now)]
94/// updated_at: FieldDateTime,
95/// }
96///
97/// #[derive(Model)]
98/// #[model(mixins(Timestamps))]
99/// struct Article {
100/// id: FieldUuid,
101/// title: FieldVarchar<1, 255>,
102/// }
103/// ```
104#[proc_macro_derive(ModelMixin, attributes(field))]
105pub fn derive_model_mixin(input: TokenStream) -> TokenStream {
106 let input = parse_macro_input!(input as DeriveInput);
107 mixin::expand(input)
108 .unwrap_or_else(|e| e.to_compile_error())
109 .into()
110}
111
112/// Returns the path to the `Model` derive: through the `rango-orm` facade, or directly.
113pub(crate) fn rango_derive_path() -> TokenStream2 {
114 match proc_macro_crate::crate_name("rango-orm") {
115 Ok(proc_macro_crate::FoundCrate::Itself) => quote! { ::rango::Model },
116 Ok(proc_macro_crate::FoundCrate::Name(name)) => {
117 let name = if name == "rango_orm" {
118 "rango"
119 } else {
120 name.as_str()
121 };
122 let ident = proc_macro2::Ident::new(name, proc_macro2::Span::call_site());
123 quote! { ::#ident::Model }
124 }
125 Err(_) => quote! { ::rango_orm_derive::Model },
126 }
127}
128
129/// Derive macro for typed partial selects: a struct holding a subset of a model's
130/// columns, read with `QueryBuilder::select_as::<Self>()`.
131///
132/// Every field must match a model column by name and type (checked at compile time).
133///
134/// ```rust,ignore
135/// #[derive(Partial)]
136/// #[partial(model = User)]
137/// struct UserLite {
138/// id: FieldUuid,
139/// email: FieldEmail,
140/// }
141/// ```
142#[proc_macro_derive(Partial, attributes(partial))]
143pub fn derive_partial(input: TokenStream) -> TokenStream {
144 let input = parse_macro_input!(input as DeriveInput);
145 partial::expand(input)
146 .unwrap_or_else(|e| e.to_compile_error())
147 .into()
148}