1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::path::Path;
6
7use crate::error::{SchemaError, SchemaResult};
8
9#[derive(Debug, Clone, Default, Deserialize, Serialize)]
11#[serde(deny_unknown_fields)]
12pub struct PraxConfig {
13 #[serde(default)]
15 pub database: DatabaseConfig,
16
17 #[serde(default)]
19 pub schema: SchemaConfig,
20
21 #[serde(default)]
23 pub generator: GeneratorConfig,
24
25 #[serde(default)]
27 pub migrations: MigrationConfig,
28
29 #[serde(default)]
31 pub seed: SeedConfig,
32
33 #[serde(default)]
35 pub debug: DebugConfig,
36
37 #[serde(default)]
39 pub environments: HashMap<String, EnvironmentOverride>,
40}
41
42impl PraxConfig {
43 pub fn from_file(path: impl AsRef<Path>) -> SchemaResult<Self> {
45 let path = path.as_ref();
46 let content = std::fs::read_to_string(path).map_err(|e| SchemaError::IoError {
47 path: path.display().to_string(),
48 source: e,
49 })?;
50
51 Self::from_str(&content)
52 }
53
54 #[allow(clippy::should_implement_trait)]
56 pub fn from_str(content: &str) -> SchemaResult<Self> {
57 let expanded = expand_env_vars(content);
59
60 toml::from_str(&expanded).map_err(|e| SchemaError::TomlError { source: e })
61 }
62
63 pub fn database_url(&self) -> Option<&str> {
65 self.database.url.as_deref()
66 }
67
68 pub fn with_environment(mut self, env: &str) -> Self {
70 if let Some(overrides) = self.environments.remove(env) {
71 if let Some(db) = overrides.database {
72 if let Some(url) = db.url {
73 self.database.url = Some(url);
74 }
75 if let Some(pool) = db.pool {
76 self.database.pool = pool;
77 }
78 }
79 if let Some(debug) = overrides.debug {
80 if let Some(log_queries) = debug.log_queries {
81 self.debug.log_queries = log_queries;
82 }
83 if let Some(pretty_sql) = debug.pretty_sql {
84 self.debug.pretty_sql = pretty_sql;
85 }
86 if let Some(threshold) = debug.slow_query_threshold {
87 self.debug.slow_query_threshold = threshold;
88 }
89 }
90 }
91 self
92 }
93}
94
95#[derive(Debug, Clone, Deserialize, Serialize)]
97#[serde(deny_unknown_fields)]
98pub struct DatabaseConfig {
99 #[serde(default = "default_provider")]
101 pub provider: DatabaseProvider,
102
103 pub url: Option<String>,
105
106 #[serde(default)]
108 pub pool: PoolConfig,
109}
110
111impl Default for DatabaseConfig {
112 fn default() -> Self {
113 Self {
114 provider: DatabaseProvider::PostgreSql,
115 url: None,
116 pool: PoolConfig::default(),
117 }
118 }
119}
120
121fn default_provider() -> DatabaseProvider {
122 DatabaseProvider::PostgreSql
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
127#[serde(rename_all = "lowercase")]
128pub enum DatabaseProvider {
129 #[serde(alias = "postgres")]
131 PostgreSql,
132 MySql,
134 #[serde(alias = "sqlite3")]
136 Sqlite,
137 #[serde(alias = "mongo")]
139 MongoDb,
140}
141
142impl DatabaseProvider {
143 pub fn as_str(&self) -> &'static str {
145 match self {
146 Self::PostgreSql => "postgresql",
147 Self::MySql => "mysql",
148 Self::Sqlite => "sqlite",
149 Self::MongoDb => "mongodb",
150 }
151 }
152}
153
154#[derive(Debug, Clone, Deserialize, Serialize)]
156#[serde(deny_unknown_fields)]
157pub struct PoolConfig {
158 #[serde(default = "default_min_connections")]
160 pub min_connections: u32,
161
162 #[serde(default = "default_max_connections")]
164 pub max_connections: u32,
165
166 #[serde(default = "default_connect_timeout")]
168 pub connect_timeout: String,
169
170 #[serde(default = "default_idle_timeout")]
172 pub idle_timeout: String,
173
174 #[serde(default = "default_max_lifetime")]
176 pub max_lifetime: String,
177}
178
179impl Default for PoolConfig {
180 fn default() -> Self {
181 Self {
182 min_connections: default_min_connections(),
183 max_connections: default_max_connections(),
184 connect_timeout: default_connect_timeout(),
185 idle_timeout: default_idle_timeout(),
186 max_lifetime: default_max_lifetime(),
187 }
188 }
189}
190
191fn default_min_connections() -> u32 {
192 2
193}
194fn default_max_connections() -> u32 {
195 10
196}
197fn default_connect_timeout() -> String {
198 "30s".to_string()
199}
200fn default_idle_timeout() -> String {
201 "10m".to_string()
202}
203fn default_max_lifetime() -> String {
204 "30m".to_string()
205}
206
207#[derive(Debug, Clone, Deserialize, Serialize)]
209#[serde(deny_unknown_fields)]
210pub struct SchemaConfig {
211 #[serde(default = "default_schema_path")]
213 pub path: String,
214}
215
216impl Default for SchemaConfig {
217 fn default() -> Self {
218 Self {
219 path: default_schema_path(),
220 }
221 }
222}
223
224fn default_schema_path() -> String {
225 "schema.prax".to_string()
226}
227
228#[derive(Debug, Clone, Default, Deserialize, Serialize)]
230#[serde(deny_unknown_fields)]
231pub struct GeneratorConfig {
232 #[serde(default)]
234 pub client: ClientGeneratorConfig,
235}
236
237#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
242#[serde(rename_all = "lowercase")]
243pub enum ModelStyle {
244 #[default]
247 Standard,
248
249 #[serde(alias = "async-graphql")]
253 GraphQL,
254}
255
256impl ModelStyle {
257 pub fn is_graphql(&self) -> bool {
259 matches!(self, Self::GraphQL)
260 }
261}
262
263#[derive(Debug, Clone, Deserialize, Serialize)]
265#[serde(deny_unknown_fields)]
266pub struct ClientGeneratorConfig {
267 #[serde(default = "default_output")]
269 pub output: String,
270
271 #[serde(default = "default_true")]
273 pub async_client: bool,
274
275 #[serde(default)]
277 pub tracing: bool,
278
279 #[serde(default)]
281 pub preview_features: Vec<String>,
282
283 #[serde(default)]
289 pub model_style: ModelStyle,
290}
291
292impl Default for ClientGeneratorConfig {
293 fn default() -> Self {
294 Self {
295 output: default_output(),
296 async_client: true,
297 tracing: false,
298 preview_features: vec![],
299 model_style: ModelStyle::default(),
300 }
301 }
302}
303
304fn default_output() -> String {
305 "./src/generated".to_string()
306}
307fn default_true() -> bool {
308 true
309}
310
311#[derive(Debug, Clone, Deserialize, Serialize)]
313#[serde(deny_unknown_fields)]
314pub struct MigrationConfig {
315 #[serde(default = "default_migrations_dir")]
317 pub directory: String,
318
319 #[serde(default)]
321 pub auto_migrate: bool,
322
323 #[serde(default = "default_migrations_table")]
325 pub table_name: String,
326}
327
328impl Default for MigrationConfig {
329 fn default() -> Self {
330 Self {
331 directory: default_migrations_dir(),
332 auto_migrate: false,
333 table_name: default_migrations_table(),
334 }
335 }
336}
337
338fn default_migrations_dir() -> String {
339 "./migrations".to_string()
340}
341fn default_migrations_table() -> String {
342 "_prax_migrations".to_string()
343}
344
345#[derive(Debug, Clone, Default, Deserialize, Serialize)]
347#[serde(deny_unknown_fields)]
348pub struct SeedConfig {
349 pub script: Option<String>,
351
352 #[serde(default)]
354 pub auto_seed: bool,
355
356 #[serde(default)]
358 pub environments: HashMap<String, bool>,
359}
360
361#[derive(Debug, Clone, Deserialize, Serialize)]
363#[serde(deny_unknown_fields)]
364pub struct DebugConfig {
365 #[serde(default)]
367 pub log_queries: bool,
368
369 #[serde(default = "default_true")]
371 pub pretty_sql: bool,
372
373 #[serde(default = "default_slow_query_threshold")]
375 pub slow_query_threshold: u64,
376}
377
378impl Default for DebugConfig {
379 fn default() -> Self {
380 Self {
381 log_queries: false,
382 pretty_sql: true,
383 slow_query_threshold: default_slow_query_threshold(),
384 }
385 }
386}
387
388fn default_slow_query_threshold() -> u64 {
389 1000
390}
391
392#[derive(Debug, Clone, Default, Deserialize, Serialize)]
394#[serde(deny_unknown_fields)]
395pub struct EnvironmentOverride {
396 pub database: Option<DatabaseOverride>,
398
399 pub debug: Option<DebugOverride>,
401}
402
403#[derive(Debug, Clone, Default, Deserialize, Serialize)]
405#[serde(deny_unknown_fields)]
406pub struct DatabaseOverride {
407 pub url: Option<String>,
409
410 pub pool: Option<PoolConfig>,
412}
413
414#[derive(Debug, Clone, Default, Deserialize, Serialize)]
416#[serde(deny_unknown_fields)]
417pub struct DebugOverride {
418 pub log_queries: Option<bool>,
420
421 pub pretty_sql: Option<bool>,
423
424 pub slow_query_threshold: Option<u64>,
426}
427
428fn expand_env_vars(content: &str) -> String {
430 let mut result = content.to_string();
431 static PATTERN: std::sync::OnceLock<regex_lite::Regex> = std::sync::OnceLock::new();
434 let re = PATTERN.get_or_init(|| regex_lite::Regex::new(r"\$\{([^}]+)\}").unwrap());
435
436 for cap in re.captures_iter(content) {
437 let var_name = &cap[1];
438 let full_match = &cap[0];
439
440 if let Ok(value) = std::env::var(var_name) {
441 result = result.replace(full_match, &value);
442 }
443 }
444
445 result
446}
447
448#[cfg(test)]
449mod tests {
450 use super::*;
451
452 #[test]
455 fn test_default_config() {
456 let config = PraxConfig::default();
457 assert_eq!(config.database.provider, DatabaseProvider::PostgreSql);
458 assert_eq!(config.schema.path, "schema.prax");
459 assert!(config.database.url.is_none());
460 assert!(config.environments.is_empty());
461 }
462
463 #[test]
464 fn test_parse_minimal_config() {
465 let toml = r#"
466 [database]
467 provider = "postgresql"
468 url = "postgres://localhost/test"
469 "#;
470
471 let config = PraxConfig::from_str(toml).unwrap();
472 assert_eq!(
473 config.database.url,
474 Some("postgres://localhost/test".to_string())
475 );
476 }
477
478 #[test]
479 fn test_parse_full_config() {
480 let toml = r#"
481 [database]
482 provider = "postgresql"
483 url = "postgres://user:pass@localhost:5432/db"
484
485 [database.pool]
486 min_connections = 5
487 max_connections = 20
488 connect_timeout = "60s"
489 idle_timeout = "5m"
490 max_lifetime = "1h"
491
492 [schema]
493 path = "prisma/schema.prax"
494
495 [generator.client]
496 output = "./src/db"
497 async_client = true
498 tracing = true
499 preview_features = ["json", "fulltext"]
500
501 [migrations]
502 directory = "./db/migrations"
503 auto_migrate = true
504 table_name = "_migrations"
505
506 [seed]
507 script = "./scripts/seed.sh"
508 auto_seed = true
509
510 [seed.environments]
511 development = true
512 test = true
513 production = false
514
515 [debug]
516 log_queries = true
517 pretty_sql = false
518 slow_query_threshold = 500
519 "#;
520
521 let config = PraxConfig::from_str(toml).unwrap();
522
523 assert_eq!(config.database.provider, DatabaseProvider::PostgreSql);
525 assert!(config.database.url.is_some());
526 assert_eq!(config.database.pool.min_connections, 5);
527 assert_eq!(config.database.pool.max_connections, 20);
528
529 assert_eq!(config.schema.path, "prisma/schema.prax");
531
532 assert_eq!(config.generator.client.output, "./src/db");
534 assert!(config.generator.client.async_client);
535 assert!(config.generator.client.tracing);
536 assert_eq!(config.generator.client.preview_features.len(), 2);
537
538 assert_eq!(config.migrations.directory, "./db/migrations");
540 assert!(config.migrations.auto_migrate);
541 assert_eq!(config.migrations.table_name, "_migrations");
542
543 assert_eq!(config.seed.script, Some("./scripts/seed.sh".to_string()));
545 assert!(config.seed.auto_seed);
546 assert!(
547 config
548 .seed
549 .environments
550 .get("development")
551 .copied()
552 .unwrap_or(false)
553 );
554
555 assert!(config.debug.log_queries);
557 assert!(!config.debug.pretty_sql);
558 assert_eq!(config.debug.slow_query_threshold, 500);
559 }
560
561 #[test]
562 fn test_database_url_method() {
563 let config = PraxConfig {
564 database: DatabaseConfig {
565 url: Some("postgres://localhost/test".to_string()),
566 ..Default::default()
567 },
568 ..Default::default()
569 };
570
571 assert_eq!(config.database_url(), Some("postgres://localhost/test"));
572 }
573
574 #[test]
575 fn test_database_url_method_none() {
576 let config = PraxConfig::default();
577 assert!(config.database_url().is_none());
578 }
579
580 #[test]
581 fn test_with_environment_overrides() {
582 let toml = r#"
583 [database]
584 url = "postgres://localhost/dev"
585
586 [debug]
587 log_queries = false
588
589 [environments.production]
590 [environments.production.database]
591 url = "postgres://prod.server/db"
592
593 [environments.production.debug]
594 log_queries = true
595 slow_query_threshold = 100
596 "#;
597
598 let config = PraxConfig::from_str(toml)
599 .unwrap()
600 .with_environment("production");
601
602 assert_eq!(
603 config.database.url,
604 Some("postgres://prod.server/db".to_string())
605 );
606 assert!(config.debug.log_queries);
607 assert_eq!(config.debug.slow_query_threshold, 100);
608 }
609
610 #[test]
611 fn test_with_environment_nonexistent() {
612 let config = PraxConfig::default().with_environment("nonexistent");
613 assert_eq!(config.database.provider, DatabaseProvider::PostgreSql);
615 }
616
617 #[test]
618 fn test_parse_invalid_toml() {
619 let toml = "this is not valid [[ toml";
620 let result = PraxConfig::from_str(toml);
621 assert!(result.is_err());
622 }
623
624 #[test]
627 fn test_database_provider_postgresql() {
628 let toml = r#"
629 [database]
630 provider = "postgresql"
631 "#;
632 let config = PraxConfig::from_str(toml).unwrap();
633 assert_eq!(config.database.provider, DatabaseProvider::PostgreSql);
634 assert_eq!(config.database.provider.as_str(), "postgresql");
635 }
636
637 #[test]
638 fn test_database_provider_postgres_alias() {
639 let toml = r#"
640 [database]
641 provider = "postgres"
642 "#;
643 let config = PraxConfig::from_str(toml).unwrap();
644 assert_eq!(config.database.provider, DatabaseProvider::PostgreSql);
645 }
646
647 #[test]
648 fn test_database_provider_mysql() {
649 let toml = r#"
650 [database]
651 provider = "mysql"
652 "#;
653 let config = PraxConfig::from_str(toml).unwrap();
654 assert_eq!(config.database.provider, DatabaseProvider::MySql);
655 assert_eq!(config.database.provider.as_str(), "mysql");
656 }
657
658 #[test]
659 fn test_database_provider_sqlite() {
660 let toml = r#"
661 [database]
662 provider = "sqlite"
663 "#;
664 let config = PraxConfig::from_str(toml).unwrap();
665 assert_eq!(config.database.provider, DatabaseProvider::Sqlite);
666 assert_eq!(config.database.provider.as_str(), "sqlite");
667 }
668
669 #[test]
670 fn test_database_provider_sqlite3_alias() {
671 let toml = r#"
672 [database]
673 provider = "sqlite3"
674 "#;
675 let config = PraxConfig::from_str(toml).unwrap();
676 assert_eq!(config.database.provider, DatabaseProvider::Sqlite);
677 }
678
679 #[test]
680 fn test_database_provider_mongodb() {
681 let toml = r#"
682 [database]
683 provider = "mongodb"
684 "#;
685 let config = PraxConfig::from_str(toml).unwrap();
686 assert_eq!(config.database.provider, DatabaseProvider::MongoDb);
687 assert_eq!(config.database.provider.as_str(), "mongodb");
688 }
689
690 #[test]
691 fn test_database_provider_mongo_alias() {
692 let toml = r#"
693 [database]
694 provider = "mongo"
695 "#;
696 let config = PraxConfig::from_str(toml).unwrap();
697 assert_eq!(config.database.provider, DatabaseProvider::MongoDb);
698 }
699
700 #[test]
703 fn test_pool_config_defaults() {
704 let config = PoolConfig::default();
705 assert_eq!(config.min_connections, 2);
706 assert_eq!(config.max_connections, 10);
707 assert_eq!(config.connect_timeout, "30s");
708 assert_eq!(config.idle_timeout, "10m");
709 assert_eq!(config.max_lifetime, "30m");
710 }
711
712 #[test]
713 fn test_pool_config_custom() {
714 let toml = r#"
715 [database]
716 provider = "postgresql"
717
718 [database.pool]
719 min_connections = 1
720 max_connections = 50
721 connect_timeout = "10s"
722 idle_timeout = "30m"
723 max_lifetime = "2h"
724 "#;
725
726 let config = PraxConfig::from_str(toml).unwrap();
727 assert_eq!(config.database.pool.min_connections, 1);
728 assert_eq!(config.database.pool.max_connections, 50);
729 assert_eq!(config.database.pool.connect_timeout, "10s");
730 }
731
732 #[test]
735 fn test_schema_config_default() {
736 let config = SchemaConfig::default();
737 assert_eq!(config.path, "schema.prax");
738 }
739
740 #[test]
741 fn test_schema_config_custom() {
742 let toml = r#"
743 [schema]
744 path = "db/schema.prax"
745 "#;
746
747 let config = PraxConfig::from_str(toml).unwrap();
748 assert_eq!(config.schema.path, "db/schema.prax");
749 }
750
751 #[test]
754 fn test_generator_config_default() {
755 let config = GeneratorConfig::default();
756 assert_eq!(config.client.output, "./src/generated");
757 assert!(config.client.async_client);
758 assert!(!config.client.tracing);
759 assert!(config.client.preview_features.is_empty());
760 assert_eq!(config.client.model_style, ModelStyle::Standard);
761 }
762
763 #[test]
764 fn test_generator_config_custom() {
765 let toml = r#"
766 [generator.client]
767 output = "./generated"
768 async_client = false
769 tracing = true
770 preview_features = ["feature1", "feature2"]
771 "#;
772
773 let config = PraxConfig::from_str(toml).unwrap();
774 assert_eq!(config.generator.client.output, "./generated");
775 assert!(!config.generator.client.async_client);
776 assert!(config.generator.client.tracing);
777 assert_eq!(config.generator.client.preview_features.len(), 2);
778 }
779
780 #[test]
781 fn test_generator_config_graphql_model_style() {
782 let toml = r#"
783 [generator.client]
784 model_style = "graphql"
785 "#;
786
787 let config = PraxConfig::from_str(toml).unwrap();
788 assert_eq!(config.generator.client.model_style, ModelStyle::GraphQL);
789 assert!(config.generator.client.model_style.is_graphql());
790 }
791
792 #[test]
793 fn test_generator_config_graphql_model_style_alias() {
794 let toml = r#"
795 [generator.client]
796 model_style = "async-graphql"
797 "#;
798
799 let config = PraxConfig::from_str(toml).unwrap();
800 assert_eq!(config.generator.client.model_style, ModelStyle::GraphQL);
801 }
802
803 #[test]
804 fn test_model_style_standard_is_not_graphql() {
805 assert!(!ModelStyle::Standard.is_graphql());
806 assert!(ModelStyle::GraphQL.is_graphql());
807 }
808
809 #[test]
812 fn test_migration_config_default() {
813 let config = MigrationConfig::default();
814 assert_eq!(config.directory, "./migrations");
815 assert!(!config.auto_migrate);
816 assert_eq!(config.table_name, "_prax_migrations");
817 }
818
819 #[test]
820 fn test_migration_config_custom() {
821 let toml = r#"
822 [migrations]
823 directory = "./db/migrate"
824 auto_migrate = true
825 table_name = "schema_migrations"
826 "#;
827
828 let config = PraxConfig::from_str(toml).unwrap();
829 assert_eq!(config.migrations.directory, "./db/migrate");
830 assert!(config.migrations.auto_migrate);
831 assert_eq!(config.migrations.table_name, "schema_migrations");
832 }
833
834 #[test]
837 fn test_seed_config_default() {
838 let config = SeedConfig::default();
839 assert!(config.script.is_none());
840 assert!(!config.auto_seed);
841 assert!(config.environments.is_empty());
842 }
843
844 #[test]
845 fn test_seed_config_custom() {
846 let toml = r#"
847 [seed]
848 script = "seed.rs"
849 auto_seed = true
850
851 [seed.environments]
852 dev = true
853 prod = false
854 "#;
855
856 let config = PraxConfig::from_str(toml).unwrap();
857 assert_eq!(config.seed.script, Some("seed.rs".to_string()));
858 assert!(config.seed.auto_seed);
859 assert_eq!(config.seed.environments.get("dev"), Some(&true));
860 assert_eq!(config.seed.environments.get("prod"), Some(&false));
861 }
862
863 #[test]
866 fn test_debug_config_default() {
867 let config = DebugConfig::default();
868 assert!(!config.log_queries);
869 assert!(config.pretty_sql);
870 assert_eq!(config.slow_query_threshold, 1000);
871 }
872
873 #[test]
874 fn test_debug_config_custom() {
875 let toml = r#"
876 [debug]
877 log_queries = true
878 pretty_sql = false
879 slow_query_threshold = 200
880 "#;
881
882 let config = PraxConfig::from_str(toml).unwrap();
883 assert!(config.debug.log_queries);
884 assert!(!config.debug.pretty_sql);
885 assert_eq!(config.debug.slow_query_threshold, 200);
886 }
887
888 #[test]
891 fn test_env_var_expansion() {
892 unsafe {
894 std::env::set_var("TEST_DB_URL", "postgres://test");
895 }
896 let expanded = expand_env_vars("url = \"${TEST_DB_URL}\"");
897 assert_eq!(expanded, "url = \"postgres://test\"");
898 unsafe {
899 std::env::remove_var("TEST_DB_URL");
900 }
901 }
902
903 #[test]
904 fn test_env_var_expansion_multiple() {
905 unsafe {
906 std::env::set_var("TEST_HOST", "localhost");
907 std::env::set_var("TEST_PORT", "5432");
908 }
909 let content = "host = \"${TEST_HOST}\"\nport = \"${TEST_PORT}\"";
910 let expanded = expand_env_vars(content);
911 assert!(expanded.contains("localhost"));
912 assert!(expanded.contains("5432"));
913 unsafe {
914 std::env::remove_var("TEST_HOST");
915 std::env::remove_var("TEST_PORT");
916 }
917 }
918
919 #[test]
920 fn test_env_var_expansion_missing_var() {
921 let content = "url = \"${DEFINITELY_NOT_SET_VAR_12345}\"";
922 let expanded = expand_env_vars(content);
923 assert_eq!(expanded, content);
925 }
926
927 #[test]
928 fn test_env_var_expansion_in_config() {
929 unsafe {
930 std::env::set_var("TEST_DATABASE_URL_2", "postgres://user:pass@localhost/db");
931 }
932
933 let toml = r#"
934 [database]
935 url = "${TEST_DATABASE_URL_2}"
936 "#;
937
938 let config = PraxConfig::from_str(toml).unwrap();
939 assert_eq!(
940 config.database.url,
941 Some("postgres://user:pass@localhost/db".to_string())
942 );
943
944 unsafe {
945 std::env::remove_var("TEST_DATABASE_URL_2");
946 }
947 }
948
949 #[test]
952 fn test_environment_override_database_url() {
953 let toml = r#"
954 [database]
955 url = "postgres://localhost/dev"
956
957 [environments.test]
958 [environments.test.database]
959 url = "postgres://localhost/test_db"
960 "#;
961
962 let config = PraxConfig::from_str(toml).unwrap().with_environment("test");
963
964 assert_eq!(
965 config.database.url,
966 Some("postgres://localhost/test_db".to_string())
967 );
968 }
969
970 #[test]
971 fn test_environment_override_pool() {
972 let toml = r#"
973 [database.pool]
974 max_connections = 10
975
976 [environments.production]
977 [environments.production.database.pool]
978 max_connections = 100
979 min_connections = 10
980 "#;
981
982 let config = PraxConfig::from_str(toml)
983 .unwrap()
984 .with_environment("production");
985
986 assert_eq!(config.database.pool.max_connections, 100);
987 assert_eq!(config.database.pool.min_connections, 10);
988 }
989
990 #[test]
991 fn test_environment_override_debug() {
992 let toml = r#"
993 [debug]
994 log_queries = false
995 pretty_sql = true
996
997 [environments.development]
998 [environments.development.debug]
999 log_queries = true
1000 pretty_sql = false
1001 slow_query_threshold = 50
1002 "#;
1003
1004 let config = PraxConfig::from_str(toml)
1005 .unwrap()
1006 .with_environment("development");
1007
1008 assert!(config.debug.log_queries);
1009 assert!(!config.debug.pretty_sql);
1010 assert_eq!(config.debug.slow_query_threshold, 50);
1011 }
1012
1013 #[test]
1016 fn test_config_serialization() {
1017 let config = PraxConfig::default();
1018 let toml_str = toml::to_string(&config).unwrap();
1019 assert!(toml_str.contains("[database]"));
1020 }
1021
1022 #[test]
1023 fn test_config_roundtrip() {
1024 let original = PraxConfig {
1025 database: DatabaseConfig {
1026 provider: DatabaseProvider::MySql,
1027 url: Some("mysql://localhost/test".to_string()),
1028 pool: PoolConfig::default(),
1029 },
1030 ..Default::default()
1031 };
1032
1033 let toml_str = toml::to_string(&original).unwrap();
1034 let parsed: PraxConfig = toml::from_str(&toml_str).unwrap();
1035
1036 assert_eq!(parsed.database.provider, original.database.provider);
1037 assert_eq!(parsed.database.url, original.database.url);
1038 }
1039
1040 #[test]
1043 fn test_config_clone() {
1044 let config = PraxConfig::default();
1045 let cloned = config.clone();
1046 assert_eq!(config.database.provider, cloned.database.provider);
1047 }
1048
1049 #[test]
1050 fn test_config_debug() {
1051 let config = PraxConfig::default();
1052 let debug_str = format!("{:?}", config);
1053 assert!(debug_str.contains("PraxConfig"));
1054 }
1055
1056 #[test]
1057 fn test_provider_equality() {
1058 assert_eq!(DatabaseProvider::PostgreSql, DatabaseProvider::PostgreSql);
1059 assert_ne!(DatabaseProvider::PostgreSql, DatabaseProvider::MySql);
1060 }
1061
1062 #[test]
1065 fn test_default_functions() {
1066 assert_eq!(default_provider(), DatabaseProvider::PostgreSql);
1067 assert_eq!(default_min_connections(), 2);
1068 assert_eq!(default_max_connections(), 10);
1069 assert_eq!(default_connect_timeout(), "30s");
1070 assert_eq!(default_idle_timeout(), "10m");
1071 assert_eq!(default_max_lifetime(), "30m");
1072 assert_eq!(default_schema_path(), "schema.prax");
1073 assert_eq!(default_output(), "./src/generated");
1074 assert!(default_true());
1075 assert_eq!(default_migrations_dir(), "./migrations");
1076 assert_eq!(default_migrations_table(), "_prax_migrations");
1077 assert_eq!(default_slow_query_threshold(), 1000);
1078 }
1079}