Skip to main content

yauth_migration/
lib.rs

1//! Declarative schema system and migration file generator for yauth.
2//!
3//! This crate has **zero ORM dependencies** — it is a pure code generator.
4//! It provides:
5//!
6//! - Schema types (`TableDef`, `ColumnDef`, `ColumnType`, etc.)
7//! - Core + plugin schema definitions
8//! - Schema collector with topological sort by FK dependencies
9//! - Dialect-specific DDL generators (Postgres, SQLite, MySQL)
10//! - Schema diff engine for incremental migrations
11//! - Migration file generators (diesel up.sql/down.sql, sqlx numbered .sql)
12//! - `yauth.toml` config file support
13//! - Schema hash for tracking
14
15mod types;
16pub use types::*;
17
18mod core;
19pub use core::core_schema;
20
21mod collector;
22pub use collector::{SchemaError, YAuthSchema, collect_schema};
23
24mod postgres;
25pub use postgres::{generate_postgres_ddl, generate_postgres_drop, generate_postgres_drops};
26
27mod sqlite;
28pub use sqlite::{generate_sqlite_ddl, generate_sqlite_drop, generate_sqlite_drops};
29
30mod mysql;
31pub use mysql::{generate_mysql_ddl, generate_mysql_drop, generate_mysql_drops};
32
33mod diesel_schema;
34pub use diesel_schema::generate_diesel_schema;
35
36mod seaorm_entities;
37pub use seaorm_entities::generate_seaorm_entities;
38
39mod toasty_models;
40pub use toasty_models::generate_toasty_models;
41
42mod tracking;
43pub use tracking::schema_hash;
44
45pub mod plugin_schemas;
46
47pub mod config;
48pub mod diff;
49pub mod generate;
50pub mod sqlx_queries;
51
52/// All known plugin names.
53pub const ALL_PLUGINS: &[&str] = &[
54    "email-password",
55    "passkey",
56    "mfa",
57    "oauth",
58    "bearer",
59    "api-key",
60    "magic-link",
61    "oauth2-server",
62    "account-lockout",
63    "webhooks",
64    "oidc",
65];
66
67/// Get the schema tables for a plugin by name.
68///
69/// Returns `None` if the plugin name is not recognized.
70pub fn plugin_schema_by_name(name: &str) -> Option<Vec<TableDef>> {
71    match name {
72        "email-password" => Some(plugin_schemas::email_password_schema()),
73        "passkey" => Some(plugin_schemas::passkey_schema()),
74        "mfa" => Some(plugin_schemas::mfa_schema()),
75        "oauth" => Some(plugin_schemas::oauth_schema()),
76        "bearer" => Some(plugin_schemas::bearer_schema()),
77        "api-key" => Some(plugin_schemas::api_key_schema()),
78        "magic-link" => Some(plugin_schemas::magic_link_schema()),
79        "oauth2-server" => Some(plugin_schemas::oauth2_server_schema()),
80        "account-lockout" => Some(plugin_schemas::account_lockout_schema()),
81        "webhooks" => Some(plugin_schemas::webhooks_schema()),
82        "oidc" => Some(plugin_schemas::oidc_schema()),
83        _ => None,
84    }
85}
86
87/// Check if a plugin name is valid (even if it has no database tables).
88/// Plugins like `admin`, `status`, `telemetry`, and `openapi` are code-only.
89pub fn is_known_plugin(name: &str) -> bool {
90    matches!(
91        name,
92        "email-password"
93            | "passkey"
94            | "mfa"
95            | "oauth"
96            | "bearer"
97            | "api-key"
98            | "magic-link"
99            | "admin"
100            | "status"
101            | "oauth2-server"
102            | "account-lockout"
103            | "webhooks"
104            | "oidc"
105            | "telemetry"
106            | "openapi"
107    )
108}
109
110/// Collect a schema from a list of plugin names plus core tables.
111///
112/// The `table_prefix` replaces the default `yauth_` prefix on all table names
113/// and FK references.
114pub fn collect_schema_for_plugins(
115    plugins: &[String],
116    table_prefix: &str,
117) -> Result<YAuthSchema, SchemaError> {
118    let mut table_lists = vec![core_schema()];
119    for plugin in plugins {
120        match plugin_schema_by_name(plugin) {
121            Some(tables) => table_lists.push(tables),
122            None if is_known_plugin(plugin) => {
123                // Plugin exists but has no tables (e.g., admin, status, telemetry, openapi)
124            }
125            None => {
126                return Err(SchemaError::UnknownPlugin(plugin.clone()));
127            }
128        }
129    }
130
131    // Apply table prefix if not the default
132    if table_prefix != "yauth_" {
133        for list in &mut table_lists {
134            for table in list.iter_mut() {
135                table.apply_prefix("yauth_", table_prefix);
136            }
137        }
138    }
139
140    collect_schema(table_lists)
141}
142
143/// Generate DDL for a schema in the given dialect.
144pub fn generate_ddl(schema: &YAuthSchema, dialect: Dialect) -> String {
145    match dialect {
146        Dialect::Postgres => generate_postgres_ddl(schema),
147        Dialect::Sqlite => generate_sqlite_ddl(schema),
148        Dialect::Mysql => generate_mysql_ddl(schema),
149    }
150}
151
152#[cfg(test)]
153mod tests;