1use std::path::PathBuf;
10use std::sync::Arc;
11
12use rainier_cache::CacheManager;
13use rainier_config::{Config, Env};
14use rainier_container::{Application, ServiceProvider};
15use rainier_crypt::{Encryption, Key, KeyRing};
16use rainier_database::Database;
17use rainier_events::Dispatcher;
18use rainier_filesystem::Storage;
19use rainier_mail::Mailer;
20use rainier_middleware::MiddlewareRegistry;
21use rainier_queue::QueueManager;
22use rainier_routing::{Router, UrlGenerator};
23use rainier_server::Kernel;
24use rainier_session::{MemorySessionStore, SessionManager};
25use rainier_support::{Error, Result};
26use rainier_view::{TemplateEngine, ViewEngine};
27
28use crate::facades::Views;
29use crate::keys;
30
31pub struct Rainier {
47 base_path: PathBuf,
48 env: Env,
49 config: Config,
50 deferred: Option<Error>,
57 events: Dispatcher,
58 middleware: MiddlewareRegistry,
59 router: Router,
60 views: Option<Arc<dyn ViewEngine>>,
61 database: Option<Database>,
62 queue: Option<QueueManager>,
63 mailer: Option<Mailer>,
64 sessions: Option<SessionManager>,
65 crypt: Option<Encryption>,
66 cache: Option<CacheManager>,
67 notifier: Option<rainier_notify::Notifier>,
68 broadcasting: Option<rainier_broadcast::Broadcasting>,
69 websockets: Option<rainier_websocket::WebSocketRoutes>,
70 #[allow(clippy::type_complexity, reason = "one boxed closure per bound value")]
72 instances: Vec<Box<dyn Fn(&Application)>>,
73 schedule: Option<rainier_scheduler::Schedule>,
74 storage: Option<Storage>,
75 providers: Vec<Arc<dyn ServiceProvider>>,
76 install_facades: bool,
77 install_tracing: bool,
78}
79
80impl Rainier {
81 pub fn new(base_path: impl Into<PathBuf>) -> Self {
88 let base_path = base_path.into();
89 let env = Env::load_or_default(base_path.join(".env"));
90 let (config, deferred) = match default_config(&env, &base_path) {
91 Ok(config) => (config, None),
92 Err(e) => (Config::new(), Some(e)),
95 };
96
97 Self {
98 base_path,
99 env,
100 config,
101 deferred,
102 events: Dispatcher::new(),
103 middleware: default_middleware(),
104 router: Router::new(),
105 views: None,
106 database: None,
107 queue: None,
108 mailer: None,
109 sessions: None,
110 crypt: None,
111 cache: None,
112 notifier: None,
113 broadcasting: None,
114 websockets: None,
115 instances: Vec::new(),
116 schedule: None,
117 storage: None,
118 providers: Vec::new(),
119 install_facades: true,
120 install_tracing: true,
121 }
122 }
123
124 pub fn env(&self) -> &Env {
126 &self.env
127 }
128
129 pub fn config(&self) -> &Config {
131 &self.config
132 }
133
134 pub fn configure(self, adjust: impl FnOnce(&Config)) -> Self {
136 adjust(&self.config);
137 self
138 }
139
140 pub fn with_events(self, register: impl FnOnce(&Dispatcher)) -> Self {
142 register(&self.events);
143 self
144 }
145
146 pub fn with_middleware(self, register: impl FnOnce(&MiddlewareRegistry)) -> Self {
148 register(&self.middleware);
149 self
150 }
151
152 pub fn with_routes(mut self, declare: impl FnOnce(&mut Router)) -> Self {
154 declare(&mut self.router);
155 self
156 }
157
158 pub fn with_views(mut self, engine: Arc<dyn ViewEngine>) -> Self {
161 self.views = Some(engine);
162 self
163 }
164
165 pub fn with_database(mut self, database: Database) -> Self {
167 self.database = Some(database);
168 self
169 }
170
171 pub fn with_queue(mut self, queue: QueueManager) -> Self {
173 self.queue = Some(queue);
174 self
175 }
176
177 pub fn with_mailer(mut self, mailer: Mailer) -> Self {
179 self.mailer = Some(mailer);
180 self
181 }
182
183 pub fn with_sessions(mut self, sessions: SessionManager) -> Self {
189 self.sessions = Some(sessions);
190 self
191 }
192
193 pub fn with_cache(mut self, cache: CacheManager) -> Self {
198 self.cache = Some(cache);
199 self
200 }
201
202 pub fn with_notifier(mut self, notifier: rainier_notify::Notifier) -> Self {
216 self.notifier = Some(notifier);
217 self
218 }
219
220 pub fn with_broadcasting(mut self, broadcasting: rainier_broadcast::Broadcasting) -> Self {
236 self.broadcasting = Some(broadcasting);
237 self
238 }
239
240 pub fn with_websockets(mut self, routes: rainier_websocket::WebSocketRoutes) -> Self {
252 self.websockets = Some(routes);
253 self
254 }
255
256 pub fn with_instance<T: Send + Sync + 'static>(mut self, value: T) -> Self {
267 let value = Arc::new(value);
268 self.instances.push(Box::new(move |app| app.instance_arc(Arc::clone(&value))));
269 self
270 }
271
272 pub fn with_instance_arc<T: Send + Sync + 'static>(mut self, value: Arc<T>) -> Self {
274 self.instances.push(Box::new(move |app| app.instance_arc(Arc::clone(&value))));
275 self
276 }
277
278 pub fn with_schedule(mut self, declare: impl FnOnce(&mut rainier_scheduler::Schedule)) -> Self {
286 let mut schedule = self.schedule.take().unwrap_or_default();
287 declare(&mut schedule);
288 self.schedule = Some(schedule);
289 self
290 }
291
292 pub fn with_storage(mut self, storage: Storage) -> Self {
297 self.storage = Some(storage);
298 self
299 }
300
301 pub fn with_encryption(mut self, crypt: Encryption) -> Self {
307 self.crypt = Some(crypt);
308 self
309 }
310
311 pub fn with_provider(mut self, provider: impl ServiceProvider) -> Self {
313 self.providers.push(Arc::new(provider));
314 self
315 }
316
317 pub fn without_facades(mut self) -> Self {
322 self.install_facades = false;
323 self
324 }
325
326 pub fn without_tracing(mut self) -> Self {
328 self.install_tracing = false;
329 self
330 }
331
332 pub async fn boot(self) -> Result<Arc<Application>> {
338 if let Some(error) = self.deferred {
341 return Err(error);
342 }
343
344 if self.install_tracing {
345 install_tracing(&self.env, &self.config);
346 }
347
348 let environment = self.config.get(keys::APP_ENV).unwrap_or_default().to_string();
349 let app = Arc::new(Application::new(self.base_path.clone()).with_environment(&environment));
350
351 if self.install_facades {
356 rainier_container::set_facade_application(Arc::clone(&app));
357 }
358
359 let views = self.views.unwrap_or_else(|| {
360 Arc::new(TemplateEngine::new(self.base_path.join("resources").join("views")))
361 });
362
363 let crypt = self.crypt.unwrap_or_else(|| {
364 let keys = app_keys(&self.env);
365
366 match self.config.setting(keys::APP_CIPHER).unwrap_or_default() {
367 rainier_crypt::CryptScheme::Native => Encryption::from_keys(keys),
368 rainier_crypt::CryptScheme::Php => Encryption::new(
372 Arc::new(rainier_crypt::PhpEncrypter::new(keys.clone())),
373 Arc::new(rainier_crypt::HmacSigner::new(keys)),
374 ),
375 }
376 });
377
378 let url_signer = rainier_crypt::UrlSigner::new(
388 crypt.keys().cloned().unwrap_or_else(|| app_keys(&self.env)),
389 );
390 let sessions = self
391 .sessions
392 .unwrap_or_else(|| SessionManager::new(Arc::new(MemorySessionStore::default())));
393
394 app.instance(self.config);
395 app.instance(self.env);
396 app.instance(self.events);
397 app.instance(Views::new(views));
398 app.instance(crypt);
399 app.instance(url_signer);
400 app.instance(sessions);
401 let cache = match self.cache {
406 Some(cache) => cache,
407 None => {
408 if let Ok(driver) = app.resolve::<Config>()?.setting(keys::CACHE_DRIVER) {
409 if driver.is_shared() {
410 tracing::warn!(
411 configured = %driver,
412 "CACHE_DRIVER asks for a shared cache but none was built, so \
413 this process is using the in-process one — locks, rate limits \
414 and anything else cached for correctness are per-instance. \
415 Build the store and pass it to `Rainier::with_cache`."
416 );
417 }
418 }
419 CacheManager::memory()
420 }
421 };
422
423 app.instance(rainier_cache::LockManager::new(Arc::clone(cache.store())));
427
428 app.instance(crate::limits::RateLimits::over_cache(Arc::clone(cache.store())));
431
432 app.instance(rainier_auth::Challenges::new(Arc::clone(cache.store())));
436 app.instance(cache);
437
438 app.instance(self.schedule.unwrap_or_default());
441
442 for bind in self.instances {
445 bind(&app);
446 }
447
448 app.instance(self.websockets.unwrap_or_default());
451
452 app.instance(self.broadcasting.unwrap_or_else(rainier_broadcast::Broadcasting::log));
456
457 app.instance(
460 self.notifier.unwrap_or_else(|| {
461 rainier_notify::Notifier::new().with(rainier_notify::LogChannel)
462 }),
463 );
464 app.instance(
465 self.storage
466 .unwrap_or_else(|| Storage::local(self.base_path.join("storage").join("app"))),
467 );
468
469 if let Some(database) = self.database {
470 app.instance(database);
471 }
472 if let Some(queue) = self.queue {
473 app.instance(queue.with_locks(rainier_cache::LockManager::new(Arc::clone(
478 app.resolve::<CacheManager>()?.store(),
479 ))));
480 }
481 if let Some(mailer) = self.mailer {
482 app.instance(mailer);
483 }
484
485 for provider in self.providers {
488 app.register_arc(provider)?;
489 }
490
491 let compiled = Arc::new(self.router.compile(&app)?);
499 let urls = Arc::new(
500 UrlGenerator::from_routes(compiled.named_routes())
501 .with_base(app.resolve::<Config>()?.get_or(keys::APP_URL, String::new())),
502 );
503
504 app.instance(crate::signed::SignedUrls::new(
508 Arc::clone(&urls),
509 app.resolve::<rainier_crypt::UrlSigner>()?,
510 ));
511
512 let mut global = self.middleware.global_stack(&app)?;
513 let settings = app.resolve::<Config>()?;
514
515 if settings.get_or(keys::SERVER_COMPRESSION, false) {
518 global.insert(0, Arc::new(rainier_middleware::Compress::new()));
519 }
520
521 let timeout_secs = settings.get_or(keys::SERVER_REQUEST_TIMEOUT_SECS, 0u64);
524 if timeout_secs > 0 {
525 global.insert(0, Arc::new(rainier_middleware::Timeout::seconds(timeout_secs)));
526 }
527
528 let kernel = Kernel::from_shared(Arc::clone(&compiled), global)
529 .with_debug(app.resolve::<Config>()?.get_or(keys::APP_DEBUG, false));
530
531 app.instance_arc(Arc::clone(&compiled));
532 app.instance_arc(urls);
533 app.instance(self.middleware);
534 app.instance(kernel);
535
536 app.boot().await?;
537
538 crate::scheduling::warn_if_locks_are_not_shared(&app);
545
546 crate::limits::warn_if_rate_limits_are_not_shared(&app, &compiled);
550
551 Ok(app)
552 }
553}
554
555fn default_config(env: &Env, base_path: &std::path::Path) -> Result<Config> {
564 let config = Config::new();
565
566 config.set(keys::APP_NAME, env.string("APP_NAME", "Rainier"))?;
567 config.set(keys::APP_ENV, env.setting("APP_ENV")?)?;
568 config.set(keys::APP_DEBUG, env.bool("APP_DEBUG", false))?;
569 config.set(keys::APP_URL, env.string("APP_URL", "http://localhost:8000"))?;
570 config.set(keys::LOG_FORMAT, env.setting("LOG_FORMAT")?)?;
574
575 config.set(keys::APP_CIPHER, env.setting("APP_CIPHER")?)?;
579 config.set(keys::APP_BASE_PATH, base_path.to_string_lossy().into_owned())?;
580
581 config.set(keys::SERVER_HOST, env.string("SERVER_HOST", "127.0.0.1"))?;
582 config.set(keys::SERVER_PORT, env.int("SERVER_PORT", 8000) as u16)?;
583 config.set(keys::SERVER_MAX_BODY_BYTES, env.int("SERVER_MAX_BODY", 2 * 1024 * 1024) as u64)?;
584 config.set(
585 keys::SERVER_REQUEST_TIMEOUT_SECS,
586 env.int("SERVER_REQUEST_TIMEOUT", 0).max(0) as u64,
587 )?;
588 config.set(keys::SERVER_COMPRESSION, env.bool("SERVER_COMPRESSION", false))?;
589
590 config.set(keys::DATABASE_URL, env.string("DATABASE_URL", "sqlite::memory:"))?;
591
592 config.set(keys::CACHE_DRIVER, env.setting("CACHE_DRIVER")?)?;
593 config.set(keys::CACHE_REDIS_URL, env.string("REDIS_URL", "redis://127.0.0.1:6379/"))?;
594 config.set(keys::CACHE_MEMCACHED_URL, env.string("MEMCACHED_URL", "127.0.0.1:11211"))?;
595
596 config.set(keys::SESSION_DRIVER, env.setting("SESSION_DRIVER")?)?;
597 config.set(keys::SESSION_LIFETIME, env.int("SESSION_LIFETIME", 7200))?;
598 config.set(keys::SESSION_COOKIE, env.string("SESSION_COOKIE", "rainier_session"))?;
599 config.set(keys::SESSION_SECURE, env.bool("SESSION_SECURE", false))?;
600
601 config.set(keys::QUEUE_DRIVER, env.setting("QUEUE_DRIVER")?)?;
602 config.set(keys::QUEUE_DEFAULT, env.string("QUEUE_DEFAULT", "default"))?;
603
604 config.set(keys::KAFKA_BROKERS, env.string("KAFKA_BROKERS", ""))?;
605 config.set(keys::KAFKA_GROUP, env.string("KAFKA_GROUP", "rainier"))?;
606 config.set(keys::KAFKA_TOPIC_PREFIX, env.string("KAFKA_TOPIC_PREFIX", ""))?;
607 config.set(keys::KAFKA_BROADCAST_TOPIC, env.string("KAFKA_BROADCAST_TOPIC", "broadcasts"))?;
608 config.set(keys::KAFKA_TLS, env.bool("KAFKA_TLS", false))?;
609 config.set(keys::KAFKA_USERNAME, env.string("KAFKA_USERNAME", ""))?;
610 config.set(keys::KAFKA_PASSWORD, env.string("KAFKA_PASSWORD", ""))?;
611 config.set(keys::KAFKA_SASL_MECHANISM, env.string("KAFKA_SASL_MECHANISM", "plain"))?;
612
613 config.set(keys::MAIL_DRIVER, env.setting("MAIL_DRIVER")?)?;
614 config.set(keys::MAIL_FROM_ADDRESS, env.string("MAIL_FROM", "hello@example.com"))?;
615 config.set(keys::MAIL_FROM_NAME, env.string("MAIL_FROM_NAME", "Rainier"))?;
616
617 Ok(config)
618}
619
620fn app_keys(env: &Env) -> KeyRing {
626 let previous: Vec<String> =
627 env.get("APP_PREVIOUS_KEYS").unwrap_or_default().split(',').map(str::to_string).collect();
628
629 match env.get("APP_KEY").filter(|key| !key.trim().is_empty()) {
630 Some(key) => match KeyRing::from_base64(&key, &previous) {
631 Ok(ring) => ring,
632 Err(e) => {
633 tracing::error!(
638 error = %e.message(),
639 "APP_KEY is not usable; generating a temporary key. Encrypted values \
640 written before this boot cannot be read, and values written now cannot \
641 be read after a restart."
642 );
643 KeyRing::new(Key::generate())
644 }
645 },
646 None => {
647 tracing::warn!(
648 "APP_KEY is not set; generating a temporary key. Set one with \
649 `APP_KEY={}` or nothing encrypted will survive a restart.",
650 Key::generate().to_base64()
651 );
652 KeyRing::new(Key::generate())
653 }
654 }
655}
656
657fn default_middleware() -> MiddlewareRegistry {
665 let registry = MiddlewareRegistry::new();
666
667 registry.global(crate::groups::normalise_input());
675
676 registry
677}
678
679fn install_tracing(env: &Env, config: &Config) {
680 let environment = config.get(keys::APP_ENV).unwrap_or_default().to_string();
681
682 crate::observability::TelemetrySettings::from_config(config)
685 .install_logging(&environment, &env.string("RUST_LOG", "info"));
686}
687
688#[cfg(test)]
689mod tests {
690 use super::*;
691 use crate::groups;
692 use rainier_http::{Method, Request, StatusCode};
693 use rainier_session::SessionRequestExt as _;
694 use rainier_session::StartSession;
695
696 async fn app() -> Arc<Application> {
697 Rainier::new(".")
698 .without_facades()
699 .without_tracing()
700 .with_routes(|router| {
701 router.get("/", || async { "home" }).name("home");
702 router.get("/posts/{post}", || async { "post" }).name("posts.show");
703 })
704 .boot()
705 .await
706 .expect("boots")
707 }
708
709 #[tokio::test]
710 async fn booting_binds_the_core_services() {
711 let app = app().await;
712
713 assert!(app.resolve::<Config>().is_ok());
714 assert!(app.resolve::<Dispatcher>().is_ok());
715 assert!(app.resolve::<Views>().is_ok());
716 assert!(app.resolve::<Kernel>().is_ok());
717 assert!(app.resolve::<UrlGenerator>().is_ok());
718 assert!(app.resolve::<MiddlewareRegistry>().is_ok());
719 assert!(app.is_booted());
720 }
721
722 #[tokio::test]
723 async fn the_kernel_serves_the_declared_routes() {
724 let app = app().await;
725 let kernel = app.resolve::<Kernel>().unwrap();
726
727 let response =
728 kernel.handle_request(Request::builder().method(Method::GET).uri("/").build()).await;
729 assert_eq!(response.status(), StatusCode::OK);
730 }
731
732 #[tokio::test]
733 async fn named_routes_reach_the_url_generator() {
734 let app = app().await;
735 let urls = app.resolve::<UrlGenerator>().unwrap();
736
737 assert_eq!(urls.route("posts.show", &[("post", "7")]).unwrap(), "/posts/7");
738 }
739
740 #[tokio::test]
741 async fn the_default_config_comes_from_the_environment() {
742 let app = app().await;
743 let config = app.resolve::<Config>().unwrap();
744
745 assert_eq!(config.get(keys::APP_NAME).as_deref(), Some("Rainier"));
746 assert_eq!(config.get(keys::SERVER_PORT), Some(8000));
747 assert_eq!(config.get(keys::APP_DEBUG), Some(false));
748 }
749
750 #[test]
751 fn the_default_drivers_are_the_ones_that_need_no_infrastructure() {
752 let config = default_config(&Env::new(), std::path::Path::new(".")).unwrap();
755
756 assert_eq!(config.setting(keys::APP_ENV).unwrap(), rainier_config::AppEnv::Production);
757 assert_eq!(config.setting(keys::CACHE_DRIVER).unwrap(), rainier_cache::CacheDriver::Memory);
758 assert_eq!(
759 config.setting(keys::SESSION_DRIVER).unwrap(),
760 rainier_session::SessionDriver::Memory
761 );
762 assert_eq!(config.setting(keys::QUEUE_DRIVER).unwrap(), rainier_queue::QueueDriver::Sync);
763 assert_eq!(config.setting(keys::MAIL_DRIVER).unwrap(), rainier_mail::MailDriver::Log);
764 }
765
766 #[test]
767 fn a_driver_written_as_its_wire_spelling_reads_back_as_the_enum() {
768 let env = Env::parse("CACHE_DRIVER=redis-cluster\nQUEUE_DRIVER=database\nAPP_ENV=local");
769 let config = default_config(&env, std::path::Path::new(".")).unwrap();
770
771 assert_eq!(
772 config.setting(keys::CACHE_DRIVER).unwrap(),
773 rainier_cache::CacheDriver::RedisCluster
774 );
775 assert_eq!(
776 config.setting(keys::QUEUE_DRIVER).unwrap(),
777 rainier_queue::QueueDriver::Database
778 );
779 assert!(config.setting(keys::APP_ENV).unwrap().is_developing());
780 }
781
782 #[test]
783 fn a_misspelled_driver_is_a_configuration_error_not_a_fallback() {
784 let env = Env::parse("CACHE_DRIVER=redys");
788 let err = default_config(&env, std::path::Path::new(".")).unwrap_err();
789
790 assert!(err.message().contains("CACHE_DRIVER"), "{}", err.message());
791 assert!(err.message().contains("`redys`"), "{}", err.message());
792 assert!(err.message().contains("`redis-cluster`"), "{}", err.message());
793 }
794
795 #[tokio::test]
796 async fn a_misspelled_driver_fails_the_boot_rather_than_being_deferred_forever() {
797 let mut builder = Rainier::new(".").without_facades().without_tracing();
801 builder.deferred = Some(Error::internal("`QUEUE_DRIVER`: nope"));
802
803 let err = builder.boot().await.unwrap_err();
804 assert!(err.message().contains("QUEUE_DRIVER"), "{}", err.message());
805 }
806
807 #[tokio::test]
808 async fn configuration_can_be_adjusted_while_building() {
809 let app = Rainier::new(".")
810 .without_facades()
811 .without_tracing()
812 .configure(|config| {
813 config.set("app.name", "Custom").unwrap();
814 })
815 .boot()
816 .await
817 .unwrap();
818
819 assert_eq!(app.resolve::<Config>().unwrap().string("app.name").as_deref(), Some("Custom"));
820 }
821
822 #[tokio::test]
823 async fn the_default_middleware_normalises_input() {
824 let app = Rainier::new(".")
825 .without_facades()
826 .without_tracing()
827 .with_routes(|router| {
828 router.post("/echo", |request: rainier_routing::Req| async move {
829 request.input_or("name", "(absent)")
830 });
831 })
832 .boot()
833 .await
834 .unwrap();
835
836 let response = app
837 .resolve::<Kernel>()
838 .unwrap()
839 .handle_request(
840 Request::builder()
841 .method(Method::POST)
842 .uri("/echo")
843 .json(&serde_json::json!({ "name": " Ada " }))
844 .build(),
845 )
846 .await;
847
848 let body = response.into_http().into_body().collect().await.unwrap();
849 assert_eq!(body, "Ada", "TrimStrings should be global");
850 }
851
852 #[tokio::test]
853 async fn the_shipped_groups_all_build() {
854 let app = Rainier::new(".")
859 .without_facades()
860 .without_tracing()
861 .with_routes(|router| {
862 router.get("/a", || async { "a" }).middleware(groups::api());
863 router.get("/b", || async { "b" }).middleware(groups::web());
864 router.get("/c", || async { "c" }).middleware(groups::api_throttled(5));
865 router.get("/d", || async { "d" }).middleware(groups::session());
866 router.get("/e", || async { "e" }).middleware(groups::trust_local_proxies());
867 router.get("/f", || async { "f" }).middleware(groups::normalise_input());
868 })
869 .boot()
870 .await;
871
872 assert!(app.is_ok(), "{:?}", app.err().map(|e| e.message().to_string()));
873 }
874
875 #[tokio::test]
876 async fn booting_binds_encryption_and_sessions() {
877 let app = app().await;
878
879 assert!(app.resolve::<Encryption>().is_ok());
880 assert!(app.resolve::<SessionManager>().is_ok());
881 assert_eq!(app.resolve::<SessionManager>().unwrap().driver(), "memory");
882 }
883
884 #[tokio::test]
885 async fn encryption_round_trips_through_the_container() {
886 let app = app().await;
887 let crypt = app.resolve::<Encryption>().unwrap();
888
889 let sealed = crypt.encrypt("a secret").unwrap();
890 assert_eq!(crypt.decrypt(&sealed).unwrap(), "a secret");
891 }
892
893 #[tokio::test]
894 async fn an_app_key_from_the_environment_is_used() {
895 let key = Key::generate();
896 let sealed = Encryption::from_keys(KeyRing::new(key.clone())).encrypt("before").unwrap();
897
898 let app = Rainier::new(".")
899 .without_facades()
900 .without_tracing()
901 .configure(|_| {})
902 .boot()
903 .await
904 .unwrap();
905
906 assert!(app.resolve::<Encryption>().unwrap().decrypt(&sealed).is_err());
908
909 let with_key = Rainier::new(".")
911 .without_facades()
912 .without_tracing()
913 .with_encryption(Encryption::from_keys(KeyRing::new(key)))
914 .boot()
915 .await
916 .unwrap();
917
918 assert_eq!(with_key.resolve::<Encryption>().unwrap().decrypt(&sealed).unwrap(), "before");
919 }
920
921 #[tokio::test]
922 async fn the_session_middleware_gives_a_route_a_session() {
923 let app = Rainier::new(".")
924 .without_facades()
925 .without_tracing()
926 .with_routes(|router| {
927 router
928 .get("/count", |request: rainier_routing::Req| async move {
929 let session = request.session().expect("StartSession ran");
930 let seen: u64 = session.get("seen").unwrap_or(0);
931 session.put("seen", seen + 1).unwrap();
932 format!("{seen}")
933 })
934 .middleware(groups::session());
935 })
936 .boot()
937 .await
938 .unwrap();
939
940 let kernel = app.resolve::<Kernel>().unwrap();
941 let response = kernel
942 .handle_request(Request::builder().method(Method::GET).uri("/count").build())
943 .await;
944
945 assert!(response.header("set-cookie").is_some(), "the session should be persisted");
946 let body = response.into_http().into_body().collect().await.unwrap();
947 assert_eq!(body, "0");
948 }
949
950 #[tokio::test]
951 async fn a_route_without_the_session_middleware_has_no_session() {
952 let app = Rainier::new(".")
953 .without_facades()
954 .without_tracing()
955 .with_routes(|router| {
956 router.get("/plain", |request: rainier_routing::Req| async move {
957 format!("{}", request.session().is_some())
958 });
959 })
960 .boot()
961 .await
962 .unwrap();
963
964 let response = app
965 .resolve::<Kernel>()
966 .unwrap()
967 .handle_request(Request::builder().method(Method::GET).uri("/plain").build())
968 .await;
969
970 let body = response.into_http().into_body().collect().await.unwrap();
971 assert_eq!(body, "false");
972 }
973
974 #[tokio::test]
975 async fn middleware_that_needs_an_unbound_service_fails_at_boot() {
976 struct NeverBound;
979
980 let result = Rainier::new(".")
981 .without_facades()
982 .without_tracing()
983 .with_routes(|router| {
984 router.get("/a", || async { "a" }).middleware(
985 rainier_middleware::MiddlewareStack::new().resolved(|_: Arc<NeverBound>| {
986 StartSession::new(Arc::new(MemorySessionStore::default()))
987 }),
988 );
989 })
990 .boot()
991 .await;
992
993 let err = result.err().expect("boot should fail");
994 assert!(err.message().contains("/a"), "{}", err.message());
995 assert!(err.message().contains("NeverBound"), "{}", err.message());
996 }
997
998 #[tokio::test]
999 async fn a_route_attaches_middleware_by_value() {
1000 let app = Rainier::new(".")
1002 .without_facades()
1003 .without_tracing()
1004 .with_routes(|router| {
1005 router
1006 .get("/guarded", || async { "ok" })
1007 .middleware(rainier_middleware::AddHeaders::security_defaults());
1008 })
1009 .boot()
1010 .await
1011 .unwrap();
1012
1013 let response = app
1014 .resolve::<Kernel>()
1015 .unwrap()
1016 .handle_request(Request::builder().method(Method::GET).uri("/guarded").build())
1017 .await;
1018
1019 assert!(response.header("x-content-type-options").is_some());
1020 }
1021
1022 #[tokio::test]
1023 async fn a_provider_registers_before_the_router_compiles() {
1024 struct AliasProvider;
1025 impl ServiceProvider for AliasProvider {
1026 fn register(&self, app: &Application) -> Result<()> {
1027 app.instance(42u32);
1028 Ok(())
1029 }
1030 }
1031
1032 let app = Rainier::new(".")
1033 .without_facades()
1034 .without_tracing()
1035 .with_provider(AliasProvider)
1036 .boot()
1037 .await
1038 .unwrap();
1039
1040 assert_eq!(*app.resolve::<u32>().unwrap(), 42);
1041 }
1042
1043 #[tokio::test]
1044 async fn listeners_registered_while_building_survive_into_the_dispatcher() {
1045 struct Ping;
1046
1047 let app = Rainier::new(".")
1048 .without_facades()
1049 .without_tracing()
1050 .with_events(|events| {
1051 events.listen(|_: Arc<Ping>| async { Ok(()) });
1052 })
1053 .boot()
1054 .await
1055 .unwrap();
1056
1057 assert!(app.resolve::<Dispatcher>().unwrap().has_listeners::<Ping>());
1058 }
1059}
1060
1061#[cfg(test)]
1062mod storage_tests {
1063 use super::*;
1064 use rainier_cache::CacheExt as _;
1065 use rainier_filesystem::{FilesystemExt as _, MemoryFilesystem};
1066
1067 async fn app() -> Arc<Application> {
1068 Rainier::new(".")
1069 .without_facades()
1070 .without_tracing()
1071 .with_storage(Storage::new(Arc::new(MemoryFilesystem::new())))
1072 .boot()
1073 .await
1074 .expect("boots")
1075 }
1076
1077 #[tokio::test]
1078 async fn booting_binds_a_cache_and_a_storage() {
1079 let app = app().await;
1080
1081 assert_eq!(app.resolve::<CacheManager>().unwrap().driver(), "memory");
1082 assert_eq!(app.resolve::<Storage>().unwrap().driver(), "memory");
1083 }
1084
1085 #[tokio::test]
1086 async fn the_default_storage_is_local_under_the_base_path() {
1087 let app = Rainier::new(".").without_facades().without_tracing().boot().await.unwrap();
1089
1090 assert_eq!(app.resolve::<Storage>().unwrap().driver(), "local");
1091 }
1092
1093 #[tokio::test]
1094 async fn the_cache_round_trips_through_the_container() {
1095 let app = app().await;
1096 let cache = app.resolve::<CacheManager>().unwrap();
1097
1098 cache.put_string("k", "v", None).await.unwrap();
1099 assert_eq!(cache.get_string("k").await.unwrap().as_deref(), Some("v"));
1100 }
1101
1102 #[tokio::test]
1103 async fn storage_round_trips_through_the_container() {
1104 let app = app().await;
1105 let storage = app.resolve::<Storage>().unwrap();
1106
1107 storage.put_string("uploads/a.txt", "hello").await.unwrap();
1108 assert_eq!(storage.get_string("uploads/a.txt").await.unwrap().as_deref(), Some("hello"));
1109 }
1110
1111 #[tokio::test]
1112 async fn a_traversal_through_the_container_is_still_refused() {
1113 let storage = app().await.resolve::<Storage>().unwrap();
1114 assert!(storage.get("../../etc/passwd").await.is_err());
1115 }
1116}