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 let re = regex_lite::Regex::new(r"\$\{([^}]+)\}").unwrap();
432
433 for cap in re.captures_iter(content) {
434 let var_name = &cap[1];
435 let full_match = &cap[0];
436
437 if let Ok(value) = std::env::var(var_name) {
438 result = result.replace(full_match, &value);
439 }
440 }
441
442 result
443}
444
445#[cfg(test)]
446mod tests {
447 use super::*;
448
449 #[test]
452 fn test_default_config() {
453 let config = PraxConfig::default();
454 assert_eq!(config.database.provider, DatabaseProvider::PostgreSql);
455 assert_eq!(config.schema.path, "schema.prax");
456 assert!(config.database.url.is_none());
457 assert!(config.environments.is_empty());
458 }
459
460 #[test]
461 fn test_parse_minimal_config() {
462 let toml = r#"
463 [database]
464 provider = "postgresql"
465 url = "postgres://localhost/test"
466 "#;
467
468 let config = PraxConfig::from_str(toml).unwrap();
469 assert_eq!(
470 config.database.url,
471 Some("postgres://localhost/test".to_string())
472 );
473 }
474
475 #[test]
476 fn test_parse_full_config() {
477 let toml = r#"
478 [database]
479 provider = "postgresql"
480 url = "postgres://user:pass@localhost:5432/db"
481
482 [database.pool]
483 min_connections = 5
484 max_connections = 20
485 connect_timeout = "60s"
486 idle_timeout = "5m"
487 max_lifetime = "1h"
488
489 [schema]
490 path = "prisma/schema.prax"
491
492 [generator.client]
493 output = "./src/db"
494 async_client = true
495 tracing = true
496 preview_features = ["json", "fulltext"]
497
498 [migrations]
499 directory = "./db/migrations"
500 auto_migrate = true
501 table_name = "_migrations"
502
503 [seed]
504 script = "./scripts/seed.sh"
505 auto_seed = true
506
507 [seed.environments]
508 development = true
509 test = true
510 production = false
511
512 [debug]
513 log_queries = true
514 pretty_sql = false
515 slow_query_threshold = 500
516 "#;
517
518 let config = PraxConfig::from_str(toml).unwrap();
519
520 assert_eq!(config.database.provider, DatabaseProvider::PostgreSql);
522 assert!(config.database.url.is_some());
523 assert_eq!(config.database.pool.min_connections, 5);
524 assert_eq!(config.database.pool.max_connections, 20);
525
526 assert_eq!(config.schema.path, "prisma/schema.prax");
528
529 assert_eq!(config.generator.client.output, "./src/db");
531 assert!(config.generator.client.async_client);
532 assert!(config.generator.client.tracing);
533 assert_eq!(config.generator.client.preview_features.len(), 2);
534
535 assert_eq!(config.migrations.directory, "./db/migrations");
537 assert!(config.migrations.auto_migrate);
538 assert_eq!(config.migrations.table_name, "_migrations");
539
540 assert_eq!(config.seed.script, Some("./scripts/seed.sh".to_string()));
542 assert!(config.seed.auto_seed);
543 assert!(
544 config
545 .seed
546 .environments
547 .get("development")
548 .copied()
549 .unwrap_or(false)
550 );
551
552 assert!(config.debug.log_queries);
554 assert!(!config.debug.pretty_sql);
555 assert_eq!(config.debug.slow_query_threshold, 500);
556 }
557
558 #[test]
559 fn test_database_url_method() {
560 let config = PraxConfig {
561 database: DatabaseConfig {
562 url: Some("postgres://localhost/test".to_string()),
563 ..Default::default()
564 },
565 ..Default::default()
566 };
567
568 assert_eq!(config.database_url(), Some("postgres://localhost/test"));
569 }
570
571 #[test]
572 fn test_database_url_method_none() {
573 let config = PraxConfig::default();
574 assert!(config.database_url().is_none());
575 }
576
577 #[test]
578 fn test_with_environment_overrides() {
579 let toml = r#"
580 [database]
581 url = "postgres://localhost/dev"
582
583 [debug]
584 log_queries = false
585
586 [environments.production]
587 [environments.production.database]
588 url = "postgres://prod.server/db"
589
590 [environments.production.debug]
591 log_queries = true
592 slow_query_threshold = 100
593 "#;
594
595 let config = PraxConfig::from_str(toml)
596 .unwrap()
597 .with_environment("production");
598
599 assert_eq!(
600 config.database.url,
601 Some("postgres://prod.server/db".to_string())
602 );
603 assert!(config.debug.log_queries);
604 assert_eq!(config.debug.slow_query_threshold, 100);
605 }
606
607 #[test]
608 fn test_with_environment_nonexistent() {
609 let config = PraxConfig::default().with_environment("nonexistent");
610 assert_eq!(config.database.provider, DatabaseProvider::PostgreSql);
612 }
613
614 #[test]
615 fn test_parse_invalid_toml() {
616 let toml = "this is not valid [[ toml";
617 let result = PraxConfig::from_str(toml);
618 assert!(result.is_err());
619 }
620
621 #[test]
624 fn test_database_provider_postgresql() {
625 let toml = r#"
626 [database]
627 provider = "postgresql"
628 "#;
629 let config = PraxConfig::from_str(toml).unwrap();
630 assert_eq!(config.database.provider, DatabaseProvider::PostgreSql);
631 assert_eq!(config.database.provider.as_str(), "postgresql");
632 }
633
634 #[test]
635 fn test_database_provider_postgres_alias() {
636 let toml = r#"
637 [database]
638 provider = "postgres"
639 "#;
640 let config = PraxConfig::from_str(toml).unwrap();
641 assert_eq!(config.database.provider, DatabaseProvider::PostgreSql);
642 }
643
644 #[test]
645 fn test_database_provider_mysql() {
646 let toml = r#"
647 [database]
648 provider = "mysql"
649 "#;
650 let config = PraxConfig::from_str(toml).unwrap();
651 assert_eq!(config.database.provider, DatabaseProvider::MySql);
652 assert_eq!(config.database.provider.as_str(), "mysql");
653 }
654
655 #[test]
656 fn test_database_provider_sqlite() {
657 let toml = r#"
658 [database]
659 provider = "sqlite"
660 "#;
661 let config = PraxConfig::from_str(toml).unwrap();
662 assert_eq!(config.database.provider, DatabaseProvider::Sqlite);
663 assert_eq!(config.database.provider.as_str(), "sqlite");
664 }
665
666 #[test]
667 fn test_database_provider_sqlite3_alias() {
668 let toml = r#"
669 [database]
670 provider = "sqlite3"
671 "#;
672 let config = PraxConfig::from_str(toml).unwrap();
673 assert_eq!(config.database.provider, DatabaseProvider::Sqlite);
674 }
675
676 #[test]
677 fn test_database_provider_mongodb() {
678 let toml = r#"
679 [database]
680 provider = "mongodb"
681 "#;
682 let config = PraxConfig::from_str(toml).unwrap();
683 assert_eq!(config.database.provider, DatabaseProvider::MongoDb);
684 assert_eq!(config.database.provider.as_str(), "mongodb");
685 }
686
687 #[test]
688 fn test_database_provider_mongo_alias() {
689 let toml = r#"
690 [database]
691 provider = "mongo"
692 "#;
693 let config = PraxConfig::from_str(toml).unwrap();
694 assert_eq!(config.database.provider, DatabaseProvider::MongoDb);
695 }
696
697 #[test]
700 fn test_pool_config_defaults() {
701 let config = PoolConfig::default();
702 assert_eq!(config.min_connections, 2);
703 assert_eq!(config.max_connections, 10);
704 assert_eq!(config.connect_timeout, "30s");
705 assert_eq!(config.idle_timeout, "10m");
706 assert_eq!(config.max_lifetime, "30m");
707 }
708
709 #[test]
710 fn test_pool_config_custom() {
711 let toml = r#"
712 [database]
713 provider = "postgresql"
714
715 [database.pool]
716 min_connections = 1
717 max_connections = 50
718 connect_timeout = "10s"
719 idle_timeout = "30m"
720 max_lifetime = "2h"
721 "#;
722
723 let config = PraxConfig::from_str(toml).unwrap();
724 assert_eq!(config.database.pool.min_connections, 1);
725 assert_eq!(config.database.pool.max_connections, 50);
726 assert_eq!(config.database.pool.connect_timeout, "10s");
727 }
728
729 #[test]
732 fn test_schema_config_default() {
733 let config = SchemaConfig::default();
734 assert_eq!(config.path, "schema.prax");
735 }
736
737 #[test]
738 fn test_schema_config_custom() {
739 let toml = r#"
740 [schema]
741 path = "db/schema.prax"
742 "#;
743
744 let config = PraxConfig::from_str(toml).unwrap();
745 assert_eq!(config.schema.path, "db/schema.prax");
746 }
747
748 #[test]
751 fn test_generator_config_default() {
752 let config = GeneratorConfig::default();
753 assert_eq!(config.client.output, "./src/generated");
754 assert!(config.client.async_client);
755 assert!(!config.client.tracing);
756 assert!(config.client.preview_features.is_empty());
757 assert_eq!(config.client.model_style, ModelStyle::Standard);
758 }
759
760 #[test]
761 fn test_generator_config_custom() {
762 let toml = r#"
763 [generator.client]
764 output = "./generated"
765 async_client = false
766 tracing = true
767 preview_features = ["feature1", "feature2"]
768 "#;
769
770 let config = PraxConfig::from_str(toml).unwrap();
771 assert_eq!(config.generator.client.output, "./generated");
772 assert!(!config.generator.client.async_client);
773 assert!(config.generator.client.tracing);
774 assert_eq!(config.generator.client.preview_features.len(), 2);
775 }
776
777 #[test]
778 fn test_generator_config_graphql_model_style() {
779 let toml = r#"
780 [generator.client]
781 model_style = "graphql"
782 "#;
783
784 let config = PraxConfig::from_str(toml).unwrap();
785 assert_eq!(config.generator.client.model_style, ModelStyle::GraphQL);
786 assert!(config.generator.client.model_style.is_graphql());
787 }
788
789 #[test]
790 fn test_generator_config_graphql_model_style_alias() {
791 let toml = r#"
792 [generator.client]
793 model_style = "async-graphql"
794 "#;
795
796 let config = PraxConfig::from_str(toml).unwrap();
797 assert_eq!(config.generator.client.model_style, ModelStyle::GraphQL);
798 }
799
800 #[test]
801 fn test_model_style_standard_is_not_graphql() {
802 assert!(!ModelStyle::Standard.is_graphql());
803 assert!(ModelStyle::GraphQL.is_graphql());
804 }
805
806 #[test]
809 fn test_migration_config_default() {
810 let config = MigrationConfig::default();
811 assert_eq!(config.directory, "./migrations");
812 assert!(!config.auto_migrate);
813 assert_eq!(config.table_name, "_prax_migrations");
814 }
815
816 #[test]
817 fn test_migration_config_custom() {
818 let toml = r#"
819 [migrations]
820 directory = "./db/migrate"
821 auto_migrate = true
822 table_name = "schema_migrations"
823 "#;
824
825 let config = PraxConfig::from_str(toml).unwrap();
826 assert_eq!(config.migrations.directory, "./db/migrate");
827 assert!(config.migrations.auto_migrate);
828 assert_eq!(config.migrations.table_name, "schema_migrations");
829 }
830
831 #[test]
834 fn test_seed_config_default() {
835 let config = SeedConfig::default();
836 assert!(config.script.is_none());
837 assert!(!config.auto_seed);
838 assert!(config.environments.is_empty());
839 }
840
841 #[test]
842 fn test_seed_config_custom() {
843 let toml = r#"
844 [seed]
845 script = "seed.rs"
846 auto_seed = true
847
848 [seed.environments]
849 dev = true
850 prod = false
851 "#;
852
853 let config = PraxConfig::from_str(toml).unwrap();
854 assert_eq!(config.seed.script, Some("seed.rs".to_string()));
855 assert!(config.seed.auto_seed);
856 assert_eq!(config.seed.environments.get("dev"), Some(&true));
857 assert_eq!(config.seed.environments.get("prod"), Some(&false));
858 }
859
860 #[test]
863 fn test_debug_config_default() {
864 let config = DebugConfig::default();
865 assert!(!config.log_queries);
866 assert!(config.pretty_sql);
867 assert_eq!(config.slow_query_threshold, 1000);
868 }
869
870 #[test]
871 fn test_debug_config_custom() {
872 let toml = r#"
873 [debug]
874 log_queries = true
875 pretty_sql = false
876 slow_query_threshold = 200
877 "#;
878
879 let config = PraxConfig::from_str(toml).unwrap();
880 assert!(config.debug.log_queries);
881 assert!(!config.debug.pretty_sql);
882 assert_eq!(config.debug.slow_query_threshold, 200);
883 }
884
885 #[test]
888 fn test_env_var_expansion() {
889 unsafe {
891 std::env::set_var("TEST_DB_URL", "postgres://test");
892 }
893 let expanded = expand_env_vars("url = \"${TEST_DB_URL}\"");
894 assert_eq!(expanded, "url = \"postgres://test\"");
895 unsafe {
896 std::env::remove_var("TEST_DB_URL");
897 }
898 }
899
900 #[test]
901 fn test_env_var_expansion_multiple() {
902 unsafe {
903 std::env::set_var("TEST_HOST", "localhost");
904 std::env::set_var("TEST_PORT", "5432");
905 }
906 let content = "host = \"${TEST_HOST}\"\nport = \"${TEST_PORT}\"";
907 let expanded = expand_env_vars(content);
908 assert!(expanded.contains("localhost"));
909 assert!(expanded.contains("5432"));
910 unsafe {
911 std::env::remove_var("TEST_HOST");
912 std::env::remove_var("TEST_PORT");
913 }
914 }
915
916 #[test]
917 fn test_env_var_expansion_missing_var() {
918 let content = "url = \"${DEFINITELY_NOT_SET_VAR_12345}\"";
919 let expanded = expand_env_vars(content);
920 assert_eq!(expanded, content);
922 }
923
924 #[test]
925 fn test_env_var_expansion_in_config() {
926 unsafe {
927 std::env::set_var("TEST_DATABASE_URL_2", "postgres://user:pass@localhost/db");
928 }
929
930 let toml = r#"
931 [database]
932 url = "${TEST_DATABASE_URL_2}"
933 "#;
934
935 let config = PraxConfig::from_str(toml).unwrap();
936 assert_eq!(
937 config.database.url,
938 Some("postgres://user:pass@localhost/db".to_string())
939 );
940
941 unsafe {
942 std::env::remove_var("TEST_DATABASE_URL_2");
943 }
944 }
945
946 #[test]
949 fn test_environment_override_database_url() {
950 let toml = r#"
951 [database]
952 url = "postgres://localhost/dev"
953
954 [environments.test]
955 [environments.test.database]
956 url = "postgres://localhost/test_db"
957 "#;
958
959 let config = PraxConfig::from_str(toml).unwrap().with_environment("test");
960
961 assert_eq!(
962 config.database.url,
963 Some("postgres://localhost/test_db".to_string())
964 );
965 }
966
967 #[test]
968 fn test_environment_override_pool() {
969 let toml = r#"
970 [database.pool]
971 max_connections = 10
972
973 [environments.production]
974 [environments.production.database.pool]
975 max_connections = 100
976 min_connections = 10
977 "#;
978
979 let config = PraxConfig::from_str(toml)
980 .unwrap()
981 .with_environment("production");
982
983 assert_eq!(config.database.pool.max_connections, 100);
984 assert_eq!(config.database.pool.min_connections, 10);
985 }
986
987 #[test]
988 fn test_environment_override_debug() {
989 let toml = r#"
990 [debug]
991 log_queries = false
992 pretty_sql = true
993
994 [environments.development]
995 [environments.development.debug]
996 log_queries = true
997 pretty_sql = false
998 slow_query_threshold = 50
999 "#;
1000
1001 let config = PraxConfig::from_str(toml)
1002 .unwrap()
1003 .with_environment("development");
1004
1005 assert!(config.debug.log_queries);
1006 assert!(!config.debug.pretty_sql);
1007 assert_eq!(config.debug.slow_query_threshold, 50);
1008 }
1009
1010 #[test]
1013 fn test_config_serialization() {
1014 let config = PraxConfig::default();
1015 let toml_str = toml::to_string(&config).unwrap();
1016 assert!(toml_str.contains("[database]"));
1017 }
1018
1019 #[test]
1020 fn test_config_roundtrip() {
1021 let original = PraxConfig {
1022 database: DatabaseConfig {
1023 provider: DatabaseProvider::MySql,
1024 url: Some("mysql://localhost/test".to_string()),
1025 pool: PoolConfig::default(),
1026 },
1027 ..Default::default()
1028 };
1029
1030 let toml_str = toml::to_string(&original).unwrap();
1031 let parsed: PraxConfig = toml::from_str(&toml_str).unwrap();
1032
1033 assert_eq!(parsed.database.provider, original.database.provider);
1034 assert_eq!(parsed.database.url, original.database.url);
1035 }
1036
1037 #[test]
1040 fn test_config_clone() {
1041 let config = PraxConfig::default();
1042 let cloned = config.clone();
1043 assert_eq!(config.database.provider, cloned.database.provider);
1044 }
1045
1046 #[test]
1047 fn test_config_debug() {
1048 let config = PraxConfig::default();
1049 let debug_str = format!("{:?}", config);
1050 assert!(debug_str.contains("PraxConfig"));
1051 }
1052
1053 #[test]
1054 fn test_provider_equality() {
1055 assert_eq!(DatabaseProvider::PostgreSql, DatabaseProvider::PostgreSql);
1056 assert_ne!(DatabaseProvider::PostgreSql, DatabaseProvider::MySql);
1057 }
1058
1059 #[test]
1062 fn test_default_functions() {
1063 assert_eq!(default_provider(), DatabaseProvider::PostgreSql);
1064 assert_eq!(default_min_connections(), 2);
1065 assert_eq!(default_max_connections(), 10);
1066 assert_eq!(default_connect_timeout(), "30s");
1067 assert_eq!(default_idle_timeout(), "10m");
1068 assert_eq!(default_max_lifetime(), "30m");
1069 assert_eq!(default_schema_path(), "schema.prax");
1070 assert_eq!(default_output(), "./src/generated");
1071 assert!(default_true());
1072 assert_eq!(default_migrations_dir(), "./migrations");
1073 assert_eq!(default_migrations_table(), "_prax_migrations");
1074 assert_eq!(default_slow_query_threshold(), 1000);
1075 }
1076}