database_replicator/
lib.rs

1// ABOUTME: Library module for database-replicator
2// ABOUTME: Exports all core functionality for use in binary and tests
3
4pub mod checkpoint;
5pub mod commands;
6pub mod config;
7pub mod daemon;
8pub mod filters;
9pub mod interactive;
10pub mod jsonb;
11pub mod migration;
12pub mod mongodb;
13pub mod mysql;
14pub mod postgres;
15pub mod preflight;
16pub mod remote;
17pub mod replication;
18pub mod serendb;
19pub mod sqlite;
20pub mod state;
21pub mod table_rules;
22pub mod utils;
23pub mod xmin;
24
25use anyhow::{bail, Result};
26
27/// Database source types supported for replication
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum SourceType {
30    /// PostgreSQL database (postgresql:// or postgres:// URL)
31    PostgreSQL,
32    /// SQLite database file (.db, .sqlite, .sqlite3)
33    SQLite,
34    /// MongoDB database (mongodb:// or mongodb+srv:// URL)
35    MongoDB,
36    /// MySQL database (mysql:// URL)
37    MySQL,
38}
39
40/// Detect the source database type from connection string or file path
41///
42/// Detection rules:
43/// - PostgreSQL: Starts with `postgresql://` or `postgres://`
44/// - SQLite: Ends with `.db`, `.sqlite`, or `.sqlite3`
45/// - MongoDB: Starts with `mongodb://` or `mongodb+srv://`
46/// - MySQL: Starts with `mysql://`
47///
48/// # Arguments
49///
50/// * `source` - Database connection string or file path
51///
52/// # Returns
53///
54/// Detected source type or error if type cannot be determined
55///
56/// # Examples
57///
58/// ```
59/// # use database_replicator::{detect_source_type, SourceType};
60/// assert_eq!(detect_source_type("postgresql://localhost/db").unwrap(), SourceType::PostgreSQL);
61/// assert_eq!(detect_source_type("database.db").unwrap(), SourceType::SQLite);
62/// assert_eq!(detect_source_type("data.sqlite3").unwrap(), SourceType::SQLite);
63/// assert!(detect_source_type("invalid").is_err());
64/// ```
65pub fn detect_source_type(source: &str) -> Result<SourceType> {
66    if source.starts_with("postgresql://") || source.starts_with("postgres://") {
67        Ok(SourceType::PostgreSQL)
68    } else if source.starts_with("mongodb://") || source.starts_with("mongodb+srv://") {
69        Ok(SourceType::MongoDB)
70    } else if source.starts_with("mysql://") {
71        Ok(SourceType::MySQL)
72    } else if source.ends_with(".db") || source.ends_with(".sqlite") || source.ends_with(".sqlite3")
73    {
74        Ok(SourceType::SQLite)
75    } else {
76        bail!(
77            "Could not detect source database type from '{}'.\n\
78             Supported sources:\n\
79             - PostgreSQL: postgresql://... or postgres://...\n\
80             - SQLite: path ending with .db, .sqlite, or .sqlite3\n\
81             - MongoDB: mongodb://... or mongodb+srv://...\n\
82             - MySQL: mysql://...",
83            source
84        )
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_detect_postgresql() {
94        assert_eq!(
95            detect_source_type("postgresql://localhost/mydb").unwrap(),
96            SourceType::PostgreSQL
97        );
98        assert_eq!(
99            detect_source_type("postgres://user:pass@host:5432/db").unwrap(),
100            SourceType::PostgreSQL
101        );
102    }
103
104    #[test]
105    fn test_detect_sqlite() {
106        assert_eq!(
107            detect_source_type("database.db").unwrap(),
108            SourceType::SQLite
109        );
110        assert_eq!(
111            detect_source_type("data.sqlite").unwrap(),
112            SourceType::SQLite
113        );
114        assert_eq!(
115            detect_source_type("/path/to/database.sqlite3").unwrap(),
116            SourceType::SQLite
117        );
118    }
119
120    #[test]
121    fn test_detect_mongodb() {
122        assert_eq!(
123            detect_source_type("mongodb://localhost/db").unwrap(),
124            SourceType::MongoDB
125        );
126        assert_eq!(
127            detect_source_type("mongodb+srv://cluster.mongodb.net/db").unwrap(),
128            SourceType::MongoDB
129        );
130    }
131
132    #[test]
133    fn test_detect_mysql() {
134        assert_eq!(
135            detect_source_type("mysql://localhost/db").unwrap(),
136            SourceType::MySQL
137        );
138        assert_eq!(
139            detect_source_type("mysql://user:pass@host:3306/db").unwrap(),
140            SourceType::MySQL
141        );
142    }
143
144    #[test]
145    fn test_detect_invalid_source() {
146        let result = detect_source_type("invalid_source");
147        assert!(result.is_err());
148        assert!(result
149            .unwrap_err()
150            .to_string()
151            .contains("Could not detect source database type"));
152    }
153
154    #[test]
155    fn test_detect_empty_string() {
156        let result = detect_source_type("");
157        assert!(result.is_err());
158    }
159}