1use serde::de::{Deserializer, Error as DeError, Visitor};
2use serde::Deserialize;
3use serde_json::Value as JsonValue;
4
5use once_cell::sync::OnceCell;
6use std::collections::HashMap;
7
8static CONFIG: OnceCell<Config> = OnceCell::new();
9
10#[derive(PartialEq, Deserialize, Debug)]
12#[serde(tag = "window", rename_all = "camelCase")]
13pub struct WindowConfig {
14 #[serde(default = "default_width")]
16 pub width: i32,
17 #[serde(default = "default_height")]
19 pub height: i32,
20 #[serde(default = "default_resizable")]
22 pub resizable: bool,
23 #[serde(default = "default_title")]
25 pub title: String,
26 #[serde(default)]
28 pub fullscreen: bool,
29}
30
31fn default_width() -> i32 {
32 800
33}
34
35fn default_height() -> i32 {
36 600
37}
38
39fn default_resizable() -> bool {
40 true
41}
42
43fn default_title() -> String {
44 "Tauri App".to_string()
45}
46
47fn default_window() -> WindowConfig {
48 WindowConfig {
49 width: default_width(),
50 height: default_height(),
51 resizable: default_resizable(),
52 title: default_title(),
53 fullscreen: false,
54 }
55}
56
57#[derive(PartialEq, Debug, Deserialize)]
59pub enum Port {
60 Value(u16),
62 Random,
64}
65
66#[derive(PartialEq, Deserialize, Debug)]
68#[serde(tag = "embeddedServer", rename_all = "camelCase")]
69pub struct EmbeddedServerConfig {
70 #[serde(default = "default_host")]
72 pub host: String,
73 #[serde(default = "default_port", deserialize_with = "port_deserializer")]
76 pub port: Port,
77}
78
79fn default_host() -> String {
80 "http://127.0.0.1".to_string()
81}
82
83fn port_deserializer<'de, D>(deserializer: D) -> Result<Port, D::Error>
84where
85 D: Deserializer<'de>,
86{
87 struct PortDeserializer;
88
89 impl<'de> Visitor<'de> for PortDeserializer {
90 type Value = Port;
91 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 formatter.write_str("a port number or the 'random' string")
93 }
94
95 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
96 where
97 E: DeError,
98 {
99 if value != "random" {
100 Err(DeError::custom(
101 "expected a 'random' string or a port number",
102 ))
103 } else {
104 Ok(Port::Random)
105 }
106 }
107
108 fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
109 where
110 E: DeError,
111 {
112 Ok(Port::Value(value as u16))
113 }
114 }
115
116 deserializer.deserialize_any(PortDeserializer {})
117}
118
119fn default_port() -> Port {
120 Port::Random
121}
122
123fn default_embedded_server() -> EmbeddedServerConfig {
124 EmbeddedServerConfig {
125 host: default_host(),
126 port: default_port(),
127 }
128}
129
130#[derive(PartialEq, Deserialize, Debug, Default)]
132#[serde(rename_all = "camelCase")]
133pub struct CliArg {
134 pub short: Option<char>,
138 pub name: String,
140 pub description: Option<String>,
143 pub long_description: Option<String>,
146 pub takes_value: Option<bool>,
153 pub multiple: Option<bool>,
160 pub multiple_occurrences: Option<bool>,
162 pub number_of_values: Option<u64>,
164 pub possible_values: Option<Vec<String>>,
167 pub min_values: Option<u64>,
171 pub max_values: Option<u64>,
175 pub required: Option<bool>,
180 pub required_unless: Option<String>,
183 pub required_unless_all: Option<Vec<String>>,
186 pub required_unless_one: Option<Vec<String>>,
189 pub conflicts_with: Option<String>,
192 pub conflicts_with_all: Option<Vec<String>>,
194 pub requires: Option<String>,
197 pub requires_all: Option<Vec<String>>,
200 pub requires_if: Option<Vec<String>>,
203 pub required_if: Option<Vec<String>>,
206 pub require_equals: Option<bool>,
209 pub index: Option<u64>,
215}
216
217#[derive(PartialEq, Deserialize, Debug)]
219#[serde(tag = "cli", rename_all = "camelCase")]
220pub struct CliConfig {
221 description: Option<String>,
222 long_description: Option<String>,
223 before_help: Option<String>,
224 after_help: Option<String>,
225 args: Option<Vec<CliArg>>,
226 subcommands: Option<HashMap<String, CliConfig>>,
227}
228
229impl CliConfig {
230 pub fn args(&self) -> Option<&Vec<CliArg>> {
232 self.args.as_ref()
233 }
234
235 pub fn subcommands(&self) -> Option<&HashMap<String, CliConfig>> {
237 self.subcommands.as_ref()
238 }
239
240 pub fn description(&self) -> Option<&String> {
242 self.description.as_ref()
243 }
244
245 pub fn long_description(&self) -> Option<&String> {
247 self.description.as_ref()
248 }
249
250 pub fn before_help(&self) -> Option<&String> {
254 self.before_help.as_ref()
255 }
256
257 pub fn after_help(&self) -> Option<&String> {
261 self.after_help.as_ref()
262 }
263}
264
265#[derive(PartialEq, Deserialize, Debug)]
267#[serde(tag = "bundle", rename_all = "camelCase")]
268pub struct BundleConfig {
269 pub identifier: String,
271}
272
273fn default_bundle() -> BundleConfig {
274 BundleConfig {
275 identifier: String::from(""),
276 }
277}
278
279#[derive(PartialEq, Deserialize, Debug)]
281#[serde(tag = "tauri", rename_all = "camelCase")]
282pub struct TauriConfig {
283 #[serde(default = "default_window")]
285 pub window: WindowConfig,
286 #[serde(default = "default_embedded_server")]
288 pub embedded_server: EmbeddedServerConfig,
289 #[serde(default)]
291 pub cli: Option<CliConfig>,
292 #[serde(default = "default_bundle")]
294 pub bundle: BundleConfig,
295}
296
297#[derive(PartialEq, Deserialize, Debug)]
299#[serde(tag = "build", rename_all = "camelCase")]
300pub struct BuildConfig {
301 #[serde(default = "default_dev_path")]
303 pub dev_path: String,
304}
305
306fn default_dev_path() -> String {
307 "".to_string()
308}
309
310type JsonObject = HashMap<String, JsonValue>;
311
312#[derive(PartialEq, Deserialize, Debug)]
314#[serde(rename_all = "camelCase")]
315pub struct Config {
316 #[serde(default = "default_tauri")]
318 pub tauri: TauriConfig,
319 #[serde(default = "default_build")]
321 pub build: BuildConfig,
322 #[serde(default)]
324 plugins: HashMap<String, JsonObject>,
325}
326
327impl Config {
328 pub fn plugin_config<S: AsRef<str>>(&self, plugin_name: S) -> Option<&JsonObject> {
330 self.plugins.get(plugin_name.as_ref())
331 }
332}
333
334fn default_tauri() -> TauriConfig {
335 TauriConfig {
336 window: default_window(),
337 embedded_server: default_embedded_server(),
338 cli: None,
339 bundle: default_bundle(),
340 }
341}
342
343fn default_build() -> BuildConfig {
344 BuildConfig {
345 dev_path: default_dev_path(),
346 }
347}
348
349pub fn get() -> crate::Result<&'static Config> {
351 if let Some(config) = CONFIG.get() {
352 return Ok(config);
353 }
354 let config: Config = match option_env!("TAURI_CONFIG") {
355 Some(config) => serde_json::from_str(config).expect("failed to parse TAURI_CONFIG env"),
356 None => {
357 let config = include_str!(concat!(env!("OUT_DIR"), "/tauri.conf.json"));
358 serde_json::from_str(&config).expect("failed to read tauri.conf.json")
359 }
360 };
361
362 CONFIG
363 .set(config)
364 .map_err(|_| anyhow::anyhow!("failed to set CONFIG"))?;
365
366 let config = CONFIG.get().unwrap();
367 Ok(config)
368}
369
370#[cfg(test)]
371mod test {
372 use super::*;
373 fn create_test_config() -> Config {
375 let mut subcommands = std::collections::HashMap::new();
376 subcommands.insert(
377 "update".to_string(),
378 CliConfig {
379 description: Some("Updates the app".to_string()),
380 long_description: None,
381 before_help: None,
382 after_help: None,
383 args: Some(vec![CliArg {
384 short: Some('b'),
385 name: "background".to_string(),
386 description: Some("Update in background".to_string()),
387 ..Default::default()
388 }]),
389 subcommands: None,
390 },
391 );
392 Config {
393 tauri: TauriConfig {
394 window: WindowConfig {
395 width: 800,
396 height: 600,
397 resizable: true,
398 title: String::from("Tauri API Validation"),
399 fullscreen: false,
400 },
401 embedded_server: EmbeddedServerConfig {
402 host: String::from("http://127.0.0.1"),
403 port: Port::Random,
404 },
405 bundle: BundleConfig {
406 identifier: String::from("com.tauri.communication"),
407 },
408 cli: Some(CliConfig {
409 description: Some("Tauri communication example".to_string()),
410 long_description: None,
411 before_help: None,
412 after_help: None,
413 args: Some(vec![
414 CliArg {
415 short: Some('c'),
416 name: "config".to_string(),
417 takes_value: Some(true),
418 description: Some("Config path".to_string()),
419 ..Default::default()
420 },
421 CliArg {
422 short: Some('t'),
423 name: "theme".to_string(),
424 takes_value: Some(true),
425 description: Some("App theme".to_string()),
426 possible_values: Some(vec![
427 "light".to_string(),
428 "dark".to_string(),
429 "system".to_string(),
430 ]),
431 ..Default::default()
432 },
433 CliArg {
434 short: Some('v'),
435 name: "verbose".to_string(),
436 multiple_occurrences: Some(true),
437 description: Some("Verbosity level".to_string()),
438 ..Default::default()
439 },
440 ]),
441 subcommands: Some(subcommands),
442 }),
443 },
444 build: BuildConfig {
445 dev_path: String::from("../dist"),
446 },
447 plugins: Default::default(),
448 }
449 }
450
451 #[test]
452 fn test_get() {
454 let test_config = create_test_config();
456
457 let config = get();
459
460 match config {
462 Ok(c) => {
464 println!("{:?}", c);
465 assert_eq!(c, &test_config)
466 }
467 Err(e) => panic!("get config failed: {:?}", e.to_string()),
468 }
469 }
470
471 #[test]
472 fn test_defaults() {
474 let t_config = default_tauri();
476 let b_config = default_build();
478 let d_path = default_dev_path();
480 let de_server = default_embedded_server();
482 let d_window = default_window();
484 let d_title = default_title();
486 let d_bundle = default_bundle();
488
489 let tauri = TauriConfig {
491 window: WindowConfig {
492 width: 800,
493 height: 600,
494 resizable: true,
495 title: String::from("Tauri App"),
496 fullscreen: false,
497 },
498 embedded_server: EmbeddedServerConfig {
499 host: String::from("http://127.0.0.1"),
500 port: Port::Random,
501 },
502 bundle: BundleConfig {
503 identifier: String::from(""),
504 },
505 cli: None,
506 };
507
508 let build = BuildConfig {
510 dev_path: String::from(""),
511 };
512
513 assert_eq!(t_config, tauri);
515 assert_eq!(b_config, build);
516 assert_eq!(de_server, tauri.embedded_server);
517 assert_eq!(d_bundle, tauri.bundle);
518 assert_eq!(d_path, String::from(""));
519 assert_eq!(d_title, tauri.window.title);
520 assert_eq!(d_window, tauri.window);
521 }
522}