Skip to main content

doido_auth/generators/
mod.rs

1//! Shared helpers for auth generators — migration rendering, name inflection,
2//! and route/module injection.
3
4pub mod field;
5pub mod migration_support;
6pub mod names;
7pub mod route_injector;
8
9pub use field::Field;
10pub use names::{to_pascal, to_snake, to_table_name};
11
12pub mod controller;
13pub mod install;
14pub mod scaffold;
15
16pub use controller::AuthControllerGenerator;
17pub use install::AuthInstallGenerator;
18pub use scaffold::AuthScaffoldGenerator;
19
20use doido_core::Result;
21
22/// A file emitted by an auth generator.
23#[derive(Debug, Clone)]
24pub struct GeneratedFile {
25    pub path: String,
26    pub content: String,
27}
28
29/// Auth generator contract — mirrors `doido_generators::Generator` without a
30/// crate dependency (avoids circular deps with `doido-generators`).
31pub trait AuthGenerator: Send + Sync {
32    fn name(&self) -> &str;
33    fn generate(&self, args: &[&str]) -> Result<Vec<GeneratedFile>>;
34}
35
36/// Implemented by `doido_generators::GeneratorRegistry` to merge auth generators.
37pub trait AuthGeneratorRegistry {
38    fn register_auth(&mut self, generator: Box<dyn AuthGenerator>);
39}
40
41/// Register all auth generators into `reg`.
42pub fn register(reg: &mut impl AuthGeneratorRegistry) {
43    reg.register_auth(Box::new(install::AuthInstallGenerator));
44    reg.register_auth(Box::new(controller::AuthControllerGenerator));
45    reg.register_auth(Box::new(scaffold::AuthScaffoldGenerator));
46}
47
48/// Load an embedded template from `doido-auth/templates/`.
49pub(crate) fn template(rel: &str) -> &'static str {
50    match rel {
51        "migration.rs.template" => include_str!("../../templates/migration.rs.template"),
52        "user.rs.template" => include_str!("../../templates/user.rs.template"),
53        "auth/mod.rs.template" => include_str!("../../templates/auth/mod.rs.template"),
54        "auth/sessions_controller_html.rs.template" => {
55            include_str!("../../templates/auth/sessions_controller_html.rs.template")
56        }
57        "auth/sessions_controller_api.rs.template" => {
58            include_str!("../../templates/auth/sessions_controller_api.rs.template")
59        }
60        "auth/registrations_controller_html.rs.template" => {
61            include_str!("../../templates/auth/registrations_controller_html.rs.template")
62        }
63        "auth/registrations_controller_api.rs.template" => {
64            include_str!("../../templates/auth/registrations_controller_api.rs.template")
65        }
66        "auth/passwords_controller_html.rs.template" => {
67            include_str!("../../templates/auth/passwords_controller_html.rs.template")
68        }
69        "auth/passwords_controller_api.rs.template" => {
70            include_str!("../../templates/auth/passwords_controller_api.rs.template")
71        }
72        "auth/oauth_controller.rs.template" => {
73            include_str!("../../templates/auth/oauth_controller.rs.template")
74        }
75        "auth/two_factor_controller_html.rs.template" => {
76            include_str!("../../templates/auth/two_factor_controller_html.rs.template")
77        }
78        "auth/two_factor_controller_api.rs.template" => {
79            include_str!("../../templates/auth/two_factor_controller_api.rs.template")
80        }
81        "auth/views/sign_in.html.tera" => {
82            include_str!("../../templates/auth/views/sign_in.html.tera")
83        }
84        "auth/views/sign_up.html.tera" => {
85            include_str!("../../templates/auth/views/sign_up.html.tera")
86        }
87        "auth/views/password_new.html.tera" => {
88            include_str!("../../templates/auth/views/password_new.html.tera")
89        }
90        "auth/views/password_edit.html.tera" => {
91            include_str!("../../templates/auth/views/password_edit.html.tera")
92        }
93        "auth/views/two_factor.html.tera" => {
94            include_str!("../../templates/auth/views/two_factor.html.tera")
95        }
96        "auth_controller.rs.template" => {
97            include_str!("../../templates/auth_controller.rs.template")
98        }
99        "scaffold/controller_html.rs.template" => {
100            include_str!("../../templates/scaffold/controller_html.rs.template")
101        }
102        "scaffold/controller_api.rs.template" => {
103            include_str!("../../templates/scaffold/controller_api.rs.template")
104        }
105        "scaffold/model.rs.template" => include_str!("../../templates/scaffold/model.rs.template"),
106        "scaffold/views/index.html.tera" => {
107            include_str!("../../templates/scaffold/views/index.html.tera")
108        }
109        "scaffold/views/show.html.tera" => {
110            include_str!("../../templates/scaffold/views/show.html.tera")
111        }
112        "scaffold/views/new.html.tera" => {
113            include_str!("../../templates/scaffold/views/new.html.tera")
114        }
115        "scaffold/views/edit.html.tera" => {
116            include_str!("../../templates/scaffold/views/edit.html.tera")
117        }
118        "scaffold/views/_form.html.tera" => {
119            include_str!("../../templates/scaffold/views/_form.html.tera")
120        }
121        other => panic!("unknown auth generator template: {other}"),
122    }
123}
124
125/// Append `pub mod <module>;` into a directory `mod.rs`, just above `marker`.
126pub(crate) fn register_module(existing: &str, module: &str, marker: &str) -> String {
127    let decl = format!("pub mod {module};");
128    if existing.lines().any(|l| l.trim() == decl) {
129        return existing.to_string();
130    }
131    let mut lines: Vec<String> = existing.lines().map(String::from).collect();
132    match lines.iter().position(|l| l.contains(marker)) {
133        Some(i) => lines.insert(i, decl),
134        None => lines.push(decl),
135    }
136    let mut out = lines.join("\n");
137    out.push('\n');
138    out
139}