use crate::Model;
#[derive(Model, Debug, Clone, serde::Serialize)]
#[rustango(table = "rustango_orgs", display = "slug", scope = "registry")]
#[allow(dead_code)] pub struct Org {
#[rustango(primary_key)]
pub id: rustango::Auto<i64>,
#[rustango(max_length = 64, unique)]
pub slug: String,
#[rustango(max_length = 255)]
pub display_name: String,
#[rustango(max_length = 16)]
pub storage_mode: String,
#[rustango(max_length = 16, default = "'postgres'")]
pub backend_kind: String,
pub database_url: Option<String>,
#[rustango(max_length = 64)]
pub schema_name: Option<String>,
#[rustango(max_length = 255)]
pub host_pattern: Option<String>,
pub port: Option<i32>,
#[rustango(max_length = 255)]
pub path_prefix: Option<String>,
pub active: bool,
pub created_at: chrono::DateTime<chrono::Utc>,
#[rustango(max_length = 80)]
pub brand_name: Option<String>,
#[rustango(max_length = 200)]
pub brand_tagline: Option<String>,
#[rustango(max_length = 120)]
pub logo_path: Option<String>,
#[rustango(max_length = 120)]
pub favicon_path: Option<String>,
#[rustango(max_length = 7)]
pub primary_color: Option<String>,
#[rustango(max_length = 8)]
pub theme_mode: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StorageMode {
Schema,
Database,
}
impl StorageMode {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Schema => "schema",
Self::Database => "database",
}
}
pub fn parse(s: &str) -> Result<Self, &str> {
match s {
"schema" => Ok(Self::Schema),
"database" => Ok(Self::Database),
other => Err(other),
}
}
}
impl core::fmt::Display for StorageMode {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BackendKind {
#[default]
Postgres,
MySql,
Sqlite,
}
impl BackendKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Postgres => "postgres",
Self::MySql => "mysql",
Self::Sqlite => "sqlite",
}
}
pub fn parse(s: &str) -> Result<Self, &str> {
match s {
"postgres" | "postgresql" | "pg" => Ok(Self::Postgres),
"mysql" | "mariadb" => Ok(Self::MySql),
"sqlite" | "sqlite3" => Ok(Self::Sqlite),
other => Err(other),
}
}
pub const fn validate_storage_mode(self, mode: StorageMode) -> Result<(), &'static str> {
match (self, mode) {
(Self::Postgres, _) => Ok(()),
(_, StorageMode::Database) => Ok(()),
(Self::MySql, StorageMode::Schema) => {
Err("MySQL backend doesn't support schema-mode tenancy \
(use storage_mode=database)")
}
(Self::Sqlite, StorageMode::Schema) => {
Err("SQLite backend doesn't support schema-mode tenancy \
(use storage_mode=database; each tenant gets its own file)")
}
}
}
}
impl core::fmt::Display for BackendKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_accepts_aliases() {
assert_eq!(
BackendKind::parse("postgres").unwrap(),
BackendKind::Postgres
);
assert_eq!(
BackendKind::parse("postgresql").unwrap(),
BackendKind::Postgres
);
assert_eq!(BackendKind::parse("pg").unwrap(), BackendKind::Postgres);
assert_eq!(BackendKind::parse("mysql").unwrap(), BackendKind::MySql);
assert_eq!(BackendKind::parse("mariadb").unwrap(), BackendKind::MySql);
assert_eq!(BackendKind::parse("sqlite").unwrap(), BackendKind::Sqlite);
assert_eq!(BackendKind::parse("sqlite3").unwrap(), BackendKind::Sqlite);
}
#[test]
fn parse_rejects_unknown() {
assert!(BackendKind::parse("oracle").is_err());
assert!(BackendKind::parse("").is_err());
}
#[test]
fn validate_storage_mode_postgres_accepts_both() {
assert!(BackendKind::Postgres
.validate_storage_mode(StorageMode::Schema)
.is_ok());
assert!(BackendKind::Postgres
.validate_storage_mode(StorageMode::Database)
.is_ok());
}
#[test]
fn validate_storage_mode_mysql_rejects_schema() {
let err = BackendKind::MySql
.validate_storage_mode(StorageMode::Schema)
.unwrap_err();
assert!(err.contains("MySQL"));
}
#[test]
fn validate_storage_mode_sqlite_rejects_schema() {
let err = BackendKind::Sqlite
.validate_storage_mode(StorageMode::Schema)
.unwrap_err();
assert!(err.contains("SQLite"));
}
#[test]
fn non_postgres_accepts_database_mode() {
assert!(BackendKind::MySql
.validate_storage_mode(StorageMode::Database)
.is_ok());
assert!(BackendKind::Sqlite
.validate_storage_mode(StorageMode::Database)
.is_ok());
}
#[test]
fn default_is_postgres() {
assert_eq!(BackendKind::default(), BackendKind::Postgres);
}
}