1use std::{future::Future, net::SocketAddr, pin::Pin};
2
3use saddle_core::{ComponentLifecycle, ErrorKind};
4use saddle_db::{Database as ManagedDatabase, DatabaseConfig};
5use saddle_observability::{Observer, ObserverConfig};
6use saddle_runtime::Application;
7use saddle_service::{
8 ExternalDispatcherBuilder, Service, ServiceDescriptor, ServiceHandler, ServiceRegistryBuilder,
9 ServiceResolver,
10};
11use serde::{Serialize, de::DeserializeOwned};
12
13use crate::{Result, SaddleError, http::HttpServer};
14
15type Exposure = Box<dyn FnOnce(&mut ExternalDispatcherBuilder) -> Result<()> + Send>;
16type ConfigureFuture = Pin<Box<dyn Future<Output = Result<Application>> + Send>>;
17
18#[derive(Clone)]
23pub struct SaddleConfig {
24 application: crate::ApplicationId,
25 database: DatabaseConfig,
26 listen: SocketAddr,
27 observability: ObserverConfig,
28}
29
30impl SaddleConfig {
31 pub fn new(
32 application: impl Into<crate::ApplicationId>,
33 database_url: impl Into<String>,
34 listen: SocketAddr,
35 ) -> Self {
36 Self {
37 application: application.into(),
38 database: DatabaseConfig::new(database_url),
39 listen,
40 observability: ObserverConfig::default(),
41 }
42 }
43}
44
45pub struct SaddleBuilder {
47 database: ManagedDatabase,
48 services: ServiceRegistryBuilder,
49 exposures: Vec<Exposure>,
50}
51
52impl SaddleBuilder {
53 fn new(database: ManagedDatabase) -> Self {
54 Self {
55 database,
56 services: ServiceRegistryBuilder::new(),
57 exposures: Vec::new(),
58 }
59 }
60
61 pub fn database(&self) -> crate::db::Database {
64 crate::db::Database(self.database.clone())
65 }
66
67 pub fn register<S, H>(&mut self, descriptor: ServiceDescriptor, handler: H) -> Result<()>
68 where
69 S: Service,
70 H: ServiceHandler<S>,
71 {
72 self.services.register::<S, H>(descriptor, handler)
73 }
74
75 pub fn register_with<S, H, F>(
76 &mut self,
77 descriptor: ServiceDescriptor,
78 factory: F,
79 ) -> Result<()>
80 where
81 S: Service,
82 H: ServiceHandler<S>,
83 F: FnOnce(&ServiceResolver<'_>) -> Result<H> + Send + 'static,
84 {
85 self.services.register_with::<S, H, F>(descriptor, factory)
86 }
87
88 pub fn expose_json<S>(&mut self, route: impl Into<String>)
90 where
91 S: Service,
92 S::Request: DeserializeOwned,
93 S::Response: Serialize,
94 {
95 let route = route.into();
96 self.exposures
97 .push(Box::new(move |builder| builder.expose_json::<S>(route)));
98 }
99}
100
101pub struct Saddle;
103
104impl Saddle {
105 pub fn run<F>(config: SaddleConfig, configure: F) -> Result<()>
106 where
107 F: FnOnce(&mut SaddleBuilder) -> Result<()> + Send + 'static,
108 {
109 Application::run_with(move || bootstrap(config, configure))
110 }
111}
112
113fn bootstrap<F>(config: SaddleConfig, configure: F) -> ConfigureFuture
114where
115 F: FnOnce(&mut SaddleBuilder) -> Result<()> + Send + 'static,
116{
117 Box::pin(async move {
118 let observer = saddle_observability::init(config.observability)
119 .map_err(|_| startup_error("saddle.observability_initialization_failed"))?
120 .clone();
121 let database = match ManagedDatabase::connect(config.database, observer.clone()).await {
122 Ok(database) => database,
123 Err(error) => {
124 let _ = observer.shutdown().await;
125 return Err(error);
126 }
127 };
128
129 let mut builder = SaddleBuilder::new(database.clone());
130 if let Err(error) = configure(&mut builder) {
131 shutdown_failed_assembly(&database, &observer).await;
132 return Err(error);
133 }
134 let registry = match builder.services.build(observer.clone()) {
135 Ok(registry) => registry,
136 Err(error) => {
137 shutdown_failed_assembly(&database, &observer).await;
138 return Err(error);
139 }
140 };
141
142 let mut application = Application::new();
143 let requests = application.request_lifecycle();
144 let application_id = config.application;
145 let mut dispatcher =
146 ExternalDispatcherBuilder::new(application_id.clone(), registry, requests);
147 for exposure in builder.exposures {
148 if let Err(error) = exposure(&mut dispatcher) {
149 shutdown_failed_assembly(&database, &observer).await;
150 return Err(error);
151 }
152 }
153 let server = HttpServer::new(
154 config.listen,
155 application_id,
156 dispatcher.build(),
157 observer.clone(),
158 );
159 if let Err(error) = application.register(observer.clone()) {
160 shutdown_failed_assembly(&database, &observer).await;
161 return Err(error);
162 }
163 if let Err(error) = application.register(database.clone()) {
164 shutdown_failed_assembly(&database, &observer).await;
165 return Err(error);
166 }
167 if let Err(error) = application.register(server) {
168 shutdown_failed_assembly(&database, &observer).await;
169 return Err(error);
170 }
171 Ok(application)
172 })
173}
174
175async fn shutdown_failed_assembly(database: &ManagedDatabase, observer: &Observer) {
176 let _ = ComponentLifecycle::shutdown(database).await;
177 let _ = ComponentLifecycle::shutdown(observer).await;
178}
179
180fn startup_error(code: &'static str) -> SaddleError {
181 SaddleError::new(
182 ErrorKind::Infrastructure,
183 code,
184 "Saddle application initialization failed",
185 )
186}