Skip to main content

cot/
project.rs

1//! This module contains the core types and traits for a Cot project.
2//!
3//! This module defines the [`Project`] and [`App`] traits, which are the main
4//! entry points for your application.
5//! # Examples
6//!
7//! ```no_run
8//! use cot::Project;
9//! use cot::cli::CliMetadata;
10//!
11//! struct MyProject;
12//! impl Project for MyProject {
13//!     fn cli_metadata(&self) -> CliMetadata {
14//!         cot::cli::metadata!()
15//!     }
16//! }
17//!
18//! #[cot::main]
19//! fn main() -> impl Project {
20//!     MyProject
21//! }
22//! ```
23use std::future::poll_fn;
24use std::panic::AssertUnwindSafe;
25use std::path::PathBuf;
26use std::sync::Arc;
27
28use async_trait::async_trait;
29use axum::handler::HandlerWithoutStateExt;
30use cot::Template;
31use cot_core::error::impl_into_cot_error;
32use cot_core::handler::BoxedHandler;
33use cot_core::request::AppName;
34use derive_more::with_trait::Debug;
35use futures_util::FutureExt;
36use thiserror::Error;
37use tower::util::BoxCloneSyncService;
38use tower::{Layer, Service};
39use tracing::{error, info, trace};
40
41use crate::admin::AdminModelManager;
42#[cfg(feature = "db")]
43use crate::auth::db::DatabaseUserBackend;
44use crate::auth::{AuthBackend, NoAuthBackend};
45#[cfg(feature = "cache")]
46use crate::cache::Cache;
47use crate::cli::Cli;
48#[cfg(feature = "cache")]
49use crate::config::CacheConfig;
50#[cfg(feature = "db")]
51use crate::config::DatabaseConfig;
52use crate::config::{AuthBackendConfig, ProjectConfig};
53#[cfg(feature = "db")]
54use crate::db::Database;
55#[cfg(feature = "db")]
56use crate::db::migrations::{MigrationEngine, SyncDynMigration};
57#[cfg(feature = "email")]
58use crate::email::Email;
59use crate::error::UncaughtPanic;
60use crate::error::handler::{DynErrorPageHandler, RequestOuterError};
61use crate::error_page::Diagnostics;
62use crate::html::Html;
63use crate::middleware::{IntoCotError, IntoCotErrorLayer, IntoCotResponse, IntoCotResponseLayer};
64use crate::request::{Request, RequestExt, RequestHead};
65use crate::response::{IntoResponse, Response};
66use crate::router::{Route, Router, RouterService};
67use crate::static_files::StaticFile;
68use crate::utils::accept_header_parser::AcceptHeaderParser;
69use crate::{Body, Error, cli, error_page};
70
71/// A building block for a Cot project.
72///
73/// A Cot app is a part (ideally, reusable) of a Cot project that is
74/// responsible for its own set of functionalities. Examples of apps could be:
75/// * admin panel
76/// * user authentication
77/// * blog
78/// * message board
79/// * session management
80/// * etc.
81///
82/// Each app can have its own set of URLs that can be mounted on the project's
83/// router, its own set of middlewares, database migrations (which can depend on
84/// other apps), etc.
85#[async_trait]
86pub trait App: Send + Sync {
87    /// The name of the app.
88    ///
89    /// This should usually be the name of the crate.
90    ///
91    /// # Examples
92    ///
93    /// ```
94    /// use cot::App;
95    ///
96    /// struct MyApp;
97    /// impl App for MyApp {
98    ///     fn name(&self) -> &str {
99    ///         env!("CARGO_PKG_NAME")
100    ///     }
101    /// }
102    /// ```
103    fn name(&self) -> &str;
104
105    /// Initializes the app.
106    ///
107    /// This method is called when the app is initialized. It can be used to
108    /// initialize whatever is needed for the app to work, possibly depending on
109    /// other apps, or the project's configuration.
110    ///
111    /// # Errors
112    ///
113    /// This method returns an error if the app fails to initialize.
114    #[expect(unused_variables)]
115    async fn init(&self, context: &mut ProjectContext) -> crate::Result<()> {
116        Ok(())
117    }
118
119    /// Returns the router for the app. By default, it returns an empty router.
120    ///
121    /// # Examples
122    ///
123    /// ```
124    /// use cot::App;
125    /// use cot::html::Html;
126    /// use cot::router::{Route, Router};
127    ///
128    /// async fn index() -> Html {
129    ///     Html::new("Hello world!")
130    /// }
131    ///
132    /// struct MyApp;
133    /// impl App for MyApp {
134    ///     fn name(&self) -> &str {
135    ///         "my_app"
136    ///     }
137    ///
138    ///     fn router(&self) -> Router {
139    ///         Router::with_urls([Route::with_handler("/", index)])
140    ///     }
141    /// }
142    /// ```
143    fn router(&self) -> Router {
144        Router::empty()
145    }
146
147    /// Returns the migrations for the app. By default, it returns an empty
148    /// list.
149    #[cfg(feature = "db")]
150    fn migrations(&self) -> Vec<Box<SyncDynMigration>> {
151        vec![]
152    }
153
154    /// Returns the admin model managers for the app. By default, it returns an
155    /// empty list.
156    fn admin_model_managers(&self) -> Vec<Box<dyn AdminModelManager>> {
157        vec![]
158    }
159
160    /// Returns a list of static files that the app serves. By default, it
161    /// returns an empty list.
162    fn static_files(&self) -> Vec<StaticFile> {
163        vec![]
164    }
165}
166
167/// The main trait for a Cot project.
168///
169/// This is the main entry point for your application. This trait defines
170/// the configuration, apps, and other project-wide resources.
171///
172/// It's mainly meant to be used with the [`cot::main`] attribute macro.
173///
174/// # Examples
175///
176/// ```no_run
177/// use cot::Project;
178/// use cot::cli::CliMetadata;
179///
180/// struct MyProject;
181/// impl Project for MyProject {
182///     fn cli_metadata(&self) -> CliMetadata {
183///         cot::cli::metadata!()
184///     }
185/// }
186///
187/// #[cot::main]
188/// fn main() -> impl Project {
189///     MyProject
190/// }
191/// ```
192pub trait Project {
193    /// Returns the metadata for the CLI.
194    ///
195    /// This method is used to set the name, version, authors, and description
196    /// of the CLI application. This is meant to be typically used with
197    /// [`cli::metadata!()`] which automatically retrieves this data from the
198    /// crate metadata.
199    ///
200    /// The default implementation sets the name, version, authors, and
201    /// description of the `cot` crate.
202    ///
203    /// # Examples
204    ///
205    /// ```
206    /// use cot::Project;
207    /// use cot::cli::CliMetadata;
208    ///
209    /// struct HelloProject;
210    /// impl Project for HelloProject {
211    ///     fn cli_metadata(&self) -> CliMetadata {
212    ///         cot::cli::metadata!()
213    ///     }
214    /// }
215    /// ```
216    fn cli_metadata(&self) -> cli::CliMetadata {
217        cli::metadata!()
218    }
219
220    /// Returns the configuration for the project.
221    ///
222    /// The default implementation reads the configuration from the `config`
223    /// directory in the current working directory (for instance, if
224    /// `config_name` is `test`, then `config/test.toml` in the current working
225    /// directory is read). If the file does not exist, it tries to read the
226    /// file directly at `config_name` path.
227    ///
228    /// You might want to override this method if you want to read the
229    /// configuration from a different source, or if you want to hardcode
230    /// it in the binary.
231    ///
232    /// # Errors
233    ///
234    /// This method may return an error if it cannot read or parse the
235    /// configuration.
236    ///
237    /// # Examples
238    ///
239    /// ```
240    /// use cot::Project;
241    /// use cot::config::ProjectConfig;
242    ///
243    /// struct MyProject;
244    /// impl Project for MyProject {
245    ///     fn config(&self, config_name: &str) -> cot::Result<ProjectConfig> {
246    ///         Ok(ProjectConfig::default())
247    ///     }
248    /// }
249    /// ```
250    fn config(&self, config_name: &str) -> crate::Result<ProjectConfig> {
251        read_config(config_name)
252    }
253
254    /// Adds a task to the CLI.
255    ///
256    /// This method is used to add a task to the CLI. The task will be available
257    /// as a subcommand of the main CLI command.
258    ///
259    /// # Examples
260    ///
261    /// ```
262    /// use async_trait::async_trait;
263    /// use clap::{ArgMatches, Command};
264    /// use cot::cli::{Cli, CliTask};
265    /// use cot::project::WithConfig;
266    /// use cot::{Bootstrapper, Project};
267    ///
268    /// struct Frobnicate;
269    ///
270    /// #[async_trait(?Send)]
271    /// impl CliTask for Frobnicate {
272    ///     fn subcommand(&self) -> Command {
273    ///         Command::new("frobnicate")
274    ///     }
275    ///
276    ///     async fn execute(
277    ///         &mut self,
278    ///         _matches: &ArgMatches,
279    ///         _bootstrapper: Bootstrapper<WithConfig>,
280    ///     ) -> cot::Result<()> {
281    ///         println!("Frobnicating...");
282    ///
283    ///         Ok(())
284    ///     }
285    /// }
286    ///
287    /// struct MyProject;
288    /// impl Project for MyProject {
289    ///     fn register_tasks(&self, cli: &mut Cli) {
290    ///         cli.add_task(Frobnicate)
291    ///     }
292    /// }
293    /// ```
294    #[expect(unused_variables)]
295    fn register_tasks(&self, cli: &mut Cli) {}
296
297    /// Registers the apps for the project.
298    ///
299    /// # Examples
300    ///
301    /// ```
302    /// use cot::project::{AppBuilder, RegisterAppsContext};
303    /// use cot::{App, Project};
304    ///
305    /// struct MyApp;
306    /// impl App for MyApp {
307    ///     fn name(&self) -> &str {
308    ///         "my_app"
309    ///     }
310    /// }
311    ///
312    /// struct MyProject;
313    /// impl Project for MyProject {
314    ///     fn register_apps(&self, apps: &mut AppBuilder, context: &RegisterAppsContext) {
315    ///         apps.register(MyApp);
316    ///     }
317    /// }
318    /// ```
319    #[expect(unused_variables)]
320    fn register_apps(&self, apps: &mut AppBuilder, context: &RegisterAppsContext) {}
321
322    /// Sets the authentication backend to use.
323    ///
324    /// Note that it's typically not necessary to override this method, as it
325    /// already provides a default implementation that uses the auth backend
326    /// specified in the project's configuration.
327    ///
328    /// # Examples
329    ///
330    /// ```
331    /// use std::sync::Arc;
332    ///
333    /// use cot::auth::{AuthBackend, NoAuthBackend};
334    /// use cot::project::AuthBackendContext;
335    /// use cot::{App, Project};
336    ///
337    /// struct HelloProject;
338    /// impl Project for HelloProject {
339    ///     fn auth_backend(&self, context: &AuthBackendContext) -> Arc<dyn AuthBackend> {
340    ///         Arc::new(NoAuthBackend)
341    ///     }
342    /// }
343    /// ```
344    fn auth_backend(&self, context: &AuthBackendContext) -> Arc<dyn AuthBackend> {
345        #[expect(trivial_casts)] // cast to Arc<dyn AuthBackend>
346        match &context.config().auth_backend {
347            AuthBackendConfig::None => Arc::new(NoAuthBackend) as Arc<dyn AuthBackend>,
348            #[cfg(feature = "db")]
349            AuthBackendConfig::Database => Arc::new(DatabaseUserBackend::new(
350                context
351                    .try_database()
352                    .expect(
353                        "Database missing when constructing database auth backend. \
354                        Make sure the database config is set up correctly or disable \
355                        authentication in the config.",
356                    )
357                    .clone(),
358            )) as Arc<dyn AuthBackend>,
359        }
360    }
361
362    /// Returns the middlewares for the project.
363    ///
364    /// This method is used to return the middlewares for the project. The
365    /// middlewares will be applied to all routes in the project.
366    ///
367    /// # Examples
368    ///
369    /// ```
370    /// use cot::Project;
371    /// use cot::middleware::LiveReloadMiddleware;
372    /// use cot::project::{MiddlewareContext, RootHandler, RootHandlerBuilder};
373    ///
374    /// struct MyProject;
375    /// impl Project for MyProject {
376    ///     fn middlewares(
377    ///         &self,
378    ///         handler: RootHandlerBuilder,
379    ///         context: &MiddlewareContext,
380    ///     ) -> RootHandler {
381    ///         handler
382    ///             .middleware(LiveReloadMiddleware::from_context(context))
383    ///             .build()
384    ///     }
385    /// }
386    /// ```
387    #[expect(unused_variables)]
388    fn middlewares(&self, handler: RootHandlerBuilder, context: &MiddlewareContext) -> RootHandler {
389        handler.build()
390    }
391
392    /// Returns the error handler for the project.
393    ///
394    /// The default handler returns a simple, minimalistic error page
395    /// that displays the status code canonical name and a
396    /// generic error message.
397    ///
398    /// # Errors
399    ///
400    /// This method may return an error if the handler fails to build a
401    /// response. In this case, the error will be logged and a generic
402    /// error page will be returned to the user. Because of that, you
403    /// should avoid returning an error from this handler and try to
404    /// handle the error gracefully instead.
405    ///
406    /// # Panics
407    ///
408    /// Note that this handler is exempt from the typical panic handling
409    /// machinery in Cot. This means that if this handler panics, no
410    /// response will be sent to a user. Because of that, you should
411    /// avoid panicking here and return [`Err`] instead.
412    ///
413    /// # Examples
414    ///
415    /// ```
416    /// use cot::Project;
417    /// use cot::error::handler::{DynErrorPageHandler, RequestError};
418    /// use cot::html::Html;
419    /// use cot::response::IntoResponse;
420    ///
421    /// struct MyProject;
422    /// impl Project for MyProject {
423    ///     fn error_handler(&self) -> DynErrorPageHandler {
424    ///         DynErrorPageHandler::new(error_handler)
425    ///     }
426    /// }
427    ///
428    /// async fn error_handler(error: RequestError) -> impl IntoResponse {
429    ///     Html::new(format!("An error occurred: {error}")).with_status(error.status_code())
430    /// }
431    /// ```
432    fn error_handler(&self) -> DynErrorPageHandler {
433        DynErrorPageHandler::new(default_error_handler)
434    }
435}
436
437/// An alias for `ProjectContext` in appropriate phase for use with the
438/// [`Project::register_apps`] method. This represents the `ProjectContext`
439/// during the [`WithConfig`] phase.
440pub type RegisterAppsContext = ProjectContext<WithConfig>;
441
442/// An alias for `ProjectContext` in appropriate phase for use with the
443/// [`Project::auth_backend`] method.
444pub type AuthBackendContext = ProjectContext<WithCache>;
445
446/// An alias for `ProjectContext` in appropriate phase for use with the
447/// [`Project::middlewares`] method.
448pub type MiddlewareContext = ProjectContext<WithCache>;
449
450/// A helper struct to build the root handler for the project.
451///
452/// This is mainly useful for attaching middlewares to the project.
453///
454/// # Examples
455///
456/// ```
457/// use cot::Project;
458/// use cot::middleware::LiveReloadMiddleware;
459/// use cot::project::{MiddlewareContext, RootHandler, RootHandlerBuilder};
460///
461/// struct MyProject;
462/// impl Project for MyProject {
463///     fn middlewares(
464///         &self,
465///         handler: RootHandlerBuilder,
466///         context: &MiddlewareContext,
467///     ) -> RootHandler {
468///         handler
469///             .middleware(LiveReloadMiddleware::from_context(context))
470///             .build()
471///     }
472/// }
473/// ```
474#[derive(Debug, Clone, PartialEq, Eq, Hash)]
475pub struct RootHandlerBuilder<S = RouterService, SE = DynErrorPageHandler> {
476    handler: S,
477    error_handler: SE,
478}
479
480impl<RootService, ErrorHandlerService> RootHandlerBuilder<RootService, ErrorHandlerService>
481where
482    RootService:
483        Service<Request, Response = Response, Error = Error> + Send + Sync + Clone + 'static,
484    RootService::Future: Send,
485    ErrorHandlerService:
486        Service<Request, Response = Response, Error = Error> + Send + Sync + Clone + 'static,
487    ErrorHandlerService::Future: Send,
488{
489    /// Adds middleware to the project.
490    ///
491    /// This method is used to add middleware to the project. The middleware
492    /// will be applied to all routes in the project.
493    ///
494    /// # Examples
495    ///
496    /// ```
497    /// use cot::Project;
498    /// use cot::middleware::LiveReloadMiddleware;
499    /// use cot::project::{MiddlewareContext, RootHandler, RootHandlerBuilder};
500    ///
501    /// struct MyProject;
502    /// impl Project for MyProject {
503    ///     fn middlewares(
504    ///         &self,
505    ///         handler: RootHandlerBuilder,
506    ///         context: &MiddlewareContext,
507    ///     ) -> RootHandler {
508    ///         handler
509    ///             .middleware(LiveReloadMiddleware::from_context(context))
510    ///             .build()
511    ///     }
512    /// }
513    /// ```
514    #[must_use]
515    pub fn middleware<M>(
516        self,
517        middleware: M,
518    ) -> RootHandlerBuilder<
519        WrappedMiddleware<M, RootService>,
520        WrappedMiddleware<M, ErrorHandlerService>,
521    >
522    where
523        M: Layer<RootService> + Layer<ErrorHandlerService>,
524    {
525        let layer = (
526            IntoCotErrorLayer::new(),
527            IntoCotResponseLayer::new(),
528            middleware,
529        );
530
531        RootHandlerBuilder {
532            handler: layer.layer(self.handler),
533            error_handler: layer.layer(self.error_handler),
534        }
535    }
536
537    /// Adds middleware only to the main request handler.
538    ///
539    /// This method is used to add middleware that should only be applied to
540    /// the main request handler, not to the error handler. This is useful when
541    /// you have middleware that should only run for successful requests, such
542    /// as logging middleware that should not interfere with error handling.
543    ///
544    /// # Examples
545    ///
546    /// ```
547    /// use cot::Project;
548    /// use cot::middleware::LiveReloadMiddleware;
549    /// use cot::project::{MiddlewareContext, RootHandler, RootHandlerBuilder};
550    ///
551    /// struct MyProject;
552    /// impl Project for MyProject {
553    ///     fn middlewares(
554    ///         &self,
555    ///         handler: RootHandlerBuilder,
556    ///         context: &MiddlewareContext,
557    ///     ) -> RootHandler {
558    ///         handler
559    ///             .main_handler_middleware(LiveReloadMiddleware::from_context(context))
560    ///             .build()
561    ///     }
562    /// }
563    /// ```
564    #[must_use]
565    pub fn main_handler_middleware<M>(
566        self,
567        middleware: M,
568    ) -> RootHandlerBuilder<WrappedMiddleware<M, RootService>, ErrorHandlerService>
569    where
570        M: Layer<RootService>,
571    {
572        let layer = (
573            IntoCotErrorLayer::new(),
574            IntoCotResponseLayer::new(),
575            middleware,
576        );
577
578        RootHandlerBuilder {
579            handler: layer.layer(self.handler),
580            error_handler: self.error_handler,
581        }
582    }
583
584    /// Adds middleware only to the error handler.
585    ///
586    /// This method is used to add middleware that should only be applied to
587    /// the error handler, not to the main request handler. This is useful when
588    /// you have middleware that should only run when handling errors, such as
589    /// error reporting middleware or middleware that modifies error responses.
590    ///
591    /// # Examples
592    ///
593    /// ```
594    /// use cot::Project;
595    /// use cot::project::{MiddlewareContext, RootHandler, RootHandlerBuilder};
596    /// use cot::static_files::StaticFilesMiddleware;
597    ///
598    /// struct MyProject;
599    /// impl Project for MyProject {
600    ///     fn middlewares(
601    ///         &self,
602    ///         handler: RootHandlerBuilder,
603    ///         context: &MiddlewareContext,
604    ///     ) -> RootHandler {
605    ///         handler
606    ///             .error_handler_middleware(StaticFilesMiddleware::from_context(context))
607    ///             .build()
608    ///     }
609    /// }
610    /// ```
611    #[must_use]
612    pub fn error_handler_middleware<M>(
613        self,
614        middleware: M,
615    ) -> RootHandlerBuilder<RootService, WrappedMiddleware<M, ErrorHandlerService>>
616    where
617        M: Layer<ErrorHandlerService>,
618    {
619        let layer = (
620            IntoCotErrorLayer::new(),
621            IntoCotResponseLayer::new(),
622            middleware,
623        );
624
625        RootHandlerBuilder {
626            handler: self.handler,
627            error_handler: layer.layer(self.error_handler),
628        }
629    }
630
631    /// Builds the root handler for the project.
632    ///
633    /// # Examples
634    ///
635    /// ```
636    /// use cot::Project;
637    /// use cot::middleware::LiveReloadMiddleware;
638    /// use cot::project::{MiddlewareContext, RootHandler, RootHandlerBuilder};
639    ///
640    /// struct MyProject;
641    /// impl Project for MyProject {
642    ///     fn middlewares(
643    ///         &self,
644    ///         handler: RootHandlerBuilder,
645    ///         context: &MiddlewareContext,
646    ///     ) -> RootHandler {
647    ///         handler
648    ///             .middleware(LiveReloadMiddleware::from_context(context))
649    ///             .build()
650    ///     }
651    /// }
652    /// ```
653    pub fn build(self) -> RootHandler {
654        RootHandler {
655            handler: BoxedHandler::new(self.handler),
656            error_handler: BoxedHandler::new(self.error_handler),
657        }
658    }
659}
660
661/// A type alias for the service type returned by the
662/// [`RootHandlerBuilder::middleware`] and similar methods.
663pub type WrappedMiddleware<M, S> = IntoCotError<IntoCotResponse<<M as Layer<S>>::Service>>;
664
665/// A built root handler that contains both the main request handler and error
666/// handler.
667///
668/// This struct is the result of building a [`RootHandlerBuilder`] and contains
669/// the finalized handlers that are ready to be used for processing requests
670/// and handling errors.
671///
672/// # Examples
673///
674/// ```
675/// use cot::Project;
676/// use cot::middleware::LiveReloadMiddleware;
677/// use cot::project::{MiddlewareContext, RootHandler, RootHandlerBuilder};
678///
679/// struct MyProject;
680/// impl Project for MyProject {
681///     fn middlewares(
682///         &self,
683///         handler: RootHandlerBuilder,
684///         context: &MiddlewareContext,
685///     ) -> RootHandler {
686///         handler
687///             .middleware(LiveReloadMiddleware::from_context(context))
688///             .build()
689///     }
690/// }
691/// ```
692#[derive(Debug)]
693pub struct RootHandler {
694    /// The main request handler that processes incoming requests.
695    pub(crate) handler: BoxedHandler,
696    /// The error handler that processes errors that occur during request
697    /// handling.
698    pub(crate) error_handler: BoxedHandler,
699}
700
701/// A helper struct to build the apps for the project.
702///
703/// # Examples
704///
705/// ```
706/// use cot::project::{AppBuilder, RegisterAppsContext};
707/// use cot::{App, Project};
708///
709/// struct MyApp;
710/// impl App for MyApp {
711///     fn name(&self) -> &str {
712///         "my_app"
713///     }
714/// }
715///
716/// struct MyProject;
717/// impl Project for MyProject {
718///     fn register_apps(&self, apps: &mut AppBuilder, context: &RegisterAppsContext) {
719///         apps.register(MyApp);
720///     }
721/// }
722/// ```
723#[derive(Debug)]
724pub struct AppBuilder {
725    #[debug("..")]
726    apps: Vec<Box<dyn App>>,
727    urls: Vec<Route>,
728}
729
730impl AppBuilder {
731    fn new() -> Self {
732        Self {
733            apps: Vec::new(),
734            urls: Vec::new(),
735        }
736    }
737
738    /// Registers an app.
739    ///
740    /// This method is used to register an app. The app's views, if any, will
741    /// not be available.
742    ///
743    /// # Examples
744    ///
745    /// ```
746    /// use cot::project::RegisterAppsContext;
747    /// use cot::{App, Project};
748    ///
749    /// struct HelloApp;
750    ///
751    /// impl App for HelloApp {
752    ///     fn name(&self) -> &'static str {
753    ///         env!("CARGO_PKG_NAME")
754    ///     }
755    /// }
756    ///
757    /// struct HelloProject;
758    /// impl Project for HelloProject {
759    ///     fn register_apps(&self, apps: &mut cot::AppBuilder, _context: &RegisterAppsContext) {
760    ///         apps.register(HelloApp);
761    ///     }
762    /// }
763    /// ```
764    pub fn register<T: App + 'static>(&mut self, module: T) {
765        self.apps.push(Box::new(module));
766    }
767
768    /// Registers an app with views.
769    ///
770    /// This method is used to register an app with views. The app's views will
771    /// be available at the given URL prefix.
772    ///
773    /// # Examples
774    ///
775    /// ```
776    /// use cot::project::RegisterAppsContext;
777    /// use cot::{App, Project};
778    ///
779    /// struct HelloApp;
780    ///
781    /// impl App for HelloApp {
782    ///     fn name(&self) -> &'static str {
783    ///         env!("CARGO_PKG_NAME")
784    ///     }
785    /// }
786    ///
787    /// struct HelloProject;
788    /// impl Project for HelloProject {
789    ///     fn register_apps(&self, apps: &mut cot::AppBuilder, _context: &RegisterAppsContext) {
790    ///         apps.register_with_views(HelloApp, "/hello");
791    ///     }
792    /// }
793    /// ```
794    pub fn register_with_views<T: App + 'static>(&mut self, app: T, url_prefix: &str) {
795        let mut router = app.router();
796        router.set_app_name(AppName(app.name().to_owned()));
797
798        self.urls.push(Route::with_router(url_prefix, router));
799        self.register(app);
800    }
801}
802
803async fn default_error_handler(error: RequestOuterError) -> crate::Result<impl IntoResponse> {
804    #[derive(Debug, Template)]
805    #[template(path = "default_error.html")]
806    struct ErrorTemplate {
807        error: RequestOuterError,
808    }
809
810    let status_code = error.status_code();
811    let error_template = ErrorTemplate { error };
812    let rendered = error_template.render()?;
813
814    Ok(Html::new(rendered).with_status(status_code))
815}
816
817/// The main struct for bootstrapping the project.
818///
819/// This is the core struct for bootstrapping the project. It goes over the
820/// different phases of bootstrapping the project which are defined in the
821/// [`BootstrapPhase`] trait. Each phase has its own subset of the project's
822/// context that is available, and you have access to specific parts of the
823/// project's context depending where you are in the bootstrapping process.
824///
825/// Note that you shouldn't have to use this struct directly most of the time.
826/// It's mainly used internally by the `cot` crate to bootstrap the project.
827/// It can be useful if you want to control the bootstrapping process in
828/// custom [`CliTask`](cli::CliTask)s.
829///
830/// # Examples
831///
832/// ```
833/// use cot::project::{Bootstrapper, WithConfig};
834/// use cot::{App, Project};
835///
836/// struct MyProject;
837/// impl Project for MyProject {}
838///
839/// # #[tokio::main]
840/// # async fn main() -> cot::Result<()> {
841/// let bootstrapper = Bootstrapper::new(MyProject)
842///     .with_config(cot::config::ProjectConfig::default())
843///     .boot()
844///     .await?;
845/// let bootstrapped_project = bootstrapper.finish();
846/// # Ok(())
847/// # }
848/// ```
849#[derive(Debug)]
850pub struct Bootstrapper<S: BootstrapPhase = Initialized> {
851    #[debug("..")]
852    project: Box<dyn Project + Send>,
853    context: ProjectContext<S>,
854    handler: S::RequestHandler,
855    error_handler: S::ErrorHandler,
856}
857
858impl Bootstrapper<Uninitialized> {
859    /// Creates a new bootstrapper.
860    ///
861    /// # Examples
862    ///
863    /// ```
864    /// use cot::project::{Bootstrapper, WithConfig};
865    /// use cot::{App, Project};
866    ///
867    /// struct MyProject;
868    /// impl Project for MyProject {}
869    ///
870    /// # #[tokio::main]
871    /// # async fn main() -> cot::Result<()> {
872    /// let bootstrapper = Bootstrapper::new(MyProject);
873    /// # Ok(())
874    /// # }
875    /// ```
876    #[must_use]
877    pub fn new<P: Project + Send + 'static>(project: P) -> Self {
878        Self {
879            project: Box::new(project),
880            context: ProjectContext::new(),
881            handler: (),
882            error_handler: (),
883        }
884    }
885}
886
887impl<S: BootstrapPhase> Bootstrapper<S> {
888    /// Returns the project for the bootstrapper.
889    ///
890    /// # Examples
891    ///
892    /// ```
893    /// use cot::project::{Bootstrapper, WithConfig};
894    /// use cot::{App, Project};
895    ///
896    /// struct MyProject;
897    /// impl Project for MyProject {}
898    ///
899    /// # #[tokio::main]
900    /// # async fn main() -> cot::Result<()> {
901    /// let bootstrapper = Bootstrapper::new(MyProject);
902    /// let project = bootstrapper.project();
903    /// # Ok(())
904    /// # }
905    /// ```
906    pub fn project(&self) -> &dyn Project {
907        self.project.as_ref()
908    }
909
910    /// Returns the context for the bootstrapper.
911    ///
912    /// # Examples
913    ///
914    /// ```
915    /// use cot::project::{Bootstrapper, WithConfig};
916    /// use cot::{App, Project};
917    ///
918    /// struct MyProject;
919    /// impl Project for MyProject {}
920    ///
921    /// # #[tokio::main]
922    /// # async fn main() -> cot::Result<()> {
923    /// let bootstrapper = Bootstrapper::new(MyProject);
924    /// let context = bootstrapper.context();
925    /// # Ok(())
926    /// # }
927    /// ```
928    #[must_use]
929    pub fn context(&self) -> &ProjectContext<S> {
930        &self.context
931    }
932}
933
934impl Bootstrapper<Uninitialized> {
935    #[expect(clippy::future_not_send)] // Send not needed; CLI is run async in a single thread
936    async fn run_cli(self) -> cot::Result<()> {
937        let mut cli = Cli::new();
938
939        cli.set_metadata(self.project.cli_metadata());
940        self.project.register_tasks(&mut cli);
941
942        let common_options = cli.common_options();
943        let self_with_context = self.with_config_name(common_options.config())?;
944
945        cli.execute(self_with_context).await
946    }
947
948    /// Reads the configuration of the project and moves to the next
949    /// bootstrapping phase.
950    ///
951    /// # Errors
952    ///
953    /// This method may return an error if it cannot read the configuration of
954    /// the project.
955    ///
956    /// # Examples
957    ///
958    /// ```
959    /// use cot::config::ProjectConfig;
960    /// use cot::{Bootstrapper, Project};
961    ///
962    /// struct MyProject;
963    /// impl Project for MyProject {
964    ///     fn config(&self, config_name: &str) -> cot::Result<ProjectConfig> {
965    ///         Ok(ProjectConfig::default())
966    ///     }
967    /// }
968    ///
969    /// # #[tokio::main]
970    /// # async fn main() -> cot::Result<()> {
971    /// let bootstrapper = Bootstrapper::new(MyProject)
972    ///     .with_config_name("test")?
973    ///     .boot()
974    ///     .await?;
975    /// let bootstrapped_project = bootstrapper.finish();
976    /// # Ok(())
977    /// # }
978    /// ```
979    pub fn with_config_name(self, config_name: &str) -> cot::Result<Bootstrapper<WithConfig>> {
980        let config = self.project.config(config_name)?;
981
982        Ok(self.with_config(config))
983    }
984
985    /// Sets the configuration for the project.
986    ///
987    /// This is mainly useful in tests, where you want to override the default
988    /// behavior of reading the configuration from a file.
989    ///
990    /// # Examples
991    ///
992    /// ```
993    /// use cot::config::ProjectConfig;
994    /// use cot::{Bootstrapper, Project};
995    ///
996    /// struct MyProject;
997    /// impl Project for MyProject {}
998    ///
999    /// # #[tokio::main]
1000    /// # async fn main() -> cot::Result<()> {
1001    /// let bootstrapper = Bootstrapper::new(MyProject)
1002    ///     .with_config(ProjectConfig::default())
1003    ///     .boot()
1004    ///     .await?;
1005    /// let bootstrapped_project = bootstrapper.finish();
1006    /// # Ok(())
1007    /// # }
1008    /// ```
1009    #[must_use]
1010    pub fn with_config(self, config: ProjectConfig) -> Bootstrapper<WithConfig> {
1011        Bootstrapper {
1012            project: self.project,
1013            context: self.context.with_config(config),
1014            handler: self.handler,
1015            error_handler: self.error_handler,
1016        }
1017    }
1018}
1019
1020fn read_config(config: &str) -> cot::Result<ProjectConfig> {
1021    trace!(config, "Reading project configuration");
1022    let result = match std::fs::read_to_string(config) {
1023        Ok(config_content) => Ok(config_content),
1024        Err(_err) => {
1025            // try to read the config from the `config` directory if it's not a file
1026            let path = PathBuf::from("config").join(config).with_extension("toml");
1027            trace!(
1028                config,
1029                path = %path.display(),
1030                "Failed to read config as a file; trying to read from the `config` directory"
1031            );
1032
1033            std::fs::read_to_string(&path)
1034        }
1035    };
1036
1037    let config_content = result.map_err(|err| LoadConfig {
1038        config: config.to_owned(),
1039        source: err,
1040    })?;
1041
1042    ProjectConfig::from_toml(&config_content)
1043}
1044
1045#[derive(Debug, Error)]
1046#[error("could not read the config file at `{config}` or `config/{config}.toml`")]
1047struct LoadConfig {
1048    config: String,
1049    source: std::io::Error,
1050}
1051impl_into_cot_error!(LoadConfig);
1052
1053impl Bootstrapper<WithConfig> {
1054    /// Builds the initialized Cot project instance.
1055    ///
1056    /// This is the final step in the bootstrapping process. It initializes the
1057    /// project with the given configuration and returns a [`Bootstrapper`]
1058    /// instance that contains the project's context and handler.
1059    ///
1060    /// You shouldn't have to use this method directly most of the time. It's
1061    /// mainly useful for controlling the bootstrapping process in custom
1062    /// [`CliTask`](cli::CliTask)s.
1063    ///
1064    /// # Errors
1065    ///
1066    /// This method may return an error if it cannot initialize any of the
1067    /// project's components, such as the database.
1068    ///
1069    /// # Examples
1070    ///
1071    /// ```
1072    /// use cot::config::ProjectConfig;
1073    /// use cot::{Bootstrapper, Project};
1074    ///
1075    /// struct MyProject;
1076    /// impl Project for MyProject {}
1077    ///
1078    /// # #[tokio::main]
1079    /// # async fn main() -> cot::Result<()> {
1080    /// let bootstrapper = Bootstrapper::new(MyProject)
1081    ///     .with_config(ProjectConfig::default())
1082    ///     .boot()
1083    ///     .await?;
1084    /// let bootstrapped_project = bootstrapper.finish();
1085    /// # Ok(())
1086    /// # }
1087    /// ```
1088    pub async fn boot(self) -> cot::Result<Bootstrapper<Initialized>> {
1089        self.with_apps().boot().await
1090    }
1091
1092    /// Moves forward to the next phase of bootstrapping, the with-apps phase.
1093    ///
1094    /// See the [`BootstrapPhase`] and [`WithApps`] documentation for more
1095    /// details.
1096    ///
1097    /// # Examples
1098    ///
1099    /// ```
1100    /// use cot::config::ProjectConfig;
1101    /// use cot::project::{Bootstrapper, WithApps};
1102    /// use cot::{AppBuilder, Project};
1103    ///
1104    /// struct MyProject;
1105    /// impl Project for MyProject {}
1106    ///
1107    /// # #[tokio::main]
1108    /// # async fn main() -> cot::Result<()> {
1109    /// let bootstrapper = Bootstrapper::new(MyProject)
1110    ///     .with_config(ProjectConfig::default())
1111    ///     .with_apps()
1112    ///     .boot()
1113    ///     .await?;
1114    /// # Ok(())
1115    /// # }
1116    /// ```
1117    #[must_use]
1118    pub fn with_apps(self) -> Bootstrapper<WithApps> {
1119        let mut module_builder = AppBuilder::new();
1120        self.project
1121            .register_apps(&mut module_builder, &self.context);
1122
1123        let router = Arc::new(Router::with_urls(module_builder.urls));
1124
1125        let context = self.context.with_apps(module_builder.apps, router);
1126
1127        Bootstrapper {
1128            project: self.project,
1129            context,
1130            handler: self.handler,
1131            error_handler: self.error_handler,
1132        }
1133    }
1134}
1135
1136impl Bootstrapper<WithApps> {
1137    /// Builds the initialized Cot project instance.
1138    ///
1139    /// This is the final step in the bootstrapping process. It initializes the
1140    /// project with the given configuration and returns a [`Bootstrapper`]
1141    /// instance that contains the project's context and handler.
1142    ///
1143    /// You shouldn't have to use this method directly most of the time. It's
1144    /// mainly useful for controlling the bootstrapping process in custom
1145    /// [`CliTask`](cli::CliTask)s.
1146    ///
1147    /// # Errors
1148    ///
1149    /// This method may return an error if it cannot initialize any of the
1150    /// project's components, such as the database.
1151    ///
1152    /// # Examples
1153    ///
1154    /// ```
1155    /// use cot::config::ProjectConfig;
1156    /// use cot::{Bootstrapper, Project};
1157    ///
1158    /// struct MyProject;
1159    /// impl Project for MyProject {}
1160    ///
1161    /// # #[tokio::main]
1162    /// # async fn main() -> cot::Result<()> {
1163    /// let bootstrapper = Bootstrapper::new(MyProject)
1164    ///     .with_config(ProjectConfig::default())
1165    ///     .with_apps()
1166    ///     .boot()
1167    ///     .await?;
1168    /// let bootstrapped_project = bootstrapper.finish();
1169    /// # Ok(())
1170    /// # }
1171    /// ```
1172    pub async fn boot(self) -> cot::Result<Bootstrapper<Initialized>> {
1173        self.with_database().await?.boot().await
1174    }
1175
1176    /// Moves forward to the next phase of bootstrapping, the with-database
1177    /// phase.
1178    ///
1179    /// See the [`BootstrapPhase`] and [`WithDatabase`] documentation for more
1180    /// details.
1181    ///
1182    /// # Errors
1183    ///
1184    /// This method may return an error if it cannot initialize the database.
1185    ///
1186    /// # Examples
1187    ///
1188    /// ```
1189    /// use cot::config::ProjectConfig;
1190    /// use cot::project::{Bootstrapper, WithApps};
1191    /// use cot::{AppBuilder, Project};
1192    ///
1193    /// struct MyProject;
1194    /// impl Project for MyProject {}
1195    ///
1196    /// # #[tokio::main]
1197    /// # async fn main() -> cot::Result<()> {
1198    /// let bootstrapper = Bootstrapper::new(MyProject)
1199    ///     .with_config(ProjectConfig::default())
1200    ///     .with_apps()
1201    ///     .with_database()
1202    ///     .await?
1203    ///     .boot()
1204    ///     .await?;
1205    /// # Ok(())
1206    /// # }
1207    /// ```
1208    pub async fn with_database(self) -> cot::Result<Bootstrapper<WithDatabase>> {
1209        #[cfg(feature = "db")]
1210        let database = Self::init_database(&self.context.config.database).await?;
1211        let context = self.context.with_database(
1212            #[cfg(feature = "db")]
1213            database,
1214        );
1215
1216        Ok(Bootstrapper {
1217            project: self.project,
1218            context,
1219            handler: self.handler,
1220            error_handler: self.error_handler,
1221        })
1222    }
1223
1224    #[cfg(feature = "db")]
1225    async fn init_database(config: &DatabaseConfig) -> cot::Result<Option<Database>> {
1226        match &config.url {
1227            Some(url) => {
1228                let database = Database::new(url.as_str()).await?;
1229                Ok(Some(database))
1230            }
1231            None => Ok(None),
1232        }
1233    }
1234}
1235
1236impl Bootstrapper<WithDatabase> {
1237    /// Builds the Cot project instance.
1238    ///
1239    /// This is the final step in the bootstrapping process. It initializes the
1240    /// project with the given configuration and returns a [`Bootstrapper`]
1241    /// instance that contains the project's context and handler.
1242    ///
1243    /// You shouldn't have to use this method directly most of the time. It's
1244    /// mainly useful for controlling the bootstrapping process in custom
1245    /// [`CliTask`](cli::CliTask)s.
1246    ///
1247    /// # Errors
1248    ///
1249    /// This method may return an error if it cannot initialize any of the
1250    /// project's components, such as the database.
1251    ///
1252    /// # Examples
1253    ///
1254    /// ```
1255    /// use cot::config::ProjectConfig;
1256    /// use cot::{Bootstrapper, Project};
1257    ///
1258    /// struct MyProject;
1259    /// impl Project for MyProject {}
1260    ///
1261    /// # #[tokio::main]
1262    /// # async fn main() -> cot::Result<()> {
1263    /// let bootstrapper = Bootstrapper::new(MyProject)
1264    ///     .with_config(ProjectConfig::default())
1265    ///     .boot()
1266    ///     .await?;
1267    /// let bootstrapped_project = bootstrapper.finish();
1268    /// # Ok(())
1269    /// # }
1270    /// ```
1271    // Function marked `async` to be consistent with the other `boot` methods
1272    pub async fn boot(self) -> cot::Result<Bootstrapper<Initialized>> {
1273        self.with_cache().await?.boot().await
1274    }
1275
1276    /// Moves forward to the next phase of bootstrapping, the with-cache phase.
1277    ///
1278    /// See the [`BootstrapPhase`] and [`WithCache`] documentation for more
1279    /// details.
1280    ///
1281    /// # Errors
1282    ///
1283    /// This method may return an error if it cannot initialize the cache.
1284    ///
1285    /// # Examples
1286    ///
1287    /// ```
1288    /// use cot::config::ProjectConfig;
1289    /// use cot::project::{Bootstrapper, WithApps};
1290    /// use cot::{AppBuilder, Project};
1291    ///
1292    /// struct MyProject;
1293    /// impl Project for MyProject {}
1294    ///
1295    /// # #[tokio::main]
1296    /// # async fn main() -> cot::Result<()> {
1297    /// let bootstrapper = Bootstrapper::new(MyProject)
1298    ///     .with_config(ProjectConfig::default())
1299    ///     .with_apps()
1300    ///     .with_database()
1301    ///     .await?
1302    ///     .with_cache()
1303    ///     .await?
1304    ///     .boot()
1305    ///     .await?;
1306    /// # Ok(())
1307    /// # }
1308    /// ```
1309    #[allow(
1310        clippy::unused_async,
1311        clippy::allow_attributes,
1312        reason = "see https://github.com/cot-rs/cot/pull/399#discussion_r2430379966"
1313    )]
1314    pub async fn with_cache(self) -> cot::Result<Bootstrapper<WithCache>> {
1315        #[cfg(feature = "cache")]
1316        let cache = Self::init_cache(&self.context.config.cache).await?;
1317
1318        let context = self.context.with_cache(
1319            #[cfg(feature = "cache")]
1320            cache,
1321        );
1322
1323        Ok(Bootstrapper {
1324            project: self.project,
1325            context,
1326            handler: self.handler,
1327            error_handler: self.error_handler,
1328        })
1329    }
1330
1331    #[cfg(feature = "cache")]
1332    async fn init_cache(config: &CacheConfig) -> cot::Result<Cache> {
1333        let cache = Cache::from_config(config).await?;
1334        Ok(cache)
1335    }
1336}
1337
1338impl Bootstrapper<WithCache> {
1339    /// Builds the initialized Cot project instance.
1340    ///
1341    /// This is the final step in the bootstrapping process. It initializes the
1342    /// project with the given configuration and returns a [`Bootstrapper`]
1343    /// instance that contains the project's context and handler.
1344    ///
1345    /// You shouldn't have to use this method directly most of the time.
1346    /// It's mainly useful for controlling the bootstrapping process in
1347    /// custom [`CliTask`](cli::CliTask)s.
1348    ///
1349    /// # Errors
1350    ///
1351    /// This method may return an error if it cannot initialize any of the
1352    /// project's components, such as the database.
1353    ///
1354    /// # Examples
1355    ///
1356    /// ```
1357    /// use cot::config::ProjectConfig;
1358    /// use cot::{Bootstrapper, Project};
1359    ///
1360    /// struct MyProject;
1361    /// impl Project for MyProject {}
1362    ///
1363    /// # #[tokio::main]
1364    /// # async fn main() -> cot::Result<()> {
1365    /// let bootstrapper = Bootstrapper::new(MyProject)
1366    ///     .with_config(ProjectConfig::default())
1367    ///     .boot()
1368    ///     .await?;
1369    /// let bootstrapped_project = bootstrapper.finish();
1370    /// # Ok(())
1371    /// # }
1372    /// ```
1373    #[expect(
1374        clippy::unused_async,
1375        reason = "for consistency with other Bootstrapper::boot methods"
1376    )]
1377    pub async fn boot(self) -> cot::Result<Bootstrapper<Initialized>> {
1378        let router_service = RouterService::new(Arc::clone(&self.context.router));
1379        let handler_builder = RootHandlerBuilder {
1380            handler: router_service,
1381            error_handler: self.project.error_handler(),
1382        };
1383        let handler = self.project.middlewares(handler_builder, &self.context);
1384
1385        let auth_backend = self.project.auth_backend(&self.context);
1386        let context = self.context.with_auth(auth_backend);
1387
1388        Ok(Bootstrapper {
1389            project: self.project,
1390            context,
1391            handler: handler.handler,
1392            error_handler: handler.error_handler,
1393        })
1394    }
1395}
1396impl Bootstrapper<Initialized> {
1397    /// Returns the context and handlers of the bootstrapper.
1398    ///
1399    /// # Examples
1400    ///
1401    /// ```
1402    /// use cot::config::ProjectConfig;
1403    /// use cot::project::Bootstrapper;
1404    /// use cot::{Project, ProjectContext};
1405    ///
1406    /// struct MyProject;
1407    /// impl Project for MyProject {}
1408    ///
1409    /// # #[tokio::main]
1410    /// # async fn main() -> cot::Result<()> {
1411    /// let bootstrapper = Bootstrapper::new(MyProject)
1412    ///     .with_config(ProjectConfig::default())
1413    ///     .boot()
1414    ///     .await?;
1415    /// let bootstrapped_project = bootstrapper.finish();
1416    /// # Ok(())
1417    /// # }
1418    /// ```
1419    #[must_use]
1420    pub fn finish(self) -> BootstrappedProject {
1421        BootstrappedProject {
1422            context: self.context,
1423            handler: self.handler,
1424            error_handler: self.error_handler,
1425        }
1426    }
1427}
1428
1429/// A fully bootstrapped project with all components initialized and ready to
1430/// run.
1431///
1432/// This struct contains the final state of a project after the bootstrapping
1433/// process is complete. It includes the project context with all dependencies
1434/// initialized, as well as the request and error handlers that are ready to
1435/// process incoming requests.
1436///
1437/// # Examples
1438///
1439/// ```
1440/// use cot::config::ProjectConfig;
1441/// use cot::project::Bootstrapper;
1442/// use cot::{Project, ProjectContext};
1443///
1444/// struct MyProject;
1445/// impl Project for MyProject {}
1446///
1447/// # #[tokio::main]
1448/// # async fn main() -> cot::Result<()> {
1449/// let bootstrapper = Bootstrapper::new(MyProject)
1450///     .with_config(ProjectConfig::default())
1451///     .boot()
1452///     .await?;
1453/// let bootstrapped_project = bootstrapper.finish();
1454/// # Ok(())
1455/// # }
1456/// ```
1457#[derive(Debug)]
1458#[non_exhaustive]
1459pub struct BootstrappedProject {
1460    /// The fully initialized project context containing all dependencies and
1461    /// configuration.
1462    pub context: ProjectContext<Initialized>,
1463    /// The main request handler that processes incoming HTTP requests.
1464    pub handler: BoxedHandler,
1465    /// The error handler that processes errors that occur during request
1466    /// handling.
1467    pub error_handler: BoxedHandler,
1468}
1469
1470mod sealed {
1471    pub trait Sealed {}
1472}
1473
1474/// A trait that represents the different phases of the bootstrapper.
1475///
1476/// This trait is used to define the types for the different phases of the
1477/// bootstrapper. It's used to ensure that you can't access nonexistent
1478/// data until the bootstrapper has reached the corresponding phase.
1479///
1480/// # Order of phases
1481///
1482/// 1. [`Uninitialized`]
1483/// 2. [`WithConfig`]
1484/// 3. [`WithApps`]
1485/// 4. [`WithDatabase`]
1486/// 5. [`WithCache`]
1487/// 6. [`Initialized`]
1488///
1489/// # Sealed
1490///
1491/// This trait is sealed and can't be implemented outside the `cot`
1492/// crate.
1493///
1494/// # Examples
1495///
1496/// ```
1497/// use cot::project::{MiddlewareContext, RegisterAppsContext, RootHandler, RootHandlerBuilder};
1498/// use cot::{AppBuilder, Project};
1499///
1500/// struct MyProject;
1501/// impl Project for MyProject {
1502///     // `WithConfig` phase here
1503///     fn register_apps(&self, apps: &mut AppBuilder, context: &RegisterAppsContext) {
1504///         unimplemented!();
1505///     }
1506///
1507///     // `WithDatabase` phase here (which comes after `WithConfig`)
1508///     fn middlewares(
1509///         &self,
1510///         handler: RootHandlerBuilder,
1511///         context: &MiddlewareContext,
1512///     ) -> RootHandler {
1513///         unimplemented!()
1514///     }
1515/// }
1516/// ```
1517pub trait BootstrapPhase: sealed::Sealed {
1518    // Bootstrapper types
1519    /// The type of the request handler.
1520    type RequestHandler: Debug;
1521    /// The type of the error handler.
1522    type ErrorHandler: Debug;
1523
1524    // App context types
1525    /// The type of the configuration.
1526    type Config: Debug;
1527    /// The type of the email service.
1528    #[cfg(feature = "email")]
1529    type Email: Debug;
1530
1531    /// The type of the apps.
1532    type Apps;
1533    /// The type of the router.
1534    type Router: Debug;
1535    /// The type of the database.
1536    #[cfg(feature = "db")]
1537    type Database: Debug;
1538    /// The type of the auth backend.
1539    type AuthBackend;
1540    /// The type of the cache.
1541    #[cfg(feature = "cache")]
1542    type Cache: Debug;
1543}
1544
1545/// First phase of bootstrapping a Cot project, the uninitialized phase.
1546///
1547/// # See also
1548///
1549/// See the details about the different bootstrap phases in the
1550/// [`BootstrapPhase`] trait documentation.
1551#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
1552pub enum Uninitialized {}
1553
1554impl sealed::Sealed for Uninitialized {}
1555impl BootstrapPhase for Uninitialized {
1556    type RequestHandler = ();
1557    type ErrorHandler = ();
1558    type Config = ();
1559    #[cfg(feature = "email")]
1560    type Email = ();
1561    type Apps = ();
1562    type Router = ();
1563    #[cfg(feature = "db")]
1564    type Database = ();
1565    type AuthBackend = ();
1566    #[cfg(feature = "cache")]
1567    type Cache = ();
1568}
1569
1570/// Second phase of bootstrapping a Cot project, the with-config phase.
1571///
1572/// # See also
1573///
1574/// See the details about the different bootstrap phases in the
1575/// [`BootstrapPhase`] trait documentation.
1576#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
1577pub enum WithConfig {}
1578
1579impl sealed::Sealed for WithConfig {}
1580impl BootstrapPhase for WithConfig {
1581    type RequestHandler = ();
1582    type ErrorHandler = ();
1583    type Config = Arc<ProjectConfig>;
1584    #[cfg(feature = "email")]
1585    type Email = Email;
1586    type Apps = ();
1587    type Router = ();
1588    #[cfg(feature = "db")]
1589    type Database = ();
1590    type AuthBackend = ();
1591    #[cfg(feature = "cache")]
1592    type Cache = ();
1593}
1594
1595/// Third phase of bootstrapping a Cot project, the with-apps phase.
1596///
1597/// # See also
1598///
1599/// See the details about the different bootstrap phases in the
1600/// [`BootstrapPhase`] trait documentation.
1601#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
1602pub enum WithApps {}
1603
1604impl sealed::Sealed for WithApps {}
1605impl BootstrapPhase for WithApps {
1606    type RequestHandler = ();
1607    type ErrorHandler = ();
1608    type Config = <WithConfig as BootstrapPhase>::Config;
1609    #[cfg(feature = "email")]
1610    type Email = <WithConfig as BootstrapPhase>::Email;
1611    type Apps = Vec<Box<dyn App>>;
1612    type Router = Arc<Router>;
1613    #[cfg(feature = "db")]
1614    type Database = ();
1615    type AuthBackend = ();
1616    #[cfg(feature = "cache")]
1617    type Cache = ();
1618}
1619
1620/// Fourth phase of bootstrapping a Cot project, the with-database phase.
1621///
1622/// # See also
1623///
1624/// See the details about the different bootstrap phases in the
1625/// [`BootstrapPhase`] trait documentation.
1626#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
1627pub enum WithDatabase {}
1628
1629impl sealed::Sealed for WithDatabase {}
1630impl BootstrapPhase for WithDatabase {
1631    type RequestHandler = ();
1632    type ErrorHandler = ();
1633    type Config = <WithApps as BootstrapPhase>::Config;
1634    #[cfg(feature = "email")]
1635    type Email = <WithApps as BootstrapPhase>::Email;
1636    type Apps = <WithApps as BootstrapPhase>::Apps;
1637    type Router = <WithApps as BootstrapPhase>::Router;
1638    #[cfg(feature = "db")]
1639    type Database = Option<Database>;
1640    type AuthBackend = <WithApps as BootstrapPhase>::AuthBackend;
1641    #[cfg(feature = "cache")]
1642    type Cache = ();
1643}
1644
1645/// Fifth phase of bootstrapping a Cot project, the with-cache phase.
1646///
1647/// # See also
1648///
1649/// See the details about the different bootstrap phases in the
1650/// [`BootstrapPhase`] trait documentation.
1651#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
1652pub enum WithCache {}
1653
1654impl sealed::Sealed for WithCache {}
1655impl BootstrapPhase for WithCache {
1656    type RequestHandler = ();
1657    type ErrorHandler = ();
1658    type Config = <WithApps as BootstrapPhase>::Config;
1659    #[cfg(feature = "email")]
1660    type Email = <WithApps as BootstrapPhase>::Email;
1661    type Apps = <WithApps as BootstrapPhase>::Apps;
1662    type Router = <WithApps as BootstrapPhase>::Router;
1663    #[cfg(feature = "db")]
1664    type Database = <WithDatabase as BootstrapPhase>::Database;
1665    type AuthBackend = <WithApps as BootstrapPhase>::AuthBackend;
1666    #[cfg(feature = "cache")]
1667    type Cache = Cache;
1668}
1669
1670/// The final phase of bootstrapping a Cot project, the initialized phase.
1671///
1672/// # See also
1673///
1674/// See the details about the different bootstrap phases in the
1675/// [`BootstrapPhase`] trait documentation.
1676#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
1677pub enum Initialized {}
1678
1679impl sealed::Sealed for Initialized {}
1680impl BootstrapPhase for Initialized {
1681    type RequestHandler = BoxedHandler;
1682    type ErrorHandler = BoxedHandler;
1683    type Config = <WithDatabase as BootstrapPhase>::Config;
1684    #[cfg(feature = "email")]
1685    type Email = <WithDatabase as BootstrapPhase>::Email;
1686    type Apps = <WithDatabase as BootstrapPhase>::Apps;
1687    type Router = <WithDatabase as BootstrapPhase>::Router;
1688    #[cfg(feature = "db")]
1689    type Database = <WithDatabase as BootstrapPhase>::Database;
1690    type AuthBackend = Arc<dyn AuthBackend>;
1691    #[cfg(feature = "cache")]
1692    type Cache = <WithCache as BootstrapPhase>::Cache;
1693}
1694
1695/// Shared context and configs for all apps. Used in conjunction with the
1696/// [`Project`] trait.
1697#[derive(Debug)]
1698pub struct ProjectContext<S: BootstrapPhase = Initialized> {
1699    config: S::Config,
1700    #[debug("..")]
1701    apps: S::Apps,
1702    router: S::Router,
1703    #[cfg(feature = "db")]
1704    database: S::Database,
1705    #[debug("..")]
1706    auth_backend: S::AuthBackend,
1707    #[cfg(feature = "cache")]
1708    cache: S::Cache,
1709    #[cfg(feature = "email")]
1710    email: S::Email,
1711}
1712
1713impl ProjectContext<Uninitialized> {
1714    #[must_use]
1715    pub(crate) const fn new() -> Self {
1716        Self {
1717            config: (),
1718            apps: (),
1719            router: (),
1720            #[cfg(feature = "db")]
1721            database: (),
1722            auth_backend: (),
1723            #[cfg(feature = "cache")]
1724            cache: (),
1725            #[cfg(feature = "email")]
1726            email: (),
1727        }
1728    }
1729
1730    fn with_config(self, config: ProjectConfig) -> ProjectContext<WithConfig> {
1731        #[cfg(feature = "email")]
1732        let email = {
1733            Email::from_config(&config.email).unwrap_or_else(|err| {
1734                panic!("failed to initialize email service: {err:?}");
1735            })
1736        };
1737
1738        ProjectContext {
1739            config: Arc::new(config),
1740            apps: self.apps,
1741            router: self.router,
1742            #[cfg(feature = "db")]
1743            database: self.database,
1744            auth_backend: self.auth_backend,
1745            #[cfg(feature = "cache")]
1746            cache: self.cache,
1747            #[cfg(feature = "email")]
1748            email,
1749        }
1750    }
1751}
1752
1753impl<S: BootstrapPhase<Config = Arc<ProjectConfig>>> ProjectContext<S> {
1754    /// Returns the configuration for the project.
1755    ///
1756    /// # Examples
1757    ///
1758    /// ```
1759    /// use cot::request::{Request, RequestExt};
1760    /// use cot::response::Response;
1761    ///
1762    /// async fn index(request: Request) -> cot::Result<Response> {
1763    ///     let config = request.context().config();
1764    ///     // can also be accessed via:
1765    ///     let config = request.project_config();
1766    ///
1767    ///     let db_url = &config.database.url;
1768    ///
1769    ///     // ...
1770    /// #    unimplemented!()
1771    /// }
1772    /// ```
1773    #[must_use]
1774    pub fn config(&self) -> &ProjectConfig {
1775        &self.config
1776    }
1777}
1778
1779impl ProjectContext<WithConfig> {
1780    #[must_use]
1781    fn with_apps(self, apps: Vec<Box<dyn App>>, router: Arc<Router>) -> ProjectContext<WithApps> {
1782        ProjectContext {
1783            config: self.config,
1784            apps,
1785            router,
1786            #[cfg(feature = "db")]
1787            database: self.database,
1788            auth_backend: self.auth_backend,
1789            #[cfg(feature = "cache")]
1790            cache: self.cache,
1791            #[cfg(feature = "email")]
1792            email: self.email,
1793        }
1794    }
1795}
1796
1797impl<S: BootstrapPhase<Apps = Vec<Box<dyn App>>>> ProjectContext<S> {
1798    /// Returns the apps for the project.
1799    ///
1800    /// # Examples
1801    ///
1802    /// ```
1803    /// use cot::request::{Request, RequestExt};
1804    /// use cot::response::Response;
1805    ///
1806    /// async fn index(request: Request) -> cot::Result<Response> {
1807    ///     let apps = request.context().apps();
1808    ///
1809    ///     // ...
1810    /// #    unimplemented!()
1811    /// }
1812    /// ```
1813    #[must_use]
1814    pub fn apps(&self) -> &[Box<dyn App>] {
1815        &self.apps
1816    }
1817}
1818
1819impl ProjectContext<WithApps> {
1820    #[must_use]
1821    fn with_database(
1822        self,
1823        #[cfg(feature = "db")] database: Option<Database>,
1824    ) -> ProjectContext<WithDatabase> {
1825        ProjectContext {
1826            config: self.config,
1827            apps: self.apps,
1828            router: self.router,
1829            #[cfg(feature = "db")]
1830            database,
1831            auth_backend: self.auth_backend,
1832            #[cfg(feature = "cache")]
1833            cache: self.cache,
1834            #[cfg(feature = "email")]
1835            email: self.email,
1836        }
1837    }
1838}
1839
1840impl ProjectContext<WithDatabase> {
1841    #[must_use]
1842    fn with_cache(self, #[cfg(feature = "cache")] cache: Cache) -> ProjectContext<WithCache> {
1843        ProjectContext {
1844            config: self.config,
1845            apps: self.apps,
1846            router: self.router,
1847            auth_backend: self.auth_backend,
1848            #[cfg(feature = "db")]
1849            database: self.database,
1850            #[cfg(feature = "cache")]
1851            cache,
1852            #[cfg(feature = "email")]
1853            email: self.email,
1854        }
1855    }
1856}
1857
1858impl ProjectContext<WithCache> {
1859    #[must_use]
1860    fn with_auth(self, auth_backend: Arc<dyn AuthBackend>) -> ProjectContext<Initialized> {
1861        ProjectContext {
1862            config: self.config,
1863            apps: self.apps,
1864            router: self.router,
1865            auth_backend,
1866            #[cfg(feature = "db")]
1867            database: self.database,
1868            #[cfg(feature = "cache")]
1869            cache: self.cache,
1870            #[cfg(feature = "email")]
1871            email: self.email,
1872        }
1873    }
1874}
1875impl ProjectContext<Initialized> {
1876    #[cfg(feature = "test")]
1877    pub(crate) fn initialized(
1878        config: <Initialized as BootstrapPhase>::Config,
1879        apps: <Initialized as BootstrapPhase>::Apps,
1880        router: <Initialized as BootstrapPhase>::Router,
1881        auth_backend: <Initialized as BootstrapPhase>::AuthBackend,
1882        #[cfg(feature = "db")] database: <Initialized as BootstrapPhase>::Database,
1883        #[cfg(feature = "cache")] cache: <Initialized as BootstrapPhase>::Cache,
1884        #[cfg(feature = "email")] email: <Initialized as BootstrapPhase>::Email,
1885    ) -> Self {
1886        Self {
1887            config,
1888            apps,
1889            router,
1890            #[cfg(feature = "db")]
1891            database,
1892            auth_backend,
1893            #[cfg(feature = "cache")]
1894            cache,
1895            #[cfg(feature = "email")]
1896            email,
1897        }
1898    }
1899}
1900
1901impl<S: BootstrapPhase<Router = Arc<Router>>> ProjectContext<S> {
1902    /// Returns the router for the project.
1903    ///
1904    /// # Examples
1905    ///
1906    /// ```
1907    /// use cot::request::{Request, RequestExt};
1908    /// use cot::response::Response;
1909    ///
1910    /// async fn index(request: Request) -> cot::Result<Response> {
1911    ///     let router = request.context().config();
1912    ///     // can also be accessed via:
1913    ///     let router = request.router();
1914    ///
1915    ///     let num_routes = router.routes().len();
1916    ///
1917    ///     // ...
1918    /// #    unimplemented!()
1919    /// }
1920    /// ```
1921    #[must_use]
1922    pub fn router(&self) -> &Arc<Router> {
1923        &self.router
1924    }
1925}
1926impl<S: BootstrapPhase<AuthBackend = Arc<dyn AuthBackend>>> ProjectContext<S> {
1927    /// Returns the authentication backend for the project.
1928    ///
1929    /// # Examples
1930    ///
1931    /// ```
1932    /// use cot::request::{Request, RequestExt};
1933    /// use cot::response::Response;
1934    ///
1935    /// async fn index(request: Request) -> cot::Result<Response> {
1936    ///     let auth_backend = request.context().auth_backend();
1937    ///     // ...
1938    /// #    unimplemented!()
1939    /// }
1940    /// ```
1941    #[must_use]
1942    pub fn auth_backend(&self) -> &Arc<dyn AuthBackend> {
1943        &self.auth_backend
1944    }
1945}
1946
1947#[cfg(feature = "email")]
1948impl<S: BootstrapPhase<Email = Email>> ProjectContext<S> {
1949    #[must_use]
1950    /// Returns the email service for the project.
1951    ///
1952    /// # Examples
1953    ///
1954    /// ```
1955    /// use cot::request::{Request, RequestExt};
1956    /// use cot::response::Response;
1957    ///
1958    /// async fn index(request: Request) -> cot::Result<Response> {
1959    ///     let email = request.context().email();
1960    ///     // ...
1961    /// #    unimplemented!()
1962    /// }
1963    /// ```
1964    pub fn email(&self) -> &Email {
1965        &self.email
1966    }
1967}
1968
1969#[cfg(feature = "cache")]
1970impl<S: BootstrapPhase<Cache = Cache>> ProjectContext<S> {
1971    /// Returns the cache for the project.
1972    ///
1973    /// # Examples
1974    ///
1975    /// ```
1976    /// use cot::request::{Request, RequestExt};
1977    /// use cot::response::Response;
1978    ///
1979    /// async fn index(request: Request) -> cot::Result<Response> {
1980    ///     let cache = request.context().cache();
1981    ///     // ...
1982    /// #    unimplemented!()
1983    /// }
1984    /// ```
1985    #[must_use]
1986    #[cfg(feature = "cache")]
1987    pub fn cache(&self) -> &Cache {
1988        &self.cache
1989    }
1990}
1991
1992#[cfg(feature = "db")]
1993impl<S: BootstrapPhase<Database = Option<Database>>> ProjectContext<S> {
1994    /// Returns the database for the project, if it is enabled.
1995    ///
1996    /// # Examples
1997    ///
1998    /// ```
1999    /// use cot::request::{Request, RequestExt};
2000    /// use cot::response::Response;
2001    ///
2002    /// async fn index(request: Request) -> cot::Result<Response> {
2003    ///     let database = request.context().try_database();
2004    ///     if let Some(database) = database {
2005    ///         // do something with the database
2006    ///     } else {
2007    ///         // database is not enabled
2008    ///     }
2009    /// #    unimplemented!()
2010    /// }
2011    /// ```
2012    #[must_use]
2013    #[cfg(feature = "db")]
2014    pub fn try_database(&self) -> Option<&Database> {
2015        self.database.as_ref()
2016    }
2017
2018    /// Returns the database for the project, if it is enabled.
2019    ///
2020    /// # Panics
2021    ///
2022    /// This method panics if the database is not enabled.
2023    ///
2024    /// # Examples
2025    ///
2026    /// ```
2027    /// use cot::request::{Request, RequestExt};
2028    /// use cot::response::Response;
2029    ///
2030    /// async fn index(request: Request) -> cot::Result<Response> {
2031    ///     let database = request.context().database();
2032    ///     // use the database
2033    /// #    unimplemented!()
2034    /// }
2035    /// ```
2036    #[cfg(feature = "db")]
2037    #[must_use]
2038    #[track_caller]
2039    pub fn database(&self) -> &Database {
2040        self.try_database().expect(
2041            "Database missing. Did you forget to add the database when configuring cot::Project?",
2042        )
2043    }
2044}
2045
2046/// Runs the Cot project on the given address.
2047///
2048/// This function takes a Cot project and an address string and runs the
2049/// project on the given address.
2050///
2051/// # Errors
2052///
2053/// This function returns an error if the server fails to start.
2054pub async fn run(bootstrapper: Bootstrapper<Initialized>, address_str: &str) -> cot::Result<()> {
2055    let listener = tokio::net::TcpListener::bind(address_str)
2056        .await
2057        .map_err(StartServerError)?;
2058
2059    run_at(bootstrapper, listener).await
2060}
2061
2062/// Runs the Cot project on the given listener.
2063///
2064/// This function takes a Cot project and a [`tokio::net::TcpListener`] and
2065/// runs the project on the given listener.
2066///
2067/// If you need more control over the server listening socket, such as modifying
2068/// the underlying buffer sizes, you can create a [`tokio::net::TcpListener`]
2069/// and pass it to this function. Otherwise, the [`run`] function will be more
2070/// convenient.
2071///
2072/// # Errors
2073///
2074/// This function returns an error if the server fails to start.
2075pub async fn run_at(
2076    bootstrapper: Bootstrapper<Initialized>,
2077    listener: tokio::net::TcpListener,
2078) -> cot::Result<()> {
2079    run_at_with_shutdown(bootstrapper, listener, shutdown_signal()).await
2080}
2081
2082/// Runs the Cot project on the given listener.
2083///
2084/// This function takes a Cot project and a [`tokio::net::TcpListener`] and
2085/// runs the project on the given listener, similarly to the [`run_at`]
2086/// function. In addition to that, it takes a shutdown signal that can be used
2087/// to gracefully shut down the server in a response to a signal or other event.
2088///
2089/// If you don't need to customize shutdown signal handling, you should instead
2090/// use the [`run`] or [`run_at`] functions, as they are more convenient.
2091///
2092/// # Errors
2093///
2094/// This function returns an error if the server fails to start.
2095pub async fn run_at_with_shutdown(
2096    bootstrapper: Bootstrapper<Initialized>,
2097    listener: tokio::net::TcpListener,
2098    shutdown_signal: impl Future<Output = ()> + Send + 'static,
2099) -> cot::Result<()> {
2100    let BootstrappedProject {
2101        mut context,
2102        mut handler,
2103        mut error_handler,
2104    } = bootstrapper.finish();
2105
2106    #[cfg(feature = "db")]
2107    if let Some(database) = &context.database {
2108        let mut migrations: Vec<Box<SyncDynMigration>> = Vec::new();
2109        for app in &context.apps {
2110            migrations.extend(app.migrations());
2111        }
2112        let migration_engine = MigrationEngine::new(migrations)?;
2113        migration_engine.run(database).await?;
2114    }
2115
2116    let mut apps = std::mem::take(&mut context.apps);
2117    for app in &mut apps {
2118        info!("Initializing app: {}", app.name());
2119
2120        app.init(&mut context).await?;
2121    }
2122    context.apps = apps;
2123
2124    let context = Arc::new(context);
2125    let is_debug = context.config().debug;
2126    let register_panic_hook = context.config().register_panic_hook;
2127    #[cfg(feature = "db")]
2128    let context_cleanup = context.clone();
2129
2130    let handler = move |axum_request: axum::extract::Request| async move {
2131        // todo root tracing span
2132        // todo per-router error handlers
2133        let request = request_axum_to_cot(axum_request, Arc::clone(&context));
2134        let (head, request) = request.into_parts();
2135        let head_for_error_handler = head.clone();
2136        let request = Request::from_parts(head, request);
2137
2138        let (request_head, request) = request_parts_for_diagnostics(request);
2139
2140        let catch_unwind_response = AssertUnwindSafe(pass_to_axum(request, &mut handler))
2141            .catch_unwind()
2142            .await;
2143
2144        let response: Result<axum::response::Response, ErrorResponse> = match catch_unwind_response
2145        {
2146            Ok(Ok(response)) => Ok(response),
2147            Ok(Err(error)) => Err(ErrorResponse::ErrorReturned(error)),
2148            Err(error) => Err(ErrorResponse::Panic(error)),
2149        };
2150
2151        match response {
2152            Ok(response) => response,
2153            Err(error_response) => {
2154                if is_debug && accepts_html(request_head.as_ref()) {
2155                    let diagnostics = Diagnostics::new(
2156                        context.config().clone(),
2157                        Arc::clone(&context.router),
2158                        request_head,
2159                    );
2160
2161                    build_cot_error_page(error_response, &diagnostics)
2162                } else {
2163                    build_custom_error_page(
2164                        &mut error_handler,
2165                        error_response,
2166                        head_for_error_handler,
2167                    )
2168                    .await
2169                }
2170            }
2171        }
2172    };
2173
2174    eprintln!(
2175        "Starting the server at http://{}",
2176        listener.local_addr().map_err(StartServerError)?
2177    );
2178
2179    if register_panic_hook {
2180        let current_hook = std::panic::take_hook();
2181        let new_hook = move |hook_info: &std::panic::PanicHookInfo<'_>| {
2182            current_hook(hook_info);
2183            error_page::error_page_panic_hook(hook_info);
2184        };
2185        std::panic::set_hook(Box::new(new_hook));
2186    }
2187    axum::serve(listener, handler.into_make_service())
2188        .with_graceful_shutdown(shutdown_signal)
2189        .await
2190        .map_err(StartServerError)?;
2191    if register_panic_hook {
2192        let _ = std::panic::take_hook();
2193    }
2194    #[cfg(feature = "db")]
2195    if let Some(database) = &context_cleanup.database {
2196        database.close().await?;
2197    }
2198
2199    Ok(())
2200}
2201
2202#[derive(Debug, Error)]
2203#[error("failed to start the server: {0}")]
2204pub(crate) struct StartServerError(#[from] pub(crate) std::io::Error);
2205
2206impl From<StartServerError> for Error {
2207    fn from(error: StartServerError) -> Self {
2208        Error::wrap(error)
2209    }
2210}
2211
2212fn accepts_html(head: Option<&RequestHead>) -> bool {
2213    head.and_then(|p| p.headers.get(http::header::ACCEPT))
2214        .is_some_and(|accept| {
2215            let value = accept.to_str().unwrap_or_default();
2216            let accept = AcceptHeaderParser::parse(value);
2217            // we check if the "Accept" header contains "text/html" explicitly
2218            // we ignore wildcards, such as "*/*", because they are
2219            // sent by tools like curl as well
2220            accept.contains_explicit(&mime::TEXT_HTML)
2221        })
2222}
2223
2224enum ErrorResponse {
2225    ErrorReturned(Error),
2226    Panic(Box<dyn std::any::Any + Send>),
2227}
2228
2229fn build_cot_error_page(
2230    error_response: ErrorResponse,
2231    diagnostics: &Diagnostics,
2232) -> axum::response::Response {
2233    match error_response {
2234        ErrorResponse::ErrorReturned(error) => {
2235            error_page::handle_response_error(&error, diagnostics)
2236        }
2237        ErrorResponse::Panic(error) => error_page::handle_response_panic(&error, diagnostics),
2238    }
2239}
2240
2241async fn build_custom_error_page(
2242    error_handler: &mut BoxCloneSyncService<Request, Response, Error>,
2243    error_response: ErrorResponse,
2244    mut request_head: RequestHead,
2245) -> axum::response::Response {
2246    let error = match error_response {
2247        ErrorResponse::ErrorReturned(error) => error,
2248        ErrorResponse::Panic(payload) => Error::from(UncaughtPanic::new(payload)),
2249    };
2250
2251    prepare_request_for_error_handler(&mut request_head, error);
2252
2253    let poll_status = poll_fn(|cx| error_handler.poll_ready(cx)).await;
2254    if let Err(error) = poll_status {
2255        error!(
2256            ?error,
2257            "Error occurred when polling the server error handler for readiness; \
2258            returning default error page"
2259        );
2260
2261        return error_page::build_cot_server_error_page();
2262    }
2263
2264    let request = Request::from_parts(request_head, Body::empty());
2265    let response = error_handler.call(request).await;
2266    response.map_or_else(
2267        |error| {
2268            error!(
2269                ?error,
2270                "Error occurred while running custom server error handler; \
2271                returning default error page"
2272            );
2273
2274            error_page::build_cot_server_error_page()
2275        },
2276        response_cot_to_axum,
2277    )
2278}
2279
2280pub(crate) fn prepare_request_for_error_handler(request_head: &mut RequestHead, error: Error) {
2281    request_head
2282        .extensions
2283        .insert(RequestOuterError::new(error));
2284}
2285
2286/// Runs the CLI for the given project.
2287///
2288/// This function takes a [`Project`] and runs the CLI for the project. You
2289/// typically don't need to call this function directly. Instead, you can use
2290/// [`cot::main`] which is a more ergonomic way to run the CLI.
2291///
2292/// # Errors
2293///
2294/// This function returns an error if the CLI command fails to execute.
2295///
2296/// # Examples
2297///
2298/// ```no_run
2299/// use cot::{App, Project, run_cli};
2300///
2301/// struct MyProject;
2302/// impl Project for MyProject {}
2303///
2304/// # #[tokio::main]
2305/// # async fn main() -> cot::Result<()> {
2306/// run_cli(MyProject).await?;
2307/// # Ok(())
2308/// # }
2309/// ```
2310#[expect(clippy::future_not_send)] // Send not needed; CLI is run async in a single thread
2311pub async fn run_cli(project: impl Project + Send + 'static) -> cot::Result<()> {
2312    Bootstrapper::new(project).run_cli().await
2313}
2314
2315fn request_parts_for_diagnostics(request: Request) -> (Option<RequestHead>, Request) {
2316    if request.project_config().debug {
2317        let (head, body) = request.into_parts();
2318        let parts_clone = head.clone();
2319        let request = Request::from_parts(head, body);
2320        (Some(parts_clone), request)
2321    } else {
2322        (None, request)
2323    }
2324}
2325
2326fn request_axum_to_cot(
2327    axum_request: axum::extract::Request,
2328    context: Arc<ProjectContext>,
2329) -> Request {
2330    let mut request = axum_request.map(Body::axum);
2331    prepare_request(&mut request, context);
2332    request
2333}
2334
2335pub(crate) fn prepare_request(request: &mut Request, context: Arc<ProjectContext>) {
2336    request.extensions_mut().insert(context);
2337}
2338
2339async fn pass_to_axum(
2340    request: Request,
2341    handler: &mut BoxedHandler,
2342) -> cot::Result<axum::response::Response> {
2343    poll_fn(|cx| handler.poll_ready(cx)).await?;
2344    let response = handler.call(request).await?;
2345
2346    Ok(response_cot_to_axum(response))
2347}
2348
2349fn response_cot_to_axum(response: Response) -> axum::response::Response {
2350    response.map(axum::body::Body::new)
2351}
2352
2353async fn shutdown_signal() {
2354    let ctrl_c = async {
2355        tokio::signal::ctrl_c()
2356            .await
2357            .expect("failed to install Ctrl+C handler");
2358    };
2359
2360    #[cfg(unix)]
2361    let terminate = async {
2362        tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
2363            .expect("failed to install signal handler")
2364            .recv()
2365            .await;
2366    };
2367
2368    #[cfg(not(unix))]
2369    let terminate = std::future::pending::<()>();
2370
2371    tokio::select! {
2372        () = ctrl_c => {},
2373        () = terminate => {},
2374    }
2375}
2376
2377#[cfg(test)]
2378mod tests {
2379    use std::task::{Context, Poll};
2380
2381    use cot::cache;
2382    use tower::util::MapResultLayer;
2383    use tower::{ServiceExt, service_fn};
2384
2385    use super::*;
2386    use crate::StatusCode;
2387    use crate::auth::UserId;
2388    use crate::config::{SecretKey, Timeout};
2389    use crate::error::handler::{RequestError, RequestOuterError};
2390    use crate::html::Html;
2391    use crate::request::extractors::FromRequestHead;
2392    use crate::test::serial_guard;
2393
2394    struct TestApp;
2395
2396    impl App for TestApp {
2397        fn name(&self) -> &'static str {
2398            "mock"
2399        }
2400    }
2401
2402    #[cot::test]
2403    async fn app_default_impl() {
2404        let app = TestApp {};
2405        assert_eq!(app.name(), "mock");
2406        assert_eq!(app.router().routes().len(), 0);
2407        assert_eq!(app.migrations().len(), 0);
2408    }
2409
2410    struct TestProject;
2411    impl Project for TestProject {}
2412
2413    #[test]
2414    fn project_default_cli_metadata() {
2415        let metadata = TestProject.cli_metadata();
2416
2417        assert_eq!(metadata.name, "cot");
2418        assert_eq!(metadata.version, env!("CARGO_PKG_VERSION"));
2419        assert_eq!(metadata.authors, env!("CARGO_PKG_AUTHORS"));
2420        assert_eq!(metadata.description, env!("CARGO_PKG_DESCRIPTION"));
2421    }
2422
2423    #[cot::test]
2424    async fn root_handler_builder_middleware() {
2425        // we create a root handler that returns an error for all requests
2426        // then we apply a middleware that returns a fixed "ok" response
2427        let root_handler_builder = RootHandlerBuilder {
2428            handler: service_fn(|_: Request| async move {
2429                Err::<Response, _>(Error::internal("error"))
2430            }),
2431            error_handler: service_fn(|_: Request| async move {
2432                Err::<Response, _>(Error::internal("error"))
2433            }),
2434        };
2435
2436        let root_handler = root_handler_builder
2437            .middleware(make_ok_middleware())
2438            .build();
2439
2440        expect_handler_ok(root_handler.handler).await;
2441        expect_handler_ok(root_handler.error_handler).await;
2442    }
2443
2444    #[cot::test]
2445    async fn root_handler_builder_main_handler_middleware() {
2446        let root_handler_builder = RootHandlerBuilder {
2447            handler: service_fn(|_: Request| async move {
2448                Err::<Response, _>(Error::internal("error"))
2449            }),
2450            error_handler: service_fn(|_: Request| async move {
2451                Err::<Response, _>(Error::internal("error"))
2452            }),
2453        };
2454
2455        let root_handler = root_handler_builder
2456            .main_handler_middleware(make_ok_middleware())
2457            .build();
2458
2459        expect_handler_ok(root_handler.handler).await;
2460        expect_handler_error(root_handler.error_handler).await;
2461    }
2462
2463    #[cot::test]
2464    async fn root_handler_builder_error_handler_middleware() {
2465        let root_handler_builder = RootHandlerBuilder {
2466            handler: service_fn(|_: Request| async move {
2467                Err::<Response, _>(Error::internal("error"))
2468            }),
2469            error_handler: service_fn(|_: Request| async move {
2470                Err::<Response, _>(Error::internal("error"))
2471            }),
2472        };
2473
2474        let root_handler = root_handler_builder
2475            .error_handler_middleware(make_ok_middleware())
2476            .build();
2477
2478        expect_handler_error(root_handler.handler).await;
2479        expect_handler_ok(root_handler.error_handler).await;
2480    }
2481
2482    #[expect(clippy::type_complexity, reason = "test function")]
2483    fn make_ok_middleware() -> MapResultLayer<fn(Result<Response, Error>) -> Result<Response, Error>>
2484    {
2485        MapResultLayer::new(|_| Ok(Response::new(Body::fixed("Hello, world!"))))
2486    }
2487
2488    async fn expect_handler_ok(handler: BoxedHandler) {
2489        let response = handler.oneshot(Request::default()).await;
2490        assert!(response.is_ok());
2491        assert_eq!(response.unwrap().status(), StatusCode::OK);
2492    }
2493
2494    async fn expect_handler_error(handler: BoxedHandler) {
2495        let response = handler.oneshot(Request::default()).await;
2496        assert!(response.is_err());
2497    }
2498
2499    #[cfg(feature = "live-reload")]
2500    #[cot::test]
2501    async fn project_middlewares() {
2502        struct TestProject;
2503        impl Project for TestProject {
2504            fn config(&self, _config_name: &str) -> cot::Result<ProjectConfig> {
2505                Ok(ProjectConfig::default())
2506            }
2507
2508            fn middlewares(
2509                &self,
2510                handler: RootHandlerBuilder,
2511                context: &MiddlewareContext,
2512            ) -> RootHandler {
2513                handler
2514                    .middleware(crate::static_files::StaticFilesMiddleware::from_context(
2515                        context,
2516                    ))
2517                    .middleware(crate::middleware::LiveReloadMiddleware::from_context(
2518                        context,
2519                    ))
2520                    .build()
2521            }
2522        }
2523
2524        let response = crate::test::Client::new(TestProject)
2525            .await
2526            .get("/")
2527            .await
2528            .unwrap();
2529        assert_eq!(response.status(), StatusCode::NOT_FOUND);
2530    }
2531
2532    #[test]
2533    fn project_default_config() {
2534        let temp_dir = tempfile::tempdir().unwrap();
2535
2536        let config_dir = temp_dir.path().join("config");
2537        std::fs::create_dir(&config_dir).unwrap();
2538        let config = r#"
2539            debug = false
2540            secret_key = "123abc"
2541        "#;
2542
2543        let config_file_path = config_dir.as_path().join("dev.toml");
2544        std::fs::write(config_file_path, config).unwrap();
2545
2546        // ensure the tests run sequentially when setting the current directory
2547        let _guard = serial_guard();
2548
2549        std::env::set_current_dir(&temp_dir).unwrap();
2550        let config = TestProject.config("dev").unwrap();
2551
2552        assert!(!config.debug);
2553        assert_eq!(config.secret_key, SecretKey::from("123abc".to_string()));
2554    }
2555
2556    #[test]
2557    fn project_default_register_apps() {
2558        let mut apps = AppBuilder::new();
2559        let context = ProjectContext::new().with_config(ProjectConfig::default());
2560
2561        TestProject.register_apps(&mut apps, &context);
2562
2563        assert!(apps.apps.is_empty());
2564    }
2565
2566    #[cot::test]
2567    async fn default_auth_backend() {
2568        let cache_memory = Cache::new(
2569            cache::store::memory::Memory::new(),
2570            None,
2571            Timeout::default(),
2572        );
2573
2574        let context = ProjectContext::new()
2575            .with_config(
2576                ProjectConfig::builder()
2577                    .auth_backend(AuthBackendConfig::None)
2578                    .build(),
2579            )
2580            .with_apps(vec![], Arc::new(Router::empty()))
2581            .with_database(None)
2582            .with_cache(cache_memory);
2583
2584        let auth_backend = TestProject.auth_backend(&context);
2585        assert!(
2586            auth_backend
2587                .get_by_id(UserId::Int(0))
2588                .await
2589                .unwrap()
2590                .is_none()
2591        );
2592    }
2593
2594    #[cot::test]
2595    #[cfg_attr(
2596        miri,
2597        ignore = "unsupported operation: can't call foreign function `sqlite3_open_v2`"
2598    )]
2599    async fn bootstrapper() {
2600        struct TestProject;
2601        impl Project for TestProject {
2602            fn register_apps(&self, apps: &mut AppBuilder, _context: &RegisterAppsContext) {
2603                apps.register_with_views(TestApp {}, "/app");
2604            }
2605        }
2606
2607        let bootstrapper = Bootstrapper::new(TestProject)
2608            .with_config(ProjectConfig::default())
2609            .boot()
2610            .await
2611            .unwrap();
2612
2613        assert_eq!(bootstrapper.context().apps.len(), 1);
2614        assert_eq!(bootstrapper.context().router.routes().len(), 1);
2615    }
2616
2617    #[cot::test]
2618    async fn build_custom_error_page_poll_ready_failure() {
2619        #[derive(Clone)]
2620        struct TestService;
2621        impl Service<Request> for TestService {
2622            type Response = Response;
2623            type Error = Error;
2624            type Future = std::future::Ready<crate::Result<Response>>;
2625
2626            fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
2627                Poll::Ready(Err(Error::internal("Poll ready failed")))
2628            }
2629
2630            fn call(&mut self, _req: Request) -> Self::Future {
2631                std::future::ready(Ok(Response::default()))
2632            }
2633        }
2634
2635        let mut error_handler = BoxCloneSyncService::new(TestService);
2636
2637        let panic_payload = Box::new("Test panic message".to_string());
2638        let error_response = ErrorResponse::Panic(panic_payload);
2639
2640        let (request_head, _) = Request::new(Body::empty()).into_parts();
2641        let response =
2642            build_custom_error_page(&mut error_handler, error_response, request_head).await;
2643
2644        test_last_resort_error(response).await;
2645    }
2646
2647    #[cot::test]
2648    async fn build_custom_error_page_call_failure() {
2649        let mock_handler = service_fn(|_request: Request| async {
2650            Err::<Response, Error>(Error::internal("handler call failed"))
2651        });
2652
2653        let mut error_handler = BoxCloneSyncService::new(mock_handler);
2654
2655        let error = Error::internal("Test error");
2656        let error_response = ErrorResponse::ErrorReturned(error);
2657
2658        let (request_head, _) = Request::new(Body::empty()).into_parts();
2659        let response =
2660            build_custom_error_page(&mut error_handler, error_response, request_head).await;
2661
2662        test_last_resort_error(response).await;
2663    }
2664    #[cot::test]
2665    async fn build_custom_error_page_success() {
2666        // Create a mock error handler that succeeds
2667        let mock_handler = service_fn(|_request: Request| async {
2668            let html = Html::new("Custom error page content")
2669                .with_status(StatusCode::INTERNAL_SERVER_ERROR);
2670            Ok::<Response, Error>(html.into_response().unwrap())
2671        });
2672
2673        let mut error_handler = BoxCloneSyncService::new(mock_handler);
2674
2675        let error = Error::internal("Test error");
2676        let error_response = ErrorResponse::ErrorReturned(error);
2677
2678        let (request_head, _) = Request::new(Body::empty()).into_parts();
2679        let response =
2680            build_custom_error_page(&mut error_handler, error_response, request_head).await;
2681
2682        assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
2683        let body_string = axum_response_into_body(response).await;
2684        assert!(body_string.contains("Custom error page content"));
2685    }
2686
2687    #[cot::test]
2688    async fn build_custom_error_page_panic_conversion() {
2689        let mock_handler = service_fn(|request: Request| async {
2690            let (parts, _body) = request.into_parts();
2691            let error = RequestError::from_request_head(&parts).await.unwrap();
2692            assert!(error.is::<UncaughtPanic>());
2693
2694            let html = Html::new("Panic error handled");
2695            Ok::<Response, Error>(html.into_response().unwrap())
2696        });
2697
2698        let mut error_handler = BoxCloneSyncService::new(mock_handler);
2699
2700        let panic_payload = Box::new("Test panic message".to_string());
2701        let error_response = ErrorResponse::Panic(panic_payload);
2702
2703        let (request_head, _) = Request::new(Body::empty()).into_parts();
2704        let response =
2705            build_custom_error_page(&mut error_handler, error_response, request_head).await;
2706
2707        assert_eq!(response.status(), StatusCode::OK);
2708        let body_string = axum_response_into_body(response).await;
2709        assert!(body_string.contains("Panic error handled"));
2710    }
2711
2712    /// Test that last-resort error page (500.html) is returned
2713    async fn test_last_resort_error(response: axum::response::Response) {
2714        assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
2715
2716        let body_string = axum_response_into_body(response).await;
2717
2718        assert!(body_string.contains("Server Error"));
2719        assert!(
2720            body_string.contains("Sorry, the page you are looking for is currently unavailable")
2721        );
2722    }
2723
2724    async fn axum_response_into_body(response: axum::response::Response) -> String {
2725        let body_bytes = axum::body::to_bytes(response.into_body(), usize::MAX)
2726            .await
2727            .unwrap();
2728        let body_string = String::from_utf8_lossy(&body_bytes);
2729        body_string.into_owned()
2730    }
2731
2732    #[cot::test]
2733    async fn build_request_for_error_handler() {
2734        let error = Error::internal("test error");
2735
2736        let (mut head, _) = Request::new(Body::empty()).into_parts();
2737        prepare_request_for_error_handler(&mut head, error);
2738
2739        let request_error = head.extensions.get::<RequestOuterError>().unwrap();
2740        assert_eq!(request_error.to_string(), "test error");
2741    }
2742}