Skip to main content

reinhardt_apps/
builder.rs

1//! Application builder module
2//!
3//! Provides a builder pattern for configuring and constructing Reinhardt applications.
4//! Inspired by Django's application configuration system.
5
6use crate::apps::{AppConfig, AppError, Apps};
7use std::collections::HashMap;
8use std::sync::Arc;
9use thiserror::Error;
10
11/// Errors that can occur when building an application
12#[derive(Debug, Error)]
13pub enum BuildError {
14	/// An error originating from the application registry.
15	#[error("Application error: {0}")]
16	App(#[from] AppError),
17
18	/// The provided configuration value is invalid.
19	#[error("Invalid configuration: {0}")]
20	InvalidConfig(String),
21
22	/// A required configuration value was not provided.
23	#[error("Missing required configuration: {0}")]
24	MissingConfig(String),
25
26	/// An error in route configuration.
27	#[error("Route configuration error: {0}")]
28	RouteError(String),
29
30	/// An error in database configuration.
31	#[error("Database configuration error: {0}")]
32	DatabaseError(String),
33}
34
35/// A specialized `Result` type for application build operations.
36pub type BuildResult<T> = Result<T, BuildError>;
37
38/// Route definition for the application
39/// Lightweight wrapper around path patterns
40#[derive(Clone)]
41pub struct RouteConfig {
42	/// URL path pattern for this route.
43	pub path: String,
44	/// Name of the handler associated with this route.
45	pub handler_name: String,
46	/// Optional name for reverse URL lookup.
47	pub name: Option<String>,
48	/// Optional namespace for grouping routes.
49	pub namespace: Option<String>,
50}
51
52impl RouteConfig {
53	/// Create a new route configuration
54	///
55	/// # Examples
56	///
57	/// ```
58	/// use reinhardt_apps::builder::RouteConfig;
59	///
60	/// let route = RouteConfig::new("/users/", "UserListHandler");
61	/// assert_eq!(route.path, "/users/");
62	/// assert_eq!(route.handler_name, "UserListHandler");
63	/// ```
64	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	/// Set the route name
74	///
75	/// # Examples
76	///
77	/// ```
78	/// use reinhardt_apps::builder::RouteConfig;
79	///
80	/// let route = RouteConfig::new("/users/", "UserListHandler")
81	///     .with_name("user-list");
82	/// assert_eq!(route.name, Some("user-list".to_string()));
83	/// ```
84	pub fn with_name(mut self, name: impl Into<String>) -> Self {
85		self.name = Some(name.into());
86		self
87	}
88
89	/// Set the route namespace
90	///
91	/// # Examples
92	///
93	/// ```
94	/// use reinhardt_apps::builder::RouteConfig;
95	///
96	/// let route = RouteConfig::new("/users/", "UserListHandler")
97	///     .with_namespace("api");
98	/// assert_eq!(route.namespace, Some("api".to_string()));
99	/// ```
100	pub fn with_namespace(mut self, namespace: impl Into<String>) -> Self {
101		self.namespace = Some(namespace.into());
102		self
103	}
104
105	/// Get the full name including namespace
106	///
107	/// # Examples
108	///
109	/// ```
110	/// use reinhardt_apps::builder::RouteConfig;
111	///
112	/// let route = RouteConfig::new("/users/", "UserListHandler")
113	///     .with_namespace("api")
114	///     .with_name("list");
115	/// assert_eq!(route.full_name(), Some("api:list".to_string()));
116	///
117	/// let route = RouteConfig::new("/users/", "UserListHandler")
118	///     .with_name("list");
119	/// assert_eq!(route.full_name(), Some("list".to_string()));
120	///
121	/// let route = RouteConfig::new("/users/", "UserListHandler");
122	/// assert_eq!(route.full_name(), None);
123	/// ```
124	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/// Database configuration for the application
134#[derive(Clone, Debug)]
135pub struct ApplicationDatabaseConfig {
136	/// Database connection URL.
137	pub url: String,
138	/// Maximum number of connections in the pool.
139	pub pool_size: Option<u32>,
140	/// Maximum number of connections that can exceed `pool_size`.
141	pub max_overflow: Option<u32>,
142	/// Connection timeout in seconds.
143	pub timeout: Option<u64>,
144}
145
146impl ApplicationDatabaseConfig {
147	/// Create a new database configuration
148	///
149	/// # Examples
150	///
151	/// ```
152	/// use reinhardt_apps::builder::ApplicationDatabaseConfig;
153	///
154	/// let db_config = ApplicationDatabaseConfig::new("postgresql://localhost/mydb");
155	/// assert_eq!(db_config.url, "postgresql://localhost/mydb");
156	/// ```
157	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	/// Set the connection pool size
167	///
168	/// # Examples
169	///
170	/// ```
171	/// use reinhardt_apps::builder::ApplicationDatabaseConfig;
172	///
173	/// let db_config = ApplicationDatabaseConfig::new("postgresql://localhost/mydb")
174	///     .with_pool_size(10);
175	/// assert_eq!(db_config.pool_size, Some(10));
176	/// ```
177	pub fn with_pool_size(mut self, size: u32) -> Self {
178		self.pool_size = Some(size);
179		self
180	}
181
182	/// Set the maximum overflow connections
183	///
184	/// # Examples
185	///
186	/// ```
187	/// use reinhardt_apps::builder::ApplicationDatabaseConfig;
188	///
189	/// let db_config = ApplicationDatabaseConfig::new("postgresql://localhost/mydb")
190	///     .with_max_overflow(5);
191	/// assert_eq!(db_config.max_overflow, Some(5));
192	/// ```
193	pub fn with_max_overflow(mut self, overflow: u32) -> Self {
194		self.max_overflow = Some(overflow);
195		self
196	}
197
198	/// Set the connection timeout
199	///
200	/// # Examples
201	///
202	/// ```
203	/// use reinhardt_apps::builder::ApplicationDatabaseConfig;
204	///
205	/// let db_config = ApplicationDatabaseConfig::new("postgresql://localhost/mydb")
206	///     .with_timeout(30);
207	/// assert_eq!(db_config.timeout, Some(30));
208	/// ```
209	pub fn with_timeout(mut self, timeout: u64) -> Self {
210		self.timeout = Some(timeout);
211		self
212	}
213}
214
215/// Builder for constructing Reinhardt applications
216/// Inspired by Django's application configuration system
217pub 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	/// Create a new application builder
227	///
228	/// # Examples
229	///
230	/// ```
231	/// use reinhardt_apps::builder::ApplicationBuilder;
232	///
233	/// let builder = ApplicationBuilder::new();
234	/// let app = builder.build().unwrap();
235	/// assert_eq!(app.apps().len(), 0);
236	/// ```
237	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	/// Add an application configuration
248	///
249	/// # Examples
250	///
251	/// ```
252	/// use reinhardt_apps::builder::ApplicationBuilder;
253	/// use reinhardt_apps::AppConfig;
254	///
255	/// let app_config = AppConfig::new("myapp", "myapp");
256	/// let builder = ApplicationBuilder::new()
257	///     .add_app(app_config);
258	/// let app = builder.build().unwrap();
259	/// assert_eq!(app.apps().len(), 1);
260	/// ```
261	pub fn add_app(mut self, app: AppConfig) -> Self {
262		self.apps.push(app);
263		self
264	}
265
266	/// Add multiple application configurations
267	///
268	/// # Examples
269	///
270	/// ```
271	/// use reinhardt_apps::builder::ApplicationBuilder;
272	/// use reinhardt_apps::AppConfig;
273	///
274	/// let apps = vec![
275	///     AppConfig::new("app1", "app1"),
276	///     AppConfig::new("app2", "app2"),
277	/// ];
278	/// let builder = ApplicationBuilder::new()
279	///     .add_apps(apps);
280	/// let app = builder.build().unwrap();
281	/// assert_eq!(app.apps().len(), 2);
282	/// ```
283	pub fn add_apps(mut self, apps: Vec<AppConfig>) -> Self {
284		self.apps.extend(apps);
285		self
286	}
287
288	/// Add a middleware
289	///
290	/// # Examples
291	///
292	/// ```
293	/// use reinhardt_apps::builder::ApplicationBuilder;
294	///
295	/// let builder = ApplicationBuilder::new()
296	///     .add_middleware("CorsMiddleware");
297	/// let app = builder.build().unwrap();
298	/// assert_eq!(app.middleware().len(), 1);
299	/// ```
300	pub fn add_middleware(mut self, middleware: impl Into<String>) -> Self {
301		self.middleware.push(middleware.into());
302		self
303	}
304
305	/// Add multiple middleware
306	///
307	/// # Examples
308	///
309	/// ```
310	/// use reinhardt_apps::builder::ApplicationBuilder;
311	///
312	/// let middleware = vec!["CorsMiddleware", "AuthMiddleware"];
313	/// let builder = ApplicationBuilder::new()
314	///     .add_middlewares(middleware);
315	/// let app = builder.build().unwrap();
316	/// assert_eq!(app.middleware().len(), 2);
317	/// ```
318	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	/// Add a URL pattern
325	///
326	/// # Examples
327	///
328	/// ```
329	/// use reinhardt_apps::builder::{ApplicationBuilder, RouteConfig};
330	///
331	/// let route = RouteConfig::new("/users/", "UserListHandler");
332	/// let builder = ApplicationBuilder::new()
333	///     .add_url_pattern(route);
334	/// let app = builder.build().unwrap();
335	/// assert_eq!(app.url_patterns().len(), 1);
336	/// ```
337	pub fn add_url_pattern(mut self, pattern: RouteConfig) -> Self {
338		self.url_patterns.push(pattern);
339		self
340	}
341
342	/// Add multiple URL patterns
343	///
344	/// # Examples
345	///
346	/// ```
347	/// use reinhardt_apps::builder::{ApplicationBuilder, RouteConfig};
348	///
349	/// let patterns = vec![
350	///     RouteConfig::new("/users/", "UserListHandler"),
351	///     RouteConfig::new("/posts/", "PostListHandler"),
352	/// ];
353	/// let builder = ApplicationBuilder::new()
354	///     .add_url_patterns(patterns);
355	/// let app = builder.build().unwrap();
356	/// assert_eq!(app.url_patterns().len(), 2);
357	/// ```
358	pub fn add_url_patterns(mut self, patterns: Vec<RouteConfig>) -> Self {
359		self.url_patterns.extend(patterns);
360		self
361	}
362
363	/// Set the database configuration
364	///
365	/// # Examples
366	///
367	/// ```
368	/// use reinhardt_apps::builder::{ApplicationBuilder, ApplicationDatabaseConfig};
369	///
370	/// let db_config = ApplicationDatabaseConfig::new("postgresql://localhost/mydb");
371	/// let builder = ApplicationBuilder::new()
372	///     .database(db_config);
373	/// let app = builder.build().unwrap();
374	/// assert!(app.database_config().is_some());
375	/// ```
376	pub fn database(mut self, config: ApplicationDatabaseConfig) -> Self {
377		self.database_config = Some(config);
378		self
379	}
380
381	/// Add a custom setting
382	///
383	/// # Examples
384	///
385	/// ```
386	/// use reinhardt_apps::builder::ApplicationBuilder;
387	///
388	/// let builder = ApplicationBuilder::new()
389	///     .add_setting("DEBUG", "true");
390	/// let app = builder.build().unwrap();
391	/// assert_eq!(app.settings().get("DEBUG"), Some(&"true".to_string()));
392	/// ```
393	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	/// Add multiple custom settings
399	///
400	/// # Examples
401	///
402	/// ```
403	/// use reinhardt_apps::builder::ApplicationBuilder;
404	/// use std::collections::HashMap;
405	///
406	/// let mut settings = HashMap::new();
407	/// settings.insert("DEBUG".to_string(), "true".to_string());
408	/// settings.insert("SECRET_KEY".to_string(), "secret".to_string());
409	///
410	/// let builder = ApplicationBuilder::new()
411	///     .add_settings(settings);
412	/// let app = builder.build().unwrap();
413	/// assert_eq!(app.settings().get("DEBUG"), Some(&"true".to_string()));
414	/// ```
415	pub fn add_settings(mut self, settings: HashMap<String, String>) -> Self {
416		self.settings.extend(settings);
417		self
418	}
419
420	/// Validate the configuration
421	fn validate(&self) -> BuildResult<()> {
422		// Validate all app configurations
423		for app in &self.apps {
424			app.validate_label()?;
425		}
426
427		// Check for duplicate app labels
428		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		// Check for duplicate route names
439		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	/// Build the application
455	///
456	/// # Examples
457	///
458	/// ```
459	/// use reinhardt_apps::builder::ApplicationBuilder;
460	/// use reinhardt_apps::AppConfig;
461	///
462	/// let app_config = AppConfig::new("myapp", "myapp");
463	/// let builder = ApplicationBuilder::new()
464	///     .add_app(app_config)
465	///     .add_middleware("CorsMiddleware");
466	///
467	/// let app = builder.build().unwrap();
468	/// assert_eq!(app.apps().len(), 1);
469	/// assert_eq!(app.middleware().len(), 1);
470	/// ```
471	pub fn build(self) -> BuildResult<Application> {
472		// Validate configuration
473		self.validate()?;
474
475		// Create the Apps registry
476		let installed_apps: Vec<String> = self.apps.iter().map(|app| app.name.clone()).collect();
477		let apps_registry = Apps::new(installed_apps);
478
479		// Register all app configurations
480		for app in &self.apps {
481			apps_registry.register(app.clone())?;
482		}
483
484		// Populate the registry
485		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	/// Build the application and register it with the DI system
498	///
499	/// This method builds the application and registers both the `Application`
500	/// and `Apps` instances in the provided `SingletonScope`.
501	///
502	/// # Examples
503	///
504	/// ```ignore
505	/// use reinhardt_apps::builder::ApplicationBuilder;
506	/// use reinhardt_apps::AppConfig;
507	/// use reinhardt_di::SingletonScope;
508	/// use std::sync::Arc;
509	///
510	/// let singleton = Arc::new(SingletonScope::new());
511	/// let app_config = AppConfig::new("myapp", "myapp");
512	/// let app = ApplicationBuilder::new()
513	///     .add_app(app_config)
514	///     .build_with_di(singleton.clone())
515	///     .unwrap();
516	/// ```
517	#[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		// Register Application in SingletonScope
526		singleton_scope.set(app.clone());
527
528		// Register Apps in SingletonScope
529		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
541/// The built application
542pub 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	/// Get the registered applications
553	///
554	/// # Examples
555	///
556	/// ```
557	/// use reinhardt_apps::builder::ApplicationBuilder;
558	/// use reinhardt_apps::AppConfig;
559	///
560	/// let app_config = AppConfig::new("myapp", "myapp");
561	/// let builder = ApplicationBuilder::new()
562	///     .add_app(app_config);
563	/// let app = builder.build().unwrap();
564	///
565	/// assert_eq!(app.apps().len(), 1);
566	/// assert_eq!(app.apps()[0].label, "myapp");
567	/// ```
568	pub fn apps(&self) -> &[AppConfig] {
569		&self.apps
570	}
571
572	/// Get the middleware stack
573	///
574	/// # Examples
575	///
576	/// ```
577	/// use reinhardt_apps::builder::ApplicationBuilder;
578	///
579	/// let builder = ApplicationBuilder::new()
580	///     .add_middleware("CorsMiddleware")
581	///     .add_middleware("AuthMiddleware");
582	/// let app = builder.build().unwrap();
583	///
584	/// assert_eq!(app.middleware().len(), 2);
585	/// assert_eq!(app.middleware()[0], "CorsMiddleware");
586	/// ```
587	pub fn middleware(&self) -> &[String] {
588		&self.middleware
589	}
590
591	/// Get the URL patterns
592	///
593	/// # Examples
594	///
595	/// ```
596	/// use reinhardt_apps::builder::{ApplicationBuilder, RouteConfig};
597	///
598	/// let route = RouteConfig::new("/users/", "UserListHandler");
599	/// let builder = ApplicationBuilder::new()
600	///     .add_url_pattern(route);
601	/// let app = builder.build().unwrap();
602	///
603	/// assert_eq!(app.url_patterns().len(), 1);
604	/// assert_eq!(app.url_patterns()[0].path, "/users/");
605	/// ```
606	pub fn url_patterns(&self) -> &[RouteConfig] {
607		&self.url_patterns
608	}
609
610	/// Get the database configuration
611	///
612	/// # Examples
613	///
614	/// ```
615	/// use reinhardt_apps::builder::{ApplicationBuilder, ApplicationDatabaseConfig};
616	///
617	/// let db_config = ApplicationDatabaseConfig::new("postgresql://localhost/mydb");
618	/// let builder = ApplicationBuilder::new()
619	///     .database(db_config);
620	/// let app = builder.build().unwrap();
621	///
622	/// assert!(app.database_config().is_some());
623	/// assert_eq!(app.database_config().unwrap().url, "postgresql://localhost/mydb");
624	/// ```
625	pub fn database_config(&self) -> Option<&ApplicationDatabaseConfig> {
626		self.database_config.as_ref()
627	}
628
629	/// Get the custom settings
630	///
631	/// # Examples
632	///
633	/// ```
634	/// use reinhardt_apps::builder::ApplicationBuilder;
635	///
636	/// let builder = ApplicationBuilder::new()
637	///     .add_setting("DEBUG", "true");
638	/// let app = builder.build().unwrap();
639	///
640	/// assert_eq!(app.settings().get("DEBUG"), Some(&"true".to_string()));
641	/// ```
642	pub fn settings(&self) -> &HashMap<String, String> {
643		&self.settings
644	}
645
646	/// Get the apps registry
647	///
648	/// # Examples
649	///
650	/// ```
651	/// use reinhardt_apps::builder::ApplicationBuilder;
652	/// use reinhardt_apps::AppConfig;
653	///
654	/// let app_config = AppConfig::new("myapp", "myapp");
655	/// let builder = ApplicationBuilder::new()
656	///     .add_app(app_config);
657	/// let app = builder.build().unwrap();
658	///
659	/// assert!(app.apps_registry().is_ready());
660	/// assert!(app.apps_registry().is_installed("myapp"));
661	/// ```
662	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		// Arrange - Reset global state before test
715		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		// Arrange - Reset global state before test
729		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		// Arrange - Reset global state before test
746		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		// Arrange - Reset global state before test
763		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		// Arrange - Reset global state before test
780		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		// Arrange - Reset global state before test
795		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		// Arrange - Reset global state before test
811		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		// Arrange - Reset global state before test
830		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		// Arrange - Reset global state before test
849		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		// Arrange - Reset global state before test
869		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		// Arrange - Reset global state before test
889		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		// Arrange - Reset global state before test
911		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		// Arrange - Reset global state before test
927		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		// Arrange - Reset global state before test
959		crate::registry::reset_global_registry();
960
961		let app = ApplicationBuilder::new().build().unwrap();
962		assert!(app.settings().is_empty());
963	}
964}