reinhardt_apps/lib.rs
1//! # Reinhardt Apps
2//!
3//! Django-inspired application configuration and registry system for Reinhardt.
4//!
5//! ## Overview
6//!
7//! This crate provides the infrastructure for managing Django-style applications
8//! in a Reinhardt project. It handles application registration, configuration,
9//! model discovery, and lifecycle management.
10//!
11//! ## Features
12//!
13//! - **[`AppConfig`]**: Application configuration with metadata and settings
14//! - **[`Apps`]**: Central registry for all installed applications
15//! - **[`ApplicationBuilder`]**: Builder pattern for fluent application construction
16//! - **Model Discovery**: Automatic model and migration discovery via [`discovery`] module
17//! - **Signals**: Application lifecycle signals via [`signals`] module
18//! - **Validation**: Registry validation for circular dependencies and duplicates
19//!
20//! ## Modules
21//!
22//! - [`apps`]: Core [`AppConfig`] and [`Apps`] registry
23//! - [`builder`]: [`ApplicationBuilder`] for fluent application construction
24//! - [`discovery`]: Automatic model, migration, and relationship discovery
25//! - [`registry`]: Global model and relationship registry ([`MODELS`], [`RELATIONSHIPS`])
26//! - [`signals`]: Application lifecycle signals
27//! - [`validation`]: Registry validation utilities
28//!
29//! ## Quick Start
30//!
31//! ```rust,ignore
32//! use reinhardt_apps::{ApplicationBuilder, AppConfig};
33//!
34//! // Build an application with multiple apps
35//! let app = ApplicationBuilder::new()
36//! .add_setting("DEBUG", "true")
37//! .add_app(AppConfig::new("users", "myproject.users"))
38//! .add_app(AppConfig::new("blog", "myproject.blog"))
39//! .build()
40//! .expect("Failed to build application");
41//!
42//! // Check if an app is installed
43//! if app.apps_registry().is_installed("users") {
44//! println!("Users app is installed");
45//! }
46//! ```
47//!
48//! ## Model Registry
49//!
50//! Models are automatically discovered and registered in the global registry:
51//!
52//! ```rust,ignore
53//! use reinhardt_apps::{get_registered_models, find_model};
54//!
55//! // Get all registered models
56//! let models = get_registered_models();
57//!
58//! // Find a specific model by name
59//! if let Some(user_model) = find_model("User") {
60//! println!("Found User model in app: {}", user_model.app_label);
61//! }
62//! ```
63//!
64//! ## Application Lifecycle
65//!
66//! 1. **Configuration**: Define [`AppConfig`] for each application
67//! 2. **Registration**: Add apps to [`ApplicationBuilder`]
68//! 3. **Discovery**: Models and migrations are automatically discovered
69//! 4. **Validation**: Registry is validated for consistency
70//! 5. **Ready**: Application signals are fired when setup is complete
71//!
72//! ## Re-exports
73//!
74//! This crate re-exports commonly used types from other Reinhardt crates:
75//!
76//! - From `reinhardt-http`: [`Request`], [`Response`], [`StreamBody`]
77//! - From `reinhardt-conf`: [`Settings`], [`DatabaseConfig`], [`MiddlewareConfig`]
78//! - From `reinhardt-exception`: [`Error`], [`Result`]
79//! - From `reinhardt-server`: [`HttpServer`], [`serve`]
80//! - From `reinhardt-types`: [`Handler`], [`Middleware`], [`MiddlewareChain`]
81
82#![warn(missing_docs)]
83
84pub mod apps;
85pub mod builder;
86pub mod discovery;
87pub mod hooks;
88pub mod registry;
89pub mod signals;
90pub mod validation;
91
92// Re-export from reinhardt-http
93pub use reinhardt_http::{Request, Response, StreamBody, StreamingResponse};
94
95// Re-export from reinhardt-conf
96#[allow(deprecated)]
97pub use reinhardt_conf::settings::{DatabaseConfig, MiddlewareConfig, Settings, TemplateConfig};
98
99// Re-export from reinhardt-exception
100pub use reinhardt_core::exception::{Error, Result};
101
102// Re-export from reinhardt-server
103pub use reinhardt_server::{HttpServer, serve};
104
105// Re-export from reinhardt-types
106pub use reinhardt_http::{Handler, Middleware, MiddlewareChain};
107
108// Re-export inventory for macro usage
109pub use inventory;
110
111// Re-export from apps module
112pub use apps::{
113 AppCommandConfig, AppConfig, AppError, AppLocaleConfig, AppMediaConfig, AppResult,
114 AppStaticFilesConfig, Apps, BaseCommand, LocaleProvider, MediaProvider, StaticFilesProvider,
115 get_app_commands, get_app_locales, get_app_media, get_app_static_files,
116};
117
118// Re-export from builder module
119pub use builder::{
120 Application, ApplicationBuilder, ApplicationDatabaseConfig, BuildError, BuildResult,
121 RouteConfig,
122};
123
124// Re-export from registry module
125pub use registry::{
126 MODELS, ModelMetadata, RELATIONSHIPS, RelationshipMetadata, RelationshipType,
127 ReverseRelationMetadata, ReverseRelationType, finalize_reverse_relations, find_model,
128 get_models_for_app, get_registered_models, get_registered_relationships,
129 get_relationships_for_model, get_relationships_to_model, get_reverse_relations_for_model,
130 register_reverse_relation,
131};
132
133// Re-export from discovery module
134pub use discovery::{
135 MigrationMetadata, RelationMetadata, RelationType, build_reverse_relations,
136 create_reverse_relation, discover_all_models, discover_migrations, discover_models,
137};
138
139// Re-export from validation module
140pub use validation::{
141 ValidationError, ValidationResult, check_circular_relationships, check_duplicate_model_names,
142 check_duplicate_table_names, validate_registry,
143};
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use bytes::Bytes;
149 use hyper::{HeaderMap, Method, Uri, Version};
150
151 #[test]
152 fn test_request_query_params() {
153 let uri = Uri::from_static("/test?foo=bar&baz=qux");
154 let request = Request::builder()
155 .method(Method::GET)
156 .uri(uri)
157 .version(Version::HTTP_11)
158 .headers(HeaderMap::new())
159 .body(Bytes::new())
160 .build()
161 .unwrap();
162
163 assert_eq!(request.query_params.get("foo"), Some(&"bar".to_string()));
164 assert_eq!(request.query_params.get("baz"), Some(&"qux".to_string()));
165 }
166
167 #[test]
168 fn test_response_creation() {
169 let response = Response::ok();
170 assert_eq!(response.status, hyper::StatusCode::OK);
171
172 let response = Response::created();
173 assert_eq!(response.status, hyper::StatusCode::CREATED);
174
175 let response = Response::not_found();
176 assert_eq!(response.status, hyper::StatusCode::NOT_FOUND);
177 }
178
179 #[test]
180 fn test_response_with_json_unit() {
181 use serde_json::json;
182
183 let data = json!({
184 "message": "Hello, world!"
185 });
186
187 let response = Response::ok().with_json(&data).unwrap();
188
189 let body_str = String::from_utf8(response.body.to_vec()).unwrap();
190 let parsed: serde_json::Value = serde_json::from_str(&body_str).unwrap();
191 assert_eq!(parsed["message"], "Hello, world!");
192 assert_eq!(
193 response.headers.get(hyper::header::CONTENT_TYPE).unwrap(),
194 "application/json"
195 );
196 }
197
198 #[test]
199 fn test_error_status_codes() {
200 assert_eq!(Error::NotFound("test".into()).status_code(), 404);
201 assert_eq!(Error::Authentication("test".into()).status_code(), 401);
202 assert_eq!(Error::Authorization("test".into()).status_code(), 403);
203 assert_eq!(Error::Validation("test".into()).status_code(), 400);
204 assert_eq!(Error::Internal("test".into()).status_code(), 500);
205 }
206}