oliphaunt_wasix/oliphaunt/
builder.rs1use std::path::PathBuf;
2
3use anyhow::{Result, bail};
4
5use crate::oliphaunt::base::{PreparedRoot, RootPlan, RootSource, RootTarget, prepare_root};
6use crate::oliphaunt::client::Oliphaunt;
7use crate::oliphaunt::config::{PostgresConfig, StartupConfig};
8#[cfg(feature = "extensions")]
9use crate::oliphaunt::extensions::{Extension, resolve_extension_set};
10use crate::oliphaunt::interface::DebugLevel;
11
12#[derive(Debug, Clone)]
14pub struct OliphauntBuilder {
15 target: Option<OliphauntTarget>,
16 template_cache: bool,
17 postgres_config: PostgresConfig,
18 startup_config: StartupConfig,
19 load_data_dir_archive: Option<Vec<u8>>,
20 #[cfg(feature = "extensions")]
21 extensions: Vec<Extension>,
22}
23
24#[derive(Debug, Clone)]
25enum OliphauntTarget {
26 Path(PathBuf),
27 AppId {
28 qualifier: String,
29 organization: String,
30 application: String,
31 },
32 Temporary,
33}
34
35impl Default for OliphauntBuilder {
36 fn default() -> Self {
37 Self {
38 target: None,
39 template_cache: true,
40 postgres_config: PostgresConfig::default(),
41 startup_config: StartupConfig::default(),
42 load_data_dir_archive: None,
43 #[cfg(feature = "extensions")]
44 extensions: Vec::new(),
45 }
46 }
47}
48
49impl OliphauntBuilder {
50 pub fn new() -> Self {
53 Self::default()
54 }
55
56 pub fn path(mut self, root: impl Into<PathBuf>) -> Self {
58 self.target = Some(OliphauntTarget::Path(root.into()));
59 self
60 }
61
62 pub fn app(
64 mut self,
65 qualifier: impl Into<String>,
66 organization: impl Into<String>,
67 application: impl Into<String>,
68 ) -> Self {
69 self.target = Some(OliphauntTarget::AppId {
70 qualifier: qualifier.into(),
71 organization: organization.into(),
72 application: application.into(),
73 });
74 self
75 }
76
77 pub fn app_id(self, app_id: (&str, &str, &str)) -> Self {
79 self.app(app_id.0, app_id.1, app_id.2)
80 }
81
82 pub fn temporary(mut self) -> Self {
87 self.target = Some(OliphauntTarget::Temporary);
88 self
89 }
90
91 pub fn template_cache(mut self, enabled: bool) -> Self {
94 self.template_cache = enabled;
95 self
96 }
97
98 pub fn fresh_temporary(self) -> Self {
105 self.temporary().template_cache(false)
106 }
107
108 pub fn postgres_config(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
110 self.postgres_config.insert(name, value);
111 self
112 }
113
114 pub fn postgres_configs<K, V>(mut self, settings: impl IntoIterator<Item = (K, V)>) -> Self
116 where
117 K: Into<String>,
118 V: Into<String>,
119 {
120 for (name, value) in settings {
121 self.postgres_config.insert(name, value);
122 }
123 self
124 }
125
126 pub fn username(mut self, username: impl Into<String>) -> Self {
129 self.startup_config.username = username.into();
130 self
131 }
132
133 pub fn database(mut self, database: impl Into<String>) -> Self {
136 self.startup_config.database = database.into();
137 self
138 }
139
140 pub fn debug_level(mut self, level: DebugLevel) -> Self {
142 self.startup_config.debug_level = Some(level);
143 self
144 }
145
146 pub fn relaxed_durability(mut self, enabled: bool) -> Self {
149 self.startup_config.relaxed_durability = enabled;
150 self
151 }
152
153 pub fn startup_arg(mut self, arg: impl Into<String>) -> Self {
156 self.startup_config.extra_args.push(arg.into());
157 self
158 }
159
160 pub fn startup_args(mut self, args: impl IntoIterator<Item = impl Into<String>>) -> Self {
162 self.startup_config
163 .extra_args
164 .extend(args.into_iter().map(Into::into));
165 self
166 }
167
168 pub fn load_data_dir_archive(mut self, archive: impl Into<Vec<u8>>) -> Self {
170 self.load_data_dir_archive = Some(archive.into());
171 self
172 }
173
174 #[cfg(feature = "extensions")]
176 pub fn extension(mut self, extension: Extension) -> Self {
177 self.extensions.push(extension);
178 self
179 }
180
181 #[cfg(feature = "extensions")]
183 pub fn extensions(mut self, extensions: impl IntoIterator<Item = Extension>) -> Self {
184 self.extensions.extend(extensions);
185 self
186 }
187
188 pub fn open(self) -> Result<Oliphaunt> {
190 self.postgres_config.validate()?;
191 self.startup_config.validate()?;
192 let target = match self.target.clone() {
193 Some(OliphauntTarget::Path(root)) => RootTarget::Path(root),
194 Some(OliphauntTarget::AppId {
195 qualifier,
196 organization,
197 application,
198 }) => RootTarget::AppId {
199 qualifier,
200 organization,
201 application,
202 },
203 Some(OliphauntTarget::Temporary) => RootTarget::Temporary,
204 None => {
205 bail!(
206 "OliphauntBuilder target is not set; call path, app_id, or temporary before open"
207 )
208 }
209 };
210 let source = if let Some(archive) = self.load_data_dir_archive.clone() {
211 RootSource::DataDirArchive(archive)
212 } else if self.template_cache {
213 RootSource::Template
214 } else {
215 RootSource::FreshInitdb
216 };
217 #[cfg(feature = "extensions")]
218 let extensions = resolve_extension_set(&self.extensions)?;
219 let plan = RootPlan::new(target, source);
220 #[cfg(feature = "extensions")]
221 let plan = plan.with_extensions(extensions.clone(), self.postgres_config.clone());
222 let prepared = prepare_root(plan)?;
223 #[cfg(feature = "extensions")]
224 {
225 self.open_prepared_root(prepared, extensions)
226 }
227 #[cfg(not(feature = "extensions"))]
228 {
229 self.open_prepared_root(prepared)
230 }
231 }
232
233 fn open_prepared_root(
234 self,
235 prepared: PreparedRoot,
236 #[cfg(feature = "extensions")] extensions: Vec<Extension>,
237 ) -> Result<Oliphaunt> {
238 let PreparedRoot {
239 temp_dir,
240 root_lock,
241 outcome,
242 ..
243 } = prepared;
244 #[cfg(feature = "extensions")]
245 let preinstalled_extensions = outcome.preinstalled_extensions.clone();
246 let mut instance = Oliphaunt::new_prepared_with_config(
247 outcome,
248 self.postgres_config,
249 self.startup_config,
250 )?;
251 if let Some(lock) = root_lock {
252 instance.attach_root_lock(lock);
253 }
254 if let Some(temp_dir) = temp_dir {
255 instance.attach_temp_dir(temp_dir);
256 }
257 #[cfg(feature = "extensions")]
258 let mut instance = instance;
259 #[cfg(feature = "extensions")]
260 for extension in extensions {
261 if preinstalled_extensions
262 .iter()
263 .any(|sql_name| sql_name == extension.sql_name())
264 {
265 instance.enable_preinstalled_extension(extension)?;
266 } else {
267 instance.enable_extension(extension)?;
268 }
269 }
270 Ok(instance)
271 }
272}