1use crate::apps::{AppConfig, AppError, Apps};
7use std::collections::HashMap;
8use std::sync::Arc;
9use thiserror::Error;
10
11#[derive(Debug, Error)]
13pub enum BuildError {
14 #[error("Application error: {0}")]
16 App(#[from] AppError),
17
18 #[error("Invalid configuration: {0}")]
20 InvalidConfig(String),
21
22 #[error("Missing required configuration: {0}")]
24 MissingConfig(String),
25
26 #[error("Route configuration error: {0}")]
28 RouteError(String),
29
30 #[error("Database configuration error: {0}")]
32 DatabaseError(String),
33}
34
35pub type BuildResult<T> = Result<T, BuildError>;
37
38#[derive(Clone)]
41pub struct RouteConfig {
42 pub path: String,
44 pub handler_name: String,
46 pub name: Option<String>,
48 pub namespace: Option<String>,
50}
51
52impl RouteConfig {
53 pub fn new(path: impl Into<String>, handler_name: impl Into<String>) -> Self {
65 Self {
66 path: path.into(),
67 handler_name: handler_name.into(),
68 name: None,
69 namespace: None,
70 }
71 }
72
73 pub fn with_name(mut self, name: impl Into<String>) -> Self {
85 self.name = Some(name.into());
86 self
87 }
88
89 pub fn with_namespace(mut self, namespace: impl Into<String>) -> Self {
101 self.namespace = Some(namespace.into());
102 self
103 }
104
105 pub fn full_name(&self) -> Option<String> {
125 match (&self.namespace, &self.name) {
126 (Some(ns), Some(name)) => Some(format!("{}:{}", ns, name)),
127 (None, Some(name)) => Some(name.clone()),
128 _ => None,
129 }
130 }
131}
132
133#[derive(Clone, Debug)]
135pub struct ApplicationDatabaseConfig {
136 pub url: String,
138 pub pool_size: Option<u32>,
140 pub max_overflow: Option<u32>,
142 pub timeout: Option<u64>,
144}
145
146impl ApplicationDatabaseConfig {
147 pub fn new(url: impl Into<String>) -> Self {
158 Self {
159 url: url.into(),
160 pool_size: None,
161 max_overflow: None,
162 timeout: None,
163 }
164 }
165
166 pub fn with_pool_size(mut self, size: u32) -> Self {
178 self.pool_size = Some(size);
179 self
180 }
181
182 pub fn with_max_overflow(mut self, overflow: u32) -> Self {
194 self.max_overflow = Some(overflow);
195 self
196 }
197
198 pub fn with_timeout(mut self, timeout: u64) -> Self {
210 self.timeout = Some(timeout);
211 self
212 }
213}
214
215pub struct ApplicationBuilder {
218 apps: Vec<AppConfig>,
219 middleware: Vec<String>,
220 url_patterns: Vec<RouteConfig>,
221 database_config: Option<ApplicationDatabaseConfig>,
222 settings: HashMap<String, String>,
223}
224
225impl ApplicationBuilder {
226 pub fn new() -> Self {
238 Self {
239 apps: Vec::new(),
240 middleware: Vec::new(),
241 url_patterns: Vec::new(),
242 database_config: None,
243 settings: HashMap::new(),
244 }
245 }
246
247 pub fn add_app(mut self, app: AppConfig) -> Self {
262 self.apps.push(app);
263 self
264 }
265
266 pub fn add_apps(mut self, apps: Vec<AppConfig>) -> Self {
284 self.apps.extend(apps);
285 self
286 }
287
288 pub fn add_middleware(mut self, middleware: impl Into<String>) -> Self {
301 self.middleware.push(middleware.into());
302 self
303 }
304
305 pub fn add_middlewares<S: Into<String>>(mut self, middleware: Vec<S>) -> Self {
319 self.middleware
320 .extend(middleware.into_iter().map(|m| m.into()));
321 self
322 }
323
324 pub fn add_url_pattern(mut self, pattern: RouteConfig) -> Self {
338 self.url_patterns.push(pattern);
339 self
340 }
341
342 pub fn add_url_patterns(mut self, patterns: Vec<RouteConfig>) -> Self {
359 self.url_patterns.extend(patterns);
360 self
361 }
362
363 pub fn database(mut self, config: ApplicationDatabaseConfig) -> Self {
377 self.database_config = Some(config);
378 self
379 }
380
381 pub fn add_setting(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
394 self.settings.insert(key.into(), value.into());
395 self
396 }
397
398 pub fn add_settings(mut self, settings: HashMap<String, String>) -> Self {
416 self.settings.extend(settings);
417 self
418 }
419
420 fn validate(&self) -> BuildResult<()> {
422 for app in &self.apps {
424 app.validate_label()?;
425 }
426
427 let mut labels = std::collections::HashSet::new();
429 for app in &self.apps {
430 if !labels.insert(&app.label) {
431 return Err(BuildError::InvalidConfig(format!(
432 "Duplicate app label: {}",
433 app.label
434 )));
435 }
436 }
437
438 let mut route_names = std::collections::HashSet::new();
440 for pattern in &self.url_patterns {
441 if let Some(full_name) = pattern.full_name()
442 && !route_names.insert(full_name.clone())
443 {
444 return Err(BuildError::RouteError(format!(
445 "Duplicate route name: {}",
446 full_name
447 )));
448 }
449 }
450
451 Ok(())
452 }
453
454 pub fn build(self) -> BuildResult<Application> {
472 self.validate()?;
474
475 let installed_apps: Vec<String> = self.apps.iter().map(|app| app.name.clone()).collect();
477 let apps_registry = Apps::new(installed_apps);
478
479 for app in &self.apps {
481 apps_registry.register(app.clone())?;
482 }
483
484 apps_registry.populate()?;
486
487 Ok(Application {
488 apps: self.apps,
489 middleware: self.middleware,
490 url_patterns: self.url_patterns,
491 database_config: self.database_config,
492 settings: self.settings,
493 apps_registry: Arc::new(apps_registry),
494 })
495 }
496
497 #[cfg(feature = "di")]
518 pub fn build_with_di(
519 self,
520 singleton_scope: Arc<reinhardt_di::SingletonScope>,
521 ) -> BuildResult<Arc<Application>> {
522 let app = self.build()?;
523 let app = Arc::new(app);
524
525 singleton_scope.set(app.clone());
527
528 singleton_scope.set(app.apps_registry.clone());
530
531 Ok(app)
532 }
533}
534
535impl Default for ApplicationBuilder {
536 fn default() -> Self {
537 Self::new()
538 }
539}
540
541pub struct Application {
543 apps: Vec<AppConfig>,
544 middleware: Vec<String>,
545 url_patterns: Vec<RouteConfig>,
546 database_config: Option<ApplicationDatabaseConfig>,
547 settings: HashMap<String, String>,
548 apps_registry: Arc<Apps>,
549}
550
551impl Application {
552 pub fn apps(&self) -> &[AppConfig] {
569 &self.apps
570 }
571
572 pub fn middleware(&self) -> &[String] {
588 &self.middleware
589 }
590
591 pub fn url_patterns(&self) -> &[RouteConfig] {
607 &self.url_patterns
608 }
609
610 pub fn database_config(&self) -> Option<&ApplicationDatabaseConfig> {
626 self.database_config.as_ref()
627 }
628
629 pub fn settings(&self) -> &HashMap<String, String> {
643 &self.settings
644 }
645
646 pub fn apps_registry(&self) -> &Apps {
663 &self.apps_registry
664 }
665}
666
667#[cfg(test)]
668mod tests {
669 use super::*;
670 use serial_test::serial;
671
672 #[test]
673 fn test_route_config_creation() {
674 let route = RouteConfig::new("/users/", "UserListHandler")
675 .with_name("user-list")
676 .with_namespace("api");
677
678 assert_eq!(route.path, "/users/");
679 assert_eq!(route.handler_name, "UserListHandler");
680 assert_eq!(route.name, Some("user-list".to_string()));
681 assert_eq!(route.namespace, Some("api".to_string()));
682 }
683
684 #[test]
685 fn test_route_config_full_name() {
686 let route = RouteConfig::new("/users/", "UserListHandler")
687 .with_namespace("api")
688 .with_name("list");
689 assert_eq!(route.full_name(), Some("api:list".to_string()));
690
691 let route = RouteConfig::new("/users/", "UserListHandler").with_name("list");
692 assert_eq!(route.full_name(), Some("list".to_string()));
693
694 let route = RouteConfig::new("/users/", "UserListHandler");
695 assert_eq!(route.full_name(), None);
696 }
697
698 #[test]
699 fn test_database_config_creation() {
700 let db_config = ApplicationDatabaseConfig::new("postgresql://localhost/mydb")
701 .with_pool_size(10)
702 .with_max_overflow(5)
703 .with_timeout(30);
704
705 assert_eq!(db_config.url, "postgresql://localhost/mydb");
706 assert_eq!(db_config.pool_size, Some(10));
707 assert_eq!(db_config.max_overflow, Some(5));
708 assert_eq!(db_config.timeout, Some(30));
709 }
710
711 #[test]
712 #[serial(apps_registry)]
713 fn test_application_builder_basic() {
714 crate::registry::reset_global_registry();
716
717 let app = ApplicationBuilder::new().build().unwrap();
718
719 assert_eq!(app.apps().len(), 0);
720 assert_eq!(app.middleware().len(), 0);
721 assert_eq!(app.url_patterns().len(), 0);
722 assert!(app.database_config().is_none());
723 }
724
725 #[test]
726 #[serial(apps_registry)]
727 fn test_application_builder_with_apps() {
728 crate::registry::reset_global_registry();
730
731 let app_config = AppConfig::new("myapp", "myapp");
732 let app = ApplicationBuilder::new()
733 .add_app(app_config)
734 .build()
735 .unwrap();
736
737 assert_eq!(app.apps().len(), 1);
738 assert_eq!(app.apps()[0].label, "myapp");
739 assert!(app.apps_registry().is_installed("myapp"));
740 }
741
742 #[test]
743 #[serial(apps_registry)]
744 fn test_application_builder_with_multiple_apps() {
745 crate::registry::reset_global_registry();
747
748 let apps = vec![
749 AppConfig::new("app1", "app1"),
750 AppConfig::new("app2", "app2"),
751 ];
752 let app = ApplicationBuilder::new().add_apps(apps).build().unwrap();
753
754 assert_eq!(app.apps().len(), 2);
755 assert!(app.apps_registry().is_installed("app1"));
756 assert!(app.apps_registry().is_installed("app2"));
757 }
758
759 #[test]
760 #[serial(apps_registry)]
761 fn test_application_builder_with_middleware() {
762 crate::registry::reset_global_registry();
764
765 let app = ApplicationBuilder::new()
766 .add_middleware("CorsMiddleware")
767 .add_middleware("AuthMiddleware")
768 .build()
769 .unwrap();
770
771 assert_eq!(app.middleware().len(), 2);
772 assert_eq!(app.middleware()[0], "CorsMiddleware");
773 assert_eq!(app.middleware()[1], "AuthMiddleware");
774 }
775
776 #[test]
777 #[serial(apps_registry)]
778 fn test_application_builder_with_middlewares() {
779 crate::registry::reset_global_registry();
781
782 let middleware = vec!["CorsMiddleware", "AuthMiddleware"];
783 let app = ApplicationBuilder::new()
784 .add_middlewares(middleware)
785 .build()
786 .unwrap();
787
788 assert_eq!(app.middleware().len(), 2);
789 }
790
791 #[test]
792 #[serial(apps_registry)]
793 fn test_application_builder_with_url_patterns() {
794 crate::registry::reset_global_registry();
796
797 let route = RouteConfig::new("/users/", "UserListHandler");
798 let app = ApplicationBuilder::new()
799 .add_url_pattern(route)
800 .build()
801 .unwrap();
802
803 assert_eq!(app.url_patterns().len(), 1);
804 assert_eq!(app.url_patterns()[0].path, "/users/");
805 }
806
807 #[test]
808 #[serial(apps_registry)]
809 fn test_application_builder_with_database() {
810 crate::registry::reset_global_registry();
812
813 let db_config = ApplicationDatabaseConfig::new("postgresql://localhost/mydb");
814 let app = ApplicationBuilder::new()
815 .database(db_config)
816 .build()
817 .unwrap();
818
819 assert!(app.database_config().is_some());
820 assert_eq!(
821 app.database_config().unwrap().url,
822 "postgresql://localhost/mydb"
823 );
824 }
825
826 #[test]
827 #[serial(apps_registry)]
828 fn test_application_builder_with_settings() {
829 crate::registry::reset_global_registry();
831
832 let app = ApplicationBuilder::new()
833 .add_setting("DEBUG", "true")
834 .add_setting("SECRET_KEY", "secret")
835 .build()
836 .unwrap();
837
838 assert_eq!(app.settings().get("DEBUG"), Some(&"true".to_string()));
839 assert_eq!(
840 app.settings().get("SECRET_KEY"),
841 Some(&"secret".to_string())
842 );
843 }
844
845 #[test]
846 #[serial(apps_registry)]
847 fn test_application_builder_validation_duplicate_apps() {
848 crate::registry::reset_global_registry();
850
851 let result = ApplicationBuilder::new()
852 .add_app(AppConfig::new("myapp", "myapp"))
853 .add_app(AppConfig::new("another", "myapp"))
854 .build();
855
856 assert!(result.is_err());
857 match result {
858 Err(BuildError::InvalidConfig(msg)) => {
859 assert_eq!(msg, "Duplicate app label: myapp");
860 }
861 _ => panic!("Expected InvalidConfig error"),
862 }
863 }
864
865 #[test]
866 #[serial(apps_registry)]
867 fn test_application_builder_validation_duplicate_routes() {
868 crate::registry::reset_global_registry();
870
871 let result = ApplicationBuilder::new()
872 .add_url_pattern(RouteConfig::new("/users/", "Handler1").with_name("users"))
873 .add_url_pattern(RouteConfig::new("/posts/", "Handler2").with_name("users"))
874 .build();
875
876 assert!(result.is_err());
877 match result {
878 Err(BuildError::RouteError(msg)) => {
879 assert_eq!(msg, "Duplicate route name: users");
880 }
881 _ => panic!("Expected RouteError"),
882 }
883 }
884
885 #[test]
886 #[serial(apps_registry)]
887 fn test_application_builder_method_chaining() {
888 crate::registry::reset_global_registry();
890
891 let app = ApplicationBuilder::new()
892 .add_app(AppConfig::new("app1", "app1"))
893 .add_middleware("CorsMiddleware")
894 .add_url_pattern(RouteConfig::new("/api/", "ApiHandler"))
895 .database(ApplicationDatabaseConfig::new("postgresql://localhost/db"))
896 .add_setting("DEBUG", "true")
897 .build()
898 .unwrap();
899
900 assert_eq!(app.apps().len(), 1);
901 assert_eq!(app.middleware().len(), 1);
902 assert_eq!(app.url_patterns().len(), 1);
903 assert!(app.database_config().is_some());
904 assert_eq!(app.settings().get("DEBUG"), Some(&"true".to_string()));
905 }
906
907 #[test]
908 #[serial(apps_registry)]
909 fn test_application_builder_apps_registry_ready() {
910 crate::registry::reset_global_registry();
912
913 let app = ApplicationBuilder::new()
914 .add_app(AppConfig::new("myapp", "myapp"))
915 .build()
916 .unwrap();
917
918 assert!(app.apps_registry().is_ready());
919 assert!(app.apps_registry().is_apps_ready());
920 assert!(app.apps_registry().is_models_ready());
921 }
922
923 #[test]
924 #[serial(apps_registry)]
925 fn test_application_builder_invalid_app_label() {
926 crate::registry::reset_global_registry();
928
929 let result = ApplicationBuilder::new()
930 .add_app(AppConfig::new("myapp", "my-app"))
931 .build();
932
933 assert!(result.is_err());
934 match result {
935 Err(BuildError::App(AppError::InvalidLabel(_))) => {}
936 _ => panic!("Expected InvalidLabel error"),
937 }
938 }
939
940 #[test]
941 fn test_route_config_without_name() {
942 let route = RouteConfig::new("/api/v1/users/", "UserHandler");
943 assert_eq!(route.full_name(), None);
944 }
945
946 #[test]
947 fn test_database_config_minimal() {
948 let db_config = ApplicationDatabaseConfig::new("sqlite::memory:");
949 assert_eq!(db_config.url, "sqlite::memory:");
950 assert_eq!(db_config.pool_size, None);
951 assert_eq!(db_config.max_overflow, None);
952 assert_eq!(db_config.timeout, None);
953 }
954
955 #[test]
956 #[serial(apps_registry)]
957 fn test_application_builder_empty_settings() {
958 crate::registry::reset_global_registry();
960
961 let app = ApplicationBuilder::new().build().unwrap();
962 assert!(app.settings().is_empty());
963 }
964}