tauri_utils/
config.rs

1// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5//! The Tauri configuration used at runtime.
6//!
7//! It is pulled from a `tauri.conf.json` file and the [`Config`] struct is generated at compile time.
8//!
9//! # Stability
10//!
11//! This is a core functionality that is not considered part of the stable API.
12//! If you use it, note that it may include breaking changes in the future.
13//!
14//! These items are intended to be non-breaking from a de/serialization standpoint only.
15//! Using and modifying existing config values will try to avoid breaking changes, but they are
16//! free to add fields in the future - causing breaking changes for creating and full destructuring.
17//!
18//! To avoid this, [ignore unknown fields when destructuring] with the `{my, config, ..}` pattern.
19//! If you need to create the Rust config directly without deserializing, then create the struct
20//! the [Struct Update Syntax] with `..Default::default()`, which may need a
21//! `#[allow(clippy::needless_update)]` attribute if you are declaring all fields.
22//!
23//! [ignore unknown fields when destructuring]: https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html#ignoring-remaining-parts-of-a-value-with-
24//! [Struct Update Syntax]: https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax
25
26use http::response::Builder;
27#[cfg(feature = "schema")]
28use schemars::schema::Schema;
29#[cfg(feature = "schema")]
30use schemars::JsonSchema;
31use semver::Version;
32use serde::{
33  de::{Deserializer, Error as DeError, Visitor},
34  Deserialize, Serialize, Serializer,
35};
36use serde_json::Value as JsonValue;
37use serde_untagged::UntaggedEnumVisitor;
38use serde_with::skip_serializing_none;
39use url::Url;
40
41use std::{
42  collections::HashMap,
43  fmt::{self, Display},
44  fs::read_to_string,
45  path::PathBuf,
46  str::FromStr,
47};
48
49#[cfg(feature = "schema")]
50fn add_description(schema: Schema, description: impl Into<String>) -> Schema {
51  let value = description.into();
52  if value.is_empty() {
53    schema
54  } else {
55    let mut schema_obj = schema.into_object();
56    schema_obj.metadata().description = value.into();
57    Schema::Object(schema_obj)
58  }
59}
60
61/// Items to help with parsing content into a [`Config`].
62pub mod parse;
63
64use crate::{acl::capability::Capability, TitleBarStyle, WindowEffect, WindowEffectState};
65
66pub use self::parse::parse;
67
68fn default_true() -> bool {
69  true
70}
71
72/// An URL to open on a Tauri webview window.
73#[derive(PartialEq, Eq, Debug, Clone, Serialize)]
74#[cfg_attr(feature = "schema", derive(JsonSchema))]
75#[serde(untagged)]
76#[non_exhaustive]
77pub enum WebviewUrl {
78  /// An external URL. Must use either the `http` or `https` schemes.
79  External(Url),
80  /// The path portion of an app URL.
81  /// For instance, to load `tauri://localhost/users/john`,
82  /// you can simply provide `users/john` in this configuration.
83  App(PathBuf),
84  /// A custom protocol url, for example, `doom://index.html`
85  CustomProtocol(Url),
86}
87
88impl<'de> Deserialize<'de> for WebviewUrl {
89  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
90  where
91    D: Deserializer<'de>,
92  {
93    #[derive(Deserialize)]
94    #[serde(untagged)]
95    enum WebviewUrlDeserializer {
96      Url(Url),
97      Path(PathBuf),
98    }
99
100    match WebviewUrlDeserializer::deserialize(deserializer)? {
101      WebviewUrlDeserializer::Url(u) => {
102        if u.scheme() == "https" || u.scheme() == "http" {
103          Ok(Self::External(u))
104        } else {
105          Ok(Self::CustomProtocol(u))
106        }
107      }
108      WebviewUrlDeserializer::Path(p) => Ok(Self::App(p)),
109    }
110  }
111}
112
113impl fmt::Display for WebviewUrl {
114  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115    match self {
116      Self::External(url) | Self::CustomProtocol(url) => write!(f, "{url}"),
117      Self::App(path) => write!(f, "{}", path.display()),
118    }
119  }
120}
121
122impl Default for WebviewUrl {
123  fn default() -> Self {
124    Self::App("index.html".into())
125  }
126}
127
128/// A bundle referenced by tauri-bundler.
129#[derive(Debug, PartialEq, Eq, Clone)]
130#[cfg_attr(feature = "schema", derive(JsonSchema))]
131#[cfg_attr(feature = "schema", schemars(rename_all = "lowercase"))]
132pub enum BundleType {
133  /// The debian bundle (.deb).
134  Deb,
135  /// The RPM bundle (.rpm).
136  Rpm,
137  /// The AppImage bundle (.appimage).
138  AppImage,
139  /// The Microsoft Installer bundle (.msi).
140  Msi,
141  /// The NSIS bundle (.exe).
142  Nsis,
143  /// The macOS application bundle (.app).
144  App,
145  /// The Apple Disk Image bundle (.dmg).
146  Dmg,
147}
148
149impl BundleType {
150  /// All bundle types.
151  fn all() -> &'static [Self] {
152    &[
153      BundleType::Deb,
154      BundleType::Rpm,
155      BundleType::AppImage,
156      BundleType::Msi,
157      BundleType::Nsis,
158      BundleType::App,
159      BundleType::Dmg,
160    ]
161  }
162}
163
164impl Display for BundleType {
165  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166    write!(
167      f,
168      "{}",
169      match self {
170        Self::Deb => "deb",
171        Self::Rpm => "rpm",
172        Self::AppImage => "appimage",
173        Self::Msi => "msi",
174        Self::Nsis => "nsis",
175        Self::App => "app",
176        Self::Dmg => "dmg",
177      }
178    )
179  }
180}
181
182impl Serialize for BundleType {
183  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
184  where
185    S: Serializer,
186  {
187    serializer.serialize_str(self.to_string().as_ref())
188  }
189}
190
191impl<'de> Deserialize<'de> for BundleType {
192  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
193  where
194    D: Deserializer<'de>,
195  {
196    let s = String::deserialize(deserializer)?;
197    match s.to_lowercase().as_str() {
198      "deb" => Ok(Self::Deb),
199      "rpm" => Ok(Self::Rpm),
200      "appimage" => Ok(Self::AppImage),
201      "msi" => Ok(Self::Msi),
202      "nsis" => Ok(Self::Nsis),
203      "app" => Ok(Self::App),
204      "dmg" => Ok(Self::Dmg),
205      _ => Err(DeError::custom(format!("unknown bundle target '{s}'"))),
206    }
207  }
208}
209
210/// Targets to bundle. Each value is case insensitive.
211#[derive(Debug, PartialEq, Eq, Clone)]
212pub enum BundleTarget {
213  /// Bundle all targets.
214  All,
215  /// A list of bundle targets.
216  List(Vec<BundleType>),
217  /// A single bundle target.
218  One(BundleType),
219}
220
221#[cfg(feature = "schema")]
222impl schemars::JsonSchema for BundleTarget {
223  fn schema_name() -> std::string::String {
224    "BundleTarget".to_owned()
225  }
226
227  fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
228    let any_of = vec![
229      schemars::schema::SchemaObject {
230        const_value: Some("all".into()),
231        metadata: Some(Box::new(schemars::schema::Metadata {
232          description: Some("Bundle all targets.".to_owned()),
233          ..Default::default()
234        })),
235        ..Default::default()
236      }
237      .into(),
238      add_description(
239        gen.subschema_for::<Vec<BundleType>>(),
240        "A list of bundle targets.",
241      ),
242      add_description(gen.subschema_for::<BundleType>(), "A single bundle target."),
243    ];
244
245    schemars::schema::SchemaObject {
246      subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
247        any_of: Some(any_of),
248        ..Default::default()
249      })),
250      metadata: Some(Box::new(schemars::schema::Metadata {
251        description: Some("Targets to bundle. Each value is case insensitive.".to_owned()),
252        ..Default::default()
253      })),
254      ..Default::default()
255    }
256    .into()
257  }
258}
259
260impl Default for BundleTarget {
261  fn default() -> Self {
262    Self::All
263  }
264}
265
266impl Serialize for BundleTarget {
267  fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
268  where
269    S: Serializer,
270  {
271    match self {
272      Self::All => serializer.serialize_str("all"),
273      Self::List(l) => l.serialize(serializer),
274      Self::One(t) => serializer.serialize_str(t.to_string().as_ref()),
275    }
276  }
277}
278
279impl<'de> Deserialize<'de> for BundleTarget {
280  fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
281  where
282    D: Deserializer<'de>,
283  {
284    #[derive(Deserialize, Serialize)]
285    #[serde(untagged)]
286    pub enum BundleTargetInner {
287      List(Vec<BundleType>),
288      One(BundleType),
289      All(String),
290    }
291
292    match BundleTargetInner::deserialize(deserializer)? {
293      BundleTargetInner::All(s) if s.to_lowercase() == "all" => Ok(Self::All),
294      BundleTargetInner::All(t) => Err(DeError::custom(format!(
295        "invalid bundle type {t}, expected one of `all`, {}",
296        BundleType::all()
297          .iter()
298          .map(|b| format!("`{b}`"))
299          .collect::<Vec<_>>()
300          .join(", ")
301      ))),
302      BundleTargetInner::List(l) => Ok(Self::List(l)),
303      BundleTargetInner::One(t) => Ok(Self::One(t)),
304    }
305  }
306}
307
308impl BundleTarget {
309  /// Gets the bundle targets as a [`Vec`]. The vector is empty when set to [`BundleTarget::All`].
310  #[allow(dead_code)]
311  pub fn to_vec(&self) -> Vec<BundleType> {
312    match self {
313      Self::All => BundleType::all().to_vec(),
314      Self::List(list) => list.clone(),
315      Self::One(i) => vec![i.clone()],
316    }
317  }
318}
319
320/// Configuration for AppImage bundles.
321///
322/// See more: <https://v2.tauri.app/reference/config/#appimageconfig>
323#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
324#[cfg_attr(feature = "schema", derive(JsonSchema))]
325#[serde(rename_all = "camelCase", deny_unknown_fields)]
326pub struct AppImageConfig {
327  /// Include additional gstreamer dependencies needed for audio and video playback.
328  /// This increases the bundle size by ~15-35MB depending on your build system.
329  #[serde(default, alias = "bundle-media-framework")]
330  pub bundle_media_framework: bool,
331  /// The files to include in the Appimage Binary.
332  #[serde(default)]
333  pub files: HashMap<PathBuf, PathBuf>,
334}
335
336/// Configuration for Debian (.deb) bundles.
337///
338/// See more: <https://v2.tauri.app/reference/config/#debconfig>
339#[skip_serializing_none]
340#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
341#[cfg_attr(feature = "schema", derive(JsonSchema))]
342#[serde(rename_all = "camelCase", deny_unknown_fields)]
343pub struct DebConfig {
344  /// The list of deb dependencies your application relies on.
345  pub depends: Option<Vec<String>>,
346  /// The list of deb dependencies your application recommends.
347  pub recommends: Option<Vec<String>>,
348  /// The list of dependencies the package provides.
349  pub provides: Option<Vec<String>>,
350  /// The list of package conflicts.
351  pub conflicts: Option<Vec<String>>,
352  /// The list of package replaces.
353  pub replaces: Option<Vec<String>>,
354  /// The files to include on the package.
355  #[serde(default)]
356  pub files: HashMap<PathBuf, PathBuf>,
357  /// Define the section in Debian Control file. See : https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections
358  pub section: Option<String>,
359  /// Change the priority of the Debian Package. By default, it is set to `optional`.
360  /// Recognized Priorities as of now are :  `required`, `important`, `standard`, `optional`, `extra`
361  pub priority: Option<String>,
362  /// Path of the uncompressed Changelog file, to be stored at /usr/share/doc/package-name/changelog.gz. See
363  /// <https://www.debian.org/doc/debian-policy/ch-docs.html#changelog-files-and-release-notes>
364  pub changelog: Option<PathBuf>,
365  /// Path to a custom desktop file Handlebars template.
366  ///
367  /// Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.
368  #[serde(alias = "desktop-template")]
369  pub desktop_template: Option<PathBuf>,
370  /// Path to script that will be executed before the package is unpacked. See
371  /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>
372  #[serde(alias = "pre-install-script")]
373  pub pre_install_script: Option<PathBuf>,
374  /// Path to script that will be executed after the package is unpacked. See
375  /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>
376  #[serde(alias = "post-install-script")]
377  pub post_install_script: Option<PathBuf>,
378  /// Path to script that will be executed before the package is removed. See
379  /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>
380  #[serde(alias = "pre-remove-script")]
381  pub pre_remove_script: Option<PathBuf>,
382  /// Path to script that will be executed after the package is removed. See
383  /// <https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html>
384  #[serde(alias = "post-remove-script")]
385  pub post_remove_script: Option<PathBuf>,
386}
387
388/// Configuration for Linux bundles.
389///
390/// See more: <https://v2.tauri.app/reference/config/#linuxconfig>
391#[skip_serializing_none]
392#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
393#[cfg_attr(feature = "schema", derive(JsonSchema))]
394#[serde(rename_all = "camelCase", deny_unknown_fields)]
395pub struct LinuxConfig {
396  /// Configuration for the AppImage bundle.
397  #[serde(default)]
398  pub appimage: AppImageConfig,
399  /// Configuration for the Debian bundle.
400  #[serde(default)]
401  pub deb: DebConfig,
402  /// Configuration for the RPM bundle.
403  #[serde(default)]
404  pub rpm: RpmConfig,
405}
406
407/// Compression algorithms used when bundling RPM packages.
408#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
409#[cfg_attr(feature = "schema", derive(JsonSchema))]
410#[serde(rename_all = "camelCase", deny_unknown_fields, tag = "type")]
411#[non_exhaustive]
412pub enum RpmCompression {
413  /// Gzip compression
414  Gzip {
415    /// Gzip compression level
416    level: u32,
417  },
418  /// Zstd compression
419  Zstd {
420    /// Zstd compression level
421    level: i32,
422  },
423  /// Xz compression
424  Xz {
425    /// Xz compression level
426    level: u32,
427  },
428  /// Bzip2 compression
429  Bzip2 {
430    /// Bzip2 compression level
431    level: u32,
432  },
433  /// Disable compression
434  None,
435}
436
437/// Configuration for RPM bundles.
438#[skip_serializing_none]
439#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
440#[cfg_attr(feature = "schema", derive(JsonSchema))]
441#[serde(rename_all = "camelCase", deny_unknown_fields)]
442pub struct RpmConfig {
443  /// The list of RPM dependencies your application relies on.
444  pub depends: Option<Vec<String>>,
445  /// The list of RPM dependencies your application recommends.
446  pub recommends: Option<Vec<String>>,
447  /// The list of RPM dependencies your application provides.
448  pub provides: Option<Vec<String>>,
449  /// The list of RPM dependencies your application conflicts with. They must not be present
450  /// in order for the package to be installed.
451  pub conflicts: Option<Vec<String>>,
452  /// The list of RPM dependencies your application supersedes - if this package is installed,
453  /// packages listed as "obsoletes" will be automatically removed (if they are present).
454  pub obsoletes: Option<Vec<String>>,
455  /// The RPM release tag.
456  #[serde(default = "default_release")]
457  pub release: String,
458  /// The RPM epoch.
459  #[serde(default)]
460  pub epoch: u32,
461  /// The files to include on the package.
462  #[serde(default)]
463  pub files: HashMap<PathBuf, PathBuf>,
464  /// Path to a custom desktop file Handlebars template.
465  ///
466  /// Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.
467  #[serde(alias = "desktop-template")]
468  pub desktop_template: Option<PathBuf>,
469  /// Path to script that will be executed before the package is unpacked. See
470  /// <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>
471  #[serde(alias = "pre-install-script")]
472  pub pre_install_script: Option<PathBuf>,
473  /// Path to script that will be executed after the package is unpacked. See
474  /// <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>
475  #[serde(alias = "post-install-script")]
476  pub post_install_script: Option<PathBuf>,
477  /// Path to script that will be executed before the package is removed. See
478  /// <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>
479  #[serde(alias = "pre-remove-script")]
480  pub pre_remove_script: Option<PathBuf>,
481  /// Path to script that will be executed after the package is removed. See
482  /// <http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html>
483  #[serde(alias = "post-remove-script")]
484  pub post_remove_script: Option<PathBuf>,
485  /// Compression algorithm and level. Defaults to `Gzip` with level 6.
486  pub compression: Option<RpmCompression>,
487}
488
489impl Default for RpmConfig {
490  fn default() -> Self {
491    Self {
492      depends: None,
493      recommends: None,
494      provides: None,
495      conflicts: None,
496      obsoletes: None,
497      release: default_release(),
498      epoch: 0,
499      files: Default::default(),
500      desktop_template: None,
501      pre_install_script: None,
502      post_install_script: None,
503      pre_remove_script: None,
504      post_remove_script: None,
505      compression: None,
506    }
507  }
508}
509
510fn default_release() -> String {
511  "1".into()
512}
513
514/// Position coordinates struct.
515#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
516#[cfg_attr(feature = "schema", derive(JsonSchema))]
517#[serde(rename_all = "camelCase", deny_unknown_fields)]
518pub struct Position {
519  /// X coordinate.
520  pub x: u32,
521  /// Y coordinate.
522  pub y: u32,
523}
524
525/// Position coordinates struct.
526#[derive(Default, Debug, PartialEq, Clone, Deserialize, Serialize)]
527#[cfg_attr(feature = "schema", derive(JsonSchema))]
528#[serde(rename_all = "camelCase", deny_unknown_fields)]
529pub struct LogicalPosition {
530  /// X coordinate.
531  pub x: f64,
532  /// Y coordinate.
533  pub y: f64,
534}
535
536/// Size of the window.
537#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
538#[cfg_attr(feature = "schema", derive(JsonSchema))]
539#[serde(rename_all = "camelCase", deny_unknown_fields)]
540pub struct Size {
541  /// Width of the window.
542  pub width: u32,
543  /// Height of the window.
544  pub height: u32,
545}
546
547/// Configuration for Apple Disk Image (.dmg) bundles.
548///
549/// See more: <https://v2.tauri.app/reference/config/#dmgconfig>
550#[skip_serializing_none]
551#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
552#[cfg_attr(feature = "schema", derive(JsonSchema))]
553#[serde(rename_all = "camelCase", deny_unknown_fields)]
554pub struct DmgConfig {
555  /// Image to use as the background in dmg file. Accepted formats: `png`/`jpg`/`gif`.
556  pub background: Option<PathBuf>,
557  /// Position of volume window on screen.
558  pub window_position: Option<Position>,
559  /// Size of volume window.
560  #[serde(default = "dmg_window_size", alias = "window-size")]
561  pub window_size: Size,
562  /// Position of app file on window.
563  #[serde(default = "dmg_app_position", alias = "app-position")]
564  pub app_position: Position,
565  /// Position of application folder on window.
566  #[serde(
567    default = "dmg_application_folder_position",
568    alias = "application-folder-position"
569  )]
570  pub application_folder_position: Position,
571}
572
573impl Default for DmgConfig {
574  fn default() -> Self {
575    Self {
576      background: None,
577      window_position: None,
578      window_size: dmg_window_size(),
579      app_position: dmg_app_position(),
580      application_folder_position: dmg_application_folder_position(),
581    }
582  }
583}
584
585fn dmg_window_size() -> Size {
586  Size {
587    width: 660,
588    height: 400,
589  }
590}
591
592fn dmg_app_position() -> Position {
593  Position { x: 180, y: 170 }
594}
595
596fn dmg_application_folder_position() -> Position {
597  Position { x: 480, y: 170 }
598}
599
600fn de_macos_minimum_system_version<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
601where
602  D: Deserializer<'de>,
603{
604  let version = Option::<String>::deserialize(deserializer)?;
605  match version {
606    Some(v) if v.is_empty() => Ok(macos_minimum_system_version()),
607    e => Ok(e),
608  }
609}
610
611/// Configuration for the macOS bundles.
612///
613/// See more: <https://v2.tauri.app/reference/config/#macconfig>
614#[skip_serializing_none]
615#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
616#[cfg_attr(feature = "schema", derive(JsonSchema))]
617#[serde(rename_all = "camelCase", deny_unknown_fields)]
618pub struct MacConfig {
619  /// A list of strings indicating any macOS X frameworks that need to be bundled with the application.
620  ///
621  /// If a name is used, ".framework" must be omitted and it will look for standard install locations. You may also use a path to a specific framework.
622  pub frameworks: Option<Vec<String>>,
623  /// The files to include in the application relative to the Contents directory.
624  #[serde(default)]
625  pub files: HashMap<PathBuf, PathBuf>,
626  /// The version of the build that identifies an iteration of the bundle.
627  ///
628  /// Translates to the bundle's CFBundleVersion property.
629  #[serde(alias = "bundle-version")]
630  pub bundle_version: Option<String>,
631  /// The name of the builder that built the bundle.
632  ///
633  /// Translates to the bundle's CFBundleName property.
634  ///
635  /// If not set, defaults to the package's product name.
636  #[serde(alias = "bundle-name")]
637  pub bundle_name: Option<String>,
638  /// A version string indicating the minimum macOS X version that the bundled application supports. Defaults to `10.13`.
639  ///
640  /// Setting it to `null` completely removes the `LSMinimumSystemVersion` field on the bundle's `Info.plist`
641  /// and the `MACOSX_DEPLOYMENT_TARGET` environment variable.
642  ///
643  /// An empty string is considered an invalid value so the default value is used.
644  #[serde(
645    deserialize_with = "de_macos_minimum_system_version",
646    default = "macos_minimum_system_version",
647    alias = "minimum-system-version"
648  )]
649  pub minimum_system_version: Option<String>,
650  /// Allows your application to communicate with the outside world.
651  /// It should be a lowercase, without port and protocol domain name.
652  #[serde(alias = "exception-domain")]
653  pub exception_domain: Option<String>,
654  /// Identity to use for code signing.
655  #[serde(alias = "signing-identity")]
656  pub signing_identity: Option<String>,
657  /// Whether the codesign should enable [hardened runtime](https://developer.apple.com/documentation/security/hardened_runtime) (for executables) or not.
658  #[serde(alias = "hardened-runtime", default = "default_true")]
659  pub hardened_runtime: bool,
660  /// Provider short name for notarization.
661  #[serde(alias = "provider-short-name")]
662  pub provider_short_name: Option<String>,
663  /// Path to the entitlements file.
664  pub entitlements: Option<String>,
665  /// DMG-specific settings.
666  #[serde(default)]
667  pub dmg: DmgConfig,
668}
669
670impl Default for MacConfig {
671  fn default() -> Self {
672    Self {
673      frameworks: None,
674      files: HashMap::new(),
675      bundle_version: None,
676      bundle_name: None,
677      minimum_system_version: macos_minimum_system_version(),
678      exception_domain: None,
679      signing_identity: None,
680      hardened_runtime: true,
681      provider_short_name: None,
682      entitlements: None,
683      dmg: Default::default(),
684    }
685  }
686}
687
688fn macos_minimum_system_version() -> Option<String> {
689  Some("10.13".into())
690}
691
692fn ios_minimum_system_version() -> String {
693  "14.0".into()
694}
695
696/// Configuration for a target language for the WiX build.
697///
698/// See more: <https://v2.tauri.app/reference/config/#wixlanguageconfig>
699#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
700#[cfg_attr(feature = "schema", derive(JsonSchema))]
701#[serde(rename_all = "camelCase", deny_unknown_fields)]
702pub struct WixLanguageConfig {
703  /// The path to a locale (`.wxl`) file. See <https://wixtoolset.org/documentation/manual/v3/howtos/ui_and_localization/build_a_localized_version.html>.
704  #[serde(alias = "locale-path")]
705  pub locale_path: Option<String>,
706}
707
708/// The languages to build using WiX.
709#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
710#[cfg_attr(feature = "schema", derive(JsonSchema))]
711#[serde(untagged)]
712pub enum WixLanguage {
713  /// A single language to build, without configuration.
714  One(String),
715  /// A list of languages to build, without configuration.
716  List(Vec<String>),
717  /// A map of languages and its configuration.
718  Localized(HashMap<String, WixLanguageConfig>),
719}
720
721impl Default for WixLanguage {
722  fn default() -> Self {
723    Self::One("en-US".into())
724  }
725}
726
727/// Configuration for the MSI bundle using WiX.
728///
729/// See more: <https://v2.tauri.app/reference/config/#wixconfig>
730#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
731#[cfg_attr(feature = "schema", derive(JsonSchema))]
732#[serde(rename_all = "camelCase", deny_unknown_fields)]
733pub struct WixConfig {
734  /// MSI installer version in the format `major.minor.patch.build` (build is optional).
735  ///
736  /// Because a valid version is required for MSI installer, it will be derived from [`Config::version`] if this field is not set.
737  ///
738  /// The first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255.
739  /// The third and foruth fields have a maximum value of 65,535.
740  ///
741  /// See <https://learn.microsoft.com/en-us/windows/win32/msi/productversion> for more info.
742  pub version: Option<String>,
743  /// A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**,
744  /// otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app.
745  ///
746  /// By default, tauri generates this code by generating a Uuid v5 using the string `<productName>.exe.app.x64` in the DNS namespace.
747  /// You can use Tauri's CLI to generate and print this code for you, run `tauri inspect wix-upgrade-code`.
748  ///
749  /// It is recommended that you set this value in your tauri config file to avoid accidental changes in your upgrade code
750  /// whenever you want to change your product name.
751  #[serde(alias = "upgrade-code")]
752  pub upgrade_code: Option<uuid::Uuid>,
753  /// The installer languages to build. See <https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables>.
754  #[serde(default)]
755  pub language: WixLanguage,
756  /// A custom .wxs template to use.
757  pub template: Option<PathBuf>,
758  /// A list of paths to .wxs files with WiX fragments to use.
759  #[serde(default, alias = "fragment-paths")]
760  pub fragment_paths: Vec<PathBuf>,
761  /// The ComponentGroup element ids you want to reference from the fragments.
762  #[serde(default, alias = "component-group-refs")]
763  pub component_group_refs: Vec<String>,
764  /// The Component element ids you want to reference from the fragments.
765  #[serde(default, alias = "component-refs")]
766  pub component_refs: Vec<String>,
767  /// The FeatureGroup element ids you want to reference from the fragments.
768  #[serde(default, alias = "feature-group-refs")]
769  pub feature_group_refs: Vec<String>,
770  /// The Feature element ids you want to reference from the fragments.
771  #[serde(default, alias = "feature-refs")]
772  pub feature_refs: Vec<String>,
773  /// The Merge element ids you want to reference from the fragments.
774  #[serde(default, alias = "merge-refs")]
775  pub merge_refs: Vec<String>,
776  /// Create an elevated update task within Windows Task Scheduler.
777  #[serde(default, alias = "enable-elevated-update-task")]
778  pub enable_elevated_update_task: bool,
779  /// Path to a bitmap file to use as the installation user interface banner.
780  /// This bitmap will appear at the top of all but the first page of the installer.
781  ///
782  /// The required dimensions are 493px × 58px.
783  #[serde(alias = "banner-path")]
784  pub banner_path: Option<PathBuf>,
785  /// Path to a bitmap file to use on the installation user interface dialogs.
786  /// It is used on the welcome and completion dialogs.
787  ///
788  /// The required dimensions are 493px × 312px.
789  #[serde(alias = "dialog-image-path")]
790  pub dialog_image_path: Option<PathBuf>,
791  /// Enables FIPS compliant algorithms.
792  /// Can also be enabled via the `TAURI_BUNDLER_WIX_FIPS_COMPLIANT` env var.
793  #[serde(default, alias = "fips-compliant")]
794  pub fips_compliant: bool,
795}
796
797/// Compression algorithms used in the NSIS installer.
798///
799/// See <https://nsis.sourceforge.io/Reference/SetCompressor>
800#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
801#[cfg_attr(feature = "schema", derive(JsonSchema))]
802#[serde(rename_all = "camelCase", deny_unknown_fields)]
803pub enum NsisCompression {
804  /// ZLIB uses the deflate algorithm, it is a quick and simple method. With the default compression level it uses about 300 KB of memory.
805  Zlib,
806  /// BZIP2 usually gives better compression ratios than ZLIB, but it is a bit slower and uses more memory. With the default compression level it uses about 4 MB of memory.
807  Bzip2,
808  /// LZMA (default) is a new compression method that gives very good compression ratios. The decompression speed is high (10-20 MB/s on a 2 GHz CPU), the compression speed is lower. The memory size that will be used for decompression is the dictionary size plus a few KBs, the default is 8 MB.
809  Lzma,
810  /// Disable compression
811  None,
812}
813
814impl Default for NsisCompression {
815  fn default() -> Self {
816    Self::Lzma
817  }
818}
819
820/// Install Modes for the NSIS installer.
821#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
822#[serde(rename_all = "camelCase", deny_unknown_fields)]
823#[cfg_attr(feature = "schema", derive(JsonSchema))]
824pub enum NSISInstallerMode {
825  /// Default mode for the installer.
826  ///
827  /// Install the app by default in a directory that doesn't require Administrator access.
828  ///
829  /// Installer metadata will be saved under the `HKCU` registry path.
830  CurrentUser,
831  /// Install the app by default in the `Program Files` folder directory requires Administrator
832  /// access for the installation.
833  ///
834  /// Installer metadata will be saved under the `HKLM` registry path.
835  PerMachine,
836  /// Combines both modes and allows the user to choose at install time
837  /// whether to install for the current user or per machine. Note that this mode
838  /// will require Administrator access even if the user wants to install it for the current user only.
839  ///
840  /// Installer metadata will be saved under the `HKLM` or `HKCU` registry path based on the user's choice.
841  Both,
842}
843
844impl Default for NSISInstallerMode {
845  fn default() -> Self {
846    Self::CurrentUser
847  }
848}
849
850/// Configuration for the Installer bundle using NSIS.
851#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
852#[cfg_attr(feature = "schema", derive(JsonSchema))]
853#[serde(rename_all = "camelCase", deny_unknown_fields)]
854pub struct NsisConfig {
855  /// A custom .nsi template to use.
856  pub template: Option<PathBuf>,
857  /// The path to a bitmap file to display on the header of installers pages.
858  ///
859  /// The recommended dimensions are 150px x 57px.
860  #[serde(alias = "header-image")]
861  pub header_image: Option<PathBuf>,
862  /// The path to a bitmap file for the Welcome page and the Finish page.
863  ///
864  /// The recommended dimensions are 164px x 314px.
865  #[serde(alias = "sidebar-image")]
866  pub sidebar_image: Option<PathBuf>,
867  /// The path to an icon file used as the installer icon.
868  #[serde(alias = "install-icon")]
869  pub installer_icon: Option<PathBuf>,
870  /// Whether the installation will be for all users or just the current user.
871  #[serde(default, alias = "install-mode")]
872  pub install_mode: NSISInstallerMode,
873  /// A list of installer languages.
874  /// By default the OS language is used. If the OS language is not in the list of languages, the first language will be used.
875  /// To allow the user to select the language, set `display_language_selector` to `true`.
876  ///
877  /// See <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files> for the complete list of languages.
878  pub languages: Option<Vec<String>>,
879  /// A key-value pair where the key is the language and the
880  /// value is the path to a custom `.nsh` file that holds the translated text for tauri's custom messages.
881  ///
882  /// See <https://github.com/tauri-apps/tauri/blob/dev/crates/tauri-bundler/src/bundle/windows/nsis/languages/English.nsh> for an example `.nsh` file.
883  ///
884  /// **Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`] languages array,
885  pub custom_language_files: Option<HashMap<String, PathBuf>>,
886  /// Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not.
887  /// By default the OS language is selected, with a fallback to the first language in the `languages` array.
888  #[serde(default, alias = "display-language-selector")]
889  pub display_language_selector: bool,
890  /// Set the compression algorithm used to compress files in the installer.
891  ///
892  /// See <https://nsis.sourceforge.io/Reference/SetCompressor>
893  #[serde(default)]
894  pub compression: NsisCompression,
895  /// Set the folder name for the start menu shortcut.
896  ///
897  /// Use this option if you have multiple apps and wish to group their shortcuts under one folder
898  /// or if you generally prefer to set your shortcut inside a folder.
899  ///
900  /// Examples:
901  /// - `AwesomePublisher`, shortcut will be placed in `%AppData%\Microsoft\Windows\Start Menu\Programs\AwesomePublisher\<your-app>.lnk`
902  /// - If unset, shortcut will be placed in `%AppData%\Microsoft\Windows\Start Menu\Programs\<your-app>.lnk`
903  #[serde(alias = "start-menu-folder")]
904  pub start_menu_folder: Option<String>,
905  /// A path to a `.nsh` file that contains special NSIS macros to be hooked into the
906  /// main installer.nsi script.
907  ///
908  /// Supported hooks are:
909  /// - `NSIS_HOOK_PREINSTALL`: This hook runs before copying files, setting registry key values and creating shortcuts.
910  /// - `NSIS_HOOK_POSTINSTALL`: This hook runs after the installer has finished copying all files, setting the registry keys and created shortcuts.
911  /// - `NSIS_HOOK_PREUNINSTALL`: This hook runs before removing any files, registry keys and shortcuts.
912  /// - `NSIS_HOOK_POSTUNINSTALL`: This hook runs after files, registry keys and shortcuts have been removed.
913  ///
914  ///
915  /// ### Example
916  ///
917  /// ```nsh
918  /// !macro NSIS_HOOK_PREINSTALL
919  ///   MessageBox MB_OK "PreInstall"
920  /// !macroend
921  ///
922  /// !macro NSIS_HOOK_POSTINSTALL
923  ///   MessageBox MB_OK "PostInstall"
924  /// !macroend
925  ///
926  /// !macro NSIS_HOOK_PREUNINSTALL
927  ///   MessageBox MB_OK "PreUnInstall"
928  /// !macroend
929  ///
930  /// !macro NSIS_HOOK_POSTUNINSTALL
931  ///   MessageBox MB_OK "PostUninstall"
932  /// !macroend
933  ///
934  /// ```
935  #[serde(alias = "installer-hooks")]
936  pub installer_hooks: Option<PathBuf>,
937  /// Try to ensure that the WebView2 version is equal to or newer than this version,
938  /// if the user's WebView2 is older than this version,
939  /// the installer will try to trigger a WebView2 update.
940  #[serde(alias = "minimum-webview2-version")]
941  pub minimum_webview2_version: Option<String>,
942}
943
944/// Install modes for the Webview2 runtime.
945/// Note that for the updater bundle [`Self::DownloadBootstrapper`] is used.
946///
947/// For more information see <https://v2.tauri.app/distribute/windows-installer/#webview2-installation-options>.
948#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
949#[serde(tag = "type", rename_all = "camelCase", deny_unknown_fields)]
950#[cfg_attr(feature = "schema", derive(JsonSchema))]
951pub enum WebviewInstallMode {
952  /// Do not install the Webview2 as part of the Windows Installer.
953  Skip,
954  /// Download the bootstrapper and run it.
955  /// Requires an internet connection.
956  /// Results in a smaller installer size, but is not recommended on Windows 7.
957  DownloadBootstrapper {
958    /// Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`.
959    #[serde(default = "default_true")]
960    silent: bool,
961  },
962  /// Embed the bootstrapper and run it.
963  /// Requires an internet connection.
964  /// Increases the installer size by around 1.8MB, but offers better support on Windows 7.
965  EmbedBootstrapper {
966    /// Instructs the installer to run the bootstrapper in silent mode. Defaults to `true`.
967    #[serde(default = "default_true")]
968    silent: bool,
969  },
970  /// Embed the offline installer and run it.
971  /// Does not require an internet connection.
972  /// Increases the installer size by around 127MB.
973  OfflineInstaller {
974    /// Instructs the installer to run the installer in silent mode. Defaults to `true`.
975    #[serde(default = "default_true")]
976    silent: bool,
977  },
978  /// Embed a fixed webview2 version and use it at runtime.
979  /// Increases the installer size by around 180MB.
980  FixedRuntime {
981    /// The path to the fixed runtime to use.
982    ///
983    /// The fixed version can be downloaded [on the official website](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section).
984    /// The `.cab` file must be extracted to a folder and this folder path must be defined on this field.
985    path: PathBuf,
986  },
987}
988
989impl Default for WebviewInstallMode {
990  fn default() -> Self {
991    Self::DownloadBootstrapper { silent: true }
992  }
993}
994
995/// Custom Signing Command configuration.
996#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
997#[cfg_attr(feature = "schema", derive(JsonSchema))]
998#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
999pub enum CustomSignCommandConfig {
1000  /// A string notation of the script to execute.
1001  ///
1002  /// "%1" will be replaced with the path to the binary to be signed.
1003  ///
1004  /// This is a simpler notation for the command.
1005  /// Tauri will split the string with `' '` and use the first element as the command name and the rest as arguments.
1006  ///
1007  /// If you need to use whitespace in the command or arguments, use the object notation [`Self::ScriptWithOptions`].
1008  Command(String),
1009  /// An object notation of the command.
1010  ///
1011  /// This is more complex notation for the command but
1012  /// this allows you to use whitespace in the command and arguments.
1013  CommandWithOptions {
1014    /// The command to run to sign the binary.
1015    cmd: String,
1016    /// The arguments to pass to the command.
1017    ///
1018    /// "%1" will be replaced with the path to the binary to be signed.
1019    args: Vec<String>,
1020  },
1021}
1022
1023/// Windows bundler configuration.
1024///
1025/// See more: <https://v2.tauri.app/reference/config/#windowsconfig>
1026#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1027#[cfg_attr(feature = "schema", derive(JsonSchema))]
1028#[serde(rename_all = "camelCase", deny_unknown_fields)]
1029pub struct WindowsConfig {
1030  /// Specifies the file digest algorithm to use for creating file signatures.
1031  /// Required for code signing. SHA-256 is recommended.
1032  #[serde(alias = "digest-algorithm")]
1033  pub digest_algorithm: Option<String>,
1034  /// Specifies the SHA1 hash of the signing certificate.
1035  #[serde(alias = "certificate-thumbprint")]
1036  pub certificate_thumbprint: Option<String>,
1037  /// Server to use during timestamping.
1038  #[serde(alias = "timestamp-url")]
1039  pub timestamp_url: Option<String>,
1040  /// Whether to use Time-Stamp Protocol (TSP, a.k.a. RFC 3161) for the timestamp server. Your code signing provider may
1041  /// use a TSP timestamp server, like e.g. SSL.com does. If so, enable TSP by setting to true.
1042  #[serde(default)]
1043  pub tsp: bool,
1044  /// The installation mode for the Webview2 runtime.
1045  #[serde(default, alias = "webview-install-mode")]
1046  pub webview_install_mode: WebviewInstallMode,
1047  /// Validates a second app installation, blocking the user from installing an older version if set to `false`.
1048  ///
1049  /// For instance, if `1.2.1` is installed, the user won't be able to install app version `1.2.0` or `1.1.5`.
1050  ///
1051  /// The default value of this flag is `true`.
1052  #[serde(default = "default_true", alias = "allow-downgrades")]
1053  pub allow_downgrades: bool,
1054  /// Configuration for the MSI generated with WiX.
1055  pub wix: Option<WixConfig>,
1056  /// Configuration for the installer generated with NSIS.
1057  pub nsis: Option<NsisConfig>,
1058  /// Specify a custom command to sign the binaries.
1059  /// This command needs to have a `%1` in args which is just a placeholder for the binary path,
1060  /// which we will detect and replace before calling the command.
1061  ///
1062  /// By Default we use `signtool.exe` which can be found only on Windows so
1063  /// if you are on another platform and want to cross-compile and sign you will
1064  /// need to use another tool like `osslsigncode`.
1065  #[serde(alias = "sign-command")]
1066  pub sign_command: Option<CustomSignCommandConfig>,
1067}
1068
1069impl Default for WindowsConfig {
1070  fn default() -> Self {
1071    Self {
1072      digest_algorithm: None,
1073      certificate_thumbprint: None,
1074      timestamp_url: None,
1075      tsp: false,
1076      webview_install_mode: Default::default(),
1077      allow_downgrades: true,
1078      wix: None,
1079      nsis: None,
1080      sign_command: None,
1081    }
1082  }
1083}
1084
1085/// macOS-only. Corresponds to CFBundleTypeRole
1086#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1087#[cfg_attr(feature = "schema", derive(JsonSchema))]
1088pub enum BundleTypeRole {
1089  /// CFBundleTypeRole.Editor. Files can be read and edited.
1090  #[default]
1091  Editor,
1092  /// CFBundleTypeRole.Viewer. Files can be read.
1093  Viewer,
1094  /// CFBundleTypeRole.Shell
1095  Shell,
1096  /// CFBundleTypeRole.QLGenerator
1097  QLGenerator,
1098  /// CFBundleTypeRole.None
1099  None,
1100}
1101
1102impl Display for BundleTypeRole {
1103  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1104    match self {
1105      Self::Editor => write!(f, "Editor"),
1106      Self::Viewer => write!(f, "Viewer"),
1107      Self::Shell => write!(f, "Shell"),
1108      Self::QLGenerator => write!(f, "QLGenerator"),
1109      Self::None => write!(f, "None"),
1110    }
1111  }
1112}
1113
1114// Issue #13159 - Missing the LSHandlerRank and Apple warns after uploading to App Store Connect.
1115// https://github.com/tauri-apps/tauri/issues/13159
1116/// Corresponds to LSHandlerRank
1117#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1118#[cfg_attr(feature = "schema", derive(JsonSchema))]
1119pub enum HandlerRank {
1120  /// LSHandlerRank.Default. This app is an opener of files of this type; this value is also used if no rank is specified.
1121  #[default]
1122  Default,
1123  /// LSHandlerRank.Owner. This app is the primary creator of files of this type.
1124  Owner,
1125  /// LSHandlerRank.Alternate. This app is a secondary viewer of files of this type.
1126  Alternate,
1127  /// LSHandlerRank.None. This app is never selected to open files of this type, but it accepts drops of files of this type.
1128  None,
1129}
1130
1131impl Display for HandlerRank {
1132  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1133    match self {
1134      Self::Default => write!(f, "Default"),
1135      Self::Owner => write!(f, "Owner"),
1136      Self::Alternate => write!(f, "Alternate"),
1137      Self::None => write!(f, "None"),
1138    }
1139  }
1140}
1141
1142/// An extension for a [`FileAssociation`].
1143///
1144/// A leading `.` is automatically stripped.
1145#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
1146#[cfg_attr(feature = "schema", derive(JsonSchema))]
1147pub struct AssociationExt(pub String);
1148
1149impl fmt::Display for AssociationExt {
1150  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1151    write!(f, "{}", self.0)
1152  }
1153}
1154
1155impl<'d> serde::Deserialize<'d> for AssociationExt {
1156  fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
1157    let ext = String::deserialize(deserializer)?;
1158    if let Some(ext) = ext.strip_prefix('.') {
1159      Ok(AssociationExt(ext.into()))
1160    } else {
1161      Ok(AssociationExt(ext))
1162    }
1163  }
1164}
1165
1166/// File association
1167#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1168#[cfg_attr(feature = "schema", derive(JsonSchema))]
1169#[serde(rename_all = "camelCase", deny_unknown_fields)]
1170pub struct FileAssociation {
1171  /// File extensions to associate with this app. e.g. 'png'
1172  pub ext: Vec<AssociationExt>,
1173  /// The name. Maps to `CFBundleTypeName` on macOS. Default to `ext[0]`
1174  pub name: Option<String>,
1175  /// The association description. Windows-only. It is displayed on the `Type` column on Windows Explorer.
1176  pub description: Option<String>,
1177  /// The app's role with respect to the type. Maps to `CFBundleTypeRole` on macOS.
1178  #[serde(default)]
1179  pub role: BundleTypeRole,
1180  /// The mime-type e.g. 'image/png' or 'text/plain'. Linux-only.
1181  #[serde(alias = "mime-type")]
1182  pub mime_type: Option<String>,
1183  /// The ranking of this app among apps that declare themselves as editors or viewers of the given file type.  Maps to `LSHandlerRank` on macOS.
1184  #[serde(default)]
1185  pub rank: HandlerRank,
1186}
1187
1188/// Deep link protocol configuration.
1189#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1190#[cfg_attr(feature = "schema", derive(JsonSchema))]
1191#[serde(rename_all = "camelCase", deny_unknown_fields)]
1192pub struct DeepLinkProtocol {
1193  /// URL schemes to associate with this app without `://`. For example `my-app`
1194  pub schemes: Vec<String>,
1195  /// The protocol name. **macOS-only** and maps to `CFBundleTypeName`. Defaults to `<bundle-id>.<schemes[0]>`
1196  pub name: Option<String>,
1197  /// The app's role for these schemes. **macOS-only** and maps to `CFBundleTypeRole`.
1198  #[serde(default)]
1199  pub role: BundleTypeRole,
1200}
1201
1202/// Definition for bundle resources.
1203/// Can be either a list of paths to include or a map of source to target paths.
1204#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1205#[cfg_attr(feature = "schema", derive(JsonSchema))]
1206#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1207pub enum BundleResources {
1208  /// A list of paths to include.
1209  List(Vec<String>),
1210  /// A map of source to target paths.
1211  Map(HashMap<String, String>),
1212}
1213
1214impl BundleResources {
1215  /// Adds a path to the resource collection.
1216  pub fn push(&mut self, path: impl Into<String>) {
1217    match self {
1218      Self::List(l) => l.push(path.into()),
1219      Self::Map(l) => {
1220        let path = path.into();
1221        l.insert(path.clone(), path);
1222      }
1223    }
1224  }
1225}
1226
1227/// Updater type
1228#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1229#[cfg_attr(feature = "schema", derive(JsonSchema))]
1230#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1231pub enum Updater {
1232  /// Generates legacy zipped v1 compatible updaters
1233  String(V1Compatible),
1234  /// Produce updaters and their signatures or not
1235  // Can't use untagged on enum field here: https://github.com/GREsau/schemars/issues/222
1236  Bool(bool),
1237}
1238
1239impl Default for Updater {
1240  fn default() -> Self {
1241    Self::Bool(false)
1242  }
1243}
1244
1245/// Generates legacy zipped v1 compatible updaters
1246#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1247#[cfg_attr(feature = "schema", derive(JsonSchema))]
1248#[serde(rename_all = "camelCase", deny_unknown_fields)]
1249pub enum V1Compatible {
1250  /// Generates legacy zipped v1 compatible updaters
1251  V1Compatible,
1252}
1253
1254/// Configuration for tauri-bundler.
1255///
1256/// See more: <https://v2.tauri.app/reference/config/#bundleconfig>
1257#[skip_serializing_none]
1258#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1259#[cfg_attr(feature = "schema", derive(JsonSchema))]
1260#[serde(rename_all = "camelCase", deny_unknown_fields)]
1261pub struct BundleConfig {
1262  /// Whether Tauri should bundle your application or just output the executable.
1263  #[serde(default)]
1264  pub active: bool,
1265  /// The bundle targets, currently supports ["deb", "rpm", "appimage", "nsis", "msi", "app", "dmg"] or "all".
1266  #[serde(default)]
1267  pub targets: BundleTarget,
1268  #[serde(default)]
1269  /// Produce updaters and their signatures or not
1270  pub create_updater_artifacts: Updater,
1271  /// The application's publisher. Defaults to the second element in the identifier string.
1272  ///
1273  /// Currently maps to the Manufacturer property of the Windows Installer
1274  /// and the Maintainer field of debian packages if the Cargo.toml does not have the authors field.
1275  pub publisher: Option<String>,
1276  /// A url to the home page of your application. If unset, will
1277  /// fallback to `homepage` defined in `Cargo.toml`.
1278  ///
1279  /// Supported bundle targets: `deb`, `rpm`, `nsis` and `msi`.
1280  pub homepage: Option<String>,
1281  /// The app's icons
1282  #[serde(default)]
1283  pub icon: Vec<String>,
1284  /// App resources to bundle.
1285  /// Each resource is a path to a file or directory.
1286  /// Glob patterns are supported.
1287  pub resources: Option<BundleResources>,
1288  /// A copyright string associated with your application.
1289  pub copyright: Option<String>,
1290  /// The package's license identifier to be included in the appropriate bundles.
1291  /// If not set, defaults to the license from the Cargo.toml file.
1292  pub license: Option<String>,
1293  /// The path to the license file to be included in the appropriate bundles.
1294  #[serde(alias = "license-file")]
1295  pub license_file: Option<PathBuf>,
1296  /// The application kind.
1297  ///
1298  /// Should be one of the following:
1299  /// Business, DeveloperTool, Education, Entertainment, Finance, Game, ActionGame, AdventureGame, ArcadeGame, BoardGame, CardGame, CasinoGame, DiceGame, EducationalGame, FamilyGame, KidsGame, MusicGame, PuzzleGame, RacingGame, RolePlayingGame, SimulationGame, SportsGame, StrategyGame, TriviaGame, WordGame, GraphicsAndDesign, HealthcareAndFitness, Lifestyle, Medical, Music, News, Photography, Productivity, Reference, SocialNetworking, Sports, Travel, Utility, Video, Weather.
1300  pub category: Option<String>,
1301  /// File associations to application.
1302  pub file_associations: Option<Vec<FileAssociation>>,
1303  /// A short description of your application.
1304  #[serde(alias = "short-description")]
1305  pub short_description: Option<String>,
1306  /// A longer, multi-line description of the application.
1307  #[serde(alias = "long-description")]
1308  pub long_description: Option<String>,
1309  /// Whether to use the project's `target` directory, for caching build tools (e.g., Wix and NSIS) when building this application. Defaults to `false`.
1310  ///
1311  /// If true, tools will be cached in `target/.tauri/`.
1312  /// If false, tools will be cached in the current user's platform-specific cache directory.
1313  ///
1314  /// An example where it can be appropriate to set this to `true` is when building this application as a Windows System user (e.g., AWS EC2 workloads),
1315  /// because the Window system's app data directory is restricted.
1316  #[serde(default, alias = "use-local-tools-dir")]
1317  pub use_local_tools_dir: bool,
1318  /// A list of—either absolute or relative—paths to binaries to embed with your application.
1319  ///
1320  /// Note that Tauri will look for system-specific binaries following the pattern "binary-name{-target-triple}{.system-extension}".
1321  ///
1322  /// E.g. for the external binary "my-binary", Tauri looks for:
1323  ///
1324  /// - "my-binary-x86_64-pc-windows-msvc.exe" for Windows
1325  /// - "my-binary-x86_64-apple-darwin" for macOS
1326  /// - "my-binary-x86_64-unknown-linux-gnu" for Linux
1327  ///
1328  /// so don't forget to provide binaries for all targeted platforms.
1329  #[serde(alias = "external-bin")]
1330  pub external_bin: Option<Vec<String>>,
1331  /// Configuration for the Windows bundles.
1332  #[serde(default)]
1333  pub windows: WindowsConfig,
1334  /// Configuration for the Linux bundles.
1335  #[serde(default)]
1336  pub linux: LinuxConfig,
1337  /// Configuration for the macOS bundles.
1338  #[serde(rename = "macOS", alias = "macos", default)]
1339  pub macos: MacConfig,
1340  /// iOS configuration.
1341  #[serde(rename = "iOS", alias = "ios", default)]
1342  pub ios: IosConfig,
1343  /// Android configuration.
1344  #[serde(default)]
1345  pub android: AndroidConfig,
1346}
1347
1348/// A tuple struct of RGBA colors. Each value has minimum of 0 and maximum of 255.
1349#[derive(Debug, PartialEq, Eq, Serialize, Default, Clone, Copy)]
1350#[serde(rename_all = "camelCase", deny_unknown_fields)]
1351pub struct Color(pub u8, pub u8, pub u8, pub u8);
1352
1353impl From<Color> for (u8, u8, u8, u8) {
1354  fn from(value: Color) -> Self {
1355    (value.0, value.1, value.2, value.3)
1356  }
1357}
1358
1359impl From<Color> for (u8, u8, u8) {
1360  fn from(value: Color) -> Self {
1361    (value.0, value.1, value.2)
1362  }
1363}
1364
1365impl From<(u8, u8, u8, u8)> for Color {
1366  fn from(value: (u8, u8, u8, u8)) -> Self {
1367    Color(value.0, value.1, value.2, value.3)
1368  }
1369}
1370
1371impl From<(u8, u8, u8)> for Color {
1372  fn from(value: (u8, u8, u8)) -> Self {
1373    Color(value.0, value.1, value.2, 255)
1374  }
1375}
1376
1377impl From<Color> for [u8; 4] {
1378  fn from(value: Color) -> Self {
1379    [value.0, value.1, value.2, value.3]
1380  }
1381}
1382
1383impl From<Color> for [u8; 3] {
1384  fn from(value: Color) -> Self {
1385    [value.0, value.1, value.2]
1386  }
1387}
1388
1389impl From<[u8; 4]> for Color {
1390  fn from(value: [u8; 4]) -> Self {
1391    Color(value[0], value[1], value[2], value[3])
1392  }
1393}
1394
1395impl From<[u8; 3]> for Color {
1396  fn from(value: [u8; 3]) -> Self {
1397    Color(value[0], value[1], value[2], 255)
1398  }
1399}
1400
1401impl FromStr for Color {
1402  type Err = String;
1403  fn from_str(mut color: &str) -> Result<Self, Self::Err> {
1404    color = color.trim().strip_prefix('#').unwrap_or(color);
1405    let color = match color.len() {
1406      // TODO: use repeat_n once our MSRV is bumped to 1.82
1407      3 => color.chars()
1408            .flat_map(|c| std::iter::repeat(c).take(2))
1409            .chain(std::iter::repeat('f').take(2))
1410            .collect(),
1411      6 => format!("{color}FF"),
1412      8 => color.to_string(),
1413      _ => return Err("Invalid hex color length, must be either 3, 6 or 8, for example: #fff, #ffffff, or #ffffffff".into()),
1414    };
1415
1416    let r = u8::from_str_radix(&color[0..2], 16).map_err(|e| e.to_string())?;
1417    let g = u8::from_str_radix(&color[2..4], 16).map_err(|e| e.to_string())?;
1418    let b = u8::from_str_radix(&color[4..6], 16).map_err(|e| e.to_string())?;
1419    let a = u8::from_str_radix(&color[6..8], 16).map_err(|e| e.to_string())?;
1420
1421    Ok(Color(r, g, b, a))
1422  }
1423}
1424
1425fn default_alpha() -> u8 {
1426  255
1427}
1428
1429#[derive(Deserialize)]
1430#[cfg_attr(feature = "schema", derive(JsonSchema))]
1431#[serde(untagged)]
1432enum InnerColor {
1433  /// Color hex string, for example: #fff, #ffffff, or #ffffffff.
1434  String(String),
1435  /// Array of RGB colors. Each value has minimum of 0 and maximum of 255.
1436  Rgb((u8, u8, u8)),
1437  /// Array of RGBA colors. Each value has minimum of 0 and maximum of 255.
1438  Rgba((u8, u8, u8, u8)),
1439  /// Object of red, green, blue, alpha color values. Each value has minimum of 0 and maximum of 255.
1440  RgbaObject {
1441    red: u8,
1442    green: u8,
1443    blue: u8,
1444    #[serde(default = "default_alpha")]
1445    alpha: u8,
1446  },
1447}
1448
1449impl<'de> Deserialize<'de> for Color {
1450  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1451  where
1452    D: Deserializer<'de>,
1453  {
1454    let color = InnerColor::deserialize(deserializer)?;
1455    let color = match color {
1456      InnerColor::String(string) => string.parse().map_err(serde::de::Error::custom)?,
1457      InnerColor::Rgb(rgb) => Color(rgb.0, rgb.1, rgb.2, 255),
1458      InnerColor::Rgba(rgb) => rgb.into(),
1459      InnerColor::RgbaObject {
1460        red,
1461        green,
1462        blue,
1463        alpha,
1464      } => Color(red, green, blue, alpha),
1465    };
1466
1467    Ok(color)
1468  }
1469}
1470
1471#[cfg(feature = "schema")]
1472impl schemars::JsonSchema for Color {
1473  fn schema_name() -> String {
1474    "Color".to_string()
1475  }
1476
1477  fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
1478    let mut schema = schemars::schema_for!(InnerColor).schema;
1479    schema.metadata = None; // Remove `title: InnerColor` from schema
1480
1481    // add hex color pattern validation
1482    let any_of = schema.subschemas().any_of.as_mut().unwrap();
1483    let schemars::schema::Schema::Object(str_schema) = any_of.first_mut().unwrap() else {
1484      unreachable!()
1485    };
1486    str_schema.string().pattern = Some("^#?([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$".into());
1487
1488    schema.into()
1489  }
1490}
1491
1492/// Background throttling policy.
1493#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1494#[cfg_attr(feature = "schema", derive(JsonSchema))]
1495#[serde(rename_all = "camelCase", deny_unknown_fields)]
1496pub enum BackgroundThrottlingPolicy {
1497  /// A policy where background throttling is disabled
1498  Disabled,
1499  /// A policy where a web view that's not in a window fully suspends tasks. This is usually the default behavior in case no policy is set.
1500  Suspend,
1501  /// A policy where a web view that's not in a window limits processing, but does not fully suspend tasks.
1502  Throttle,
1503}
1504
1505/// The window effects configuration object
1506#[skip_serializing_none]
1507#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
1508#[cfg_attr(feature = "schema", derive(JsonSchema))]
1509#[serde(rename_all = "camelCase", deny_unknown_fields)]
1510pub struct WindowEffectsConfig {
1511  /// List of Window effects to apply to the Window.
1512  /// Conflicting effects will apply the first one and ignore the rest.
1513  pub effects: Vec<WindowEffect>,
1514  /// Window effect state **macOS Only**
1515  pub state: Option<WindowEffectState>,
1516  /// Window effect corner radius **macOS Only**
1517  pub radius: Option<f64>,
1518  /// Window effect color. Affects [`WindowEffect::Blur`] and [`WindowEffect::Acrylic`] only
1519  /// on Windows 10 v1903+. Doesn't have any effect on Windows 7 or Windows 11.
1520  pub color: Option<Color>,
1521}
1522
1523/// Enable prevent overflow with a margin
1524/// so that the window's size + this margin won't overflow the workarea
1525#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
1526#[cfg_attr(feature = "schema", derive(JsonSchema))]
1527#[serde(rename_all = "camelCase", deny_unknown_fields)]
1528pub struct PreventOverflowMargin {
1529  /// Horizontal margin in physical unit
1530  pub width: u32,
1531  /// Vertical margin in physical unit
1532  pub height: u32,
1533}
1534
1535/// Prevent overflow with a margin
1536#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1537#[cfg_attr(feature = "schema", derive(JsonSchema))]
1538#[serde(untagged)]
1539pub enum PreventOverflowConfig {
1540  /// Enable prevent overflow or not
1541  Enable(bool),
1542  /// Enable prevent overflow with a margin
1543  /// so that the window's size + this margin won't overflow the workarea
1544  Margin(PreventOverflowMargin),
1545}
1546
1547/// The window configuration object.
1548///
1549/// See more: <https://v2.tauri.app/reference/config/#windowconfig>
1550#[skip_serializing_none]
1551#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
1552#[cfg_attr(feature = "schema", derive(JsonSchema))]
1553#[serde(rename_all = "camelCase", deny_unknown_fields)]
1554pub struct WindowConfig {
1555  /// The window identifier. It must be alphanumeric.
1556  #[serde(default = "default_window_label")]
1557  pub label: String,
1558  /// Whether Tauri should create this window at app startup or not.
1559  ///
1560  /// When this is set to `false` you must manually grab the config object via `app.config().app.windows`
1561  /// and create it with [`WebviewWindowBuilder::from_config`](https://docs.rs/tauri/2/tauri/webview/struct.WebviewWindowBuilder.html#method.from_config).
1562  #[serde(default = "default_true")]
1563  pub create: bool,
1564  /// The window webview URL.
1565  #[serde(default)]
1566  pub url: WebviewUrl,
1567  /// The user agent for the webview
1568  #[serde(alias = "user-agent")]
1569  pub user_agent: Option<String>,
1570  /// Whether the drag and drop is enabled or not on the webview. By default it is enabled.
1571  ///
1572  /// Disabling it is required to use HTML5 drag and drop on the frontend on Windows.
1573  #[serde(default = "default_true", alias = "drag-drop-enabled")]
1574  pub drag_drop_enabled: bool,
1575  /// Whether or not the window starts centered or not.
1576  #[serde(default)]
1577  pub center: bool,
1578  /// The horizontal position of the window's top left corner
1579  pub x: Option<f64>,
1580  /// The vertical position of the window's top left corner
1581  pub y: Option<f64>,
1582  /// The window width.
1583  #[serde(default = "default_width")]
1584  pub width: f64,
1585  /// The window height.
1586  #[serde(default = "default_height")]
1587  pub height: f64,
1588  /// The min window width.
1589  #[serde(alias = "min-width")]
1590  pub min_width: Option<f64>,
1591  /// The min window height.
1592  #[serde(alias = "min-height")]
1593  pub min_height: Option<f64>,
1594  /// The max window width.
1595  #[serde(alias = "max-width")]
1596  pub max_width: Option<f64>,
1597  /// The max window height.
1598  #[serde(alias = "max-height")]
1599  pub max_height: Option<f64>,
1600  /// Whether or not to prevent the window from overflowing the workarea
1601  ///
1602  /// ## Platform-specific
1603  ///
1604  /// - **iOS / Android:** Unsupported.
1605  #[serde(alias = "prevent-overflow")]
1606  pub prevent_overflow: Option<PreventOverflowConfig>,
1607  /// Whether the window is resizable or not. When resizable is set to false, native window's maximize button is automatically disabled.
1608  #[serde(default = "default_true")]
1609  pub resizable: bool,
1610  /// Whether the window's native maximize button is enabled or not.
1611  /// If resizable is set to false, this setting is ignored.
1612  ///
1613  /// ## Platform-specific
1614  ///
1615  /// - **macOS:** Disables the "zoom" button in the window titlebar, which is also used to enter fullscreen mode.
1616  /// - **Linux / iOS / Android:** Unsupported.
1617  #[serde(default = "default_true")]
1618  pub maximizable: bool,
1619  /// Whether the window's native minimize button is enabled or not.
1620  ///
1621  /// ## Platform-specific
1622  ///
1623  /// - **Linux / iOS / Android:** Unsupported.
1624  #[serde(default = "default_true")]
1625  pub minimizable: bool,
1626  /// Whether the window's native close button is enabled or not.
1627  ///
1628  /// ## Platform-specific
1629  ///
1630  /// - **Linux:** "GTK+ will do its best to convince the window manager not to show a close button.
1631  ///   Depending on the system, this function may not have any effect when called on a window that is already visible"
1632  /// - **iOS / Android:** Unsupported.
1633  #[serde(default = "default_true")]
1634  pub closable: bool,
1635  /// The window title.
1636  #[serde(default = "default_title")]
1637  pub title: String,
1638  /// Whether the window starts as fullscreen or not.
1639  #[serde(default)]
1640  pub fullscreen: bool,
1641  /// Whether the window will be initially focused or not.
1642  #[serde(default = "default_true")]
1643  pub focus: bool,
1644  /// Whether the window will be focusable or not.
1645  #[serde(default = "default_true")]
1646  pub focusable: bool,
1647  /// Whether the window is transparent or not.
1648  ///
1649  /// Note that on `macOS` this requires the `macos-private-api` feature flag, enabled under `tauri > macOSPrivateApi`.
1650  /// WARNING: Using private APIs on `macOS` prevents your application from being accepted to the `App Store`.
1651  #[serde(default)]
1652  pub transparent: bool,
1653  /// Whether the window is maximized or not.
1654  #[serde(default)]
1655  pub maximized: bool,
1656  /// Whether the window is visible or not.
1657  #[serde(default = "default_true")]
1658  pub visible: bool,
1659  /// Whether the window should have borders and bars.
1660  #[serde(default = "default_true")]
1661  pub decorations: bool,
1662  /// Whether the window should always be below other windows.
1663  #[serde(default, alias = "always-on-bottom")]
1664  pub always_on_bottom: bool,
1665  /// Whether the window should always be on top of other windows.
1666  #[serde(default, alias = "always-on-top")]
1667  pub always_on_top: bool,
1668  /// Whether the window should be visible on all workspaces or virtual desktops.
1669  ///
1670  /// ## Platform-specific
1671  ///
1672  /// - **Windows / iOS / Android:** Unsupported.
1673  #[serde(default, alias = "visible-on-all-workspaces")]
1674  pub visible_on_all_workspaces: bool,
1675  /// Prevents the window contents from being captured by other apps.
1676  #[serde(default, alias = "content-protected")]
1677  pub content_protected: bool,
1678  /// If `true`, hides the window icon from the taskbar on Windows and Linux.
1679  #[serde(default, alias = "skip-taskbar")]
1680  pub skip_taskbar: bool,
1681  /// The name of the window class created on Windows to create the window. **Windows only**.
1682  pub window_classname: Option<String>,
1683  /// The initial window theme. Defaults to the system theme. Only implemented on Windows and macOS 10.14+.
1684  pub theme: Option<crate::Theme>,
1685  /// The style of the macOS title bar.
1686  #[serde(default, alias = "title-bar-style")]
1687  pub title_bar_style: TitleBarStyle,
1688  /// The position of the window controls on macOS.
1689  ///
1690  /// Requires titleBarStyle: Overlay and decorations: true.
1691  #[serde(default, alias = "traffic-light-position")]
1692  pub traffic_light_position: Option<LogicalPosition>,
1693  /// If `true`, sets the window title to be hidden on macOS.
1694  #[serde(default, alias = "hidden-title")]
1695  pub hidden_title: bool,
1696  /// Whether clicking an inactive window also clicks through to the webview on macOS.
1697  #[serde(default, alias = "accept-first-mouse")]
1698  pub accept_first_mouse: bool,
1699  /// Defines the window [tabbing identifier] for macOS.
1700  ///
1701  /// Windows with matching tabbing identifiers will be grouped together.
1702  /// If the tabbing identifier is not set, automatic tabbing will be disabled.
1703  ///
1704  /// [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
1705  #[serde(default, alias = "tabbing-identifier")]
1706  pub tabbing_identifier: Option<String>,
1707  /// Defines additional browser arguments on Windows. By default wry passes `--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection`
1708  /// so if you use this method, you also need to disable these components by yourself if you want.
1709  #[serde(default, alias = "additional-browser-args")]
1710  pub additional_browser_args: Option<String>,
1711  /// Whether or not the window has shadow.
1712  ///
1713  /// ## Platform-specific
1714  ///
1715  /// - **Windows:**
1716  ///   - `false` has no effect on decorated window, shadow are always ON.
1717  ///   - `true` will make undecorated window have a 1px white border,
1718  /// and on Windows 11, it will have a rounded corners.
1719  /// - **Linux:** Unsupported.
1720  #[serde(default = "default_true")]
1721  pub shadow: bool,
1722  /// Window effects.
1723  ///
1724  /// Requires the window to be transparent.
1725  ///
1726  /// ## Platform-specific:
1727  ///
1728  /// - **Windows**: If using decorations or shadows, you may want to try this workaround <https://github.com/tauri-apps/tao/issues/72#issuecomment-975607891>
1729  /// - **Linux**: Unsupported
1730  #[serde(default, alias = "window-effects")]
1731  pub window_effects: Option<WindowEffectsConfig>,
1732  /// Whether or not the webview should be launched in incognito  mode.
1733  ///
1734  ///  ## Platform-specific:
1735  ///
1736  ///  - **Android**: Unsupported.
1737  #[serde(default)]
1738  pub incognito: bool,
1739  /// Sets the window associated with this label to be the parent of the window to be created.
1740  ///
1741  /// ## Platform-specific
1742  ///
1743  /// - **Windows**: This sets the passed parent as an owner window to the window to be created.
1744  ///   From [MSDN owned windows docs](https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows):
1745  ///     - An owned window is always above its owner in the z-order.
1746  ///     - The system automatically destroys an owned window when its owner is destroyed.
1747  ///     - An owned window is hidden when its owner is minimized.
1748  /// - **Linux**: This makes the new window transient for parent, see <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>
1749  /// - **macOS**: This adds the window as a child of parent, see <https://developer.apple.com/documentation/appkit/nswindow/1419152-addchildwindow?language=objc>
1750  pub parent: Option<String>,
1751  /// The proxy URL for the WebView for all network requests.
1752  ///
1753  /// Must be either a `http://` or a `socks5://` URL.
1754  ///
1755  /// ## Platform-specific
1756  ///
1757  /// - **macOS**: Requires the `macos-proxy` feature flag and only compiles for macOS 14+.
1758  #[serde(alias = "proxy-url")]
1759  pub proxy_url: Option<Url>,
1760  /// Whether page zooming by hotkeys is enabled
1761  ///
1762  /// ## Platform-specific:
1763  ///
1764  /// - **Windows**: Controls WebView2's [`IsZoomControlEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2settings?view=webview2-winrt-1.0.2420.47#iszoomcontrolenabled) setting.
1765  /// - **MacOS / Linux**: Injects a polyfill that zooms in and out with `ctrl/command` + `-/=`,
1766  /// 20% in each step, ranging from 20% to 1000%. Requires `webview:allow-set-webview-zoom` permission
1767  ///
1768  /// - **Android / iOS**: Unsupported.
1769  #[serde(default, alias = "zoom-hotkeys-enabled")]
1770  pub zoom_hotkeys_enabled: bool,
1771  /// Whether browser extensions can be installed for the webview process
1772  ///
1773  /// ## Platform-specific:
1774  ///
1775  /// - **Windows**: Enables the WebView2 environment's [`AreBrowserExtensionsEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2environmentoptions?view=webview2-winrt-1.0.2739.15#arebrowserextensionsenabled)
1776  /// - **MacOS / Linux / iOS / Android** - Unsupported.
1777  #[serde(default, alias = "browser-extensions-enabled")]
1778  pub browser_extensions_enabled: bool,
1779
1780  /// Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.
1781  ///
1782  /// ## Note
1783  ///
1784  /// Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `<scheme>://localhost` protocols used on macOS and Linux.
1785  ///
1786  /// ## Warning
1787  ///
1788  /// Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data.
1789  #[serde(default, alias = "use-https-scheme")]
1790  pub use_https_scheme: bool,
1791  /// Enable web inspector which is usually called browser devtools. Enabled by default.
1792  ///
1793  /// This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds.
1794  ///
1795  /// ## Platform-specific
1796  ///
1797  /// - macOS: This will call private functions on **macOS**.
1798  /// - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android.
1799  /// - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window.
1800  pub devtools: Option<bool>,
1801
1802  /// Set the window and webview background color.
1803  ///
1804  /// ## Platform-specific:
1805  ///
1806  /// - **Windows**: alpha channel is ignored for the window layer.
1807  /// - **Windows**: On Windows 7, alpha channel is ignored for the webview layer.
1808  /// - **Windows**: On Windows 8 and newer, if alpha channel is not `0`, it will be ignored for the webview layer.
1809  #[serde(alias = "background-color")]
1810  pub background_color: Option<Color>,
1811
1812  /// Change the default background throttling behaviour.
1813  ///
1814  /// By default, browsers use a suspend policy that will throttle timers and even unload
1815  /// the whole tab (view) to free resources after roughly 5 minutes when a view became
1816  /// minimized or hidden. This will pause all tasks until the documents visibility state
1817  /// changes back from hidden to visible by bringing the view back to the foreground.
1818  ///
1819  /// ## Platform-specific
1820  ///
1821  /// - **Linux / Windows / Android**: Unsupported. Workarounds like a pending WebLock transaction might suffice.
1822  /// - **iOS**: Supported since version 17.0+.
1823  /// - **macOS**: Supported since version 14.0+.
1824  ///
1825  /// see https://github.com/tauri-apps/tauri/issues/5250#issuecomment-2569380578
1826  #[serde(default, alias = "background-throttling")]
1827  pub background_throttling: Option<BackgroundThrottlingPolicy>,
1828  /// Whether we should disable JavaScript code execution on the webview or not.
1829  #[serde(default, alias = "javascript-disabled")]
1830  pub javascript_disabled: bool,
1831  /// on macOS and iOS there is a link preview on long pressing links, this is enabled by default.
1832  /// see https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview
1833  #[serde(default = "default_true", alias = "allow-link-preview")]
1834  pub allow_link_preview: bool,
1835  /// Allows disabling the input accessory view on iOS.
1836  ///
1837  /// The accessory view is the view that appears above the keyboard when a text input element is focused.
1838  /// It usually displays a view with "Done", "Next" buttons.
1839  #[serde(
1840    default,
1841    alias = "disable-input-accessory-view",
1842    alias = "disable_input_accessory_view"
1843  )]
1844  pub disable_input_accessory_view: bool,
1845}
1846
1847impl Default for WindowConfig {
1848  fn default() -> Self {
1849    Self {
1850      label: default_window_label(),
1851      url: WebviewUrl::default(),
1852      create: true,
1853      user_agent: None,
1854      drag_drop_enabled: true,
1855      center: false,
1856      x: None,
1857      y: None,
1858      width: default_width(),
1859      height: default_height(),
1860      min_width: None,
1861      min_height: None,
1862      max_width: None,
1863      max_height: None,
1864      prevent_overflow: None,
1865      resizable: true,
1866      maximizable: true,
1867      minimizable: true,
1868      closable: true,
1869      title: default_title(),
1870      fullscreen: false,
1871      focus: false,
1872      focusable: true,
1873      transparent: false,
1874      maximized: false,
1875      visible: true,
1876      decorations: true,
1877      always_on_bottom: false,
1878      always_on_top: false,
1879      visible_on_all_workspaces: false,
1880      content_protected: false,
1881      skip_taskbar: false,
1882      window_classname: None,
1883      theme: None,
1884      title_bar_style: Default::default(),
1885      traffic_light_position: None,
1886      hidden_title: false,
1887      accept_first_mouse: false,
1888      tabbing_identifier: None,
1889      additional_browser_args: None,
1890      shadow: true,
1891      window_effects: None,
1892      incognito: false,
1893      parent: None,
1894      proxy_url: None,
1895      zoom_hotkeys_enabled: false,
1896      browser_extensions_enabled: false,
1897      use_https_scheme: false,
1898      devtools: None,
1899      background_color: None,
1900      background_throttling: None,
1901      javascript_disabled: false,
1902      allow_link_preview: true,
1903      disable_input_accessory_view: false,
1904    }
1905  }
1906}
1907
1908fn default_window_label() -> String {
1909  "main".to_string()
1910}
1911
1912fn default_width() -> f64 {
1913  800f64
1914}
1915
1916fn default_height() -> f64 {
1917  600f64
1918}
1919
1920fn default_title() -> String {
1921  "Tauri App".to_string()
1922}
1923
1924/// A Content-Security-Policy directive source list.
1925/// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/Sources#sources>.
1926#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1927#[cfg_attr(feature = "schema", derive(JsonSchema))]
1928#[serde(rename_all = "camelCase", untagged)]
1929pub enum CspDirectiveSources {
1930  /// An inline list of CSP sources. Same as [`Self::List`], but concatenated with a space separator.
1931  Inline(String),
1932  /// A list of CSP sources. The collection will be concatenated with a space separator for the CSP string.
1933  List(Vec<String>),
1934}
1935
1936impl Default for CspDirectiveSources {
1937  fn default() -> Self {
1938    Self::List(Vec::new())
1939  }
1940}
1941
1942impl From<CspDirectiveSources> for Vec<String> {
1943  fn from(sources: CspDirectiveSources) -> Self {
1944    match sources {
1945      CspDirectiveSources::Inline(source) => source.split(' ').map(|s| s.to_string()).collect(),
1946      CspDirectiveSources::List(l) => l,
1947    }
1948  }
1949}
1950
1951impl CspDirectiveSources {
1952  /// Whether the given source is configured on this directive or not.
1953  pub fn contains(&self, source: &str) -> bool {
1954    match self {
1955      Self::Inline(s) => s.contains(&format!("{source} ")) || s.contains(&format!(" {source}")),
1956      Self::List(l) => l.contains(&source.into()),
1957    }
1958  }
1959
1960  /// Appends the given source to this directive.
1961  pub fn push<S: AsRef<str>>(&mut self, source: S) {
1962    match self {
1963      Self::Inline(s) => {
1964        s.push(' ');
1965        s.push_str(source.as_ref());
1966      }
1967      Self::List(l) => {
1968        l.push(source.as_ref().to_string());
1969      }
1970    }
1971  }
1972
1973  /// Extends this CSP directive source list with the given array of sources.
1974  pub fn extend(&mut self, sources: Vec<String>) {
1975    for s in sources {
1976      self.push(s);
1977    }
1978  }
1979}
1980
1981/// A Content-Security-Policy definition.
1982/// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.
1983#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1984#[cfg_attr(feature = "schema", derive(JsonSchema))]
1985#[serde(rename_all = "camelCase", untagged)]
1986pub enum Csp {
1987  /// The entire CSP policy in a single text string.
1988  Policy(String),
1989  /// An object mapping a directive with its sources values as a list of strings.
1990  DirectiveMap(HashMap<String, CspDirectiveSources>),
1991}
1992
1993impl From<HashMap<String, CspDirectiveSources>> for Csp {
1994  fn from(map: HashMap<String, CspDirectiveSources>) -> Self {
1995    Self::DirectiveMap(map)
1996  }
1997}
1998
1999impl From<Csp> for HashMap<String, CspDirectiveSources> {
2000  fn from(csp: Csp) -> Self {
2001    match csp {
2002      Csp::Policy(policy) => {
2003        let mut map = HashMap::new();
2004        for directive in policy.split(';') {
2005          let mut tokens = directive.trim().split(' ');
2006          if let Some(directive) = tokens.next() {
2007            let sources = tokens.map(|s| s.to_string()).collect::<Vec<String>>();
2008            map.insert(directive.to_string(), CspDirectiveSources::List(sources));
2009          }
2010        }
2011        map
2012      }
2013      Csp::DirectiveMap(m) => m,
2014    }
2015  }
2016}
2017
2018impl Display for Csp {
2019  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2020    match self {
2021      Self::Policy(s) => write!(f, "{s}"),
2022      Self::DirectiveMap(m) => {
2023        let len = m.len();
2024        let mut i = 0;
2025        for (directive, sources) in m {
2026          let sources: Vec<String> = sources.clone().into();
2027          write!(f, "{} {}", directive, sources.join(" "))?;
2028          i += 1;
2029          if i != len {
2030            write!(f, "; ")?;
2031          }
2032        }
2033        Ok(())
2034      }
2035    }
2036  }
2037}
2038
2039/// The possible values for the `dangerous_disable_asset_csp_modification` config option.
2040#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2041#[serde(untagged)]
2042#[cfg_attr(feature = "schema", derive(JsonSchema))]
2043pub enum DisabledCspModificationKind {
2044  /// If `true`, disables all CSP modification.
2045  /// `false` is the default value and it configures Tauri to control the CSP.
2046  Flag(bool),
2047  /// Disables the given list of CSP directives modifications.
2048  List(Vec<String>),
2049}
2050
2051impl DisabledCspModificationKind {
2052  /// Determines whether the given CSP directive can be modified or not.
2053  pub fn can_modify(&self, directive: &str) -> bool {
2054    match self {
2055      Self::Flag(f) => !f,
2056      Self::List(l) => !l.contains(&directive.into()),
2057    }
2058  }
2059}
2060
2061impl Default for DisabledCspModificationKind {
2062  fn default() -> Self {
2063    Self::Flag(false)
2064  }
2065}
2066
2067/// Protocol scope definition.
2068/// It is a list of glob patterns that restrict the API access from the webview.
2069///
2070/// Each pattern can start with a variable that resolves to a system base directory.
2071/// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,
2072/// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,
2073/// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`,
2074/// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.
2075#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2076#[serde(untagged)]
2077#[cfg_attr(feature = "schema", derive(JsonSchema))]
2078pub enum FsScope {
2079  /// A list of paths that are allowed by this scope.
2080  AllowedPaths(Vec<PathBuf>),
2081  /// A complete scope configuration.
2082  #[serde(rename_all = "camelCase")]
2083  Scope {
2084    /// A list of paths that are allowed by this scope.
2085    #[serde(default)]
2086    allow: Vec<PathBuf>,
2087    /// A list of paths that are not allowed by this scope.
2088    /// This gets precedence over the [`Self::Scope::allow`] list.
2089    #[serde(default)]
2090    deny: Vec<PathBuf>,
2091    /// Whether or not paths that contain components that start with a `.`
2092    /// will require that `.` appears literally in the pattern; `*`, `?`, `**`,
2093    /// or `[...]` will not match. This is useful because such files are
2094    /// conventionally considered hidden on Unix systems and it might be
2095    /// desirable to skip them when listing files.
2096    ///
2097    /// Defaults to `true` on Unix systems and `false` on Windows
2098    // dotfiles are not supposed to be exposed by default on unix
2099    #[serde(alias = "require-literal-leading-dot")]
2100    require_literal_leading_dot: Option<bool>,
2101  },
2102}
2103
2104impl Default for FsScope {
2105  fn default() -> Self {
2106    Self::AllowedPaths(Vec::new())
2107  }
2108}
2109
2110impl FsScope {
2111  /// The list of allowed paths.
2112  pub fn allowed_paths(&self) -> &Vec<PathBuf> {
2113    match self {
2114      Self::AllowedPaths(p) => p,
2115      Self::Scope { allow, .. } => allow,
2116    }
2117  }
2118
2119  /// The list of forbidden paths.
2120  pub fn forbidden_paths(&self) -> Option<&Vec<PathBuf>> {
2121    match self {
2122      Self::AllowedPaths(_) => None,
2123      Self::Scope { deny, .. } => Some(deny),
2124    }
2125  }
2126}
2127
2128/// Config for the asset custom protocol.
2129///
2130/// See more: <https://v2.tauri.app/reference/config/#assetprotocolconfig>
2131#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2132#[cfg_attr(feature = "schema", derive(JsonSchema))]
2133#[serde(rename_all = "camelCase", deny_unknown_fields)]
2134pub struct AssetProtocolConfig {
2135  /// The access scope for the asset protocol.
2136  #[serde(default)]
2137  pub scope: FsScope,
2138  /// Enables the asset protocol.
2139  #[serde(default)]
2140  pub enable: bool,
2141}
2142
2143/// definition of a header source
2144///
2145/// The header value to a header name
2146#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2147#[cfg_attr(feature = "schema", derive(JsonSchema))]
2148#[serde(rename_all = "camelCase", untagged)]
2149pub enum HeaderSource {
2150  /// string version of the header Value
2151  Inline(String),
2152  /// list version of the header value. Item are joined by "," for the real header value
2153  List(Vec<String>),
2154  /// (Rust struct | Json | JavaScript Object) equivalent of the header value. Items are composed from: key + space + value. Item are then joined by ";" for the real header value
2155  Map(HashMap<String, String>),
2156}
2157
2158impl Display for HeaderSource {
2159  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2160    match self {
2161      Self::Inline(s) => write!(f, "{s}"),
2162      Self::List(l) => write!(f, "{}", l.join(", ")),
2163      Self::Map(m) => {
2164        let len = m.len();
2165        let mut i = 0;
2166        for (key, value) in m {
2167          write!(f, "{key} {value}")?;
2168          i += 1;
2169          if i != len {
2170            write!(f, "; ")?;
2171          }
2172        }
2173        Ok(())
2174      }
2175    }
2176  }
2177}
2178
2179/// A trait which implements on the [`Builder`] of the http create
2180///
2181/// Must add headers defined in the tauri configuration file to http responses
2182pub trait HeaderAddition {
2183  /// adds all headers defined on the config file, given the current HeaderConfig
2184  fn add_configured_headers(self, headers: Option<&HeaderConfig>) -> http::response::Builder;
2185}
2186
2187impl HeaderAddition for Builder {
2188  /// Add the headers defined in the tauri configuration file to http responses
2189  ///
2190  /// this is a utility function, which is used in the same way as the `.header(..)` of the rust http library
2191  fn add_configured_headers(mut self, headers: Option<&HeaderConfig>) -> http::response::Builder {
2192    if let Some(headers) = headers {
2193      // Add the header Access-Control-Allow-Credentials, if we find a value for it
2194      if let Some(value) = &headers.access_control_allow_credentials {
2195        self = self.header("Access-Control-Allow-Credentials", value.to_string());
2196      };
2197
2198      // Add the header Access-Control-Allow-Headers, if we find a value for it
2199      if let Some(value) = &headers.access_control_allow_headers {
2200        self = self.header("Access-Control-Allow-Headers", value.to_string());
2201      };
2202
2203      // Add the header Access-Control-Allow-Methods, if we find a value for it
2204      if let Some(value) = &headers.access_control_allow_methods {
2205        self = self.header("Access-Control-Allow-Methods", value.to_string());
2206      };
2207
2208      // Add the header Access-Control-Expose-Headers, if we find a value for it
2209      if let Some(value) = &headers.access_control_expose_headers {
2210        self = self.header("Access-Control-Expose-Headers", value.to_string());
2211      };
2212
2213      // Add the header Access-Control-Max-Age, if we find a value for it
2214      if let Some(value) = &headers.access_control_max_age {
2215        self = self.header("Access-Control-Max-Age", value.to_string());
2216      };
2217
2218      // Add the header Cross-Origin-Embedder-Policy, if we find a value for it
2219      if let Some(value) = &headers.cross_origin_embedder_policy {
2220        self = self.header("Cross-Origin-Embedder-Policy", value.to_string());
2221      };
2222
2223      // Add the header Cross-Origin-Opener-Policy, if we find a value for it
2224      if let Some(value) = &headers.cross_origin_opener_policy {
2225        self = self.header("Cross-Origin-Opener-Policy", value.to_string());
2226      };
2227
2228      // Add the header Cross-Origin-Resource-Policy, if we find a value for it
2229      if let Some(value) = &headers.cross_origin_resource_policy {
2230        self = self.header("Cross-Origin-Resource-Policy", value.to_string());
2231      };
2232
2233      // Add the header Permission-Policy, if we find a value for it
2234      if let Some(value) = &headers.permissions_policy {
2235        self = self.header("Permission-Policy", value.to_string());
2236      };
2237
2238      if let Some(value) = &headers.service_worker_allowed {
2239        self = self.header("Service-Worker-Allowed", value.to_string());
2240      }
2241
2242      // Add the header Timing-Allow-Origin, if we find a value for it
2243      if let Some(value) = &headers.timing_allow_origin {
2244        self = self.header("Timing-Allow-Origin", value.to_string());
2245      };
2246
2247      // Add the header X-Content-Type-Options, if we find a value for it
2248      if let Some(value) = &headers.x_content_type_options {
2249        self = self.header("X-Content-Type-Options", value.to_string());
2250      };
2251
2252      // Add the header Tauri-Custom-Header, if we find a value for it
2253      if let Some(value) = &headers.tauri_custom_header {
2254        // Keep in mind to correctly set the Access-Control-Expose-Headers
2255        self = self.header("Tauri-Custom-Header", value.to_string());
2256      };
2257    }
2258    self
2259  }
2260}
2261
2262/// A struct, where the keys are some specific http header names.
2263///
2264/// If the values to those keys are defined, then they will be send as part of a response message.
2265/// This does not include error messages and ipc messages
2266///
2267/// ## Example configuration
2268/// ```javascript
2269/// {
2270///  //..
2271///   app:{
2272///     //..
2273///     security: {
2274///       headers: {
2275///         "Cross-Origin-Opener-Policy": "same-origin",
2276///         "Cross-Origin-Embedder-Policy": "require-corp",
2277///         "Timing-Allow-Origin": [
2278///           "https://developer.mozilla.org",
2279///           "https://example.com",
2280///         ],
2281///         "Access-Control-Expose-Headers": "Tauri-Custom-Header",
2282///         "Tauri-Custom-Header": {
2283///           "key1": "'value1' 'value2'",
2284///           "key2": "'value3'"
2285///         }
2286///       },
2287///       csp: "default-src 'self'; connect-src ipc: http://ipc.localhost",
2288///     }
2289///     //..
2290///   }
2291///  //..
2292/// }
2293/// ```
2294/// In this example `Cross-Origin-Opener-Policy` and `Cross-Origin-Embedder-Policy` are set to allow for the use of [`SharedArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer).
2295/// The result is, that those headers are then set on every response sent via the `get_response` function in crates/tauri/src/protocol/tauri.rs.
2296/// The Content-Security-Policy header is defined separately, because it is also handled separately.
2297///
2298/// For the helloworld example, this config translates into those response headers:
2299/// ```http
2300/// access-control-allow-origin:  http://tauri.localhost
2301/// access-control-expose-headers: Tauri-Custom-Header
2302/// content-security-policy: default-src 'self'; connect-src ipc: http://ipc.localhost; script-src 'self' 'sha256-Wjjrs6qinmnr+tOry8x8PPwI77eGpUFR3EEGZktjJNs='
2303/// content-type: text/html
2304/// cross-origin-embedder-policy: require-corp
2305/// cross-origin-opener-policy: same-origin
2306/// tauri-custom-header: key1 'value1' 'value2'; key2 'value3'
2307/// timing-allow-origin: https://developer.mozilla.org, https://example.com
2308/// ```
2309/// Since the resulting header values are always 'string-like'. So depending on the what data type the HeaderSource is, they need to be converted.
2310///  - `String`(JS/Rust): stay the same for the resulting header value
2311///  - `Array`(JS)/`Vec\<String\>`(Rust): Item are joined by ", " for the resulting header value
2312///  - `Object`(JS)/ `Hashmap\<String,String\>`(Rust): Items are composed from: key + space + value. Item are then joined by "; " for the resulting header value
2313#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2314#[cfg_attr(feature = "schema", derive(JsonSchema))]
2315#[serde(deny_unknown_fields)]
2316pub struct HeaderConfig {
2317  /// The Access-Control-Allow-Credentials response header tells browsers whether the
2318  /// server allows cross-origin HTTP requests to include credentials.
2319  ///
2320  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials>
2321  #[serde(rename = "Access-Control-Allow-Credentials")]
2322  pub access_control_allow_credentials: Option<HeaderSource>,
2323  /// The Access-Control-Allow-Headers response header is used in response
2324  /// to a preflight request which includes the Access-Control-Request-Headers
2325  /// to indicate which HTTP headers can be used during the actual request.
2326  ///
2327  /// This header is required if the request has an Access-Control-Request-Headers header.
2328  ///
2329  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers>
2330  #[serde(rename = "Access-Control-Allow-Headers")]
2331  pub access_control_allow_headers: Option<HeaderSource>,
2332  /// The Access-Control-Allow-Methods response header specifies one or more methods
2333  /// allowed when accessing a resource in response to a preflight request.
2334  ///
2335  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods>
2336  #[serde(rename = "Access-Control-Allow-Methods")]
2337  pub access_control_allow_methods: Option<HeaderSource>,
2338  /// The Access-Control-Expose-Headers response header allows a server to indicate
2339  /// which response headers should be made available to scripts running in the browser,
2340  /// in response to a cross-origin request.
2341  ///
2342  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers>
2343  #[serde(rename = "Access-Control-Expose-Headers")]
2344  pub access_control_expose_headers: Option<HeaderSource>,
2345  /// The Access-Control-Max-Age response header indicates how long the results of a
2346  /// preflight request (that is the information contained in the
2347  /// Access-Control-Allow-Methods and Access-Control-Allow-Headers headers) can
2348  /// be cached.
2349  ///
2350  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age>
2351  #[serde(rename = "Access-Control-Max-Age")]
2352  pub access_control_max_age: Option<HeaderSource>,
2353  /// The HTTP Cross-Origin-Embedder-Policy (COEP) response header configures embedding
2354  /// cross-origin resources into the document.
2355  ///
2356  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy>
2357  #[serde(rename = "Cross-Origin-Embedder-Policy")]
2358  pub cross_origin_embedder_policy: Option<HeaderSource>,
2359  /// The HTTP Cross-Origin-Opener-Policy (COOP) response header allows you to ensure a
2360  /// top-level document does not share a browsing context group with cross-origin documents.
2361  /// COOP will process-isolate your document and potential attackers can't access your global
2362  /// object if they were to open it in a popup, preventing a set of cross-origin attacks dubbed XS-Leaks.
2363  ///
2364  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy>
2365  #[serde(rename = "Cross-Origin-Opener-Policy")]
2366  pub cross_origin_opener_policy: Option<HeaderSource>,
2367  /// The HTTP Cross-Origin-Resource-Policy response header conveys a desire that the
2368  /// browser blocks no-cors cross-origin/cross-site requests to the given resource.
2369  ///
2370  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy>
2371  #[serde(rename = "Cross-Origin-Resource-Policy")]
2372  pub cross_origin_resource_policy: Option<HeaderSource>,
2373  /// The HTTP Permissions-Policy header provides a mechanism to allow and deny the
2374  /// use of browser features in a document or within any \<iframe\> elements in the document.
2375  ///
2376  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy>
2377  #[serde(rename = "Permissions-Policy")]
2378  pub permissions_policy: Option<HeaderSource>,
2379  /// The HTTP Service-Worker-Allowed response header is used to broaden the path restriction for a
2380  /// service worker's default scope.
2381  ///
2382  /// By default, the scope for a service worker registration is the directory where the service
2383  /// worker script is located. For example, if the script `sw.js` is located in `/js/sw.js`,
2384  /// it can only control URLs under `/js/` by default. Servers can use the `Service-Worker-Allowed`
2385  /// header to allow a service worker to control URLs outside of its own directory.
2386  ///
2387  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Service-Worker-Allowed>
2388  #[serde(rename = "Service-Worker-Allowed")]
2389  pub service_worker_allowed: Option<HeaderSource>,
2390  /// The Timing-Allow-Origin response header specifies origins that are allowed to see values
2391  /// of attributes retrieved via features of the Resource Timing API, which would otherwise be
2392  /// reported as zero due to cross-origin restrictions.
2393  ///
2394  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Timing-Allow-Origin>
2395  #[serde(rename = "Timing-Allow-Origin")]
2396  pub timing_allow_origin: Option<HeaderSource>,
2397  /// The X-Content-Type-Options response HTTP header is a marker used by the server to indicate
2398  /// that the MIME types advertised in the Content-Type headers should be followed and not be
2399  /// changed. The header allows you to avoid MIME type sniffing by saying that the MIME types
2400  /// are deliberately configured.
2401  ///
2402  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options>
2403  #[serde(rename = "X-Content-Type-Options")]
2404  pub x_content_type_options: Option<HeaderSource>,
2405  /// A custom header field Tauri-Custom-Header, don't use it.
2406  /// Remember to set Access-Control-Expose-Headers accordingly
2407  ///
2408  /// **NOT INTENDED FOR PRODUCTION USE**
2409  #[serde(rename = "Tauri-Custom-Header")]
2410  pub tauri_custom_header: Option<HeaderSource>,
2411}
2412
2413impl HeaderConfig {
2414  /// creates a new header config
2415  pub fn new() -> Self {
2416    HeaderConfig {
2417      access_control_allow_credentials: None,
2418      access_control_allow_methods: None,
2419      access_control_allow_headers: None,
2420      access_control_expose_headers: None,
2421      access_control_max_age: None,
2422      cross_origin_embedder_policy: None,
2423      cross_origin_opener_policy: None,
2424      cross_origin_resource_policy: None,
2425      permissions_policy: None,
2426      service_worker_allowed: None,
2427      timing_allow_origin: None,
2428      x_content_type_options: None,
2429      tauri_custom_header: None,
2430    }
2431  }
2432}
2433
2434/// Security configuration.
2435///
2436/// See more: <https://v2.tauri.app/reference/config/#securityconfig>
2437#[skip_serializing_none]
2438#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
2439#[cfg_attr(feature = "schema", derive(JsonSchema))]
2440#[serde(rename_all = "camelCase", deny_unknown_fields)]
2441pub struct SecurityConfig {
2442  /// The Content Security Policy that will be injected on all HTML files on the built application.
2443  /// If [`dev_csp`](#SecurityConfig.devCsp) is not specified, this value is also injected on dev.
2444  ///
2445  /// This is a really important part of the configuration since it helps you ensure your WebView is secured.
2446  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.
2447  pub csp: Option<Csp>,
2448  /// The Content Security Policy that will be injected on all HTML files on development.
2449  ///
2450  /// This is a really important part of the configuration since it helps you ensure your WebView is secured.
2451  /// See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.
2452  #[serde(alias = "dev-csp")]
2453  pub dev_csp: Option<Csp>,
2454  /// Freeze the `Object.prototype` when using the custom protocol.
2455  #[serde(default, alias = "freeze-prototype")]
2456  pub freeze_prototype: bool,
2457  /// Disables the Tauri-injected CSP sources.
2458  ///
2459  /// At compile time, Tauri parses all the frontend assets and changes the Content-Security-Policy
2460  /// to only allow loading of your own scripts and styles by injecting nonce and hash sources.
2461  /// This stricts your CSP, which may introduce issues when using along with other flexing sources.
2462  ///
2463  /// This configuration option allows both a boolean and a list of strings as value.
2464  /// A boolean instructs Tauri to disable the injection for all CSP injections,
2465  /// and a list of strings indicates the CSP directives that Tauri cannot inject.
2466  ///
2467  /// **WARNING:** Only disable this if you know what you are doing and have properly configured the CSP.
2468  /// Your application might be vulnerable to XSS attacks without this Tauri protection.
2469  #[serde(default, alias = "dangerous-disable-asset-csp-modification")]
2470  pub dangerous_disable_asset_csp_modification: DisabledCspModificationKind,
2471  /// Custom protocol config.
2472  #[serde(default, alias = "asset-protocol")]
2473  pub asset_protocol: AssetProtocolConfig,
2474  /// The pattern to use.
2475  #[serde(default)]
2476  pub pattern: PatternKind,
2477  /// List of capabilities that are enabled on the application.
2478  ///
2479  /// By default (not set or empty list), all capability files from `./capabilities/` are included,
2480  /// by setting values in this entry, you have fine grained control over which capabilities are included
2481  ///
2482  /// You can either reference a capability file defined in `./capabilities/` with its identifier or inline a [`Capability`]
2483  ///
2484  /// ### Example
2485  ///
2486  /// ```json
2487  /// {
2488  ///   "app": {
2489  ///     "capabilities": [
2490  ///       "main-window",
2491  ///       {
2492  ///         "identifier": "drag-window",
2493  ///         "permissions": ["core:window:allow-start-dragging"]
2494  ///       }
2495  ///     ]
2496  ///   }
2497  /// }
2498  /// ```
2499  #[serde(default)]
2500  pub capabilities: Vec<CapabilityEntry>,
2501  /// The headers, which are added to every http response from tauri to the web view
2502  /// This doesn't include IPC Messages and error responses
2503  #[serde(default)]
2504  pub headers: Option<HeaderConfig>,
2505}
2506
2507/// A capability entry which can be either an inlined capability or a reference to a capability defined on its own file.
2508#[derive(Debug, Clone, PartialEq, Serialize)]
2509#[cfg_attr(feature = "schema", derive(JsonSchema))]
2510#[serde(untagged)]
2511pub enum CapabilityEntry {
2512  /// An inlined capability.
2513  Inlined(Capability),
2514  /// Reference to a capability identifier.
2515  Reference(String),
2516}
2517
2518impl<'de> Deserialize<'de> for CapabilityEntry {
2519  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2520  where
2521    D: Deserializer<'de>,
2522  {
2523    UntaggedEnumVisitor::new()
2524      .string(|string| Ok(Self::Reference(string.to_owned())))
2525      .map(|map| map.deserialize::<Capability>().map(Self::Inlined))
2526      .deserialize(deserializer)
2527  }
2528}
2529
2530/// The application pattern.
2531#[skip_serializing_none]
2532#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
2533#[serde(rename_all = "lowercase", tag = "use", content = "options")]
2534#[cfg_attr(feature = "schema", derive(JsonSchema))]
2535pub enum PatternKind {
2536  /// Brownfield pattern.
2537  Brownfield,
2538  /// Isolation pattern. Recommended for security purposes.
2539  Isolation {
2540    /// The dir containing the index.html file that contains the secure isolation application.
2541    dir: PathBuf,
2542  },
2543}
2544
2545impl Default for PatternKind {
2546  fn default() -> Self {
2547    Self::Brownfield
2548  }
2549}
2550
2551/// The App configuration object.
2552///
2553/// See more: <https://v2.tauri.app/reference/config/#appconfig>
2554#[skip_serializing_none]
2555#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
2556#[cfg_attr(feature = "schema", derive(JsonSchema))]
2557#[serde(rename_all = "camelCase", deny_unknown_fields)]
2558pub struct AppConfig {
2559  /// The app windows configuration.
2560  #[serde(default)]
2561  pub windows: Vec<WindowConfig>,
2562  /// Security configuration.
2563  #[serde(default)]
2564  pub security: SecurityConfig,
2565  /// Configuration for app tray icon.
2566  #[serde(alias = "tray-icon")]
2567  pub tray_icon: Option<TrayIconConfig>,
2568  /// MacOS private API configuration. Enables the transparent background API and sets the `fullScreenEnabled` preference to `true`.
2569  #[serde(rename = "macOSPrivateApi", alias = "macos-private-api", default)]
2570  pub macos_private_api: bool,
2571  /// Whether we should inject the Tauri API on `window.__TAURI__` or not.
2572  #[serde(default, alias = "with-global-tauri")]
2573  pub with_global_tauri: bool,
2574  /// If set to true "identifier" will be set as GTK app ID (on systems that use GTK).
2575  #[serde(rename = "enableGTKAppId", alias = "enable-gtk-app-id", default)]
2576  pub enable_gtk_app_id: bool,
2577}
2578
2579impl AppConfig {
2580  /// Returns all Cargo features.
2581  pub fn all_features() -> Vec<&'static str> {
2582    vec![
2583      "tray-icon",
2584      "macos-private-api",
2585      "protocol-asset",
2586      "isolation",
2587    ]
2588  }
2589
2590  /// Returns the enabled Cargo features.
2591  pub fn features(&self) -> Vec<&str> {
2592    let mut features = Vec::new();
2593    if self.tray_icon.is_some() {
2594      features.push("tray-icon");
2595    }
2596    if self.macos_private_api {
2597      features.push("macos-private-api");
2598    }
2599    if self.security.asset_protocol.enable {
2600      features.push("protocol-asset");
2601    }
2602
2603    if let PatternKind::Isolation { .. } = self.security.pattern {
2604      features.push("isolation");
2605    }
2606
2607    features.sort_unstable();
2608    features
2609  }
2610}
2611
2612/// Configuration for application tray icon.
2613///
2614/// See more: <https://v2.tauri.app/reference/config/#trayiconconfig>
2615#[skip_serializing_none]
2616#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2617#[cfg_attr(feature = "schema", derive(JsonSchema))]
2618#[serde(rename_all = "camelCase", deny_unknown_fields)]
2619pub struct TrayIconConfig {
2620  /// Set an id for this tray icon so you can reference it later, defaults to `main`.
2621  pub id: Option<String>,
2622  /// Path to the default icon to use for the tray icon.
2623  ///
2624  /// Note: this stores the image in raw pixels to the final binary,
2625  /// so keep the icon size (width and height) small
2626  /// or else it's going to bloat your final executable
2627  #[serde(alias = "icon-path")]
2628  pub icon_path: PathBuf,
2629  /// A Boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS.
2630  #[serde(default, alias = "icon-as-template")]
2631  pub icon_as_template: bool,
2632  /// A Boolean value that determines whether the menu should appear when the tray icon receives a left click.
2633  ///
2634  /// ## Platform-specific:
2635  ///
2636  /// - **Linux**: Unsupported.
2637  #[serde(default = "default_true", alias = "menu-on-left-click")]
2638  #[deprecated(since = "2.2.0", note = "Use `show_menu_on_left_click` instead.")]
2639  pub menu_on_left_click: bool,
2640  /// A Boolean value that determines whether the menu should appear when the tray icon receives a left click.
2641  ///
2642  /// ## Platform-specific:
2643  ///
2644  /// - **Linux**: Unsupported.
2645  #[serde(default = "default_true", alias = "show-menu-on-left-click")]
2646  pub show_menu_on_left_click: bool,
2647  /// Title for MacOS tray
2648  pub title: Option<String>,
2649  /// Tray icon tooltip on Windows and macOS
2650  pub tooltip: Option<String>,
2651}
2652
2653/// General configuration for the iOS target.
2654#[skip_serializing_none]
2655#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2656#[cfg_attr(feature = "schema", derive(JsonSchema))]
2657#[serde(rename_all = "camelCase", deny_unknown_fields)]
2658pub struct IosConfig {
2659  /// A custom [XcodeGen] project.yml template to use.
2660  ///
2661  /// [XcodeGen]: <https://github.com/yonaskolb/XcodeGen>
2662  pub template: Option<PathBuf>,
2663  /// A list of strings indicating any iOS frameworks that need to be bundled with the application.
2664  ///
2665  /// Note that you need to recreate the iOS project for the changes to be applied.
2666  pub frameworks: Option<Vec<String>>,
2667  /// The development team. This value is required for iOS development because code signing is enforced.
2668  /// The `APPLE_DEVELOPMENT_TEAM` environment variable can be set to overwrite it.
2669  #[serde(alias = "development-team")]
2670  pub development_team: Option<String>,
2671  /// The version of the build that identifies an iteration of the bundle.
2672  ///
2673  /// Translates to the bundle's CFBundleVersion property.
2674  #[serde(alias = "bundle-version")]
2675  pub bundle_version: Option<String>,
2676  /// A version string indicating the minimum iOS version that the bundled application supports. Defaults to `13.0`.
2677  ///
2678  /// Maps to the IPHONEOS_DEPLOYMENT_TARGET value.
2679  #[serde(
2680    alias = "minimum-system-version",
2681    default = "ios_minimum_system_version"
2682  )]
2683  pub minimum_system_version: String,
2684}
2685
2686impl Default for IosConfig {
2687  fn default() -> Self {
2688    Self {
2689      template: None,
2690      frameworks: None,
2691      development_team: None,
2692      bundle_version: None,
2693      minimum_system_version: ios_minimum_system_version(),
2694    }
2695  }
2696}
2697
2698/// General configuration for the Android target.
2699#[skip_serializing_none]
2700#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2701#[cfg_attr(feature = "schema", derive(JsonSchema))]
2702#[serde(rename_all = "camelCase", deny_unknown_fields)]
2703pub struct AndroidConfig {
2704  /// The minimum API level required for the application to run.
2705  /// The Android system will prevent the user from installing the application if the system's API level is lower than the value specified.
2706  #[serde(alias = "min-sdk-version", default = "default_min_sdk_version")]
2707  pub min_sdk_version: u32,
2708
2709  /// The version code of the application.
2710  /// It is limited to 2,100,000,000 as per Google Play Store requirements.
2711  ///
2712  /// By default we use your configured version and perform the following math:
2713  /// versionCode = version.major * 1000000 + version.minor * 1000 + version.patch
2714  #[serde(alias = "version-code")]
2715  #[cfg_attr(feature = "schema", validate(range(min = 1, max = 2_100_000_000)))]
2716  pub version_code: Option<u32>,
2717}
2718
2719impl Default for AndroidConfig {
2720  fn default() -> Self {
2721    Self {
2722      min_sdk_version: default_min_sdk_version(),
2723      version_code: None,
2724    }
2725  }
2726}
2727
2728fn default_min_sdk_version() -> u32 {
2729  24
2730}
2731
2732/// Defines the URL or assets to embed in the application.
2733#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2734#[cfg_attr(feature = "schema", derive(JsonSchema))]
2735#[serde(untagged, deny_unknown_fields)]
2736#[non_exhaustive]
2737pub enum FrontendDist {
2738  /// An external URL that should be used as the default application URL.
2739  Url(Url),
2740  /// Path to a directory containing the frontend dist assets.
2741  Directory(PathBuf),
2742  /// An array of files to embed on the app.
2743  Files(Vec<PathBuf>),
2744}
2745
2746impl std::fmt::Display for FrontendDist {
2747  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2748    match self {
2749      Self::Url(url) => write!(f, "{url}"),
2750      Self::Directory(p) => write!(f, "{}", p.display()),
2751      Self::Files(files) => write!(f, "{}", serde_json::to_string(files).unwrap()),
2752    }
2753  }
2754}
2755
2756/// Describes the shell command to run before `tauri dev`.
2757#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2758#[cfg_attr(feature = "schema", derive(JsonSchema))]
2759#[serde(rename_all = "camelCase", untagged)]
2760pub enum BeforeDevCommand {
2761  /// Run the given script with the default options.
2762  Script(String),
2763  /// Run the given script with custom options.
2764  ScriptWithOptions {
2765    /// The script to execute.
2766    script: String,
2767    /// The current working directory.
2768    cwd: Option<String>,
2769    /// Whether `tauri dev` should wait for the command to finish or not. Defaults to `false`.
2770    #[serde(default)]
2771    wait: bool,
2772  },
2773}
2774
2775/// Describes a shell command to be executed when a CLI hook is triggered.
2776#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2777#[cfg_attr(feature = "schema", derive(JsonSchema))]
2778#[serde(rename_all = "camelCase", untagged)]
2779pub enum HookCommand {
2780  /// Run the given script with the default options.
2781  Script(String),
2782  /// Run the given script with custom options.
2783  ScriptWithOptions {
2784    /// The script to execute.
2785    script: String,
2786    /// The current working directory.
2787    cwd: Option<String>,
2788  },
2789}
2790
2791/// The runner configuration.
2792#[skip_serializing_none]
2793#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2794#[cfg_attr(feature = "schema", derive(JsonSchema))]
2795#[serde(untagged)]
2796pub enum RunnerConfig {
2797  /// A string specifying the binary to run.
2798  String(String),
2799  /// An object with advanced configuration options.
2800  Object {
2801    /// The binary to run.
2802    cmd: String,
2803    /// The current working directory to run the command from.
2804    cwd: Option<String>,
2805    /// Arguments to pass to the command.
2806    args: Option<Vec<String>>,
2807  },
2808}
2809
2810impl Default for RunnerConfig {
2811  fn default() -> Self {
2812    RunnerConfig::String("cargo".to_string())
2813  }
2814}
2815
2816impl RunnerConfig {
2817  /// Returns the command to run.
2818  pub fn cmd(&self) -> &str {
2819    match self {
2820      RunnerConfig::String(cmd) => cmd,
2821      RunnerConfig::Object { cmd, .. } => cmd,
2822    }
2823  }
2824
2825  /// Returns the working directory.
2826  pub fn cwd(&self) -> Option<&str> {
2827    match self {
2828      RunnerConfig::String(_) => None,
2829      RunnerConfig::Object { cwd, .. } => cwd.as_deref(),
2830    }
2831  }
2832
2833  /// Returns the arguments.
2834  pub fn args(&self) -> Option<&[String]> {
2835    match self {
2836      RunnerConfig::String(_) => None,
2837      RunnerConfig::Object { args, .. } => args.as_deref(),
2838    }
2839  }
2840}
2841
2842impl std::str::FromStr for RunnerConfig {
2843  type Err = std::convert::Infallible;
2844
2845  fn from_str(s: &str) -> Result<Self, Self::Err> {
2846    Ok(RunnerConfig::String(s.to_string()))
2847  }
2848}
2849
2850impl From<&str> for RunnerConfig {
2851  fn from(s: &str) -> Self {
2852    RunnerConfig::String(s.to_string())
2853  }
2854}
2855
2856impl From<String> for RunnerConfig {
2857  fn from(s: String) -> Self {
2858    RunnerConfig::String(s)
2859  }
2860}
2861
2862/// The Build configuration object.
2863///
2864/// See more: <https://v2.tauri.app/reference/config/#buildconfig>
2865#[skip_serializing_none]
2866#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)]
2867#[cfg_attr(feature = "schema", derive(JsonSchema))]
2868#[serde(rename_all = "camelCase", deny_unknown_fields)]
2869pub struct BuildConfig {
2870  /// The binary used to build and run the application.
2871  pub runner: Option<RunnerConfig>,
2872  /// The URL to load in development.
2873  ///
2874  /// This is usually an URL to a dev server, which serves your application assets with hot-reload and HMR.
2875  /// Most modern JavaScript bundlers like [Vite](https://vite.dev/guide/) provides a way to start a dev server by default.
2876  ///
2877  /// If you don't have a dev server or don't want to use one, ignore this option and use [`frontendDist`](BuildConfig::frontend_dist)
2878  /// and point to a web assets directory, and Tauri CLI will run its built-in dev server and provide a simple hot-reload experience.
2879  #[serde(alias = "dev-url")]
2880  pub dev_url: Option<Url>,
2881  /// The path to the application assets (usually the `dist` folder of your javascript bundler)
2882  /// or a URL that could be either a custom protocol registered in the tauri app (for example: `myprotocol://`)
2883  /// or a remote URL (for example: `https://site.com/app`).
2884  ///
2885  /// When a path relative to the configuration file is provided,
2886  /// it is read recursively and all files are embedded in the application binary.
2887  /// Tauri then looks for an `index.html` and serves it as the default entry point for your application.
2888  ///
2889  /// You can also provide a list of paths to be embedded, which allows granular control over what files are added to the binary.
2890  /// In this case, all files are added to the root and you must reference it that way in your HTML files.
2891  ///
2892  /// When a URL is provided, the application won't have bundled assets
2893  /// and the application will load that URL by default.
2894  #[serde(alias = "frontend-dist")]
2895  pub frontend_dist: Option<FrontendDist>,
2896  /// A shell command to run before `tauri dev` kicks in.
2897  ///
2898  /// The TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG environment variables are set if you perform conditional compilation.
2899  #[serde(alias = "before-dev-command")]
2900  pub before_dev_command: Option<BeforeDevCommand>,
2901  /// A shell command to run before `tauri build` kicks in.
2902  ///
2903  /// The TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG environment variables are set if you perform conditional compilation.
2904  #[serde(alias = "before-build-command")]
2905  pub before_build_command: Option<HookCommand>,
2906  /// A shell command to run before the bundling phase in `tauri build` kicks in.
2907  ///
2908  /// The TAURI_ENV_PLATFORM, TAURI_ENV_ARCH, TAURI_ENV_FAMILY, TAURI_ENV_PLATFORM_VERSION, TAURI_ENV_PLATFORM_TYPE and TAURI_ENV_DEBUG environment variables are set if you perform conditional compilation.
2909  #[serde(alias = "before-bundle-command")]
2910  pub before_bundle_command: Option<HookCommand>,
2911  /// Features passed to `cargo` commands.
2912  pub features: Option<Vec<String>>,
2913  /// Try to remove unused commands registered from plugins base on the ACL list during `tauri build`,
2914  /// the way it works is that tauri-cli will read this and set the environment variables for the build script and macros,
2915  /// and they'll try to get all the allowed commands and remove the rest
2916  ///
2917  /// Note:
2918  ///   - This won't be accounting for dynamically added ACLs when you use features from the `dynamic-acl` (currently enabled by default) feature flag, so make sure to check it when using this
2919  ///   - This feature requires tauri-plugin 2.1 and tauri 2.4
2920  #[serde(alias = "remove-unused-commands", default)]
2921  pub remove_unused_commands: bool,
2922  /// Additional paths to watch for changes when running `tauri dev`.
2923  #[serde(alias = "additional-watch-directories", default)]
2924  pub additional_watch_folders: Vec<PathBuf>,
2925}
2926
2927#[derive(Debug, PartialEq, Eq)]
2928struct PackageVersion(String);
2929
2930impl<'d> serde::Deserialize<'d> for PackageVersion {
2931  fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
2932    struct PackageVersionVisitor;
2933
2934    impl Visitor<'_> for PackageVersionVisitor {
2935      type Value = PackageVersion;
2936
2937      fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2938        write!(
2939          formatter,
2940          "a semver string or a path to a package.json file"
2941        )
2942      }
2943
2944      fn visit_str<E: DeError>(self, value: &str) -> Result<PackageVersion, E> {
2945        let path = PathBuf::from(value);
2946        if path.exists() {
2947          let json_str = read_to_string(&path)
2948            .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
2949          let package_json: serde_json::Value = serde_json::from_str(&json_str)
2950            .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
2951          if let Some(obj) = package_json.as_object() {
2952            let version = obj
2953              .get("version")
2954              .ok_or_else(|| DeError::custom("JSON must contain a `version` field"))?
2955              .as_str()
2956              .ok_or_else(|| {
2957                DeError::custom(format!("`{} > version` must be a string", path.display()))
2958              })?;
2959            Ok(PackageVersion(
2960              Version::from_str(version)
2961                .map_err(|_| DeError::custom("`package > version` must be a semver string"))?
2962                .to_string(),
2963            ))
2964          } else {
2965            Err(DeError::custom(
2966              "`package > version` value is not a path to a JSON object",
2967            ))
2968          }
2969        } else {
2970          Ok(PackageVersion(
2971            Version::from_str(value)
2972              .map_err(|_| DeError::custom("`package > version` must be a semver string"))?
2973              .to_string(),
2974          ))
2975        }
2976      }
2977    }
2978
2979    deserializer.deserialize_string(PackageVersionVisitor {})
2980  }
2981}
2982
2983fn version_deserializer<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
2984where
2985  D: Deserializer<'de>,
2986{
2987  Option::<PackageVersion>::deserialize(deserializer).map(|v| v.map(|v| v.0))
2988}
2989
2990/// The Tauri configuration object.
2991/// It is read from a file where you can define your frontend assets,
2992/// configure the bundler and define a tray icon.
2993///
2994/// The configuration file is generated by the
2995/// [`tauri init`](https://v2.tauri.app/reference/cli/#init) command that lives in
2996/// your Tauri application source directory (src-tauri).
2997///
2998/// Once generated, you may modify it at will to customize your Tauri application.
2999///
3000/// ## File Formats
3001///
3002/// By default, the configuration is defined as a JSON file named `tauri.conf.json`.
3003///
3004/// Tauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively.
3005/// The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`.
3006/// The TOML file name is `Tauri.toml`.
3007///
3008/// ## Platform-Specific Configuration
3009///
3010/// In addition to the default configuration file, Tauri can
3011/// read a platform-specific configuration from `tauri.linux.conf.json`,
3012/// `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json`
3013/// (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used),
3014/// which gets merged with the main configuration object.
3015///
3016/// ## Configuration Structure
3017///
3018/// The configuration is composed of the following objects:
3019///
3020/// - [`app`](#appconfig): The Tauri configuration
3021/// - [`build`](#buildconfig): The build configuration
3022/// - [`bundle`](#bundleconfig): The bundle configurations
3023/// - [`plugins`](#pluginconfig): The plugins configuration
3024///
3025/// Example tauri.config.json file:
3026///
3027/// ```json
3028/// {
3029///   "productName": "tauri-app",
3030///   "version": "0.1.0",
3031///   "build": {
3032///     "beforeBuildCommand": "",
3033///     "beforeDevCommand": "",
3034///     "devUrl": "http://localhost:3000",
3035///     "frontendDist": "../dist"
3036///   },
3037///   "app": {
3038///     "security": {
3039///       "csp": null
3040///     },
3041///     "windows": [
3042///       {
3043///         "fullscreen": false,
3044///         "height": 600,
3045///         "resizable": true,
3046///         "title": "Tauri App",
3047///         "width": 800
3048///       }
3049///     ]
3050///   },
3051///   "bundle": {},
3052///   "plugins": {}
3053/// }
3054/// ```
3055#[skip_serializing_none]
3056#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
3057#[cfg_attr(feature = "schema", derive(JsonSchema))]
3058#[serde(rename_all = "camelCase", deny_unknown_fields)]
3059pub struct Config {
3060  /// The JSON schema for the Tauri config.
3061  #[serde(rename = "$schema")]
3062  pub schema: Option<String>,
3063  /// App name.
3064  #[serde(alias = "product-name")]
3065  #[cfg_attr(feature = "schema", validate(regex(pattern = "^[^/\\:*?\"<>|]+$")))]
3066  pub product_name: Option<String>,
3067  /// Overrides app's main binary filename.
3068  ///
3069  /// By default, Tauri uses the output binary from `cargo`, by setting this, we will rename that binary in `tauri-cli`'s
3070  /// `tauri build` command, and target `tauri bundle` to it
3071  ///
3072  /// If possible, change the [`package name`] or set the [`name field`] instead,
3073  /// and if that's not enough and you're using nightly, consider using the [`different-binary-name`] feature instead
3074  ///
3075  /// Note: this config should not include the binary extension (e.g. `.exe`), we'll add that for you
3076  ///
3077  /// [`package name`]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-name-field
3078  /// [`name field`]: https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-name-field
3079  /// [`different-binary-name`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#different-binary-name
3080  #[serde(alias = "main-binary-name")]
3081  pub main_binary_name: Option<String>,
3082  /// App version. It is a semver version number or a path to a `package.json` file containing the `version` field.
3083  ///
3084  /// If removed the version number from `Cargo.toml` is used.
3085  /// It's recommended to manage the app versioning in the Tauri config.
3086  ///
3087  /// ## Platform-specific
3088  ///
3089  /// - **macOS**: Translates to the bundle's CFBundleShortVersionString property and is used as the default CFBundleVersion.
3090  ///    You can set an specific bundle version using [`bundle > macOS > bundleVersion`](MacConfig::bundle_version).
3091  /// - **iOS**: Translates to the bundle's CFBundleShortVersionString property and is used as the default CFBundleVersion.
3092  ///    You can set an specific bundle version using [`bundle > iOS > bundleVersion`](IosConfig::bundle_version).
3093  ///    The `tauri ios build` CLI command has a `--build-number <number>` option that lets you append a build number to the app version.
3094  /// - **Android**: By default version 1.0 is used. You can set a version code using [`bundle > android > versionCode`](AndroidConfig::version_code).
3095  ///
3096  /// By default version 1.0 is used on Android.
3097  #[serde(deserialize_with = "version_deserializer", default)]
3098  pub version: Option<String>,
3099  /// The application identifier in reverse domain name notation (e.g. `com.tauri.example`).
3100  /// This string must be unique across applications since it is used in system configurations like
3101  /// the bundle ID and path to the webview data directory.
3102  /// This string must contain only alphanumeric characters (A-Z, a-z, and 0-9), hyphens (-),
3103  /// and periods (.).
3104  pub identifier: String,
3105  /// The App configuration.
3106  #[serde(default)]
3107  pub app: AppConfig,
3108  /// The build configuration.
3109  #[serde(default)]
3110  pub build: BuildConfig,
3111  /// The bundler configuration.
3112  #[serde(default)]
3113  pub bundle: BundleConfig,
3114  /// The plugins config.
3115  #[serde(default)]
3116  pub plugins: PluginConfig,
3117}
3118
3119/// The plugin configs holds a HashMap mapping a plugin name to its configuration object.
3120///
3121/// See more: <https://v2.tauri.app/reference/config/#pluginconfig>
3122#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
3123#[cfg_attr(feature = "schema", derive(JsonSchema))]
3124pub struct PluginConfig(pub HashMap<String, JsonValue>);
3125
3126/// Implement `ToTokens` for all config structs, allowing a literal `Config` to be built.
3127///
3128/// This allows for a build script to output the values in a `Config` to a `TokenStream`, which can
3129/// then be consumed by another crate. Useful for passing a config to both the build script and the
3130/// application using tauri while only parsing it once (in the build script).
3131#[cfg(feature = "build")]
3132mod build {
3133  use super::*;
3134  use crate::{literal_struct, tokens::*};
3135  use proc_macro2::TokenStream;
3136  use quote::{quote, ToTokens, TokenStreamExt};
3137  use std::convert::identity;
3138
3139  impl ToTokens for WebviewUrl {
3140    fn to_tokens(&self, tokens: &mut TokenStream) {
3141      let prefix = quote! { ::tauri::utils::config::WebviewUrl };
3142
3143      tokens.append_all(match self {
3144        Self::App(path) => {
3145          let path = path_buf_lit(path);
3146          quote! { #prefix::App(#path) }
3147        }
3148        Self::External(url) => {
3149          let url = url_lit(url);
3150          quote! { #prefix::External(#url) }
3151        }
3152        Self::CustomProtocol(url) => {
3153          let url = url_lit(url);
3154          quote! { #prefix::CustomProtocol(#url) }
3155        }
3156      })
3157    }
3158  }
3159
3160  impl ToTokens for BackgroundThrottlingPolicy {
3161    fn to_tokens(&self, tokens: &mut TokenStream) {
3162      let prefix = quote! { ::tauri::utils::config::BackgroundThrottlingPolicy };
3163      tokens.append_all(match self {
3164        Self::Disabled => quote! { #prefix::Disabled },
3165        Self::Throttle => quote! { #prefix::Throttle },
3166        Self::Suspend => quote! { #prefix::Suspend },
3167      })
3168    }
3169  }
3170
3171  impl ToTokens for crate::Theme {
3172    fn to_tokens(&self, tokens: &mut TokenStream) {
3173      let prefix = quote! { ::tauri::utils::Theme };
3174
3175      tokens.append_all(match self {
3176        Self::Light => quote! { #prefix::Light },
3177        Self::Dark => quote! { #prefix::Dark },
3178      })
3179    }
3180  }
3181
3182  impl ToTokens for Color {
3183    fn to_tokens(&self, tokens: &mut TokenStream) {
3184      let Color(r, g, b, a) = self;
3185      tokens.append_all(quote! {::tauri::utils::config::Color(#r,#g,#b,#a)});
3186    }
3187  }
3188  impl ToTokens for WindowEffectsConfig {
3189    fn to_tokens(&self, tokens: &mut TokenStream) {
3190      let effects = vec_lit(self.effects.clone(), |d| d);
3191      let state = opt_lit(self.state.as_ref());
3192      let radius = opt_lit(self.radius.as_ref());
3193      let color = opt_lit(self.color.as_ref());
3194
3195      literal_struct!(
3196        tokens,
3197        ::tauri::utils::config::WindowEffectsConfig,
3198        effects,
3199        state,
3200        radius,
3201        color
3202      )
3203    }
3204  }
3205
3206  impl ToTokens for crate::TitleBarStyle {
3207    fn to_tokens(&self, tokens: &mut TokenStream) {
3208      let prefix = quote! { ::tauri::utils::TitleBarStyle };
3209
3210      tokens.append_all(match self {
3211        Self::Visible => quote! { #prefix::Visible },
3212        Self::Transparent => quote! { #prefix::Transparent },
3213        Self::Overlay => quote! { #prefix::Overlay },
3214      })
3215    }
3216  }
3217
3218  impl ToTokens for LogicalPosition {
3219    fn to_tokens(&self, tokens: &mut TokenStream) {
3220      let LogicalPosition { x, y } = self;
3221      literal_struct!(tokens, ::tauri::utils::config::LogicalPosition, x, y)
3222    }
3223  }
3224
3225  impl ToTokens for crate::WindowEffect {
3226    fn to_tokens(&self, tokens: &mut TokenStream) {
3227      let prefix = quote! { ::tauri::utils::WindowEffect };
3228
3229      #[allow(deprecated)]
3230      tokens.append_all(match self {
3231        WindowEffect::AppearanceBased => quote! { #prefix::AppearanceBased},
3232        WindowEffect::Light => quote! { #prefix::Light},
3233        WindowEffect::Dark => quote! { #prefix::Dark},
3234        WindowEffect::MediumLight => quote! { #prefix::MediumLight},
3235        WindowEffect::UltraDark => quote! { #prefix::UltraDark},
3236        WindowEffect::Titlebar => quote! { #prefix::Titlebar},
3237        WindowEffect::Selection => quote! { #prefix::Selection},
3238        WindowEffect::Menu => quote! { #prefix::Menu},
3239        WindowEffect::Popover => quote! { #prefix::Popover},
3240        WindowEffect::Sidebar => quote! { #prefix::Sidebar},
3241        WindowEffect::HeaderView => quote! { #prefix::HeaderView},
3242        WindowEffect::Sheet => quote! { #prefix::Sheet},
3243        WindowEffect::WindowBackground => quote! { #prefix::WindowBackground},
3244        WindowEffect::HudWindow => quote! { #prefix::HudWindow},
3245        WindowEffect::FullScreenUI => quote! { #prefix::FullScreenUI},
3246        WindowEffect::Tooltip => quote! { #prefix::Tooltip},
3247        WindowEffect::ContentBackground => quote! { #prefix::ContentBackground},
3248        WindowEffect::UnderWindowBackground => quote! { #prefix::UnderWindowBackground},
3249        WindowEffect::UnderPageBackground => quote! { #prefix::UnderPageBackground},
3250        WindowEffect::Mica => quote! { #prefix::Mica},
3251        WindowEffect::MicaDark => quote! { #prefix::MicaDark},
3252        WindowEffect::MicaLight => quote! { #prefix::MicaLight},
3253        WindowEffect::Blur => quote! { #prefix::Blur},
3254        WindowEffect::Acrylic => quote! { #prefix::Acrylic},
3255        WindowEffect::Tabbed => quote! { #prefix::Tabbed },
3256        WindowEffect::TabbedDark => quote! { #prefix::TabbedDark },
3257        WindowEffect::TabbedLight => quote! { #prefix::TabbedLight },
3258      })
3259    }
3260  }
3261
3262  impl ToTokens for crate::WindowEffectState {
3263    fn to_tokens(&self, tokens: &mut TokenStream) {
3264      let prefix = quote! { ::tauri::utils::WindowEffectState };
3265
3266      #[allow(deprecated)]
3267      tokens.append_all(match self {
3268        WindowEffectState::Active => quote! { #prefix::Active},
3269        WindowEffectState::FollowsWindowActiveState => quote! { #prefix::FollowsWindowActiveState},
3270        WindowEffectState::Inactive => quote! { #prefix::Inactive},
3271      })
3272    }
3273  }
3274
3275  impl ToTokens for PreventOverflowMargin {
3276    fn to_tokens(&self, tokens: &mut TokenStream) {
3277      let width = self.width;
3278      let height = self.height;
3279
3280      literal_struct!(
3281        tokens,
3282        ::tauri::utils::config::PreventOverflowMargin,
3283        width,
3284        height
3285      )
3286    }
3287  }
3288
3289  impl ToTokens for PreventOverflowConfig {
3290    fn to_tokens(&self, tokens: &mut TokenStream) {
3291      let prefix = quote! { ::tauri::utils::config::PreventOverflowConfig };
3292
3293      #[allow(deprecated)]
3294      tokens.append_all(match self {
3295        Self::Enable(enable) => quote! { #prefix::Enable(#enable) },
3296        Self::Margin(margin) => quote! { #prefix::Margin(#margin) },
3297      })
3298    }
3299  }
3300
3301  impl ToTokens for WindowConfig {
3302    fn to_tokens(&self, tokens: &mut TokenStream) {
3303      let label = str_lit(&self.label);
3304      let create = &self.create;
3305      let url = &self.url;
3306      let user_agent = opt_str_lit(self.user_agent.as_ref());
3307      let drag_drop_enabled = self.drag_drop_enabled;
3308      let center = self.center;
3309      let x = opt_lit(self.x.as_ref());
3310      let y = opt_lit(self.y.as_ref());
3311      let width = self.width;
3312      let height = self.height;
3313      let min_width = opt_lit(self.min_width.as_ref());
3314      let min_height = opt_lit(self.min_height.as_ref());
3315      let max_width = opt_lit(self.max_width.as_ref());
3316      let max_height = opt_lit(self.max_height.as_ref());
3317      let prevent_overflow = opt_lit(self.prevent_overflow.as_ref());
3318      let resizable = self.resizable;
3319      let maximizable = self.maximizable;
3320      let minimizable = self.minimizable;
3321      let closable = self.closable;
3322      let title = str_lit(&self.title);
3323      let proxy_url = opt_lit(self.proxy_url.as_ref().map(url_lit).as_ref());
3324      let fullscreen = self.fullscreen;
3325      let focus = self.focus;
3326      let focusable = self.focusable;
3327      let transparent = self.transparent;
3328      let maximized = self.maximized;
3329      let visible = self.visible;
3330      let decorations = self.decorations;
3331      let always_on_bottom = self.always_on_bottom;
3332      let always_on_top = self.always_on_top;
3333      let visible_on_all_workspaces = self.visible_on_all_workspaces;
3334      let content_protected = self.content_protected;
3335      let skip_taskbar = self.skip_taskbar;
3336      let window_classname = opt_str_lit(self.window_classname.as_ref());
3337      let theme = opt_lit(self.theme.as_ref());
3338      let title_bar_style = &self.title_bar_style;
3339      let traffic_light_position = opt_lit(self.traffic_light_position.as_ref());
3340      let hidden_title = self.hidden_title;
3341      let accept_first_mouse = self.accept_first_mouse;
3342      let tabbing_identifier = opt_str_lit(self.tabbing_identifier.as_ref());
3343      let additional_browser_args = opt_str_lit(self.additional_browser_args.as_ref());
3344      let shadow = self.shadow;
3345      let window_effects = opt_lit(self.window_effects.as_ref());
3346      let incognito = self.incognito;
3347      let parent = opt_str_lit(self.parent.as_ref());
3348      let zoom_hotkeys_enabled = self.zoom_hotkeys_enabled;
3349      let browser_extensions_enabled = self.browser_extensions_enabled;
3350      let use_https_scheme = self.use_https_scheme;
3351      let devtools = opt_lit(self.devtools.as_ref());
3352      let background_color = opt_lit(self.background_color.as_ref());
3353      let background_throttling = opt_lit(self.background_throttling.as_ref());
3354      let javascript_disabled = self.javascript_disabled;
3355      let allow_link_preview = self.allow_link_preview;
3356      let disable_input_accessory_view = self.disable_input_accessory_view;
3357
3358      literal_struct!(
3359        tokens,
3360        ::tauri::utils::config::WindowConfig,
3361        label,
3362        url,
3363        create,
3364        user_agent,
3365        drag_drop_enabled,
3366        center,
3367        x,
3368        y,
3369        width,
3370        height,
3371        min_width,
3372        min_height,
3373        max_width,
3374        max_height,
3375        prevent_overflow,
3376        resizable,
3377        maximizable,
3378        minimizable,
3379        closable,
3380        title,
3381        proxy_url,
3382        fullscreen,
3383        focus,
3384        focusable,
3385        transparent,
3386        maximized,
3387        visible,
3388        decorations,
3389        always_on_bottom,
3390        always_on_top,
3391        visible_on_all_workspaces,
3392        content_protected,
3393        skip_taskbar,
3394        window_classname,
3395        theme,
3396        title_bar_style,
3397        traffic_light_position,
3398        hidden_title,
3399        accept_first_mouse,
3400        tabbing_identifier,
3401        additional_browser_args,
3402        shadow,
3403        window_effects,
3404        incognito,
3405        parent,
3406        zoom_hotkeys_enabled,
3407        browser_extensions_enabled,
3408        use_https_scheme,
3409        devtools,
3410        background_color,
3411        background_throttling,
3412        javascript_disabled,
3413        allow_link_preview,
3414        disable_input_accessory_view
3415      );
3416    }
3417  }
3418
3419  impl ToTokens for PatternKind {
3420    fn to_tokens(&self, tokens: &mut TokenStream) {
3421      let prefix = quote! { ::tauri::utils::config::PatternKind };
3422
3423      tokens.append_all(match self {
3424        Self::Brownfield => quote! { #prefix::Brownfield },
3425        #[cfg(not(feature = "isolation"))]
3426        Self::Isolation { dir: _ } => quote! { #prefix::Brownfield },
3427        #[cfg(feature = "isolation")]
3428        Self::Isolation { dir } => {
3429          let dir = path_buf_lit(dir);
3430          quote! { #prefix::Isolation { dir: #dir } }
3431        }
3432      })
3433    }
3434  }
3435
3436  impl ToTokens for WebviewInstallMode {
3437    fn to_tokens(&self, tokens: &mut TokenStream) {
3438      let prefix = quote! { ::tauri::utils::config::WebviewInstallMode };
3439
3440      tokens.append_all(match self {
3441        Self::Skip => quote! { #prefix::Skip },
3442        Self::DownloadBootstrapper { silent } => {
3443          quote! { #prefix::DownloadBootstrapper { silent: #silent } }
3444        }
3445        Self::EmbedBootstrapper { silent } => {
3446          quote! { #prefix::EmbedBootstrapper { silent: #silent } }
3447        }
3448        Self::OfflineInstaller { silent } => {
3449          quote! { #prefix::OfflineInstaller { silent: #silent } }
3450        }
3451        Self::FixedRuntime { path } => {
3452          let path = path_buf_lit(path);
3453          quote! { #prefix::FixedRuntime { path: #path } }
3454        }
3455      })
3456    }
3457  }
3458
3459  impl ToTokens for WindowsConfig {
3460    fn to_tokens(&self, tokens: &mut TokenStream) {
3461      let webview_install_mode = &self.webview_install_mode;
3462      tokens.append_all(quote! { ::tauri::utils::config::WindowsConfig {
3463        webview_install_mode: #webview_install_mode,
3464        ..Default::default()
3465      }})
3466    }
3467  }
3468
3469  impl ToTokens for BundleConfig {
3470    fn to_tokens(&self, tokens: &mut TokenStream) {
3471      let publisher = quote!(None);
3472      let homepage = quote!(None);
3473      let icon = vec_lit(&self.icon, str_lit);
3474      let active = self.active;
3475      let targets = quote!(Default::default());
3476      let create_updater_artifacts = quote!(Default::default());
3477      let resources = quote!(None);
3478      let copyright = quote!(None);
3479      let category = quote!(None);
3480      let file_associations = quote!(None);
3481      let short_description = quote!(None);
3482      let long_description = quote!(None);
3483      let use_local_tools_dir = self.use_local_tools_dir;
3484      let external_bin = opt_vec_lit(self.external_bin.as_ref(), str_lit);
3485      let windows = &self.windows;
3486      let license = opt_str_lit(self.license.as_ref());
3487      let license_file = opt_lit(self.license_file.as_ref().map(path_buf_lit).as_ref());
3488      let linux = quote!(Default::default());
3489      let macos = quote!(Default::default());
3490      let ios = quote!(Default::default());
3491      let android = quote!(Default::default());
3492
3493      literal_struct!(
3494        tokens,
3495        ::tauri::utils::config::BundleConfig,
3496        active,
3497        publisher,
3498        homepage,
3499        icon,
3500        targets,
3501        create_updater_artifacts,
3502        resources,
3503        copyright,
3504        category,
3505        license,
3506        license_file,
3507        file_associations,
3508        short_description,
3509        long_description,
3510        use_local_tools_dir,
3511        external_bin,
3512        windows,
3513        linux,
3514        macos,
3515        ios,
3516        android
3517      );
3518    }
3519  }
3520
3521  impl ToTokens for FrontendDist {
3522    fn to_tokens(&self, tokens: &mut TokenStream) {
3523      let prefix = quote! { ::tauri::utils::config::FrontendDist };
3524
3525      tokens.append_all(match self {
3526        Self::Url(url) => {
3527          let url = url_lit(url);
3528          quote! { #prefix::Url(#url) }
3529        }
3530        Self::Directory(path) => {
3531          let path = path_buf_lit(path);
3532          quote! { #prefix::Directory(#path) }
3533        }
3534        Self::Files(files) => {
3535          let files = vec_lit(files, path_buf_lit);
3536          quote! { #prefix::Files(#files) }
3537        }
3538      })
3539    }
3540  }
3541
3542  impl ToTokens for RunnerConfig {
3543    fn to_tokens(&self, tokens: &mut TokenStream) {
3544      let prefix = quote! { ::tauri::utils::config::RunnerConfig };
3545
3546      tokens.append_all(match self {
3547        Self::String(cmd) => {
3548          let cmd = cmd.as_str();
3549          quote!(#prefix::String(#cmd.into()))
3550        }
3551        Self::Object { cmd, cwd, args } => {
3552          let cmd = cmd.as_str();
3553          let cwd = opt_str_lit(cwd.as_ref());
3554          let args = opt_lit(args.as_ref().map(|v| vec_lit(v, str_lit)).as_ref());
3555          quote!(#prefix::Object {
3556            cmd: #cmd.into(),
3557            cwd: #cwd,
3558            args: #args,
3559          })
3560        }
3561      })
3562    }
3563  }
3564
3565  impl ToTokens for BuildConfig {
3566    fn to_tokens(&self, tokens: &mut TokenStream) {
3567      let dev_url = opt_lit(self.dev_url.as_ref().map(url_lit).as_ref());
3568      let frontend_dist = opt_lit(self.frontend_dist.as_ref());
3569      let runner = opt_lit(self.runner.as_ref());
3570      let before_dev_command = quote!(None);
3571      let before_build_command = quote!(None);
3572      let before_bundle_command = quote!(None);
3573      let features = quote!(None);
3574      let remove_unused_commands = quote!(false);
3575      let additional_watch_folders = quote!(Vec::new());
3576
3577      literal_struct!(
3578        tokens,
3579        ::tauri::utils::config::BuildConfig,
3580        runner,
3581        dev_url,
3582        frontend_dist,
3583        before_dev_command,
3584        before_build_command,
3585        before_bundle_command,
3586        features,
3587        remove_unused_commands,
3588        additional_watch_folders
3589      );
3590    }
3591  }
3592
3593  impl ToTokens for CspDirectiveSources {
3594    fn to_tokens(&self, tokens: &mut TokenStream) {
3595      let prefix = quote! { ::tauri::utils::config::CspDirectiveSources };
3596
3597      tokens.append_all(match self {
3598        Self::Inline(sources) => {
3599          let sources = sources.as_str();
3600          quote!(#prefix::Inline(#sources.into()))
3601        }
3602        Self::List(list) => {
3603          let list = vec_lit(list, str_lit);
3604          quote!(#prefix::List(#list))
3605        }
3606      })
3607    }
3608  }
3609
3610  impl ToTokens for Csp {
3611    fn to_tokens(&self, tokens: &mut TokenStream) {
3612      let prefix = quote! { ::tauri::utils::config::Csp };
3613
3614      tokens.append_all(match self {
3615        Self::Policy(policy) => {
3616          let policy = policy.as_str();
3617          quote!(#prefix::Policy(#policy.into()))
3618        }
3619        Self::DirectiveMap(list) => {
3620          let map = map_lit(
3621            quote! { ::std::collections::HashMap },
3622            list,
3623            str_lit,
3624            identity,
3625          );
3626          quote!(#prefix::DirectiveMap(#map))
3627        }
3628      })
3629    }
3630  }
3631
3632  impl ToTokens for DisabledCspModificationKind {
3633    fn to_tokens(&self, tokens: &mut TokenStream) {
3634      let prefix = quote! { ::tauri::utils::config::DisabledCspModificationKind };
3635
3636      tokens.append_all(match self {
3637        Self::Flag(flag) => {
3638          quote! { #prefix::Flag(#flag) }
3639        }
3640        Self::List(directives) => {
3641          let directives = vec_lit(directives, str_lit);
3642          quote! { #prefix::List(#directives) }
3643        }
3644      });
3645    }
3646  }
3647
3648  impl ToTokens for CapabilityEntry {
3649    fn to_tokens(&self, tokens: &mut TokenStream) {
3650      let prefix = quote! { ::tauri::utils::config::CapabilityEntry };
3651
3652      tokens.append_all(match self {
3653        Self::Inlined(capability) => {
3654          quote! { #prefix::Inlined(#capability) }
3655        }
3656        Self::Reference(id) => {
3657          let id = str_lit(id);
3658          quote! { #prefix::Reference(#id) }
3659        }
3660      });
3661    }
3662  }
3663
3664  impl ToTokens for HeaderSource {
3665    fn to_tokens(&self, tokens: &mut TokenStream) {
3666      let prefix = quote! { ::tauri::utils::config::HeaderSource };
3667
3668      tokens.append_all(match self {
3669        Self::Inline(s) => {
3670          let line = s.as_str();
3671          quote!(#prefix::Inline(#line.into()))
3672        }
3673        Self::List(l) => {
3674          let list = vec_lit(l, str_lit);
3675          quote!(#prefix::List(#list))
3676        }
3677        Self::Map(m) => {
3678          let map = map_lit(quote! { ::std::collections::HashMap }, m, str_lit, str_lit);
3679          quote!(#prefix::Map(#map))
3680        }
3681      })
3682    }
3683  }
3684
3685  impl ToTokens for HeaderConfig {
3686    fn to_tokens(&self, tokens: &mut TokenStream) {
3687      let access_control_allow_credentials =
3688        opt_lit(self.access_control_allow_credentials.as_ref());
3689      let access_control_allow_headers = opt_lit(self.access_control_allow_headers.as_ref());
3690      let access_control_allow_methods = opt_lit(self.access_control_allow_methods.as_ref());
3691      let access_control_expose_headers = opt_lit(self.access_control_expose_headers.as_ref());
3692      let access_control_max_age = opt_lit(self.access_control_max_age.as_ref());
3693      let cross_origin_embedder_policy = opt_lit(self.cross_origin_embedder_policy.as_ref());
3694      let cross_origin_opener_policy = opt_lit(self.cross_origin_opener_policy.as_ref());
3695      let cross_origin_resource_policy = opt_lit(self.cross_origin_resource_policy.as_ref());
3696      let permissions_policy = opt_lit(self.permissions_policy.as_ref());
3697      let service_worker_allowed = opt_lit(self.service_worker_allowed.as_ref());
3698      let timing_allow_origin = opt_lit(self.timing_allow_origin.as_ref());
3699      let x_content_type_options = opt_lit(self.x_content_type_options.as_ref());
3700      let tauri_custom_header = opt_lit(self.tauri_custom_header.as_ref());
3701
3702      literal_struct!(
3703        tokens,
3704        ::tauri::utils::config::HeaderConfig,
3705        access_control_allow_credentials,
3706        access_control_allow_headers,
3707        access_control_allow_methods,
3708        access_control_expose_headers,
3709        access_control_max_age,
3710        cross_origin_embedder_policy,
3711        cross_origin_opener_policy,
3712        cross_origin_resource_policy,
3713        permissions_policy,
3714        service_worker_allowed,
3715        timing_allow_origin,
3716        x_content_type_options,
3717        tauri_custom_header
3718      );
3719    }
3720  }
3721
3722  impl ToTokens for SecurityConfig {
3723    fn to_tokens(&self, tokens: &mut TokenStream) {
3724      let csp = opt_lit(self.csp.as_ref());
3725      let dev_csp = opt_lit(self.dev_csp.as_ref());
3726      let freeze_prototype = self.freeze_prototype;
3727      let dangerous_disable_asset_csp_modification = &self.dangerous_disable_asset_csp_modification;
3728      let asset_protocol = &self.asset_protocol;
3729      let pattern = &self.pattern;
3730      let capabilities = vec_lit(&self.capabilities, identity);
3731      let headers = opt_lit(self.headers.as_ref());
3732
3733      literal_struct!(
3734        tokens,
3735        ::tauri::utils::config::SecurityConfig,
3736        csp,
3737        dev_csp,
3738        freeze_prototype,
3739        dangerous_disable_asset_csp_modification,
3740        asset_protocol,
3741        pattern,
3742        capabilities,
3743        headers
3744      );
3745    }
3746  }
3747
3748  impl ToTokens for TrayIconConfig {
3749    fn to_tokens(&self, tokens: &mut TokenStream) {
3750      // For [`Self::menu_on_left_click`]
3751      tokens.append_all(quote!(#[allow(deprecated)]));
3752
3753      let id = opt_str_lit(self.id.as_ref());
3754      let icon_as_template = self.icon_as_template;
3755      #[allow(deprecated)]
3756      let menu_on_left_click = self.menu_on_left_click;
3757      let show_menu_on_left_click = self.show_menu_on_left_click;
3758      let icon_path = path_buf_lit(&self.icon_path);
3759      let title = opt_str_lit(self.title.as_ref());
3760      let tooltip = opt_str_lit(self.tooltip.as_ref());
3761      literal_struct!(
3762        tokens,
3763        ::tauri::utils::config::TrayIconConfig,
3764        id,
3765        icon_path,
3766        icon_as_template,
3767        menu_on_left_click,
3768        show_menu_on_left_click,
3769        title,
3770        tooltip
3771      );
3772    }
3773  }
3774
3775  impl ToTokens for FsScope {
3776    fn to_tokens(&self, tokens: &mut TokenStream) {
3777      let prefix = quote! { ::tauri::utils::config::FsScope };
3778
3779      tokens.append_all(match self {
3780        Self::AllowedPaths(allow) => {
3781          let allowed_paths = vec_lit(allow, path_buf_lit);
3782          quote! { #prefix::AllowedPaths(#allowed_paths) }
3783        }
3784        Self::Scope { allow, deny , require_literal_leading_dot} => {
3785          let allow = vec_lit(allow, path_buf_lit);
3786          let deny = vec_lit(deny, path_buf_lit);
3787          let  require_literal_leading_dot = opt_lit(require_literal_leading_dot.as_ref());
3788          quote! { #prefix::Scope { allow: #allow, deny: #deny, require_literal_leading_dot: #require_literal_leading_dot } }
3789        }
3790      });
3791    }
3792  }
3793
3794  impl ToTokens for AssetProtocolConfig {
3795    fn to_tokens(&self, tokens: &mut TokenStream) {
3796      let scope = &self.scope;
3797      tokens.append_all(quote! { ::tauri::utils::config::AssetProtocolConfig { scope: #scope, ..Default::default() } })
3798    }
3799  }
3800
3801  impl ToTokens for AppConfig {
3802    fn to_tokens(&self, tokens: &mut TokenStream) {
3803      let windows = vec_lit(&self.windows, identity);
3804      let security = &self.security;
3805      let tray_icon = opt_lit(self.tray_icon.as_ref());
3806      let macos_private_api = self.macos_private_api;
3807      let with_global_tauri = self.with_global_tauri;
3808      let enable_gtk_app_id = self.enable_gtk_app_id;
3809
3810      literal_struct!(
3811        tokens,
3812        ::tauri::utils::config::AppConfig,
3813        windows,
3814        security,
3815        tray_icon,
3816        macos_private_api,
3817        with_global_tauri,
3818        enable_gtk_app_id
3819      );
3820    }
3821  }
3822
3823  impl ToTokens for PluginConfig {
3824    fn to_tokens(&self, tokens: &mut TokenStream) {
3825      let config = map_lit(
3826        quote! { ::std::collections::HashMap },
3827        &self.0,
3828        str_lit,
3829        json_value_lit,
3830      );
3831      tokens.append_all(quote! { ::tauri::utils::config::PluginConfig(#config) })
3832    }
3833  }
3834
3835  impl ToTokens for Config {
3836    fn to_tokens(&self, tokens: &mut TokenStream) {
3837      let schema = quote!(None);
3838      let product_name = opt_str_lit(self.product_name.as_ref());
3839      let main_binary_name = opt_str_lit(self.main_binary_name.as_ref());
3840      let version = opt_str_lit(self.version.as_ref());
3841      let identifier = str_lit(&self.identifier);
3842      let app = &self.app;
3843      let build = &self.build;
3844      let bundle = &self.bundle;
3845      let plugins = &self.plugins;
3846
3847      literal_struct!(
3848        tokens,
3849        ::tauri::utils::config::Config,
3850        schema,
3851        product_name,
3852        main_binary_name,
3853        version,
3854        identifier,
3855        app,
3856        build,
3857        bundle,
3858        plugins
3859      );
3860    }
3861  }
3862}
3863
3864#[cfg(test)]
3865mod test {
3866  use super::*;
3867
3868  // TODO: create a test that compares a config to a json config
3869
3870  #[test]
3871  // test all of the default functions
3872  fn test_defaults() {
3873    // get default app config
3874    let a_config = AppConfig::default();
3875    // get default build config
3876    let b_config = BuildConfig::default();
3877    // get default window
3878    let d_windows: Vec<WindowConfig> = vec![];
3879    // get default bundle
3880    let d_bundle = BundleConfig::default();
3881
3882    // create a tauri config.
3883    let app = AppConfig {
3884      windows: vec![],
3885      security: SecurityConfig {
3886        csp: None,
3887        dev_csp: None,
3888        freeze_prototype: false,
3889        dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),
3890        asset_protocol: AssetProtocolConfig::default(),
3891        pattern: Default::default(),
3892        capabilities: Vec::new(),
3893        headers: None,
3894      },
3895      tray_icon: None,
3896      macos_private_api: false,
3897      with_global_tauri: false,
3898      enable_gtk_app_id: false,
3899    };
3900
3901    // create a build config
3902    let build = BuildConfig {
3903      runner: None,
3904      dev_url: None,
3905      frontend_dist: None,
3906      before_dev_command: None,
3907      before_build_command: None,
3908      before_bundle_command: None,
3909      features: None,
3910      remove_unused_commands: false,
3911      additional_watch_folders: Vec::new(),
3912    };
3913
3914    // create a bundle config
3915    let bundle = BundleConfig {
3916      active: false,
3917      targets: Default::default(),
3918      create_updater_artifacts: Default::default(),
3919      publisher: None,
3920      homepage: None,
3921      icon: Vec::new(),
3922      resources: None,
3923      copyright: None,
3924      category: None,
3925      file_associations: None,
3926      short_description: None,
3927      long_description: None,
3928      use_local_tools_dir: false,
3929      license: None,
3930      license_file: None,
3931      linux: Default::default(),
3932      macos: Default::default(),
3933      external_bin: None,
3934      windows: Default::default(),
3935      ios: Default::default(),
3936      android: Default::default(),
3937    };
3938
3939    // test the configs
3940    assert_eq!(a_config, app);
3941    assert_eq!(b_config, build);
3942    assert_eq!(d_bundle, bundle);
3943    assert_eq!(d_windows, app.windows);
3944  }
3945
3946  #[test]
3947  fn parse_hex_color() {
3948    use super::Color;
3949
3950    assert_eq!(Color(255, 255, 255, 255), "fff".parse().unwrap());
3951    assert_eq!(Color(255, 255, 255, 255), "#fff".parse().unwrap());
3952    assert_eq!(Color(0, 0, 0, 255), "#000000".parse().unwrap());
3953    assert_eq!(Color(0, 0, 0, 255), "#000000ff".parse().unwrap());
3954    assert_eq!(Color(0, 255, 0, 255), "#00ff00ff".parse().unwrap());
3955  }
3956
3957  #[test]
3958  fn test_runner_config_string_format() {
3959    use super::RunnerConfig;
3960
3961    // Test string format deserialization
3962    let json = r#""cargo""#;
3963    let runner: RunnerConfig = serde_json::from_str(json).unwrap();
3964
3965    assert_eq!(runner.cmd(), "cargo");
3966    assert_eq!(runner.cwd(), None);
3967    assert_eq!(runner.args(), None);
3968
3969    // Test string format serialization
3970    let serialized = serde_json::to_string(&runner).unwrap();
3971    assert_eq!(serialized, r#""cargo""#);
3972  }
3973
3974  #[test]
3975  fn test_runner_config_object_format_full() {
3976    use super::RunnerConfig;
3977
3978    // Test object format with all fields
3979    let json = r#"{"cmd": "my_runner", "cwd": "/tmp/build", "args": ["--quiet", "--verbose"]}"#;
3980    let runner: RunnerConfig = serde_json::from_str(json).unwrap();
3981
3982    assert_eq!(runner.cmd(), "my_runner");
3983    assert_eq!(runner.cwd(), Some("/tmp/build"));
3984    assert_eq!(
3985      runner.args(),
3986      Some(&["--quiet".to_string(), "--verbose".to_string()][..])
3987    );
3988
3989    // Test object format serialization
3990    let serialized = serde_json::to_string(&runner).unwrap();
3991    let deserialized: RunnerConfig = serde_json::from_str(&serialized).unwrap();
3992    assert_eq!(runner, deserialized);
3993  }
3994
3995  #[test]
3996  fn test_runner_config_object_format_minimal() {
3997    use super::RunnerConfig;
3998
3999    // Test object format with only cmd field
4000    let json = r#"{"cmd": "cross"}"#;
4001    let runner: RunnerConfig = serde_json::from_str(json).unwrap();
4002
4003    assert_eq!(runner.cmd(), "cross");
4004    assert_eq!(runner.cwd(), None);
4005    assert_eq!(runner.args(), None);
4006  }
4007
4008  #[test]
4009  fn test_runner_config_default() {
4010    use super::RunnerConfig;
4011
4012    let default_runner = RunnerConfig::default();
4013    assert_eq!(default_runner.cmd(), "cargo");
4014    assert_eq!(default_runner.cwd(), None);
4015    assert_eq!(default_runner.args(), None);
4016  }
4017
4018  #[test]
4019  fn test_runner_config_from_str() {
4020    use super::RunnerConfig;
4021
4022    // Test From<&str> trait
4023    let runner: RunnerConfig = "my_runner".into();
4024    assert_eq!(runner.cmd(), "my_runner");
4025    assert_eq!(runner.cwd(), None);
4026    assert_eq!(runner.args(), None);
4027  }
4028
4029  #[test]
4030  fn test_runner_config_from_string() {
4031    use super::RunnerConfig;
4032
4033    // Test From<String> trait
4034    let runner: RunnerConfig = "another_runner".to_string().into();
4035    assert_eq!(runner.cmd(), "another_runner");
4036    assert_eq!(runner.cwd(), None);
4037    assert_eq!(runner.args(), None);
4038  }
4039
4040  #[test]
4041  fn test_runner_config_from_str_parse() {
4042    use super::RunnerConfig;
4043    use std::str::FromStr;
4044
4045    // Test FromStr trait
4046    let runner = RunnerConfig::from_str("parsed_runner").unwrap();
4047    assert_eq!(runner.cmd(), "parsed_runner");
4048    assert_eq!(runner.cwd(), None);
4049    assert_eq!(runner.args(), None);
4050  }
4051
4052  #[test]
4053  fn test_runner_config_in_build_config() {
4054    use super::BuildConfig;
4055
4056    // Test string format in BuildConfig
4057    let json = r#"{"runner": "cargo"}"#;
4058    let build_config: BuildConfig = serde_json::from_str(json).unwrap();
4059
4060    let runner = build_config.runner.unwrap();
4061    assert_eq!(runner.cmd(), "cargo");
4062    assert_eq!(runner.cwd(), None);
4063    assert_eq!(runner.args(), None);
4064  }
4065
4066  #[test]
4067  fn test_runner_config_in_build_config_object() {
4068    use super::BuildConfig;
4069
4070    // Test object format in BuildConfig
4071    let json = r#"{"runner": {"cmd": "cross", "cwd": "/workspace", "args": ["--target", "x86_64-unknown-linux-gnu"]}}"#;
4072    let build_config: BuildConfig = serde_json::from_str(json).unwrap();
4073
4074    let runner = build_config.runner.unwrap();
4075    assert_eq!(runner.cmd(), "cross");
4076    assert_eq!(runner.cwd(), Some("/workspace"));
4077    assert_eq!(
4078      runner.args(),
4079      Some(
4080        &[
4081          "--target".to_string(),
4082          "x86_64-unknown-linux-gnu".to_string()
4083        ][..]
4084      )
4085    );
4086  }
4087
4088  #[test]
4089  fn test_runner_config_in_full_config() {
4090    use super::Config;
4091
4092    // Test runner config in full Tauri config
4093    let json = r#"{
4094      "productName": "Test App",
4095      "version": "1.0.0",
4096      "identifier": "com.test.app",
4097      "build": {
4098        "runner": {
4099          "cmd": "my_custom_cargo",
4100          "cwd": "/tmp/build",
4101          "args": ["--quiet", "--verbose"]
4102        }
4103      }
4104    }"#;
4105
4106    let config: Config = serde_json::from_str(json).unwrap();
4107    let runner = config.build.runner.unwrap();
4108
4109    assert_eq!(runner.cmd(), "my_custom_cargo");
4110    assert_eq!(runner.cwd(), Some("/tmp/build"));
4111    assert_eq!(
4112      runner.args(),
4113      Some(&["--quiet".to_string(), "--verbose".to_string()][..])
4114    );
4115  }
4116
4117  #[test]
4118  fn test_runner_config_equality() {
4119    use super::RunnerConfig;
4120
4121    let runner1 = RunnerConfig::String("cargo".to_string());
4122    let runner2 = RunnerConfig::String("cargo".to_string());
4123    let runner3 = RunnerConfig::String("cross".to_string());
4124
4125    assert_eq!(runner1, runner2);
4126    assert_ne!(runner1, runner3);
4127
4128    let runner4 = RunnerConfig::Object {
4129      cmd: "cargo".to_string(),
4130      cwd: Some("/tmp".to_string()),
4131      args: Some(vec!["--quiet".to_string()]),
4132    };
4133    let runner5 = RunnerConfig::Object {
4134      cmd: "cargo".to_string(),
4135      cwd: Some("/tmp".to_string()),
4136      args: Some(vec!["--quiet".to_string()]),
4137    };
4138
4139    assert_eq!(runner4, runner5);
4140    assert_ne!(runner1, runner4);
4141  }
4142
4143  #[test]
4144  fn test_runner_config_untagged_serialization() {
4145    use super::RunnerConfig;
4146
4147    // Test that serde untagged works correctly - string should serialize as string, not object
4148    let string_runner = RunnerConfig::String("cargo".to_string());
4149    let string_json = serde_json::to_string(&string_runner).unwrap();
4150    assert_eq!(string_json, r#""cargo""#);
4151
4152    // Test that object serializes as object
4153    let object_runner = RunnerConfig::Object {
4154      cmd: "cross".to_string(),
4155      cwd: None,
4156      args: None,
4157    };
4158    let object_json = serde_json::to_string(&object_runner).unwrap();
4159    assert!(object_json.contains("\"cmd\":\"cross\""));
4160    // With skip_serializing_none, null values should not be included
4161    assert!(object_json.contains("\"cwd\":null") || !object_json.contains("cwd"));
4162    assert!(object_json.contains("\"args\":null") || !object_json.contains("args"));
4163  }
4164}