tauri_api/
config.rs

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/// The window configuration object.
11#[derive(PartialEq, Deserialize, Debug)]
12#[serde(tag = "window", rename_all = "camelCase")]
13pub struct WindowConfig {
14  /// The window width.
15  #[serde(default = "default_width")]
16  pub width: i32,
17  /// The window height.
18  #[serde(default = "default_height")]
19  pub height: i32,
20  /// Whether the window is resizable or not.
21  #[serde(default = "default_resizable")]
22  pub resizable: bool,
23  /// The window title.
24  #[serde(default = "default_title")]
25  pub title: String,
26  /// Whether the window starts as fullscreen or not.
27  #[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/// The embedded server port.
58#[derive(PartialEq, Debug, Deserialize)]
59pub enum Port {
60  /// Port with a numeric value.
61  Value(u16),
62  /// Random port.
63  Random,
64}
65
66/// The embeddedServer configuration object.
67#[derive(PartialEq, Deserialize, Debug)]
68#[serde(tag = "embeddedServer", rename_all = "camelCase")]
69pub struct EmbeddedServerConfig {
70  /// The embedded server host.
71  #[serde(default = "default_host")]
72  pub host: String,
73  /// The embedded server port.
74  /// If it's `random`, we'll generate one at runtime.
75  #[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/// A CLI argument definition
131#[derive(PartialEq, Deserialize, Debug, Default)]
132#[serde(rename_all = "camelCase")]
133pub struct CliArg {
134  /// The short version of the argument, without the preceding -.
135  ///
136  /// NOTE: Any leading - characters will be stripped, and only the first non - character will be used as the short version.
137  pub short: Option<char>,
138  /// The unique argument name
139  pub name: String,
140  /// The argument description which will be shown on the help information.
141  /// Typically, this is a short (one line) description of the arg.
142  pub description: Option<String>,
143  /// The argument long description which will be shown on the help information.
144  /// Typically this a more detailed (multi-line) message that describes the argument.
145  pub long_description: Option<String>,
146  /// Specifies that the argument takes a value at run time.
147  ///
148  /// NOTE: values for arguments may be specified in any of the following methods
149  /// - Using a space such as -o value or --option value
150  /// - Using an equals and no space such as -o=value or --option=value
151  /// - Use a short and no space such as -ovalue
152  pub takes_value: Option<bool>,
153  /// Specifies that the argument may appear more than once.
154  ///
155  /// - For flags, this results in the number of occurrences of the flag being recorded.
156  /// For example -ddd or -d -d -d would count as three occurrences.
157  /// - For options there is a distinct difference in multiple occurrences vs multiple values.
158  /// For example, --opt val1 val2 is one occurrence, but two values. Whereas --opt val1 --opt val2 is two occurrences.
159  pub multiple: Option<bool>,
160  ///
161  pub multiple_occurrences: Option<bool>,
162  ///
163  pub number_of_values: Option<u64>,
164  /// Specifies a list of possible values for this argument.
165  /// At runtime, the CLI verifies that only one of the specified values was used, or fails with an error message.
166  pub possible_values: Option<Vec<String>>,
167  /// Specifies the minimum number of values for this argument.
168  /// For example, if you had a -f <file> argument where you wanted at least 2 'files',
169  /// you would set `minValues: 2`, and this argument would be satisfied if the user provided, 2 or more values.
170  pub min_values: Option<u64>,
171  /// Specifies the maximum number of values are for this argument.
172  /// For example, if you had a -f <file> argument where you wanted up to 3 'files',
173  /// you would set .max_values(3), and this argument would be satisfied if the user provided, 1, 2, or 3 values.
174  pub max_values: Option<u64>,
175  /// Sets whether or not the argument is required by default.
176  ///
177  /// - Required by default means it is required, when no other conflicting rules have been evaluated
178  /// - Conflicting rules take precedence over being required.
179  pub required: Option<bool>,
180  /// Sets an arg that override this arg's required setting
181  /// i.e. this arg will be required unless this other argument is present.
182  pub required_unless: Option<String>,
183  /// Sets args that override this arg's required setting
184  /// i.e. this arg will be required unless all these other arguments are present.
185  pub required_unless_all: Option<Vec<String>>,
186  /// Sets args that override this arg's required setting
187  /// i.e. this arg will be required unless at least one of these other arguments are present.
188  pub required_unless_one: Option<Vec<String>>,
189  /// Sets a conflicting argument by name
190  /// i.e. when using this argument, the following argument can't be present and vice versa.
191  pub conflicts_with: Option<String>,
192  /// The same as conflictsWith but allows specifying multiple two-way conflicts per argument.
193  pub conflicts_with_all: Option<Vec<String>>,
194  /// Tets an argument by name that is required when this one is present
195  /// i.e. when using this argument, the following argument must be present.
196  pub requires: Option<String>,
197  /// Sts multiple arguments by names that are required when this one is present
198  /// i.e. when using this argument, the following arguments must be present.
199  pub requires_all: Option<Vec<String>>,
200  /// Allows a conditional requirement with the signature [arg, value]
201  /// the requirement will only become valid if `arg`'s value equals `${value}`.
202  pub requires_if: Option<Vec<String>>,
203  /// Allows specifying that an argument is required conditionally with the signature [arg, value]
204  /// the requirement will only become valid if the `arg`'s value equals `${value}`.
205  pub required_if: Option<Vec<String>>,
206  /// Requires that options use the --option=val syntax
207  /// i.e. an equals between the option and associated value.
208  pub require_equals: Option<bool>,
209  /// The positional argument index, starting at 1.
210  ///
211  /// The index refers to position according to other positional argument.
212  /// It does not define position in the argument list as a whole. When utilized with multiple=true,
213  /// only the last positional argument may be defined as multiple (i.e. the one with the highest index).
214  pub index: Option<u64>,
215}
216
217/// The CLI root command definition.
218#[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  /// List of args for the command
231  pub fn args(&self) -> Option<&Vec<CliArg>> {
232    self.args.as_ref()
233  }
234
235  /// List of subcommands of this command
236  pub fn subcommands(&self) -> Option<&HashMap<String, CliConfig>> {
237    self.subcommands.as_ref()
238  }
239
240  /// Command description which will be shown on the help information.
241  pub fn description(&self) -> Option<&String> {
242    self.description.as_ref()
243  }
244
245  /// Command long description which will be shown on the help information.
246  pub fn long_description(&self) -> Option<&String> {
247    self.description.as_ref()
248  }
249
250  /// Adds additional help information to be displayed in addition to auto-generated help.
251  /// This information is displayed before the auto-generated help information.
252  /// This is often used for header information.
253  pub fn before_help(&self) -> Option<&String> {
254    self.before_help.as_ref()
255  }
256
257  /// Adds additional help information to be displayed in addition to auto-generated help.
258  /// This information is displayed after the auto-generated help information.
259  /// This is often used to describe how to use the arguments, or caveats to be noted.
260  pub fn after_help(&self) -> Option<&String> {
261    self.after_help.as_ref()
262  }
263}
264
265/// The bundler configuration object.
266#[derive(PartialEq, Deserialize, Debug)]
267#[serde(tag = "bundle", rename_all = "camelCase")]
268pub struct BundleConfig {
269  /// The bundle identifier.
270  pub identifier: String,
271}
272
273fn default_bundle() -> BundleConfig {
274  BundleConfig {
275    identifier: String::from(""),
276  }
277}
278
279/// The Tauri configuration object.
280#[derive(PartialEq, Deserialize, Debug)]
281#[serde(tag = "tauri", rename_all = "camelCase")]
282pub struct TauriConfig {
283  /// The window configuration.
284  #[serde(default = "default_window")]
285  pub window: WindowConfig,
286  /// The embeddedServer configuration.
287  #[serde(default = "default_embedded_server")]
288  pub embedded_server: EmbeddedServerConfig,
289  /// The CLI configuration.
290  #[serde(default)]
291  pub cli: Option<CliConfig>,
292  /// The bundler configuration.
293  #[serde(default = "default_bundle")]
294  pub bundle: BundleConfig,
295}
296
297/// The Build configuration object.
298#[derive(PartialEq, Deserialize, Debug)]
299#[serde(tag = "build", rename_all = "camelCase")]
300pub struct BuildConfig {
301  /// the devPath config.
302  #[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/// The tauri.conf.json mapper.
313#[derive(PartialEq, Deserialize, Debug)]
314#[serde(rename_all = "camelCase")]
315pub struct Config {
316  /// The Tauri configuration.
317  #[serde(default = "default_tauri")]
318  pub tauri: TauriConfig,
319  /// The build configuration.
320  #[serde(default = "default_build")]
321  pub build: BuildConfig,
322  /// The plugins config.
323  #[serde(default)]
324  plugins: HashMap<String, JsonObject>,
325}
326
327impl Config {
328  /// Gets a plugin configuration.
329  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
349/// Gets the static parsed config from `tauri.conf.json`.
350pub 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  // generate a test_config based on the test fixture
374  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  // test the get function.  Will only resolve to true if the TAURI_CONFIG variable is set properly to the fixture.
453  fn test_get() {
454    // get test_config
455    let test_config = create_test_config();
456
457    // call get();
458    let config = get();
459
460    // check to see if there is an OK or Err, on Err fail test.
461    match config {
462      // On Ok, check that the config is the same as the test config.
463      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  // test all of the default functions
473  fn test_defaults() {
474    // get default tauri config
475    let t_config = default_tauri();
476    // get default build config
477    let b_config = default_build();
478    // get default dev path
479    let d_path = default_dev_path();
480    // get default embedded server
481    let de_server = default_embedded_server();
482    // get default window
483    let d_window = default_window();
484    // get default title
485    let d_title = default_title();
486    // get default bundle
487    let d_bundle = default_bundle();
488
489    // create a tauri config.
490    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    // create a build config
509    let build = BuildConfig {
510      dev_path: String::from(""),
511    };
512
513    // test the configs
514    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}