1use http::response::Builder;
27#[cfg(feature = "schema")]
28use schemars::JsonSchema;
29#[cfg(feature = "schema")]
30use schemars::schema::Schema;
31use semver::Version;
32use serde::{
33 Deserialize, Serialize, Serializer,
34 de::{Deserializer, Error as DeError, Visitor},
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::{BTreeMap, HashMap, HashSet},
43 fmt::{self, Display},
44 fs::read_to_string,
45 path::{Component, 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
61pub mod parse;
63
64use crate::{TitleBarStyle, WindowEffect, WindowEffectState, acl::capability::Capability};
65
66pub use self::parse::parse;
67
68fn default_true() -> bool {
69 true
70}
71
72#[derive(PartialEq, Eq, Debug, Clone, Serialize)]
74#[cfg_attr(feature = "schema", derive(JsonSchema))]
75#[serde(untagged)]
76#[non_exhaustive]
77pub enum WebviewUrl {
78 External(Url),
80 App(PathBuf),
84 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#[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 Deb,
135 Rpm,
137 AppImage,
139 Msi,
141 Nsis,
143 App,
145 Dmg,
147}
148
149impl BundleType {
150 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#[derive(Debug, PartialEq, Eq, Clone, Default)]
212pub enum BundleTarget {
213 #[default]
215 All,
216 List(Vec<BundleType>),
218 One(BundleType),
220}
221
222#[cfg(feature = "schema")]
223impl schemars::JsonSchema for BundleTarget {
224 fn schema_name() -> std::string::String {
225 "BundleTarget".to_owned()
226 }
227
228 fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
229 let any_of = vec![
230 schemars::schema::SchemaObject {
231 const_value: Some("all".into()),
232 metadata: Some(Box::new(schemars::schema::Metadata {
233 description: Some("Bundle all targets.".to_owned()),
234 ..Default::default()
235 })),
236 ..Default::default()
237 }
238 .into(),
239 add_description(
240 generator.subschema_for::<Vec<BundleType>>(),
241 "A list of bundle targets.",
242 ),
243 add_description(
244 generator.subschema_for::<BundleType>(),
245 "A single bundle target.",
246 ),
247 ];
248
249 schemars::schema::SchemaObject {
250 subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
251 any_of: Some(any_of),
252 ..Default::default()
253 })),
254 metadata: Some(Box::new(schemars::schema::Metadata {
255 description: Some("Targets to bundle. Each value is case insensitive.".to_owned()),
256 ..Default::default()
257 })),
258 ..Default::default()
259 }
260 .into()
261 }
262}
263
264impl Serialize for BundleTarget {
265 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
266 where
267 S: Serializer,
268 {
269 match self {
270 Self::All => serializer.serialize_str("all"),
271 Self::List(l) => l.serialize(serializer),
272 Self::One(t) => serializer.serialize_str(t.to_string().as_ref()),
273 }
274 }
275}
276
277impl<'de> Deserialize<'de> for BundleTarget {
278 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
279 where
280 D: Deserializer<'de>,
281 {
282 #[derive(Deserialize, Serialize)]
283 #[serde(untagged)]
284 pub enum BundleTargetInner {
285 List(Vec<BundleType>),
286 One(BundleType),
287 All(String),
288 }
289
290 match BundleTargetInner::deserialize(deserializer)? {
291 BundleTargetInner::All(s) if s.to_lowercase() == "all" => Ok(Self::All),
292 BundleTargetInner::All(t) => Err(DeError::custom(format!(
293 "invalid bundle type {t}, expected one of `all`, {}",
294 BundleType::all()
295 .iter()
296 .map(|b| format!("`{b}`"))
297 .collect::<Vec<_>>()
298 .join(", ")
299 ))),
300 BundleTargetInner::List(l) => Ok(Self::List(l)),
301 BundleTargetInner::One(t) => Ok(Self::One(t)),
302 }
303 }
304}
305
306impl BundleTarget {
307 #[allow(dead_code)]
309 pub fn to_vec(&self) -> Vec<BundleType> {
310 match self {
311 Self::All => BundleType::all().to_vec(),
312 Self::List(list) => list.clone(),
313 Self::One(i) => vec![i.clone()],
314 }
315 }
316}
317
318#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
322#[cfg_attr(feature = "schema", derive(JsonSchema))]
323#[serde(rename_all = "camelCase", deny_unknown_fields)]
324pub struct AppImageConfig {
325 #[serde(default, alias = "bundle-media-framework")]
328 pub bundle_media_framework: bool,
329 #[serde(default)]
331 pub files: HashMap<PathBuf, PathBuf>,
332}
333
334#[skip_serializing_none]
338#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
339#[cfg_attr(feature = "schema", derive(JsonSchema))]
340#[serde(rename_all = "camelCase", deny_unknown_fields)]
341pub struct DebConfig {
342 pub depends: Option<Vec<String>>,
344 pub recommends: Option<Vec<String>>,
346 pub provides: Option<Vec<String>>,
348 pub conflicts: Option<Vec<String>>,
350 pub replaces: Option<Vec<String>>,
352 #[serde(default)]
354 pub files: HashMap<PathBuf, PathBuf>,
355 pub section: Option<String>,
357 pub priority: Option<String>,
360 pub changelog: Option<PathBuf>,
363 #[serde(alias = "desktop-template")]
367 pub desktop_template: Option<PathBuf>,
368 #[serde(alias = "pre-install-script")]
371 pub pre_install_script: Option<PathBuf>,
372 #[serde(alias = "post-install-script")]
375 pub post_install_script: Option<PathBuf>,
376 #[serde(alias = "pre-remove-script")]
379 pub pre_remove_script: Option<PathBuf>,
380 #[serde(alias = "post-remove-script")]
383 pub post_remove_script: Option<PathBuf>,
384}
385
386#[skip_serializing_none]
390#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
391#[cfg_attr(feature = "schema", derive(JsonSchema))]
392#[serde(rename_all = "camelCase", deny_unknown_fields)]
393pub struct LinuxConfig {
394 #[serde(default)]
396 pub appimage: AppImageConfig,
397 #[serde(default)]
399 pub deb: DebConfig,
400 #[serde(default)]
402 pub rpm: RpmConfig,
403}
404
405#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
407#[cfg_attr(feature = "schema", derive(JsonSchema))]
408#[serde(rename_all = "camelCase", deny_unknown_fields, tag = "type")]
409#[non_exhaustive]
410pub enum RpmCompression {
411 Gzip {
413 level: u32,
415 },
416 Zstd {
418 level: i32,
420 },
421 Xz {
423 level: u32,
425 },
426 Bzip2 {
428 level: u32,
430 },
431 None,
433}
434
435#[skip_serializing_none]
437#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
438#[cfg_attr(feature = "schema", derive(JsonSchema))]
439#[serde(rename_all = "camelCase", deny_unknown_fields)]
440pub struct RpmConfig {
441 pub depends: Option<Vec<String>>,
443 pub recommends: Option<Vec<String>>,
445 pub provides: Option<Vec<String>>,
447 pub conflicts: Option<Vec<String>>,
450 pub obsoletes: Option<Vec<String>>,
453 #[serde(default = "default_release")]
455 pub release: String,
456 #[serde(default)]
458 pub epoch: u32,
459 #[serde(default)]
461 pub files: HashMap<PathBuf, PathBuf>,
462 #[serde(alias = "desktop-template")]
466 pub desktop_template: Option<PathBuf>,
467 #[serde(alias = "pre-install-script")]
470 pub pre_install_script: Option<PathBuf>,
471 #[serde(alias = "post-install-script")]
474 pub post_install_script: Option<PathBuf>,
475 #[serde(alias = "pre-remove-script")]
478 pub pre_remove_script: Option<PathBuf>,
479 #[serde(alias = "post-remove-script")]
482 pub post_remove_script: Option<PathBuf>,
483 pub compression: Option<RpmCompression>,
485}
486
487impl Default for RpmConfig {
488 fn default() -> Self {
489 Self {
490 depends: None,
491 recommends: None,
492 provides: None,
493 conflicts: None,
494 obsoletes: None,
495 release: default_release(),
496 epoch: 0,
497 files: Default::default(),
498 desktop_template: None,
499 pre_install_script: None,
500 post_install_script: None,
501 pre_remove_script: None,
502 post_remove_script: None,
503 compression: None,
504 }
505 }
506}
507
508fn default_release() -> String {
509 "1".into()
510}
511
512#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
514#[cfg_attr(feature = "schema", derive(JsonSchema))]
515#[serde(rename_all = "camelCase", deny_unknown_fields)]
516pub struct Position {
517 pub x: u32,
519 pub y: u32,
521}
522
523#[derive(Default, Debug, PartialEq, Clone, Deserialize, Serialize)]
525#[cfg_attr(feature = "schema", derive(JsonSchema))]
526#[serde(rename_all = "camelCase", deny_unknown_fields)]
527pub struct LogicalPosition {
528 pub x: f64,
530 pub y: f64,
532}
533
534#[derive(Default, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
536#[cfg_attr(feature = "schema", derive(JsonSchema))]
537#[serde(rename_all = "camelCase", deny_unknown_fields)]
538pub struct Size {
539 pub width: u32,
541 pub height: u32,
543}
544
545#[skip_serializing_none]
549#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
550#[cfg_attr(feature = "schema", derive(JsonSchema))]
551#[serde(rename_all = "camelCase", deny_unknown_fields)]
552pub struct DmgConfig {
553 pub background: Option<PathBuf>,
555 pub window_position: Option<Position>,
557 #[serde(default = "dmg_window_size", alias = "window-size")]
559 pub window_size: Size,
560 #[serde(default = "dmg_app_position", alias = "app-position")]
562 pub app_position: Position,
563 #[serde(
565 default = "dmg_application_folder_position",
566 alias = "application-folder-position"
567 )]
568 pub application_folder_position: Position,
569}
570
571impl Default for DmgConfig {
572 fn default() -> Self {
573 Self {
574 background: None,
575 window_position: None,
576 window_size: dmg_window_size(),
577 app_position: dmg_app_position(),
578 application_folder_position: dmg_application_folder_position(),
579 }
580 }
581}
582
583fn dmg_window_size() -> Size {
584 Size {
585 width: 660,
586 height: 400,
587 }
588}
589
590fn dmg_app_position() -> Position {
591 Position { x: 180, y: 170 }
592}
593
594fn dmg_application_folder_position() -> Position {
595 Position { x: 480, y: 170 }
596}
597
598fn de_macos_minimum_system_version<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
599where
600 D: Deserializer<'de>,
601{
602 let version = Option::<String>::deserialize(deserializer)?;
603 match version {
604 Some(v) if v.is_empty() => Ok(macos_minimum_system_version()),
605 e => Ok(e),
606 }
607}
608
609#[skip_serializing_none]
613#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
614#[cfg_attr(feature = "schema", derive(JsonSchema))]
615#[serde(rename_all = "camelCase", deny_unknown_fields)]
616pub struct MacConfig {
617 pub frameworks: Option<Vec<String>>,
621 #[serde(default)]
623 pub files: HashMap<PathBuf, PathBuf>,
624 #[serde(alias = "bundle-version")]
628 pub bundle_version: Option<String>,
629 #[serde(alias = "bundle-name")]
635 pub bundle_name: Option<String>,
636 #[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 #[serde(alias = "exception-domain")]
653 pub exception_domain: Option<String>,
654 #[serde(alias = "signing-identity")]
656 pub signing_identity: Option<String>,
657 #[serde(alias = "hardened-runtime", default = "default_true")]
659 pub hardened_runtime: bool,
660 #[serde(alias = "provider-short-name")]
662 pub provider_short_name: Option<String>,
663 pub entitlements: Option<String>,
665 #[serde(alias = "info-plist")]
669 pub info_plist: Option<PathBuf>,
670 #[serde(default)]
672 pub dmg: DmgConfig,
673}
674
675impl Default for MacConfig {
676 fn default() -> Self {
677 Self {
678 frameworks: None,
679 files: HashMap::new(),
680 bundle_version: None,
681 bundle_name: None,
682 minimum_system_version: macos_minimum_system_version(),
683 exception_domain: None,
684 signing_identity: None,
685 hardened_runtime: true,
686 provider_short_name: None,
687 entitlements: None,
688 info_plist: None,
689 dmg: Default::default(),
690 }
691 }
692}
693
694fn macos_minimum_system_version() -> Option<String> {
695 Some("10.13".into())
696}
697
698fn ios_minimum_system_version() -> String {
699 "15.0".into()
700}
701
702#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
706#[cfg_attr(feature = "schema", derive(JsonSchema))]
707#[serde(rename_all = "camelCase", deny_unknown_fields)]
708pub struct WixLanguageConfig {
709 #[serde(alias = "locale-path")]
711 pub locale_path: Option<String>,
712}
713
714#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
716#[cfg_attr(feature = "schema", derive(JsonSchema))]
717#[serde(untagged)]
718pub enum WixLanguage {
719 One(String),
721 List(Vec<String>),
723 Localized(HashMap<String, WixLanguageConfig>),
725}
726
727impl Default for WixLanguage {
728 fn default() -> Self {
729 Self::One("en-US".into())
730 }
731}
732
733#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
737#[cfg_attr(feature = "schema", derive(JsonSchema))]
738#[serde(rename_all = "camelCase", deny_unknown_fields)]
739pub struct WixConfig {
740 pub version: Option<String>,
749 #[serde(alias = "upgrade-code")]
758 pub upgrade_code: Option<uuid::Uuid>,
759 #[serde(default)]
761 pub language: WixLanguage,
762 pub template: Option<PathBuf>,
764 #[serde(default, alias = "fragment-paths")]
766 pub fragment_paths: Vec<PathBuf>,
767 #[serde(default, alias = "component-group-refs")]
769 pub component_group_refs: Vec<String>,
770 #[serde(default, alias = "component-refs")]
772 pub component_refs: Vec<String>,
773 #[serde(default, alias = "feature-group-refs")]
775 pub feature_group_refs: Vec<String>,
776 #[serde(default, alias = "feature-refs")]
778 pub feature_refs: Vec<String>,
779 #[serde(default, alias = "merge-refs")]
781 pub merge_refs: Vec<String>,
782 #[serde(default, alias = "enable-elevated-update-task")]
784 pub enable_elevated_update_task: bool,
785 #[serde(alias = "banner-path")]
790 pub banner_path: Option<PathBuf>,
791 #[serde(alias = "dialog-image-path")]
796 pub dialog_image_path: Option<PathBuf>,
797 #[serde(default, alias = "fips-compliant")]
800 pub fips_compliant: bool,
801}
802
803#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Default)]
807#[cfg_attr(feature = "schema", derive(JsonSchema))]
808#[serde(rename_all = "camelCase", deny_unknown_fields)]
809pub enum NsisCompression {
810 Zlib,
812 Bzip2,
814 #[default]
816 Lzma,
817 None,
819}
820
821#[derive(Default, Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
823#[serde(rename_all = "camelCase", deny_unknown_fields)]
824#[cfg_attr(feature = "schema", derive(JsonSchema))]
825pub enum NSISInstallerMode {
826 #[default]
832 CurrentUser,
833 PerMachine,
838 Both,
844}
845
846#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
848#[cfg_attr(feature = "schema", derive(JsonSchema))]
849#[serde(rename_all = "camelCase", deny_unknown_fields)]
850pub struct NsisConfig {
851 pub template: Option<PathBuf>,
853 #[serde(alias = "header-image")]
857 pub header_image: Option<PathBuf>,
858 #[serde(alias = "sidebar-image")]
862 pub sidebar_image: Option<PathBuf>,
863 #[serde(alias = "install-icon")]
866 pub installer_icon: Option<PathBuf>,
867 #[serde(alias = "uninstaller-icon")]
869 pub uninstaller_icon: Option<PathBuf>,
870 #[serde(alias = "uninstaller-header-image")]
875 pub uninstaller_header_image: Option<PathBuf>,
876 #[serde(default, alias = "install-mode")]
878 pub install_mode: NSISInstallerMode,
879 pub languages: Option<Vec<String>>,
886 pub custom_language_files: Option<HashMap<String, PathBuf>>,
893 #[serde(default, alias = "display-language-selector")]
896 pub display_language_selector: bool,
897 #[serde(default)]
901 pub compression: NsisCompression,
902 #[serde(alias = "start-menu-folder")]
911 pub start_menu_folder: Option<String>,
912 #[serde(alias = "installer-hooks")]
942 pub installer_hooks: Option<PathBuf>,
943 #[deprecated(
949 since = "2.10.0",
950 note = "Use `WindowsConfig::minimum_webview2_version` instead."
951 )]
952 #[serde(alias = "minimum-webview2-version")]
953 pub minimum_webview2_version: Option<String>,
954}
955
956#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
961#[serde(tag = "type", rename_all = "camelCase", deny_unknown_fields)]
962#[cfg_attr(feature = "schema", derive(JsonSchema))]
963pub enum WebviewInstallMode {
964 Skip,
966 DownloadBootstrapper {
970 #[serde(default = "default_true")]
972 silent: bool,
973 },
974 EmbedBootstrapper {
978 #[serde(default = "default_true")]
980 silent: bool,
981 },
982 OfflineInstaller {
986 #[serde(default = "default_true")]
988 silent: bool,
989 },
990 FixedRuntime {
993 path: PathBuf,
998 },
999}
1000
1001impl Default for WebviewInstallMode {
1002 fn default() -> Self {
1003 Self::DownloadBootstrapper { silent: true }
1004 }
1005}
1006
1007#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1009#[cfg_attr(feature = "schema", derive(JsonSchema))]
1010#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1011pub enum CustomSignCommandConfig {
1012 Command(String),
1021 CommandWithOptions {
1026 cmd: String,
1028 args: Vec<String>,
1032 },
1033}
1034
1035#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1039#[cfg_attr(feature = "schema", derive(JsonSchema))]
1040#[serde(rename_all = "camelCase", deny_unknown_fields)]
1041pub struct WindowsConfig {
1042 #[serde(alias = "digest-algorithm")]
1045 pub digest_algorithm: Option<String>,
1046 #[serde(alias = "certificate-thumbprint")]
1048 pub certificate_thumbprint: Option<String>,
1049 #[serde(alias = "timestamp-url")]
1051 pub timestamp_url: Option<String>,
1052 #[serde(default)]
1055 pub tsp: bool,
1056 #[serde(default, alias = "webview-install-mode")]
1058 pub webview_install_mode: WebviewInstallMode,
1059 #[serde(default = "default_true", alias = "allow-downgrades")]
1069 pub allow_downgrades: bool,
1070 #[serde(alias = "minimum-webview2-version")]
1074 pub minimum_webview2_version: Option<String>,
1075 pub wix: Option<WixConfig>,
1077 pub nsis: Option<NsisConfig>,
1079 #[serde(alias = "sign-command")]
1087 pub sign_command: Option<CustomSignCommandConfig>,
1088 #[serde(
1095 default,
1096 rename = "bundleVCRuntime",
1097 alias = "bundle-vc-runtime",
1098 alias = "bundleVcRuntime"
1099 )]
1100 pub bundle_vc_runtime: bool,
1101}
1102
1103impl Default for WindowsConfig {
1104 fn default() -> Self {
1105 Self {
1106 digest_algorithm: None,
1107 certificate_thumbprint: None,
1108 timestamp_url: None,
1109 tsp: false,
1110 webview_install_mode: Default::default(),
1111 allow_downgrades: true,
1112 minimum_webview2_version: None,
1113 wix: None,
1114 nsis: None,
1115 sign_command: None,
1116 bundle_vc_runtime: false,
1117 }
1118 }
1119}
1120
1121#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1123#[cfg_attr(feature = "schema", derive(JsonSchema))]
1124pub enum BundleTypeRole {
1125 #[default]
1127 Editor,
1128 Viewer,
1130 Shell,
1132 QLGenerator,
1134 None,
1136}
1137
1138impl Display for BundleTypeRole {
1139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1140 match self {
1141 Self::Editor => write!(f, "Editor"),
1142 Self::Viewer => write!(f, "Viewer"),
1143 Self::Shell => write!(f, "Shell"),
1144 Self::QLGenerator => write!(f, "QLGenerator"),
1145 Self::None => write!(f, "None"),
1146 }
1147 }
1148}
1149
1150#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1154#[cfg_attr(feature = "schema", derive(JsonSchema))]
1155pub enum HandlerRank {
1156 #[default]
1158 Default,
1159 Owner,
1161 Alternate,
1163 None,
1165}
1166
1167impl Display for HandlerRank {
1168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1169 match self {
1170 Self::Default => write!(f, "Default"),
1171 Self::Owner => write!(f, "Owner"),
1172 Self::Alternate => write!(f, "Alternate"),
1173 Self::None => write!(f, "None"),
1174 }
1175 }
1176}
1177
1178#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
1182#[cfg_attr(feature = "schema", derive(JsonSchema))]
1183pub struct AssociationExt(pub String);
1184
1185impl fmt::Display for AssociationExt {
1186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1187 write!(f, "{}", self.0)
1188 }
1189}
1190
1191impl<'d> serde::Deserialize<'d> for AssociationExt {
1192 fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
1193 let ext = String::deserialize(deserializer)?;
1194 if let Some(ext) = ext.strip_prefix('.') {
1195 Ok(AssociationExt(ext.into()))
1196 } else {
1197 Ok(AssociationExt(ext))
1198 }
1199 }
1200}
1201
1202#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1204#[cfg_attr(feature = "schema", derive(JsonSchema))]
1205#[serde(rename_all = "camelCase", deny_unknown_fields)]
1206pub struct FileAssociation {
1207 pub ext: Vec<AssociationExt>,
1209 #[serde(alias = "content-types")]
1214 pub content_types: Option<Vec<String>>,
1215 pub name: Option<String>,
1217 pub description: Option<String>,
1219 #[serde(default)]
1221 pub role: BundleTypeRole,
1222 #[serde(alias = "mime-type")]
1230 pub mime_type: Option<String>,
1231 #[serde(default)]
1233 pub rank: HandlerRank,
1234 pub exported_type: Option<ExportedFileAssociation>,
1238 #[serde(alias = "android-intent-action-filters")]
1242 pub android_intent_action_filters: Option<Vec<AndroidIntentAction>>,
1243}
1244
1245#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Hash)]
1247#[cfg_attr(feature = "schema", derive(JsonSchema))]
1248#[serde(rename_all = "camelCase")]
1249#[non_exhaustive]
1250pub enum AndroidIntentAction {
1251 Send,
1255 SendMultiple,
1259 View,
1263}
1264
1265#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1267#[cfg_attr(feature = "schema", derive(JsonSchema))]
1268#[serde(rename_all = "camelCase", deny_unknown_fields)]
1269pub struct ExportedFileAssociation {
1270 pub identifier: String,
1272 #[serde(alias = "conforms-to")]
1276 pub conforms_to: Option<Vec<String>>,
1277}
1278
1279impl FileAssociation {
1280 pub fn infer_content_types(&self) -> HashSet<String> {
1287 let mut content_types = HashSet::new();
1288
1289 if let Some(exported_type) = &self.exported_type {
1291 content_types.insert(exported_type.identifier.clone());
1292 return content_types;
1293 }
1294
1295 if let Some(explicit_types) = &self.content_types {
1297 content_types.extend(explicit_types.iter().cloned());
1298 }
1299
1300 for ext in &self.ext {
1302 if let Some(uti) = extension_to_uti(&ext.0) {
1303 content_types.insert(uti.to_string());
1304 }
1305 }
1306
1307 if let Some(mime_type) = &self.mime_type {
1309 if let Some(uti) = mime_type_to_uti(mime_type) {
1310 content_types.insert(uti.to_string());
1311 }
1312 }
1313
1314 content_types
1315 }
1316}
1317
1318pub fn file_associations_plist(associations: &[FileAssociation]) -> Option<plist::Value> {
1324 use plist::{Dictionary, Value};
1325
1326 if associations.is_empty() {
1327 return None;
1328 }
1329
1330 let exported_associations = associations
1331 .iter()
1332 .filter_map(|association| {
1333 association.exported_type.as_ref().map(|exported_type| {
1334 let mut dict = Dictionary::new();
1335
1336 dict.insert(
1337 "UTTypeIdentifier".into(),
1338 exported_type.identifier.clone().into(),
1339 );
1340 if let Some(description) = &association.description {
1341 dict.insert("UTTypeDescription".into(), description.clone().into());
1342 }
1343 if let Some(conforms_to) = &exported_type.conforms_to {
1344 dict.insert(
1345 "UTTypeConformsTo".into(),
1346 Value::Array(conforms_to.iter().map(|s| s.clone().into()).collect()),
1347 );
1348 }
1349
1350 let mut specification = Dictionary::new();
1351 specification.insert(
1352 "public.filename-extension".into(),
1353 Value::Array(
1354 association
1355 .ext
1356 .iter()
1357 .map(|s| s.to_string().into())
1358 .collect(),
1359 ),
1360 );
1361 if let Some(mime_type) = &association.mime_type {
1362 specification.insert("public.mime-type".into(), mime_type.clone().into());
1363 }
1364
1365 dict.insert("UTTypeTagSpecification".into(), specification.into());
1366
1367 Value::Dictionary(dict)
1368 })
1369 })
1370 .collect::<Vec<_>>();
1371
1372 let document_types = associations
1373 .iter()
1374 .map(|association| {
1375 let mut dict = Dictionary::new();
1376
1377 if !association.ext.is_empty() {
1378 dict.insert(
1379 "CFBundleTypeExtensions".into(),
1380 Value::Array(
1381 association
1382 .ext
1383 .iter()
1384 .map(|ext| ext.to_string().into())
1385 .collect(),
1386 ),
1387 );
1388 }
1389
1390 let content_types = association.infer_content_types();
1392
1393 if !content_types.is_empty() {
1395 dict.insert(
1396 "LSItemContentTypes".into(),
1397 Value::Array(content_types.iter().map(|s| s.clone().into()).collect()),
1398 );
1399 }
1400
1401 let type_name = association
1402 .name
1403 .clone()
1404 .or_else(|| association.ext.first().map(|ext| ext.0.clone()))
1405 .unwrap_or_default();
1406 dict.insert("CFBundleTypeName".into(), type_name.into());
1407 dict.insert(
1408 "CFBundleTypeRole".into(),
1409 association.role.to_string().into(),
1410 );
1411 dict.insert("LSHandlerRank".into(), association.rank.to_string().into());
1412
1413 Value::Dictionary(dict)
1414 })
1415 .collect::<Vec<_>>();
1416
1417 if exported_associations.is_empty() && document_types.is_empty() {
1418 return None;
1419 }
1420
1421 let mut plist = Dictionary::new();
1422 if !exported_associations.is_empty() {
1423 plist.insert(
1424 "UTExportedTypeDeclarations".into(),
1425 Value::Array(exported_associations),
1426 );
1427 }
1428 if !document_types.is_empty() {
1429 plist.insert("CFBundleDocumentTypes".into(), Value::Array(document_types));
1430 }
1431
1432 Some(Value::Dictionary(plist))
1433}
1434
1435fn extension_to_uti(ext: &str) -> Option<&'static str> {
1437 match ext.to_lowercase().as_str() {
1438 "png" => Some("public.png"),
1440 "jpg" | "jpeg" => Some("public.jpeg"),
1441 "gif" => Some("com.compuserve.gif"),
1442 "bmp" => Some("com.microsoft.bmp"),
1443 "tiff" | "tif" => Some("public.tiff"),
1444 "ico" => Some("com.microsoft.ico"),
1445 "heic" | "heif" => Some("public.heif-standard-image"),
1446 "webp" => Some("org.webmproject.webp"),
1447 "svg" => Some("public.svg-image"),
1448 "mp4" => Some("public.mpeg-4"),
1450 "mov" => Some("com.apple.quicktime-movie"),
1451 "avi" => Some("public.avi"),
1452 "mkv" => Some("public.mpeg-4"),
1453 "mp3" => Some("public.mp3"),
1455 "wav" => Some("com.microsoft.waveform-audio"),
1456 "aac" => Some("public.aac-audio"),
1457 "m4a" => Some("public.mpeg-4-audio"),
1458 "pdf" => Some("com.adobe.pdf"),
1460 "txt" => Some("public.plain-text"),
1461 "rtf" => Some("public.rtf"),
1462 "html" | "htm" => Some("public.html"),
1463 "json" => Some("public.json"),
1464 "xml" => Some("public.xml"),
1465 _ => None,
1466 }
1467}
1468
1469fn mime_type_to_uti(mime_type: &str) -> Option<&'static str> {
1471 match mime_type {
1472 "image/png" => Some("public.png"),
1473 "image/jpeg" | "image/jpg" => Some("public.jpeg"),
1474 "image/gif" => Some("com.compuserve.gif"),
1475 "image/bmp" => Some("com.microsoft.bmp"),
1476 "image/tiff" => Some("public.tiff"),
1477 "image/heic" | "image/heif" => Some("public.heif-standard-image"),
1478 "image/webp" => Some("org.webmproject.webp"),
1479 "image/svg+xml" => Some("public.svg-image"),
1480 mime if mime.starts_with("image/") => Some("public.image"),
1481 "video/mp4" => Some("public.mpeg-4"),
1482 "video/quicktime" => Some("com.apple.quicktime-movie"),
1483 "video/x-msvideo" => Some("public.avi"),
1484 mime if mime.starts_with("video/") => Some("public.movie"),
1485 "audio/mpeg" | "audio/mp3" => Some("public.mp3"),
1486 "audio/wav" | "audio/wave" => Some("com.microsoft.waveform-audio"),
1487 "audio/aac" => Some("public.aac-audio"),
1488 "audio/mp4" => Some("public.mpeg-4-audio"),
1489 mime if mime.starts_with("audio/") => Some("public.audio"),
1490 "application/pdf" => Some("com.adobe.pdf"),
1491 "text/plain" => Some("public.plain-text"),
1492 "text/rtf" => Some("public.rtf"),
1493 "text/html" => Some("public.html"),
1494 "application/json" => Some("public.json"),
1495 "application/xml" | "text/xml" => Some("public.xml"),
1496 _ => None,
1497 }
1498}
1499
1500#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1502#[cfg_attr(feature = "schema", derive(JsonSchema))]
1503#[serde(rename_all = "camelCase", deny_unknown_fields)]
1504pub struct DeepLinkProtocol {
1505 #[serde(default)]
1507 pub schemes: Vec<String>,
1508 #[serde(default)]
1516 pub domains: Vec<String>,
1517 pub name: Option<String>,
1519 #[serde(default)]
1521 pub role: BundleTypeRole,
1522}
1523
1524#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1527#[cfg_attr(feature = "schema", derive(JsonSchema))]
1528#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1529pub enum BundleResources {
1530 List(Vec<String>),
1532 Map(HashMap<String, String>),
1534}
1535
1536impl BundleResources {
1537 pub fn push(&mut self, path: impl Into<String>) {
1539 match self {
1540 Self::List(l) => l.push(path.into()),
1541 Self::Map(l) => {
1542 let path = path.into();
1543 l.insert(path.clone(), path);
1544 }
1545 }
1546 }
1547}
1548
1549#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1551#[cfg_attr(feature = "schema", derive(JsonSchema))]
1552#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1553pub enum Updater {
1554 String(V1Compatible),
1556 Bool(bool),
1559}
1560
1561impl Default for Updater {
1562 fn default() -> Self {
1563 Self::Bool(false)
1564 }
1565}
1566
1567#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1569#[cfg_attr(feature = "schema", derive(JsonSchema))]
1570#[serde(rename_all = "camelCase", deny_unknown_fields)]
1571pub enum V1Compatible {
1572 V1Compatible,
1574}
1575
1576#[skip_serializing_none]
1580#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1581#[cfg_attr(feature = "schema", derive(JsonSchema))]
1582#[serde(rename_all = "camelCase", deny_unknown_fields)]
1583pub struct BundleConfig {
1584 #[serde(default)]
1586 pub active: bool,
1587 #[serde(default)]
1589 pub targets: BundleTarget,
1590 #[serde(default)]
1591 pub create_updater_artifacts: Updater,
1593 pub publisher: Option<String>,
1598 pub homepage: Option<String>,
1603 #[serde(default)]
1605 pub icon: Vec<String>,
1606 pub resources: Option<BundleResources>,
1651 pub copyright: Option<String>,
1653 pub license: Option<String>,
1656 #[serde(alias = "license-file")]
1658 pub license_file: Option<PathBuf>,
1659 pub category: Option<String>,
1664 pub file_associations: Option<Vec<FileAssociation>>,
1666 #[serde(alias = "short-description")]
1668 pub short_description: Option<String>,
1669 #[serde(alias = "long-description")]
1671 pub long_description: Option<String>,
1672 #[serde(default, alias = "use-local-tools-dir")]
1680 pub use_local_tools_dir: bool,
1681 #[serde(alias = "external-bin")]
1693 pub external_bin: Option<Vec<String>>,
1694 #[serde(default)]
1696 pub windows: WindowsConfig,
1697 #[serde(default)]
1699 pub linux: LinuxConfig,
1700 #[serde(rename = "macOS", alias = "macos", default)]
1702 pub macos: MacConfig,
1703 #[serde(rename = "iOS", alias = "ios", default)]
1705 pub ios: IosConfig,
1706 #[serde(default)]
1708 pub android: AndroidConfig,
1709}
1710
1711#[derive(Debug, PartialEq, Eq, Serialize, Default, Clone, Copy)]
1713#[serde(rename_all = "camelCase", deny_unknown_fields)]
1714pub struct Color(pub u8, pub u8, pub u8, pub u8);
1715
1716impl From<Color> for (u8, u8, u8, u8) {
1717 fn from(value: Color) -> Self {
1718 (value.0, value.1, value.2, value.3)
1719 }
1720}
1721
1722impl From<Color> for (u8, u8, u8) {
1723 fn from(value: Color) -> Self {
1724 (value.0, value.1, value.2)
1725 }
1726}
1727
1728impl From<(u8, u8, u8, u8)> for Color {
1729 fn from(value: (u8, u8, u8, u8)) -> Self {
1730 Color(value.0, value.1, value.2, value.3)
1731 }
1732}
1733
1734impl From<(u8, u8, u8)> for Color {
1735 fn from(value: (u8, u8, u8)) -> Self {
1736 Color(value.0, value.1, value.2, 255)
1737 }
1738}
1739
1740impl From<Color> for [u8; 4] {
1741 fn from(value: Color) -> Self {
1742 [value.0, value.1, value.2, value.3]
1743 }
1744}
1745
1746impl From<Color> for [u8; 3] {
1747 fn from(value: Color) -> Self {
1748 [value.0, value.1, value.2]
1749 }
1750}
1751
1752impl From<[u8; 4]> for Color {
1753 fn from(value: [u8; 4]) -> Self {
1754 Color(value[0], value[1], value[2], value[3])
1755 }
1756}
1757
1758impl From<[u8; 3]> for Color {
1759 fn from(value: [u8; 3]) -> Self {
1760 Color(value[0], value[1], value[2], 255)
1761 }
1762}
1763
1764impl FromStr for Color {
1765 type Err = String;
1766 fn from_str(mut color: &str) -> Result<Self, Self::Err> {
1767 color = color.trim().strip_prefix('#').unwrap_or(color);
1768 let color = match color.len() {
1769 3 => color.chars()
1770 .flat_map(|c| std::iter::repeat_n(c, 2))
1771 .chain(std::iter::repeat_n('f', 2))
1772 .collect(),
1773 6 => format!("{color}FF"),
1774 8 => color.to_string(),
1775 _ => return Err("Invalid hex color length, must be either 3, 6 or 8, for example: #fff, #ffffff, or #ffffffff".into()),
1776 };
1777
1778 let r = u8::from_str_radix(&color[0..2], 16).map_err(|e| e.to_string())?;
1779 let g = u8::from_str_radix(&color[2..4], 16).map_err(|e| e.to_string())?;
1780 let b = u8::from_str_radix(&color[4..6], 16).map_err(|e| e.to_string())?;
1781 let a = u8::from_str_radix(&color[6..8], 16).map_err(|e| e.to_string())?;
1782
1783 Ok(Color(r, g, b, a))
1784 }
1785}
1786
1787fn default_alpha() -> u8 {
1788 255
1789}
1790
1791#[derive(Deserialize)]
1792#[cfg_attr(feature = "schema", derive(JsonSchema))]
1793#[serde(untagged)]
1794enum InnerColor {
1795 String(String),
1797 Rgb((u8, u8, u8)),
1799 Rgba((u8, u8, u8, u8)),
1801 RgbaObject {
1803 red: u8,
1804 green: u8,
1805 blue: u8,
1806 #[serde(default = "default_alpha")]
1807 alpha: u8,
1808 },
1809}
1810
1811impl<'de> Deserialize<'de> for Color {
1812 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1813 where
1814 D: Deserializer<'de>,
1815 {
1816 let color = InnerColor::deserialize(deserializer)?;
1817 let color = match color {
1818 InnerColor::String(string) => string.parse().map_err(serde::de::Error::custom)?,
1819 InnerColor::Rgb(rgb) => Color(rgb.0, rgb.1, rgb.2, 255),
1820 InnerColor::Rgba(rgb) => rgb.into(),
1821 InnerColor::RgbaObject {
1822 red,
1823 green,
1824 blue,
1825 alpha,
1826 } => Color(red, green, blue, alpha),
1827 };
1828
1829 Ok(color)
1830 }
1831}
1832
1833#[cfg(feature = "schema")]
1834impl schemars::JsonSchema for Color {
1835 fn schema_name() -> String {
1836 "Color".to_string()
1837 }
1838
1839 fn json_schema(_generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
1840 let mut schema = schemars::schema_for!(InnerColor).schema;
1841 schema.metadata = None; let any_of = schema.subschemas().any_of.as_mut().unwrap();
1845 let schemars::schema::Schema::Object(str_schema) = any_of.first_mut().unwrap() else {
1846 unreachable!()
1847 };
1848 str_schema.string().pattern = Some("^#?([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$".into());
1849
1850 schema.into()
1851 }
1852}
1853
1854#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1856#[cfg_attr(feature = "schema", derive(JsonSchema))]
1857#[serde(rename_all = "camelCase", deny_unknown_fields)]
1858pub enum BackgroundThrottlingPolicy {
1859 Disabled,
1861 Suspend,
1863 Throttle,
1865}
1866
1867#[skip_serializing_none]
1869#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
1870#[cfg_attr(feature = "schema", derive(JsonSchema))]
1871#[serde(rename_all = "camelCase", deny_unknown_fields)]
1872pub struct WindowEffectsConfig {
1873 pub effects: Vec<WindowEffect>,
1879 pub state: Option<WindowEffectState>,
1881 pub radius: Option<f64>,
1883 pub color: Option<Color>,
1891 #[serde(default)]
1895 pub interactive: bool,
1896}
1897
1898#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
1901#[cfg_attr(feature = "schema", derive(JsonSchema))]
1902#[serde(rename_all = "camelCase", deny_unknown_fields)]
1903pub struct PreventOverflowMargin {
1904 pub width: u32,
1906 pub height: u32,
1908}
1909
1910#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1912#[cfg_attr(feature = "schema", derive(JsonSchema))]
1913#[serde(untagged)]
1914pub enum PreventOverflowConfig {
1915 Enable(bool),
1917 Margin(PreventOverflowMargin),
1920}
1921
1922#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
1928#[cfg_attr(feature = "schema", derive(JsonSchema))]
1929#[serde(rename_all = "camelCase", deny_unknown_fields)]
1930#[non_exhaustive]
1931pub enum ScrollBarStyle {
1932 #[default]
1933 Default,
1937
1938 FluentOverlay,
1943}
1944
1945#[skip_serializing_none]
1949#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
1950#[cfg_attr(feature = "schema", derive(JsonSchema))]
1951#[serde(rename_all = "camelCase", deny_unknown_fields)]
1952pub struct WindowConfig {
1953 #[serde(default = "default_window_label")]
1955 pub label: String,
1956 #[serde(default = "default_true")]
1971 pub create: bool,
1972 #[serde(default)]
1974 pub url: WebviewUrl,
1975 #[serde(alias = "user-agent")]
1977 pub user_agent: Option<String>,
1978 #[serde(default = "default_true", alias = "drag-drop-enabled")]
1988 pub drag_drop_enabled: bool,
1989 #[serde(default)]
1991 pub center: bool,
1992 pub x: Option<f64>,
1994 pub y: Option<f64>,
1996 #[serde(default = "default_width")]
1998 pub width: f64,
1999 #[serde(default = "default_height")]
2001 pub height: f64,
2002 #[serde(alias = "min-width")]
2004 pub min_width: Option<f64>,
2005 #[serde(alias = "min-height")]
2007 pub min_height: Option<f64>,
2008 #[serde(alias = "max-width")]
2010 pub max_width: Option<f64>,
2011 #[serde(alias = "max-height")]
2013 pub max_height: Option<f64>,
2014 #[serde(alias = "prevent-overflow")]
2020 pub prevent_overflow: Option<PreventOverflowConfig>,
2021 #[serde(default = "default_true")]
2023 pub resizable: bool,
2024 #[serde(default = "default_true")]
2032 pub maximizable: bool,
2033 #[serde(default = "default_true")]
2039 pub minimizable: bool,
2040 #[serde(default = "default_true")]
2048 pub closable: bool,
2049 #[serde(default = "default_title")]
2051 pub title: String,
2052 #[serde(default)]
2054 pub fullscreen: bool,
2055 #[serde(default = "default_true")]
2057 pub focus: bool,
2058 #[serde(default = "default_true")]
2060 pub focusable: bool,
2061 #[serde(default)]
2067 pub transparent: bool,
2068 #[serde(default)]
2070 pub maximized: bool,
2071 #[serde(default = "default_true")]
2073 pub visible: bool,
2074 #[serde(default = "default_true")]
2076 pub decorations: bool,
2077 #[serde(default, alias = "always-on-bottom")]
2079 pub always_on_bottom: bool,
2080 #[serde(default, alias = "always-on-top")]
2082 pub always_on_top: bool,
2083 #[serde(default, alias = "visible-on-all-workspaces")]
2089 pub visible_on_all_workspaces: bool,
2090 #[serde(default, alias = "content-protected")]
2092 pub content_protected: bool,
2093 #[serde(default, alias = "skip-taskbar")]
2095 pub skip_taskbar: bool,
2096 pub window_classname: Option<String>,
2098 #[serde(default, alias = "no-redirection-bitmap")]
2103 pub no_redirection_bitmap: bool,
2104 pub theme: Option<crate::Theme>,
2106 #[serde(default, alias = "title-bar-style")]
2108 pub title_bar_style: TitleBarStyle,
2109 #[serde(default, alias = "traffic-light-position")]
2113 pub traffic_light_position: Option<LogicalPosition>,
2114 #[serde(default, alias = "hidden-title")]
2116 pub hidden_title: bool,
2117 #[serde(default, alias = "accept-first-mouse")]
2119 pub accept_first_mouse: bool,
2120 #[serde(default, alias = "tabbing-identifier")]
2127 pub tabbing_identifier: Option<String>,
2128 #[serde(default, alias = "additional-browser-args")]
2137 pub additional_browser_args: Option<String>,
2138 #[serde(default = "default_true")]
2148 pub shadow: bool,
2149 #[serde(default, alias = "window-effects")]
2158 pub window_effects: Option<WindowEffectsConfig>,
2159 #[serde(default)]
2165 pub incognito: bool,
2166 pub parent: Option<String>,
2178 #[serde(alias = "proxy-url")]
2186 pub proxy_url: Option<Url>,
2187 #[serde(default, alias = "zoom-hotkeys-enabled")]
2197 pub zoom_hotkeys_enabled: bool,
2198 #[serde(default, alias = "browser-extensions-enabled")]
2205 pub browser_extensions_enabled: bool,
2206
2207 #[serde(default, alias = "use-https-scheme")]
2217 pub use_https_scheme: bool,
2218 pub devtools: Option<bool>,
2228
2229 #[serde(alias = "background-color")]
2237 pub background_color: Option<Color>,
2238
2239 #[serde(default, alias = "background-throttling")]
2254 pub background_throttling: Option<BackgroundThrottlingPolicy>,
2255 #[serde(default, alias = "javascript-disabled")]
2257 pub javascript_disabled: bool,
2258 #[serde(default = "default_true", alias = "allow-link-preview")]
2261 pub allow_link_preview: bool,
2262 #[serde(
2267 default,
2268 alias = "disable-input-accessory-view",
2269 alias = "disable_input_accessory_view"
2270 )]
2271 pub disable_input_accessory_view: bool,
2272 #[serde(default, alias = "data-directory")]
2287 pub data_directory: Option<PathBuf>,
2288 #[serde(default, alias = "data-store-identifier")]
2300 pub data_store_identifier: Option<[u8; 16]>,
2301
2302 #[serde(default, alias = "scroll-bar-style")]
2315 pub scroll_bar_style: ScrollBarStyle,
2316
2317 #[serde(default, alias = "limit-navigations-to-app-bound-domains")]
2364 pub limit_navigations_to_app_bound_domains: bool,
2365 #[serde(default, alias = "activity-name")]
2367 pub activity_name: Option<String>,
2368 #[serde(default, alias = "created-by-activity-name")]
2372 pub created_by_activity_name: Option<String>,
2373
2374 #[serde(default, alias = "requested-by-scene-identifier")]
2379 pub requested_by_scene_identifier: Option<String>,
2380 #[serde(default = "default_true", alias = "general-autofill-enabled")]
2398 pub general_autofill_enabled: bool,
2399}
2400
2401impl Default for WindowConfig {
2402 fn default() -> Self {
2403 Self {
2404 label: default_window_label(),
2405 url: WebviewUrl::default(),
2406 create: true,
2407 user_agent: None,
2408 drag_drop_enabled: true,
2409 center: false,
2410 x: None,
2411 y: None,
2412 width: default_width(),
2413 height: default_height(),
2414 min_width: None,
2415 min_height: None,
2416 max_width: None,
2417 max_height: None,
2418 prevent_overflow: None,
2419 resizable: true,
2420 maximizable: true,
2421 minimizable: true,
2422 closable: true,
2423 title: default_title(),
2424 fullscreen: false,
2425 focus: true,
2426 focusable: true,
2427 transparent: false,
2428 maximized: false,
2429 visible: true,
2430 decorations: true,
2431 always_on_bottom: false,
2432 always_on_top: false,
2433 visible_on_all_workspaces: false,
2434 content_protected: false,
2435 skip_taskbar: false,
2436 window_classname: None,
2437 no_redirection_bitmap: false,
2438 theme: None,
2439 title_bar_style: Default::default(),
2440 traffic_light_position: None,
2441 hidden_title: false,
2442 accept_first_mouse: false,
2443 tabbing_identifier: None,
2444 additional_browser_args: None,
2445 shadow: true,
2446 window_effects: None,
2447 incognito: false,
2448 parent: None,
2449 proxy_url: None,
2450 zoom_hotkeys_enabled: false,
2451 browser_extensions_enabled: false,
2452 use_https_scheme: false,
2453 devtools: None,
2454 background_color: None,
2455 background_throttling: None,
2456 javascript_disabled: false,
2457 allow_link_preview: true,
2458 disable_input_accessory_view: false,
2459 data_directory: None,
2460 data_store_identifier: None,
2461 scroll_bar_style: ScrollBarStyle::Default,
2462 limit_navigations_to_app_bound_domains: false,
2463 activity_name: None,
2464 created_by_activity_name: None,
2465 requested_by_scene_identifier: None,
2466 general_autofill_enabled: true,
2467 }
2468 }
2469}
2470
2471fn default_window_label() -> String {
2472 "main".to_string()
2473}
2474
2475fn default_width() -> f64 {
2476 800.
2477}
2478
2479fn default_height() -> f64 {
2480 600.
2481}
2482
2483fn default_title() -> String {
2484 "Tauri App".to_string()
2485}
2486
2487#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2490#[cfg_attr(feature = "schema", derive(JsonSchema))]
2491#[serde(rename_all = "camelCase", untagged)]
2492pub enum CspDirectiveSources {
2493 Inline(String),
2495 List(Vec<String>),
2497}
2498
2499impl Default for CspDirectiveSources {
2500 fn default() -> Self {
2501 Self::List(Vec::new())
2502 }
2503}
2504
2505impl From<CspDirectiveSources> for Vec<String> {
2506 fn from(sources: CspDirectiveSources) -> Self {
2507 match sources {
2508 CspDirectiveSources::Inline(source) => source.split(' ').map(|s| s.to_string()).collect(),
2509 CspDirectiveSources::List(l) => l,
2510 }
2511 }
2512}
2513
2514impl CspDirectiveSources {
2515 pub fn contains(&self, source: &str) -> bool {
2517 match self {
2518 Self::Inline(s) => s.contains(&format!("{source} ")) || s.contains(&format!(" {source}")),
2519 Self::List(l) => l.contains(&source.into()),
2520 }
2521 }
2522
2523 pub fn push<S: AsRef<str>>(&mut self, source: S) {
2525 match self {
2526 Self::Inline(s) => {
2527 s.push(' ');
2528 s.push_str(source.as_ref());
2529 }
2530 Self::List(l) => {
2531 l.push(source.as_ref().to_string());
2532 }
2533 }
2534 }
2535
2536 pub fn extend(&mut self, sources: Vec<String>) {
2538 for s in sources {
2539 self.push(s);
2540 }
2541 }
2542}
2543
2544#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
2547#[cfg_attr(feature = "schema", derive(JsonSchema))]
2548#[serde(rename_all = "camelCase", untagged)]
2549pub enum Csp {
2550 Policy(String),
2552 DirectiveMap(HashMap<String, CspDirectiveSources>),
2554}
2555
2556impl Serialize for Csp {
2557 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
2558 where
2559 S: Serializer,
2560 {
2561 match self {
2562 Self::Policy(policy) => serializer.serialize_str(policy),
2563 Self::DirectiveMap(map) => {
2564 let btree_map: BTreeMap<_, _> = map.iter().collect();
2568 btree_map.serialize(serializer)
2569 }
2570 }
2571 }
2572}
2573
2574impl From<HashMap<String, CspDirectiveSources>> for Csp {
2575 fn from(map: HashMap<String, CspDirectiveSources>) -> Self {
2576 Self::DirectiveMap(map)
2577 }
2578}
2579
2580impl From<Csp> for HashMap<String, CspDirectiveSources> {
2581 fn from(csp: Csp) -> Self {
2582 match csp {
2583 Csp::Policy(policy) => {
2584 let mut map = HashMap::new();
2585 for directive in policy.split(';') {
2586 let mut tokens = directive.trim().split(' ');
2587 if let Some(directive) = tokens.next() {
2588 let sources = tokens.map(|s| s.to_string()).collect::<Vec<String>>();
2589 map.insert(directive.to_string(), CspDirectiveSources::List(sources));
2590 }
2591 }
2592 map
2593 }
2594 Csp::DirectiveMap(m) => m,
2595 }
2596 }
2597}
2598
2599impl Display for Csp {
2600 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2601 match self {
2602 Self::Policy(s) => write!(f, "{s}"),
2603 Self::DirectiveMap(m) => {
2604 let len = m.len();
2605 let mut i = 0;
2606 for (directive, sources) in m {
2607 let sources: Vec<String> = sources.clone().into();
2608 write!(f, "{} {}", directive, sources.join(" "))?;
2609 i += 1;
2610 if i != len {
2611 write!(f, "; ")?;
2612 }
2613 }
2614 Ok(())
2615 }
2616 }
2617 }
2618}
2619
2620#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2622#[serde(untagged)]
2623#[cfg_attr(feature = "schema", derive(JsonSchema))]
2624pub enum DisabledCspModificationKind {
2625 Flag(bool),
2628 List(Vec<String>),
2630}
2631
2632impl DisabledCspModificationKind {
2633 pub fn can_modify(&self, directive: &str) -> bool {
2635 match self {
2636 Self::Flag(f) => !f,
2637 Self::List(l) => !l.contains(&directive.into()),
2638 }
2639 }
2640}
2641
2642impl Default for DisabledCspModificationKind {
2643 fn default() -> Self {
2644 Self::Flag(false)
2645 }
2646}
2647
2648#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2657#[serde(untagged)]
2658#[cfg_attr(feature = "schema", derive(JsonSchema))]
2659pub enum FsScope {
2660 AllowedPaths(Vec<PathBuf>),
2662 #[serde(rename_all = "camelCase")]
2664 Scope {
2665 #[serde(default)]
2667 allow: Vec<PathBuf>,
2668 #[serde(default)]
2671 deny: Vec<PathBuf>,
2672 #[serde(alias = "require-literal-leading-dot")]
2681 require_literal_leading_dot: Option<bool>,
2682 },
2683}
2684
2685impl Default for FsScope {
2686 fn default() -> Self {
2687 Self::AllowedPaths(Vec::new())
2688 }
2689}
2690
2691impl FsScope {
2692 pub fn allowed_paths(&self) -> &Vec<PathBuf> {
2694 match self {
2695 Self::AllowedPaths(p) => p,
2696 Self::Scope { allow, .. } => allow,
2697 }
2698 }
2699
2700 pub fn forbidden_paths(&self) -> Option<&Vec<PathBuf>> {
2702 match self {
2703 Self::AllowedPaths(_) => None,
2704 Self::Scope { deny, .. } => Some(deny),
2705 }
2706 }
2707}
2708
2709#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2713#[cfg_attr(feature = "schema", derive(JsonSchema))]
2714#[serde(rename_all = "camelCase", deny_unknown_fields)]
2715pub struct AssetProtocolConfig {
2716 #[serde(default)]
2718 pub scope: FsScope,
2719 #[serde(default)]
2721 pub enable: bool,
2722}
2723
2724#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
2728#[cfg_attr(feature = "schema", derive(JsonSchema))]
2729#[serde(rename_all = "camelCase", untagged)]
2730pub enum HeaderSource {
2731 Inline(String),
2733 List(Vec<String>),
2735 Map(HashMap<String, String>),
2737}
2738
2739impl Serialize for HeaderSource {
2740 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
2741 where
2742 S: Serializer,
2743 {
2744 match self {
2745 Self::Inline(s) => serializer.serialize_str(s),
2746 Self::List(l) => l.serialize(serializer),
2747 Self::Map(m) => {
2748 let btree_map: BTreeMap<_, _> = m.iter().collect();
2752 btree_map.serialize(serializer)
2753 }
2754 }
2755 }
2756}
2757
2758impl Display for HeaderSource {
2759 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2760 match self {
2761 Self::Inline(s) => write!(f, "{s}"),
2762 Self::List(l) => write!(f, "{}", l.join(", ")),
2763 Self::Map(m) => {
2764 let map: BTreeMap<_, _> = m.iter().collect();
2768 let len = map.len();
2769 for (i, (key, value)) in map.into_iter().enumerate() {
2770 write!(f, "{key} {value}")?;
2771 if i + 1 != len {
2772 write!(f, "; ")?;
2773 }
2774 }
2775 Ok(())
2776 }
2777 }
2778 }
2779}
2780
2781pub trait HeaderAddition {
2785 fn add_configured_headers(self, headers: Option<&HeaderConfig>) -> http::response::Builder;
2787}
2788
2789impl HeaderAddition for Builder {
2790 fn add_configured_headers(mut self, headers: Option<&HeaderConfig>) -> http::response::Builder {
2794 if let Some(headers) = headers {
2795 if let Some(value) = &headers.access_control_allow_credentials {
2797 self = self.header("Access-Control-Allow-Credentials", value.to_string());
2798 };
2799
2800 if let Some(value) = &headers.access_control_allow_headers {
2802 self = self.header("Access-Control-Allow-Headers", value.to_string());
2803 };
2804
2805 if let Some(value) = &headers.access_control_allow_methods {
2807 self = self.header("Access-Control-Allow-Methods", value.to_string());
2808 };
2809
2810 if let Some(value) = &headers.access_control_expose_headers {
2812 self = self.header("Access-Control-Expose-Headers", value.to_string());
2813 };
2814
2815 if let Some(value) = &headers.access_control_max_age {
2817 self = self.header("Access-Control-Max-Age", value.to_string());
2818 };
2819
2820 if let Some(value) = &headers.cross_origin_embedder_policy {
2822 self = self.header("Cross-Origin-Embedder-Policy", value.to_string());
2823 };
2824
2825 if let Some(value) = &headers.cross_origin_opener_policy {
2827 self = self.header("Cross-Origin-Opener-Policy", value.to_string());
2828 };
2829
2830 if let Some(value) = &headers.cross_origin_resource_policy {
2832 self = self.header("Cross-Origin-Resource-Policy", value.to_string());
2833 };
2834
2835 if let Some(value) = &headers.permissions_policy {
2837 self = self.header("Permissions-Policy", value.to_string());
2838 };
2839
2840 if let Some(value) = &headers.service_worker_allowed {
2841 self = self.header("Service-Worker-Allowed", value.to_string());
2842 }
2843
2844 if let Some(value) = &headers.timing_allow_origin {
2846 self = self.header("Timing-Allow-Origin", value.to_string());
2847 };
2848
2849 if let Some(value) = &headers.x_content_type_options {
2851 self = self.header("X-Content-Type-Options", value.to_string());
2852 };
2853
2854 if let Some(value) = &headers.tauri_custom_header {
2856 self = self.header("Tauri-Custom-Header", value.to_string());
2858 };
2859 }
2860 self
2861 }
2862}
2863
2864#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
2916#[cfg_attr(feature = "schema", derive(JsonSchema))]
2917#[serde(deny_unknown_fields)]
2918pub struct HeaderConfig {
2919 #[serde(rename = "Access-Control-Allow-Credentials")]
2924 pub access_control_allow_credentials: Option<HeaderSource>,
2925 #[serde(rename = "Access-Control-Allow-Headers")]
2933 pub access_control_allow_headers: Option<HeaderSource>,
2934 #[serde(rename = "Access-Control-Allow-Methods")]
2939 pub access_control_allow_methods: Option<HeaderSource>,
2940 #[serde(rename = "Access-Control-Expose-Headers")]
2946 pub access_control_expose_headers: Option<HeaderSource>,
2947 #[serde(rename = "Access-Control-Max-Age")]
2954 pub access_control_max_age: Option<HeaderSource>,
2955 #[serde(rename = "Cross-Origin-Embedder-Policy")]
2960 pub cross_origin_embedder_policy: Option<HeaderSource>,
2961 #[serde(rename = "Cross-Origin-Opener-Policy")]
2968 pub cross_origin_opener_policy: Option<HeaderSource>,
2969 #[serde(rename = "Cross-Origin-Resource-Policy")]
2974 pub cross_origin_resource_policy: Option<HeaderSource>,
2975 #[serde(rename = "Permissions-Policy")]
2980 pub permissions_policy: Option<HeaderSource>,
2981 #[serde(rename = "Service-Worker-Allowed")]
2991 pub service_worker_allowed: Option<HeaderSource>,
2992 #[serde(rename = "Timing-Allow-Origin")]
2998 pub timing_allow_origin: Option<HeaderSource>,
2999 #[serde(rename = "X-Content-Type-Options")]
3006 pub x_content_type_options: Option<HeaderSource>,
3007 #[serde(rename = "Tauri-Custom-Header")]
3012 pub tauri_custom_header: Option<HeaderSource>,
3013}
3014
3015impl HeaderConfig {
3016 pub fn new() -> Self {
3018 HeaderConfig {
3019 access_control_allow_credentials: None,
3020 access_control_allow_methods: None,
3021 access_control_allow_headers: None,
3022 access_control_expose_headers: None,
3023 access_control_max_age: None,
3024 cross_origin_embedder_policy: None,
3025 cross_origin_opener_policy: None,
3026 cross_origin_resource_policy: None,
3027 permissions_policy: None,
3028 service_worker_allowed: None,
3029 timing_allow_origin: None,
3030 x_content_type_options: None,
3031 tauri_custom_header: None,
3032 }
3033 }
3034}
3035
3036#[skip_serializing_none]
3040#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
3041#[cfg_attr(feature = "schema", derive(JsonSchema))]
3042#[serde(rename_all = "camelCase", deny_unknown_fields)]
3043pub struct SecurityConfig {
3044 pub csp: Option<Csp>,
3050 #[serde(alias = "dev-csp")]
3055 pub dev_csp: Option<Csp>,
3056 #[serde(default, alias = "freeze-prototype")]
3069 pub freeze_prototype: bool,
3070 #[serde(default, alias = "dangerous-disable-asset-csp-modification")]
3083 pub dangerous_disable_asset_csp_modification: DisabledCspModificationKind,
3084 #[serde(default, alias = "asset-protocol")]
3086 pub asset_protocol: AssetProtocolConfig,
3087 #[serde(default)]
3099 pub pattern: PatternKind,
3100 #[serde(default)]
3125 pub capabilities: Vec<CapabilityEntry>,
3126 #[serde(default)]
3129 pub headers: Option<HeaderConfig>,
3130}
3131
3132#[derive(Debug, Clone, PartialEq, Serialize)]
3134#[cfg_attr(feature = "schema", derive(JsonSchema))]
3135#[serde(untagged)]
3136pub enum CapabilityEntry {
3137 Inlined(Capability),
3139 Reference(String),
3141}
3142
3143impl<'de> Deserialize<'de> for CapabilityEntry {
3144 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3145 where
3146 D: Deserializer<'de>,
3147 {
3148 UntaggedEnumVisitor::new()
3149 .string(|string| Ok(Self::Reference(string.to_owned())))
3150 .map(|map| map.deserialize::<Capability>().map(Self::Inlined))
3151 .deserialize(deserializer)
3152 }
3153}
3154
3155#[skip_serializing_none]
3157#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)]
3158#[serde(rename_all = "lowercase", tag = "use", content = "options")]
3159#[cfg_attr(feature = "schema", derive(JsonSchema))]
3160pub enum PatternKind {
3161 #[default]
3163 Brownfield,
3164 Isolation {
3166 dir: PathBuf,
3168 },
3169}
3170
3171const APP_DIRECTORIES_OVERRIDE_VARIABLES: &[&str] = &[
3177 "$AUDIO",
3178 "$CACHE",
3179 "$CONFIG",
3180 "$DATA",
3181 "$LOCALDATA",
3182 "$DESKTOP",
3183 "$DOCUMENT",
3184 "$DOWNLOAD",
3185 "$HOME",
3186 "$PICTURE",
3187 "$PUBLIC",
3188 "$TEMP",
3189 "$VIDEO",
3190];
3191
3192fn validate_app_directory_override(path: &Path) -> Result<(), String> {
3194 let mut components = path.components();
3195 let first = components.next();
3196
3197 if let Some(Component::Normal(first)) = first {
3198 if let Some(variable) = first.to_str().filter(|s| s.starts_with('$')) {
3199 if !APP_DIRECTORIES_OVERRIDE_VARIABLES.contains(&variable) {
3200 return Err(format!(
3201 "`{}` starts with the unsupported base directory variable `{variable}`, expected one of {}",
3202 path.display(),
3203 APP_DIRECTORIES_OVERRIDE_VARIABLES
3204 .iter()
3205 .map(|v| format!("`{v}`"))
3206 .collect::<Vec<_>>()
3207 .join(", ")
3208 ));
3209 }
3210 return Ok(());
3211 }
3212 }
3213
3214 if !path.is_absolute() && (path.has_root() || matches!(first, Some(Component::Prefix(_)))) {
3217 return Err(format!(
3218 "`{}` must be an absolute path, a path relative to the executable or a path starting with a base directory variable",
3219 path.display()
3220 ));
3221 }
3222
3223 Ok(())
3224}
3225
3226#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
3230#[cfg_attr(feature = "schema", derive(JsonSchema))]
3231#[serde(untagged)]
3232pub enum AppDirectoriesOverride {
3233 Root(PathBuf),
3238 Directories(AppDirectoryOverrides),
3242}
3243
3244impl AppDirectoriesOverride {
3245 fn paths(&self) -> impl Iterator<Item = &PathBuf> {
3247 match self {
3248 Self::Root(root) => vec![Some(root)],
3249 Self::Directories(directories) => vec![
3250 directories.config.as_ref(),
3251 directories.data.as_ref(),
3252 directories.local_data.as_ref(),
3253 directories.cache.as_ref(),
3254 directories.log.as_ref(),
3255 ],
3256 }
3257 .into_iter()
3258 .flatten()
3259 }
3260}
3261
3262impl<'de> Deserialize<'de> for AppDirectoriesOverride {
3263 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3264 where
3265 D: Deserializer<'de>,
3266 {
3267 let value = UntaggedEnumVisitor::new()
3268 .string(|path| Ok(Self::Root(PathBuf::from(path))))
3269 .map(|map| {
3270 map
3271 .deserialize::<AppDirectoryOverrides>()
3272 .map(Self::Directories)
3273 })
3274 .deserialize(deserializer)?;
3275
3276 for path in value.paths() {
3277 validate_app_directory_override(path).map_err(DeError::custom)?;
3278 }
3279
3280 Ok(value)
3281 }
3282}
3283
3284#[skip_serializing_none]
3286#[derive(Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize)]
3287#[cfg_attr(feature = "schema", derive(JsonSchema))]
3288#[serde(rename_all = "camelCase", deny_unknown_fields)]
3289pub struct AppDirectoryOverrides {
3290 pub config: Option<PathBuf>,
3292 pub data: Option<PathBuf>,
3294 #[serde(alias = "local-data", alias = "local_data")]
3298 pub local_data: Option<PathBuf>,
3299 pub cache: Option<PathBuf>,
3301 pub log: Option<PathBuf>,
3303}
3304
3305#[skip_serializing_none]
3309#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
3310#[cfg_attr(feature = "schema", derive(JsonSchema))]
3311#[serde(rename_all = "camelCase", deny_unknown_fields)]
3312pub struct AppConfig {
3313 #[serde(default)]
3368 pub windows: Vec<WindowConfig>,
3369 #[serde(default)]
3371 pub security: SecurityConfig,
3372 #[serde(alias = "tray-icon")]
3374 pub tray_icon: Option<TrayIconConfig>,
3375 #[serde(rename = "macOSPrivateApi", alias = "macos-private-api", default)]
3379 pub macos_private_api: bool,
3380 #[serde(default, alias = "with-global-tauri")]
3382 pub with_global_tauri: bool,
3383 #[serde(rename = "enableGTKAppId", alias = "enable-gtk-app-id", default)]
3399 pub enable_gtk_app_id: bool,
3400 #[serde(alias = "app-directories-override")]
3512 pub app_directories_override: Option<AppDirectoriesOverride>,
3513}
3514
3515impl AppConfig {
3516 pub fn all_features() -> Vec<&'static str> {
3518 vec![
3519 "tray-icon",
3520 "macos-private-api",
3522 "protocol-asset",
3523 "isolation",
3524 ]
3525 }
3526
3527 pub fn features(&self) -> Vec<&str> {
3529 let mut features = Vec::new();
3530 if self.tray_icon.is_some() {
3531 features.push("tray-icon");
3532 }
3533 if self.macos_private_api {
3535 features.push("macos-private-api");
3536 }
3537 if self.security.asset_protocol.enable {
3538 features.push("protocol-asset");
3539 }
3540
3541 if let PatternKind::Isolation { .. } = self.security.pattern {
3542 features.push("isolation");
3543 }
3544
3545 features.sort_unstable();
3546 features
3547 }
3548}
3549
3550#[skip_serializing_none]
3554#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
3555#[cfg_attr(feature = "schema", derive(JsonSchema))]
3556#[serde(rename_all = "camelCase", deny_unknown_fields)]
3557pub struct TrayIconConfig {
3558 pub id: Option<String>,
3560 #[serde(alias = "icon-path")]
3566 pub icon_path: PathBuf,
3567 #[serde(default, alias = "icon-as-template")]
3569 pub icon_as_template: bool,
3570 #[serde(default = "default_true", alias = "menu-on-left-click")]
3578 #[deprecated(
3579 since = "2.2.0",
3580 note = "No longer works, use `show_menu_on_left_click` instead."
3581 )]
3582 pub menu_on_left_click: bool,
3583 #[serde(default = "default_true", alias = "show-menu-on-left-click")]
3589 pub show_menu_on_left_click: bool,
3590 pub title: Option<String>,
3592 pub tooltip: Option<String>,
3594}
3595
3596#[skip_serializing_none]
3598#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3599#[cfg_attr(feature = "schema", derive(JsonSchema))]
3600#[serde(rename_all = "camelCase", deny_unknown_fields)]
3601pub struct IosConfig {
3602 pub template: Option<PathBuf>,
3606 pub frameworks: Option<Vec<String>>,
3610 #[serde(alias = "development-team")]
3613 pub development_team: Option<String>,
3614 #[serde(alias = "bundle-version")]
3618 pub bundle_version: Option<String>,
3619 #[serde(
3623 alias = "minimum-system-version",
3624 default = "ios_minimum_system_version"
3625 )]
3626 pub minimum_system_version: String,
3627 #[serde(alias = "info-plist")]
3631 pub info_plist: Option<PathBuf>,
3632}
3633
3634impl Default for IosConfig {
3635 fn default() -> Self {
3636 Self {
3637 template: None,
3638 frameworks: None,
3639 development_team: None,
3640 bundle_version: None,
3641 minimum_system_version: ios_minimum_system_version(),
3642 info_plist: None,
3643 }
3644 }
3645}
3646
3647#[skip_serializing_none]
3649#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3650#[cfg_attr(feature = "schema", derive(JsonSchema))]
3651#[serde(rename_all = "camelCase", deny_unknown_fields)]
3652pub struct AndroidConfig {
3653 #[serde(alias = "min-sdk-version", default = "default_min_sdk_version")]
3656 pub min_sdk_version: u32,
3657
3658 #[serde(alias = "version-code")]
3664 #[cfg_attr(feature = "schema", validate(range(min = 1, max = 2_100_000_000)))]
3665 pub version_code: Option<u32>,
3666
3667 #[serde(alias = "auto-increment-version-code", default)]
3675 pub auto_increment_version_code: bool,
3676
3677 #[serde(alias = "debug-application-id-suffix")]
3681 pub debug_application_id_suffix: Option<String>,
3682}
3683
3684impl Default for AndroidConfig {
3685 fn default() -> Self {
3686 Self {
3687 min_sdk_version: default_min_sdk_version(),
3688 version_code: None,
3689 auto_increment_version_code: false,
3690 debug_application_id_suffix: None,
3691 }
3692 }
3693}
3694
3695fn default_min_sdk_version() -> u32 {
3696 24
3697}
3698
3699#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3701#[cfg_attr(feature = "schema", derive(JsonSchema))]
3702#[serde(untagged, deny_unknown_fields)]
3703#[non_exhaustive]
3704pub enum FrontendDist {
3705 Url(Url),
3707 Directory(PathBuf),
3709 Files(Vec<PathBuf>),
3711}
3712
3713impl std::fmt::Display for FrontendDist {
3714 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3715 match self {
3716 Self::Url(url) => write!(f, "{url}"),
3717 Self::Directory(p) => write!(f, "{}", p.display()),
3718 Self::Files(files) => write!(f, "{}", serde_json::to_string(files).unwrap()),
3719 }
3720 }
3721}
3722
3723#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3725#[cfg_attr(feature = "schema", derive(JsonSchema))]
3726#[serde(rename_all = "camelCase", untagged)]
3727pub enum BeforeDevCommand {
3728 Script(String),
3730 ScriptWithOptions {
3732 script: String,
3734 cwd: Option<String>,
3736 #[serde(default)]
3738 wait: bool,
3739 },
3740}
3741
3742#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3744#[cfg_attr(feature = "schema", derive(JsonSchema))]
3745#[serde(rename_all = "camelCase", untagged)]
3746pub enum HookCommand {
3747 Script(String),
3749 ScriptWithOptions {
3751 script: String,
3753 cwd: Option<String>,
3755 },
3756}
3757
3758#[skip_serializing_none]
3760#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
3761#[cfg_attr(feature = "schema", derive(JsonSchema))]
3762#[serde(untagged)]
3763pub enum RunnerConfig {
3764 String(String),
3766 Object {
3768 cmd: String,
3770 cwd: Option<String>,
3772 args: Option<Vec<String>>,
3774 },
3775}
3776
3777impl Default for RunnerConfig {
3778 fn default() -> Self {
3779 RunnerConfig::String("cargo".to_string())
3780 }
3781}
3782
3783impl RunnerConfig {
3784 pub fn cmd(&self) -> &str {
3786 match self {
3787 RunnerConfig::String(cmd) => cmd,
3788 RunnerConfig::Object { cmd, .. } => cmd,
3789 }
3790 }
3791
3792 pub fn cwd(&self) -> Option<&str> {
3794 match self {
3795 RunnerConfig::String(_) => None,
3796 RunnerConfig::Object { cwd, .. } => cwd.as_deref(),
3797 }
3798 }
3799
3800 pub fn args(&self) -> Option<&[String]> {
3802 match self {
3803 RunnerConfig::String(_) => None,
3804 RunnerConfig::Object { args, .. } => args.as_deref(),
3805 }
3806 }
3807}
3808
3809impl std::str::FromStr for RunnerConfig {
3810 type Err = std::convert::Infallible;
3811
3812 fn from_str(s: &str) -> Result<Self, Self::Err> {
3813 Ok(RunnerConfig::String(s.to_string()))
3814 }
3815}
3816
3817impl From<&str> for RunnerConfig {
3818 fn from(s: &str) -> Self {
3819 RunnerConfig::String(s.to_string())
3820 }
3821}
3822
3823impl From<String> for RunnerConfig {
3824 fn from(s: String) -> Self {
3825 RunnerConfig::String(s)
3826 }
3827}
3828
3829#[skip_serializing_none]
3833#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)]
3834#[cfg_attr(feature = "schema", derive(JsonSchema))]
3835#[serde(rename_all = "camelCase", deny_unknown_fields)]
3836pub struct BuildConfig {
3837 pub runner: Option<RunnerConfig>,
3839 #[serde(alias = "dev-url")]
3847 pub dev_url: Option<Url>,
3848 #[serde(alias = "frontend-dist")]
3862 pub frontend_dist: Option<FrontendDist>,
3863 #[serde(alias = "before-dev-command")]
3870 pub before_dev_command: Option<BeforeDevCommand>,
3871 #[serde(alias = "before-build-command")]
3878 pub before_build_command: Option<HookCommand>,
3879 #[serde(alias = "before-bundle-command")]
3886 pub before_bundle_command: Option<HookCommand>,
3887 pub features: Option<Vec<String>>,
3889 #[serde(alias = "remove-unused-commands", default)]
3897 pub remove_unused_commands: bool,
3898 #[serde(
3900 alias = "additional-watch-folders",
3901 alias = "additional-watch-directories",
3902 default
3903 )]
3904 pub additional_watch_folders: Vec<PathBuf>,
3905 #[serde(default)]
3907 pub windows: WindowsBuildConfig,
3908}
3909
3910#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3912#[cfg_attr(feature = "schema", derive(JsonSchema))]
3913#[serde(rename_all = "camelCase", deny_unknown_fields)]
3914pub struct WindowsBuildConfig {
3915 #[serde(
3917 default = "default_true",
3918 rename = "staticVCRuntime",
3919 alias = "static-vc-runtime",
3920 alias = "staticVcRuntime"
3921 )]
3922 pub static_vc_runtime: bool,
3923}
3924
3925impl Default for WindowsBuildConfig {
3926 fn default() -> Self {
3927 Self {
3928 static_vc_runtime: true,
3929 }
3930 }
3931}
3932
3933#[derive(Debug, PartialEq, Eq)]
3934struct PackageVersion(String);
3935
3936impl<'d> serde::Deserialize<'d> for PackageVersion {
3937 fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
3938 struct PackageVersionVisitor;
3939
3940 impl Visitor<'_> for PackageVersionVisitor {
3941 type Value = PackageVersion;
3942
3943 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
3944 write!(
3945 formatter,
3946 "a semver string or a path to a package.json file"
3947 )
3948 }
3949
3950 fn visit_str<E: DeError>(self, value: &str) -> Result<PackageVersion, E> {
3951 let path = PathBuf::from(value);
3952 if path.exists() {
3953 let json_str = read_to_string(&path)
3954 .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
3955 let package_json: serde_json::Value = serde_json::from_str(&json_str)
3956 .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
3957 if let Some(obj) = package_json.as_object() {
3958 let version = obj
3959 .get("version")
3960 .ok_or_else(|| DeError::custom("JSON must contain a `version` field"))?
3961 .as_str()
3962 .ok_or_else(|| {
3963 DeError::custom(format!("`{} > version` must be a string", path.display()))
3964 })?;
3965 Ok(PackageVersion(
3966 Version::from_str(version)
3967 .map_err(|_| {
3968 DeError::custom("`tauri.conf.json > version` must be a semver string")
3969 })?
3970 .to_string(),
3971 ))
3972 } else {
3973 Err(DeError::custom(
3974 "`tauri.conf.json > version` value is not a path to a JSON object",
3975 ))
3976 }
3977 } else {
3978 Ok(PackageVersion(
3979 Version::from_str(value)
3980 .map_err(|_| DeError::custom("`tauri.conf.json > version` must be a semver string"))?
3981 .to_string(),
3982 ))
3983 }
3984 }
3985 }
3986
3987 deserializer.deserialize_string(PackageVersionVisitor {})
3988 }
3989}
3990
3991fn version_deserializer<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
3992where
3993 D: Deserializer<'de>,
3994{
3995 Option::<PackageVersion>::deserialize(deserializer).map(|v| v.map(|v| v.0))
3996}
3997
3998#[skip_serializing_none]
4064#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
4065#[cfg_attr(feature = "schema", derive(JsonSchema))]
4066#[serde(rename_all = "camelCase", deny_unknown_fields)]
4067pub struct Config {
4068 #[serde(rename = "$schema")]
4070 pub schema: Option<String>,
4071 #[serde(alias = "product-name")]
4089 #[cfg_attr(feature = "schema", validate(regex(pattern = "^[^/\\:*?\"<>|]+$")))]
4090 pub product_name: Option<String>,
4091 #[serde(alias = "main-binary-name")]
4105 pub main_binary_name: Option<String>,
4106 #[serde(deserialize_with = "version_deserializer", default)]
4122 pub version: Option<String>,
4123 pub identifier: String,
4131 #[serde(default)]
4133 pub app: AppConfig,
4134 #[serde(default)]
4136 pub build: BuildConfig,
4137 #[serde(default)]
4139 pub bundle: BundleConfig,
4140 #[serde(default)]
4142 pub plugins: PluginConfig,
4143}
4144
4145#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
4149#[cfg_attr(feature = "schema", derive(JsonSchema))]
4150pub struct PluginConfig(pub HashMap<String, JsonValue>);
4151
4152impl Serialize for PluginConfig {
4153 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
4154 where
4155 S: Serializer,
4156 {
4157 let btree_map: BTreeMap<_, _> = self.0.iter().collect();
4161 btree_map.serialize(serializer)
4162 }
4163}
4164
4165#[cfg(any(feature = "build", feature = "build-2"))]
4171mod build {
4172 use super::*;
4173 use crate::{literal_struct, tokens::*};
4174 use proc_macro2::TokenStream;
4175 use quote::{ToTokens, TokenStreamExt, quote};
4176 use std::convert::identity;
4177
4178 impl ToTokens for WebviewUrl {
4179 fn to_tokens(&self, tokens: &mut TokenStream) {
4180 let prefix = quote! { ::tauri::utils::config::WebviewUrl };
4181
4182 tokens.append_all(match self {
4183 Self::App(path) => {
4184 let path = path_buf_lit(path);
4185 quote! { #prefix::App(#path) }
4186 }
4187 Self::External(url) => {
4188 let url = url_lit(url);
4189 quote! { #prefix::External(#url) }
4190 }
4191 Self::CustomProtocol(url) => {
4192 let url = url_lit(url);
4193 quote! { #prefix::CustomProtocol(#url) }
4194 }
4195 })
4196 }
4197 }
4198
4199 impl ToTokens for BackgroundThrottlingPolicy {
4200 fn to_tokens(&self, tokens: &mut TokenStream) {
4201 let prefix = quote! { ::tauri::utils::config::BackgroundThrottlingPolicy };
4202 tokens.append_all(match self {
4203 Self::Disabled => quote! { #prefix::Disabled },
4204 Self::Throttle => quote! { #prefix::Throttle },
4205 Self::Suspend => quote! { #prefix::Suspend },
4206 })
4207 }
4208 }
4209
4210 impl ToTokens for crate::Theme {
4211 fn to_tokens(&self, tokens: &mut TokenStream) {
4212 let prefix = quote! { ::tauri::utils::Theme };
4213
4214 tokens.append_all(match self {
4215 Self::Light => quote! { #prefix::Light },
4216 Self::Dark => quote! { #prefix::Dark },
4217 })
4218 }
4219 }
4220
4221 impl ToTokens for Color {
4222 fn to_tokens(&self, tokens: &mut TokenStream) {
4223 let Color(r, g, b, a) = self;
4224 tokens.append_all(quote! {::tauri::utils::config::Color(#r,#g,#b,#a)});
4225 }
4226 }
4227 impl ToTokens for WindowEffectsConfig {
4228 fn to_tokens(&self, tokens: &mut TokenStream) {
4229 let effects = vec_lit(self.effects.clone(), |d| d);
4230 let state = opt_lit(self.state.as_ref());
4231 let radius = opt_lit(self.radius.as_ref());
4232 let color = opt_lit(self.color.as_ref());
4233 let interactive = self.interactive;
4234
4235 literal_struct!(
4236 tokens,
4237 ::tauri::utils::config::WindowEffectsConfig,
4238 effects,
4239 state,
4240 radius,
4241 color,
4242 interactive
4243 )
4244 }
4245 }
4246
4247 impl ToTokens for crate::TitleBarStyle {
4248 fn to_tokens(&self, tokens: &mut TokenStream) {
4249 let prefix = quote! { ::tauri::utils::TitleBarStyle };
4250
4251 tokens.append_all(match self {
4252 Self::Visible => quote! { #prefix::Visible },
4253 Self::Transparent => quote! { #prefix::Transparent },
4254 Self::Overlay => quote! { #prefix::Overlay },
4255 })
4256 }
4257 }
4258
4259 impl ToTokens for LogicalPosition {
4260 fn to_tokens(&self, tokens: &mut TokenStream) {
4261 let LogicalPosition { x, y } = self;
4262 literal_struct!(tokens, ::tauri::utils::config::LogicalPosition, x, y)
4263 }
4264 }
4265
4266 impl ToTokens for crate::WindowEffect {
4267 fn to_tokens(&self, tokens: &mut TokenStream) {
4268 let prefix = quote! { ::tauri::utils::WindowEffect };
4269
4270 #[allow(deprecated)]
4271 tokens.append_all(match self {
4272 WindowEffect::AppearanceBased => quote! { #prefix::AppearanceBased},
4273 WindowEffect::Light => quote! { #prefix::Light},
4274 WindowEffect::Dark => quote! { #prefix::Dark},
4275 WindowEffect::MediumLight => quote! { #prefix::MediumLight},
4276 WindowEffect::UltraDark => quote! { #prefix::UltraDark},
4277 WindowEffect::Titlebar => quote! { #prefix::Titlebar},
4278 WindowEffect::Selection => quote! { #prefix::Selection},
4279 WindowEffect::Menu => quote! { #prefix::Menu},
4280 WindowEffect::Popover => quote! { #prefix::Popover},
4281 WindowEffect::Sidebar => quote! { #prefix::Sidebar},
4282 WindowEffect::HeaderView => quote! { #prefix::HeaderView},
4283 WindowEffect::Sheet => quote! { #prefix::Sheet},
4284 WindowEffect::WindowBackground => quote! { #prefix::WindowBackground},
4285 WindowEffect::HudWindow => quote! { #prefix::HudWindow},
4286 WindowEffect::FullScreenUI => quote! { #prefix::FullScreenUI},
4287 WindowEffect::Tooltip => quote! { #prefix::Tooltip},
4288 WindowEffect::ContentBackground => quote! { #prefix::ContentBackground},
4289 WindowEffect::UnderWindowBackground => quote! { #prefix::UnderWindowBackground},
4290 WindowEffect::UnderPageBackground => quote! { #prefix::UnderPageBackground},
4291 WindowEffect::LiquidGlassRegular => quote! { #prefix::LiquidGlassRegular },
4292 WindowEffect::LiquidGlassClear => quote! { #prefix::LiquidGlassClear },
4293 WindowEffect::Mica => quote! { #prefix::Mica},
4294 WindowEffect::MicaDark => quote! { #prefix::MicaDark},
4295 WindowEffect::MicaLight => quote! { #prefix::MicaLight},
4296 WindowEffect::Blur => quote! { #prefix::Blur},
4297 WindowEffect::Acrylic => quote! { #prefix::Acrylic},
4298 WindowEffect::Tabbed => quote! { #prefix::Tabbed },
4299 WindowEffect::TabbedDark => quote! { #prefix::TabbedDark },
4300 WindowEffect::TabbedLight => quote! { #prefix::TabbedLight },
4301 })
4302 }
4303 }
4304
4305 impl ToTokens for crate::WindowEffectState {
4306 fn to_tokens(&self, tokens: &mut TokenStream) {
4307 let prefix = quote! { ::tauri::utils::WindowEffectState };
4308
4309 #[allow(deprecated)]
4310 tokens.append_all(match self {
4311 WindowEffectState::Active => quote! { #prefix::Active},
4312 WindowEffectState::FollowsWindowActiveState => quote! { #prefix::FollowsWindowActiveState},
4313 WindowEffectState::Inactive => quote! { #prefix::Inactive},
4314 })
4315 }
4316 }
4317
4318 impl ToTokens for PreventOverflowMargin {
4319 fn to_tokens(&self, tokens: &mut TokenStream) {
4320 let width = self.width;
4321 let height = self.height;
4322
4323 literal_struct!(
4324 tokens,
4325 ::tauri::utils::config::PreventOverflowMargin,
4326 width,
4327 height
4328 )
4329 }
4330 }
4331
4332 impl ToTokens for PreventOverflowConfig {
4333 fn to_tokens(&self, tokens: &mut TokenStream) {
4334 let prefix = quote! { ::tauri::utils::config::PreventOverflowConfig };
4335
4336 #[allow(deprecated)]
4337 tokens.append_all(match self {
4338 Self::Enable(enable) => quote! { #prefix::Enable(#enable) },
4339 Self::Margin(margin) => quote! { #prefix::Margin(#margin) },
4340 })
4341 }
4342 }
4343
4344 impl ToTokens for ScrollBarStyle {
4345 fn to_tokens(&self, tokens: &mut TokenStream) {
4346 let prefix = quote! { ::tauri::utils::config::ScrollBarStyle };
4347
4348 tokens.append_all(match self {
4349 Self::Default => quote! { #prefix::Default },
4350 Self::FluentOverlay => quote! { #prefix::FluentOverlay },
4351 })
4352 }
4353 }
4354
4355 impl ToTokens for WindowConfig {
4356 fn to_tokens(&self, tokens: &mut TokenStream) {
4357 let label = str_lit(&self.label);
4358 let create = &self.create;
4359 let url = &self.url;
4360 let user_agent = opt_str_lit(self.user_agent.as_ref());
4361 let drag_drop_enabled = self.drag_drop_enabled;
4362 let center = self.center;
4363 let x = opt_lit(self.x.as_ref());
4364 let y = opt_lit(self.y.as_ref());
4365 let width = self.width;
4366 let height = self.height;
4367 let min_width = opt_lit(self.min_width.as_ref());
4368 let min_height = opt_lit(self.min_height.as_ref());
4369 let max_width = opt_lit(self.max_width.as_ref());
4370 let max_height = opt_lit(self.max_height.as_ref());
4371 let prevent_overflow = opt_lit(self.prevent_overflow.as_ref());
4372 let resizable = self.resizable;
4373 let maximizable = self.maximizable;
4374 let minimizable = self.minimizable;
4375 let closable = self.closable;
4376 let title = str_lit(&self.title);
4377 let proxy_url = opt_lit(self.proxy_url.as_ref().map(url_lit).as_ref());
4378 let fullscreen = self.fullscreen;
4379 let focus = self.focus;
4380 let focusable = self.focusable;
4381 let transparent = self.transparent;
4382 let maximized = self.maximized;
4383 let visible = self.visible;
4384 let decorations = self.decorations;
4385 let always_on_bottom = self.always_on_bottom;
4386 let always_on_top = self.always_on_top;
4387 let visible_on_all_workspaces = self.visible_on_all_workspaces;
4388 let content_protected = self.content_protected;
4389 let skip_taskbar = self.skip_taskbar;
4390 let window_classname = opt_str_lit(self.window_classname.as_ref());
4391 let no_redirection_bitmap = self.no_redirection_bitmap;
4392 let theme = opt_lit(self.theme.as_ref());
4393 let title_bar_style = &self.title_bar_style;
4394 let traffic_light_position = opt_lit(self.traffic_light_position.as_ref());
4395 let hidden_title = self.hidden_title;
4396 let accept_first_mouse = self.accept_first_mouse;
4397 let tabbing_identifier = opt_str_lit(self.tabbing_identifier.as_ref());
4398 let additional_browser_args = opt_str_lit(self.additional_browser_args.as_ref());
4399 let shadow = self.shadow;
4400 let window_effects = opt_lit(self.window_effects.as_ref());
4401 let incognito = self.incognito;
4402 let parent = opt_str_lit(self.parent.as_ref());
4403 let zoom_hotkeys_enabled = self.zoom_hotkeys_enabled;
4404 let browser_extensions_enabled = self.browser_extensions_enabled;
4405 let use_https_scheme = self.use_https_scheme;
4406 let devtools = opt_lit(self.devtools.as_ref());
4407 let background_color = opt_lit(self.background_color.as_ref());
4408 let background_throttling = opt_lit(self.background_throttling.as_ref());
4409 let javascript_disabled = self.javascript_disabled;
4410 let allow_link_preview = self.allow_link_preview;
4411 let disable_input_accessory_view = self.disable_input_accessory_view;
4412 let data_directory = opt_lit(self.data_directory.as_ref().map(path_buf_lit).as_ref());
4413 let data_store_identifier = opt_vec_lit(self.data_store_identifier, identity);
4414 let scroll_bar_style = &self.scroll_bar_style;
4415 let limit_navigations_to_app_bound_domains = self.limit_navigations_to_app_bound_domains;
4416 let activity_name = opt_lit(self.activity_name.as_ref());
4417 let created_by_activity_name = opt_lit(self.created_by_activity_name.as_ref());
4418 let requested_by_scene_identifier = opt_lit(self.requested_by_scene_identifier.as_ref());
4419 let general_autofill_enabled = self.general_autofill_enabled;
4420
4421 literal_struct!(
4422 tokens,
4423 ::tauri::utils::config::WindowConfig,
4424 label,
4425 url,
4426 create,
4427 user_agent,
4428 drag_drop_enabled,
4429 center,
4430 x,
4431 y,
4432 width,
4433 height,
4434 min_width,
4435 min_height,
4436 max_width,
4437 max_height,
4438 prevent_overflow,
4439 resizable,
4440 maximizable,
4441 minimizable,
4442 closable,
4443 title,
4444 proxy_url,
4445 fullscreen,
4446 focus,
4447 focusable,
4448 transparent,
4449 maximized,
4450 visible,
4451 decorations,
4452 always_on_bottom,
4453 always_on_top,
4454 visible_on_all_workspaces,
4455 content_protected,
4456 skip_taskbar,
4457 window_classname,
4458 no_redirection_bitmap,
4459 theme,
4460 title_bar_style,
4461 traffic_light_position,
4462 hidden_title,
4463 accept_first_mouse,
4464 tabbing_identifier,
4465 additional_browser_args,
4466 shadow,
4467 window_effects,
4468 incognito,
4469 parent,
4470 zoom_hotkeys_enabled,
4471 browser_extensions_enabled,
4472 use_https_scheme,
4473 devtools,
4474 background_color,
4475 background_throttling,
4476 javascript_disabled,
4477 allow_link_preview,
4478 disable_input_accessory_view,
4479 data_directory,
4480 data_store_identifier,
4481 scroll_bar_style,
4482 limit_navigations_to_app_bound_domains,
4483 activity_name,
4484 created_by_activity_name,
4485 requested_by_scene_identifier,
4486 general_autofill_enabled
4487 );
4488 }
4489 }
4490
4491 impl ToTokens for PatternKind {
4492 fn to_tokens(&self, tokens: &mut TokenStream) {
4493 let prefix = quote! { ::tauri::utils::config::PatternKind };
4494
4495 tokens.append_all(match self {
4496 Self::Brownfield => quote! { #prefix::Brownfield },
4497 #[cfg(not(feature = "isolation"))]
4498 Self::Isolation { dir: _ } => quote! { #prefix::Brownfield },
4499 #[cfg(feature = "isolation")]
4500 Self::Isolation { dir } => {
4501 let dir = path_buf_lit(dir);
4502 quote! { #prefix::Isolation { dir: #dir } }
4503 }
4504 })
4505 }
4506 }
4507
4508 impl ToTokens for WebviewInstallMode {
4509 fn to_tokens(&self, tokens: &mut TokenStream) {
4510 let prefix = quote! { ::tauri::utils::config::WebviewInstallMode };
4511
4512 tokens.append_all(match self {
4513 Self::Skip => quote! { #prefix::Skip },
4514 Self::DownloadBootstrapper { silent } => {
4515 quote! { #prefix::DownloadBootstrapper { silent: #silent } }
4516 }
4517 Self::EmbedBootstrapper { silent } => {
4518 quote! { #prefix::EmbedBootstrapper { silent: #silent } }
4519 }
4520 Self::OfflineInstaller { silent } => {
4521 quote! { #prefix::OfflineInstaller { silent: #silent } }
4522 }
4523 Self::FixedRuntime { path } => {
4524 let path = path_buf_lit(path);
4525 quote! { #prefix::FixedRuntime { path: #path } }
4526 }
4527 })
4528 }
4529 }
4530
4531 impl ToTokens for WindowsConfig {
4532 fn to_tokens(&self, tokens: &mut TokenStream) {
4533 let webview_install_mode = &self.webview_install_mode;
4534 tokens.append_all(quote! { ::tauri::utils::config::WindowsConfig {
4535 webview_install_mode: #webview_install_mode,
4536 ..Default::default()
4537 }})
4538 }
4539 }
4540
4541 impl ToTokens for BundleConfig {
4542 fn to_tokens(&self, tokens: &mut TokenStream) {
4543 let publisher = quote!(None);
4544 let homepage = quote!(None);
4545 let icon = vec_lit(&self.icon, str_lit);
4546 let active = self.active;
4547 let targets = quote!(Default::default());
4548 let create_updater_artifacts = quote!(Default::default());
4549 let resources = quote!(None);
4550 let copyright = quote!(None);
4551 let category = quote!(None);
4552 let file_associations = quote!(None);
4553 let short_description = quote!(None);
4554 let long_description = quote!(None);
4555 let use_local_tools_dir = self.use_local_tools_dir;
4556 let external_bin = opt_vec_lit(self.external_bin.as_ref(), str_lit);
4557 let windows = &self.windows;
4558 let license = opt_str_lit(self.license.as_ref());
4559 let license_file = opt_lit(self.license_file.as_ref().map(path_buf_lit).as_ref());
4560 let linux = quote!(Default::default());
4561 let macos = quote!(Default::default());
4562 let ios = quote!(Default::default());
4563 let android = quote!(Default::default());
4564
4565 literal_struct!(
4566 tokens,
4567 ::tauri::utils::config::BundleConfig,
4568 active,
4569 publisher,
4570 homepage,
4571 icon,
4572 targets,
4573 create_updater_artifacts,
4574 resources,
4575 copyright,
4576 category,
4577 license,
4578 license_file,
4579 file_associations,
4580 short_description,
4581 long_description,
4582 use_local_tools_dir,
4583 external_bin,
4584 windows,
4585 linux,
4586 macos,
4587 ios,
4588 android
4589 );
4590 }
4591 }
4592
4593 impl ToTokens for FrontendDist {
4594 fn to_tokens(&self, tokens: &mut TokenStream) {
4595 let prefix = quote! { ::tauri::utils::config::FrontendDist };
4596
4597 tokens.append_all(match self {
4598 Self::Url(url) => {
4599 let url = url_lit(url);
4600 quote! { #prefix::Url(#url) }
4601 }
4602 Self::Directory(path) => {
4603 let path = path_buf_lit(path);
4604 quote! { #prefix::Directory(#path) }
4605 }
4606 Self::Files(files) => {
4607 let files = vec_lit(files, path_buf_lit);
4608 quote! { #prefix::Files(#files) }
4609 }
4610 })
4611 }
4612 }
4613
4614 impl ToTokens for RunnerConfig {
4615 fn to_tokens(&self, tokens: &mut TokenStream) {
4616 let prefix = quote! { ::tauri::utils::config::RunnerConfig };
4617
4618 tokens.append_all(match self {
4619 Self::String(cmd) => {
4620 let cmd = cmd.as_str();
4621 quote!(#prefix::String(#cmd.into()))
4622 }
4623 Self::Object { cmd, cwd, args } => {
4624 let cmd = cmd.as_str();
4625 let cwd = opt_str_lit(cwd.as_ref());
4626 let args = opt_lit(args.as_ref().map(|v| vec_lit(v, str_lit)).as_ref());
4627 quote!(#prefix::Object {
4628 cmd: #cmd.into(),
4629 cwd: #cwd,
4630 args: #args,
4631 })
4632 }
4633 })
4634 }
4635 }
4636
4637 impl ToTokens for BuildConfig {
4638 fn to_tokens(&self, tokens: &mut TokenStream) {
4639 let dev_url = opt_lit(self.dev_url.as_ref().map(url_lit).as_ref());
4640 let frontend_dist = opt_lit(self.frontend_dist.as_ref());
4641 let runner = opt_lit(self.runner.as_ref());
4642 let before_dev_command = quote!(None);
4643 let before_build_command = quote!(None);
4644 let before_bundle_command = quote!(None);
4645 let features = quote!(None);
4646 let remove_unused_commands = quote!(false);
4647 let additional_watch_folders = quote!(Vec::new());
4648 let windows = &self.windows;
4649
4650 literal_struct!(
4651 tokens,
4652 ::tauri::utils::config::BuildConfig,
4653 runner,
4654 dev_url,
4655 frontend_dist,
4656 before_dev_command,
4657 before_build_command,
4658 before_bundle_command,
4659 features,
4660 remove_unused_commands,
4661 additional_watch_folders,
4662 windows
4663 );
4664 }
4665 }
4666
4667 impl ToTokens for WindowsBuildConfig {
4668 fn to_tokens(&self, tokens: &mut TokenStream) {
4669 let static_vc_runtime = self.static_vc_runtime;
4670
4671 literal_struct!(
4672 tokens,
4673 ::tauri::utils::config::WindowsBuildConfig,
4674 static_vc_runtime
4675 );
4676 }
4677 }
4678
4679 impl ToTokens for CspDirectiveSources {
4680 fn to_tokens(&self, tokens: &mut TokenStream) {
4681 let prefix = quote! { ::tauri::utils::config::CspDirectiveSources };
4682
4683 tokens.append_all(match self {
4684 Self::Inline(sources) => {
4685 let sources = sources.as_str();
4686 quote!(#prefix::Inline(#sources.into()))
4687 }
4688 Self::List(list) => {
4689 let list = vec_lit(list, str_lit);
4690 quote!(#prefix::List(#list))
4691 }
4692 })
4693 }
4694 }
4695
4696 impl ToTokens for Csp {
4697 fn to_tokens(&self, tokens: &mut TokenStream) {
4698 let prefix = quote! { ::tauri::utils::config::Csp };
4699
4700 tokens.append_all(match self {
4701 Self::Policy(policy) => {
4702 let policy = policy.as_str();
4703 quote!(#prefix::Policy(#policy.into()))
4704 }
4705 Self::DirectiveMap(list) => {
4706 let mut sorted: Vec<_> = list.iter().collect();
4710 sorted.sort_by_key(|(k, _)| *k);
4711 let map = map_lit(
4712 quote! { ::std::collections::HashMap },
4713 sorted,
4714 str_lit,
4715 identity,
4716 );
4717 quote!(#prefix::DirectiveMap(#map))
4718 }
4719 })
4720 }
4721 }
4722
4723 impl ToTokens for DisabledCspModificationKind {
4724 fn to_tokens(&self, tokens: &mut TokenStream) {
4725 let prefix = quote! { ::tauri::utils::config::DisabledCspModificationKind };
4726
4727 tokens.append_all(match self {
4728 Self::Flag(flag) => {
4729 quote! { #prefix::Flag(#flag) }
4730 }
4731 Self::List(directives) => {
4732 let directives = vec_lit(directives, str_lit);
4733 quote! { #prefix::List(#directives) }
4734 }
4735 });
4736 }
4737 }
4738
4739 impl ToTokens for CapabilityEntry {
4740 fn to_tokens(&self, tokens: &mut TokenStream) {
4741 let prefix = quote! { ::tauri::utils::config::CapabilityEntry };
4742
4743 tokens.append_all(match self {
4744 Self::Inlined(capability) => {
4745 quote! { #prefix::Inlined(#capability) }
4746 }
4747 Self::Reference(id) => {
4748 let id = str_lit(id);
4749 quote! { #prefix::Reference(#id) }
4750 }
4751 });
4752 }
4753 }
4754
4755 impl ToTokens for HeaderSource {
4756 fn to_tokens(&self, tokens: &mut TokenStream) {
4757 let prefix = quote! { ::tauri::utils::config::HeaderSource };
4758
4759 tokens.append_all(match self {
4760 Self::Inline(s) => {
4761 let line = s.as_str();
4762 quote!(#prefix::Inline(#line.into()))
4763 }
4764 Self::List(l) => {
4765 let list = vec_lit(l, str_lit);
4766 quote!(#prefix::List(#list))
4767 }
4768 Self::Map(m) => {
4769 let mut sorted: Vec<_> = m.iter().collect();
4773 sorted.sort_by_key(|(k, _)| *k);
4774 let map = map_lit(
4775 quote! { ::std::collections::HashMap },
4776 sorted,
4777 str_lit,
4778 str_lit,
4779 );
4780 quote!(#prefix::Map(#map))
4781 }
4782 })
4783 }
4784 }
4785
4786 impl ToTokens for HeaderConfig {
4787 fn to_tokens(&self, tokens: &mut TokenStream) {
4788 let access_control_allow_credentials =
4789 opt_lit(self.access_control_allow_credentials.as_ref());
4790 let access_control_allow_headers = opt_lit(self.access_control_allow_headers.as_ref());
4791 let access_control_allow_methods = opt_lit(self.access_control_allow_methods.as_ref());
4792 let access_control_expose_headers = opt_lit(self.access_control_expose_headers.as_ref());
4793 let access_control_max_age = opt_lit(self.access_control_max_age.as_ref());
4794 let cross_origin_embedder_policy = opt_lit(self.cross_origin_embedder_policy.as_ref());
4795 let cross_origin_opener_policy = opt_lit(self.cross_origin_opener_policy.as_ref());
4796 let cross_origin_resource_policy = opt_lit(self.cross_origin_resource_policy.as_ref());
4797 let permissions_policy = opt_lit(self.permissions_policy.as_ref());
4798 let service_worker_allowed = opt_lit(self.service_worker_allowed.as_ref());
4799 let timing_allow_origin = opt_lit(self.timing_allow_origin.as_ref());
4800 let x_content_type_options = opt_lit(self.x_content_type_options.as_ref());
4801 let tauri_custom_header = opt_lit(self.tauri_custom_header.as_ref());
4802
4803 literal_struct!(
4804 tokens,
4805 ::tauri::utils::config::HeaderConfig,
4806 access_control_allow_credentials,
4807 access_control_allow_headers,
4808 access_control_allow_methods,
4809 access_control_expose_headers,
4810 access_control_max_age,
4811 cross_origin_embedder_policy,
4812 cross_origin_opener_policy,
4813 cross_origin_resource_policy,
4814 permissions_policy,
4815 service_worker_allowed,
4816 timing_allow_origin,
4817 x_content_type_options,
4818 tauri_custom_header
4819 );
4820 }
4821 }
4822
4823 impl ToTokens for SecurityConfig {
4824 fn to_tokens(&self, tokens: &mut TokenStream) {
4825 let csp = opt_lit(self.csp.as_ref());
4826 let dev_csp = opt_lit(self.dev_csp.as_ref());
4827 let freeze_prototype = self.freeze_prototype;
4828 let dangerous_disable_asset_csp_modification = &self.dangerous_disable_asset_csp_modification;
4829 let asset_protocol = &self.asset_protocol;
4830 let pattern = &self.pattern;
4831 let capabilities = vec_lit(&self.capabilities, identity);
4832 let headers = opt_lit(self.headers.as_ref());
4833
4834 literal_struct!(
4835 tokens,
4836 ::tauri::utils::config::SecurityConfig,
4837 csp,
4838 dev_csp,
4839 freeze_prototype,
4840 dangerous_disable_asset_csp_modification,
4841 asset_protocol,
4842 pattern,
4843 capabilities,
4844 headers
4845 );
4846 }
4847 }
4848
4849 impl ToTokens for TrayIconConfig {
4850 fn to_tokens(&self, tokens: &mut TokenStream) {
4851 tokens.append_all(quote!(#[allow(deprecated)]));
4853
4854 let id = opt_str_lit(self.id.as_ref());
4855 let icon_as_template = self.icon_as_template;
4856 #[allow(deprecated)]
4857 let menu_on_left_click = self.menu_on_left_click;
4858 let show_menu_on_left_click = self.show_menu_on_left_click;
4859 let icon_path = path_buf_lit(&self.icon_path);
4860 let title = opt_str_lit(self.title.as_ref());
4861 let tooltip = opt_str_lit(self.tooltip.as_ref());
4862 literal_struct!(
4863 tokens,
4864 ::tauri::utils::config::TrayIconConfig,
4865 id,
4866 icon_path,
4867 icon_as_template,
4868 menu_on_left_click,
4869 show_menu_on_left_click,
4870 title,
4871 tooltip
4872 );
4873 }
4874 }
4875
4876 impl ToTokens for FsScope {
4877 fn to_tokens(&self, tokens: &mut TokenStream) {
4878 let prefix = quote! { ::tauri::utils::config::FsScope };
4879
4880 tokens.append_all(match self {
4881 Self::AllowedPaths(allow) => {
4882 let allowed_paths = vec_lit(allow, path_buf_lit);
4883 quote! { #prefix::AllowedPaths(#allowed_paths) }
4884 }
4885 Self::Scope { allow, deny , require_literal_leading_dot} => {
4886 let allow = vec_lit(allow, path_buf_lit);
4887 let deny = vec_lit(deny, path_buf_lit);
4888 let require_literal_leading_dot = opt_lit(require_literal_leading_dot.as_ref());
4889 quote! { #prefix::Scope { allow: #allow, deny: #deny, require_literal_leading_dot: #require_literal_leading_dot } }
4890 }
4891 });
4892 }
4893 }
4894
4895 impl ToTokens for AssetProtocolConfig {
4896 fn to_tokens(&self, tokens: &mut TokenStream) {
4897 let scope = &self.scope;
4898 tokens.append_all(quote! { ::tauri::utils::config::AssetProtocolConfig { scope: #scope, ..Default::default() } })
4899 }
4900 }
4901
4902 impl ToTokens for AppDirectoryOverrides {
4903 fn to_tokens(&self, tokens: &mut TokenStream) {
4904 let config = opt_lit_owned(self.config.as_ref().map(path_buf_lit));
4905 let data = opt_lit_owned(self.data.as_ref().map(path_buf_lit));
4906 let local_data = opt_lit_owned(self.local_data.as_ref().map(path_buf_lit));
4907 let cache = opt_lit_owned(self.cache.as_ref().map(path_buf_lit));
4908 let log = opt_lit_owned(self.log.as_ref().map(path_buf_lit));
4909
4910 literal_struct!(
4911 tokens,
4912 ::tauri::utils::config::AppDirectoryOverrides,
4913 config,
4914 data,
4915 local_data,
4916 cache,
4917 log
4918 );
4919 }
4920 }
4921
4922 impl ToTokens for AppDirectoriesOverride {
4923 fn to_tokens(&self, tokens: &mut TokenStream) {
4924 let prefix = quote! { ::tauri::utils::config::AppDirectoriesOverride };
4925
4926 tokens.append_all(match self {
4927 Self::Root(root) => {
4928 let root = path_buf_lit(root);
4929 quote! { #prefix::Root(#root) }
4930 }
4931 Self::Directories(directories) => quote! { #prefix::Directories(#directories) },
4932 })
4933 }
4934 }
4935
4936 impl ToTokens for AppConfig {
4937 fn to_tokens(&self, tokens: &mut TokenStream) {
4938 let windows = vec_lit(&self.windows, identity);
4939 let security = &self.security;
4940 let tray_icon = opt_lit(self.tray_icon.as_ref());
4941 let macos_private_api = self.macos_private_api;
4942 let with_global_tauri = self.with_global_tauri;
4943 let enable_gtk_app_id = self.enable_gtk_app_id;
4944 let app_directories_override = opt_lit(self.app_directories_override.as_ref());
4945
4946 literal_struct!(
4947 tokens,
4948 ::tauri::utils::config::AppConfig,
4949 windows,
4950 security,
4951 tray_icon,
4952 macos_private_api,
4953 with_global_tauri,
4954 enable_gtk_app_id,
4955 app_directories_override
4956 );
4957 }
4958 }
4959
4960 impl ToTokens for PluginConfig {
4961 fn to_tokens(&self, tokens: &mut TokenStream) {
4962 let mut sorted: Vec<_> = self.0.iter().collect();
4966 sorted.sort_by_key(|(k, _)| *k);
4967 let config = map_lit(
4968 quote! { ::std::collections::HashMap },
4969 sorted,
4970 str_lit,
4971 json_value_lit,
4972 );
4973 tokens.append_all(quote! { ::tauri::utils::config::PluginConfig(#config) })
4974 }
4975 }
4976
4977 impl ToTokens for Config {
4978 fn to_tokens(&self, tokens: &mut TokenStream) {
4979 let schema = quote!(None);
4980 let product_name = opt_str_lit(self.product_name.as_ref());
4981 let main_binary_name = opt_str_lit(self.main_binary_name.as_ref());
4982 let version = opt_str_lit(self.version.as_ref());
4983 let identifier = str_lit(&self.identifier);
4984 let app = &self.app;
4985 let build = &self.build;
4986 let bundle = &self.bundle;
4987 let plugins = &self.plugins;
4988
4989 literal_struct!(
4990 tokens,
4991 ::tauri::utils::config::Config,
4992 schema,
4993 product_name,
4994 main_binary_name,
4995 version,
4996 identifier,
4997 app,
4998 build,
4999 bundle,
5000 plugins
5001 );
5002 }
5003 }
5004}
5005
5006#[cfg(test)]
5007mod test {
5008 use super::*;
5009
5010 #[test]
5013 fn test_defaults() {
5015 let a_config = AppConfig::default();
5017 let b_config = BuildConfig::default();
5019 let d_windows: Vec<WindowConfig> = vec![];
5021 let d_bundle = BundleConfig::default();
5023
5024 let app = AppConfig {
5026 windows: vec![],
5027 security: SecurityConfig {
5028 csp: None,
5029 dev_csp: None,
5030 freeze_prototype: false,
5031 dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),
5032 asset_protocol: AssetProtocolConfig::default(),
5033 pattern: Default::default(),
5034 capabilities: Vec::new(),
5035 headers: None,
5036 },
5037 tray_icon: None,
5038 macos_private_api: false,
5039 with_global_tauri: false,
5040 enable_gtk_app_id: false,
5041 app_directories_override: None,
5042 };
5043
5044 let build = BuildConfig {
5046 runner: None,
5047 dev_url: None,
5048 frontend_dist: None,
5049 before_dev_command: None,
5050 before_build_command: None,
5051 before_bundle_command: None,
5052 features: None,
5053 remove_unused_commands: false,
5054 additional_watch_folders: Vec::new(),
5055 windows: WindowsBuildConfig::default(),
5056 };
5057
5058 let bundle = BundleConfig {
5060 active: false,
5061 targets: Default::default(),
5062 create_updater_artifacts: Default::default(),
5063 publisher: None,
5064 homepage: None,
5065 icon: Vec::new(),
5066 resources: None,
5067 copyright: None,
5068 category: None,
5069 file_associations: None,
5070 short_description: None,
5071 long_description: None,
5072 use_local_tools_dir: false,
5073 license: None,
5074 license_file: None,
5075 linux: Default::default(),
5076 macos: Default::default(),
5077 external_bin: None,
5078 windows: Default::default(),
5079 ios: Default::default(),
5080 android: Default::default(),
5081 };
5082
5083 assert_eq!(a_config, app);
5085 assert_eq!(b_config, build);
5086 assert_eq!(d_bundle, bundle);
5087 assert_eq!(d_windows, app.windows);
5088 }
5089
5090 #[test]
5091 fn app_directories_override_root() {
5092 let config: AppDirectoriesOverride = serde_json::from_str(r#""./""#).unwrap();
5093 assert_eq!(config, AppDirectoriesOverride::Root("./".into()));
5094
5095 let config: AppDirectoriesOverride = serde_json::from_str(r#""$DATA/my-app""#).unwrap();
5096 assert_eq!(config, AppDirectoriesOverride::Root("$DATA/my-app".into()));
5097 }
5098
5099 #[test]
5100 fn app_directories_override_directories() {
5101 let config: AppDirectoriesOverride = serde_json::from_str(
5102 r#"{ "log": "$DATA/logs", "cache": "$CACHE/my-app", "local-data": "data" }"#,
5103 )
5104 .unwrap();
5105 assert_eq!(
5106 config,
5107 AppDirectoriesOverride::Directories(AppDirectoryOverrides {
5108 config: None,
5109 data: None,
5110 local_data: Some("data".into()),
5111 cache: Some("$CACHE/my-app".into()),
5112 log: Some("$DATA/logs".into()),
5113 })
5114 );
5115
5116 let config: AppDirectoriesOverride =
5117 serde_json::from_str(r#"{ "config": "conf", "data": "data", "localData": "local" }"#)
5118 .unwrap();
5119 assert_eq!(
5120 config,
5121 AppDirectoriesOverride::Directories(AppDirectoryOverrides {
5122 config: Some("conf".into()),
5123 data: Some("data".into()),
5124 local_data: Some("local".into()),
5125 cache: None,
5126 log: None,
5127 })
5128 );
5129
5130 let config: AppDirectoriesOverride = serde_json::from_str("{}").unwrap();
5131 assert_eq!(
5132 config,
5133 AppDirectoriesOverride::Directories(AppDirectoryOverrides::default())
5134 );
5135 }
5136
5137 #[test]
5138 fn app_directories_override_rejects_unknown_directories() {
5139 let err = serde_json::from_str::<AppDirectoriesOverride>(r#"{ "logs": "x" }"#).unwrap_err();
5140 assert!(err.to_string().contains("unknown field `logs`"), "{err}");
5141 }
5142
5143 #[test]
5144 fn app_directories_override_accepts_supported_variables() {
5145 for variable in APP_DIRECTORIES_OVERRIDE_VARIABLES {
5146 for path in [
5147 variable.to_string(),
5148 format!("{variable}/my-app"),
5149 format!("{variable}/../my-app"),
5150 ] {
5151 let json = serde_json::to_string(&path).unwrap();
5152 let config: AppDirectoriesOverride = serde_json::from_str(&json).unwrap();
5153 assert_eq!(config, AppDirectoriesOverride::Root(path.into()));
5154 }
5155 }
5156 }
5157
5158 #[test]
5159 fn app_directories_override_rejects_unsupported_variables() {
5160 for variable in [
5161 "$APPCONFIG",
5162 "$APPDATA",
5163 "$APPLOCALDATA",
5164 "$APPCACHE",
5165 "$APPLOG",
5166 "$EXE",
5167 "$FONT",
5168 "$RESOURCE",
5169 "$RUNTIME",
5170 "$TEMPLATE",
5171 "$UNKNOWN",
5172 ] {
5173 let err = serde_json::from_str::<AppDirectoriesOverride>(&format!(r#""{variable}/my-app""#))
5174 .unwrap_err();
5175 assert!(
5176 err
5177 .to_string()
5178 .contains(&format!("unsupported base directory variable `{variable}`")),
5179 "{variable}: {err}"
5180 );
5181
5182 let err =
5183 serde_json::from_str::<AppDirectoriesOverride>(&format!(r#"{{ "log": "{variable}" }}"#))
5184 .unwrap_err();
5185 assert!(
5186 err
5187 .to_string()
5188 .contains("unsupported base directory variable"),
5189 "{variable}: {err}"
5190 );
5191 }
5192 }
5193
5194 #[cfg(windows)]
5195 #[test]
5196 fn app_directories_override_rejects_root_relative_paths() {
5197 for path in [r"\my-app", "C:my-app"] {
5198 let json = serde_json::to_string(path).unwrap();
5199 let err = serde_json::from_str::<AppDirectoriesOverride>(&json).unwrap_err();
5200 assert!(
5201 err.to_string().contains("must be an absolute path"),
5202 "{path}: {err}"
5203 );
5204 }
5205 }
5206
5207 #[cfg(feature = "build")]
5208 #[test]
5209 fn app_directories_override_to_tokens() {
5210 use quote::ToTokens;
5211
5212 let tokens = AppDirectoriesOverride::Root("./".into())
5213 .to_token_stream()
5214 .to_string()
5215 .replace(' ', "");
5216 assert_eq!(
5217 tokens,
5218 r#"::tauri::utils::config::AppDirectoriesOverride::Root(::std::path::PathBuf::from("./"))"#
5219 );
5220
5221 let tokens = AppDirectoriesOverride::Directories(AppDirectoryOverrides {
5222 log: Some("$DATA/logs".into()),
5223 ..Default::default()
5224 })
5225 .to_token_stream()
5226 .to_string()
5227 .replace(' ', "");
5228 assert_eq!(
5229 tokens,
5230 r#"::tauri::utils::config::AppDirectoriesOverride::Directories(::tauri::utils::config::AppDirectoryOverrides{config:::core::option::Option::None,data:::core::option::Option::None,local_data:::core::option::Option::None,cache:::core::option::Option::None,log:::core::option::Option::Some(::std::path::PathBuf::from("$DATA/logs"))})"#
5231 );
5232 }
5233
5234 #[test]
5235 fn parse_hex_color() {
5236 use super::Color;
5237
5238 assert_eq!(Color(255, 255, 255, 255), "fff".parse().unwrap());
5239 assert_eq!(Color(255, 255, 255, 255), "#fff".parse().unwrap());
5240 assert_eq!(Color(0, 0, 0, 255), "#000000".parse().unwrap());
5241 assert_eq!(Color(0, 0, 0, 255), "#000000ff".parse().unwrap());
5242 assert_eq!(Color(0, 255, 0, 255), "#00ff00ff".parse().unwrap());
5243 }
5244
5245 #[test]
5246 fn test_runner_config_string_format() {
5247 use super::RunnerConfig;
5248
5249 let json = r#""cargo""#;
5251 let runner: RunnerConfig = serde_json::from_str(json).unwrap();
5252
5253 assert_eq!(runner.cmd(), "cargo");
5254 assert_eq!(runner.cwd(), None);
5255 assert_eq!(runner.args(), None);
5256
5257 let serialized = serde_json::to_string(&runner).unwrap();
5259 assert_eq!(serialized, r#""cargo""#);
5260 }
5261
5262 #[test]
5263 fn test_runner_config_object_format_full() {
5264 use super::RunnerConfig;
5265
5266 let json = r#"{"cmd": "my_runner", "cwd": "/tmp/build", "args": ["--quiet", "--verbose"]}"#;
5268 let runner: RunnerConfig = serde_json::from_str(json).unwrap();
5269
5270 assert_eq!(runner.cmd(), "my_runner");
5271 assert_eq!(runner.cwd(), Some("/tmp/build"));
5272 assert_eq!(
5273 runner.args(),
5274 Some(&["--quiet".to_string(), "--verbose".to_string()][..])
5275 );
5276
5277 let serialized = serde_json::to_string(&runner).unwrap();
5279 let deserialized: RunnerConfig = serde_json::from_str(&serialized).unwrap();
5280 assert_eq!(runner, deserialized);
5281 }
5282
5283 #[test]
5284 fn test_runner_config_object_format_minimal() {
5285 use super::RunnerConfig;
5286
5287 let json = r#"{"cmd": "cross"}"#;
5289 let runner: RunnerConfig = serde_json::from_str(json).unwrap();
5290
5291 assert_eq!(runner.cmd(), "cross");
5292 assert_eq!(runner.cwd(), None);
5293 assert_eq!(runner.args(), None);
5294 }
5295
5296 #[test]
5297 fn test_runner_config_default() {
5298 use super::RunnerConfig;
5299
5300 let default_runner = RunnerConfig::default();
5301 assert_eq!(default_runner.cmd(), "cargo");
5302 assert_eq!(default_runner.cwd(), None);
5303 assert_eq!(default_runner.args(), None);
5304 }
5305
5306 #[test]
5307 fn test_runner_config_from_str() {
5308 use super::RunnerConfig;
5309
5310 let runner: RunnerConfig = "my_runner".into();
5312 assert_eq!(runner.cmd(), "my_runner");
5313 assert_eq!(runner.cwd(), None);
5314 assert_eq!(runner.args(), None);
5315 }
5316
5317 #[test]
5318 fn test_runner_config_from_string() {
5319 use super::RunnerConfig;
5320
5321 let runner: RunnerConfig = "another_runner".to_string().into();
5323 assert_eq!(runner.cmd(), "another_runner");
5324 assert_eq!(runner.cwd(), None);
5325 assert_eq!(runner.args(), None);
5326 }
5327
5328 #[test]
5329 fn test_runner_config_from_str_parse() {
5330 use super::RunnerConfig;
5331 use std::str::FromStr;
5332
5333 let runner = RunnerConfig::from_str("parsed_runner").unwrap();
5335 assert_eq!(runner.cmd(), "parsed_runner");
5336 assert_eq!(runner.cwd(), None);
5337 assert_eq!(runner.args(), None);
5338 }
5339
5340 #[test]
5341 fn test_runner_config_in_build_config() {
5342 use super::BuildConfig;
5343
5344 let json = r#"{"runner": "cargo"}"#;
5346 let build_config: BuildConfig = serde_json::from_str(json).unwrap();
5347
5348 let runner = build_config.runner.unwrap();
5349 assert_eq!(runner.cmd(), "cargo");
5350 assert_eq!(runner.cwd(), None);
5351 assert_eq!(runner.args(), None);
5352 }
5353
5354 #[test]
5355 fn test_runner_config_in_build_config_object() {
5356 use super::BuildConfig;
5357
5358 let json = r#"{"runner": {"cmd": "cross", "cwd": "/workspace", "args": ["--target", "x86_64-unknown-linux-gnu"]}}"#;
5360 let build_config: BuildConfig = serde_json::from_str(json).unwrap();
5361
5362 let runner = build_config.runner.unwrap();
5363 assert_eq!(runner.cmd(), "cross");
5364 assert_eq!(runner.cwd(), Some("/workspace"));
5365 assert_eq!(
5366 runner.args(),
5367 Some(
5368 &[
5369 "--target".to_string(),
5370 "x86_64-unknown-linux-gnu".to_string()
5371 ][..]
5372 )
5373 );
5374 }
5375
5376 #[test]
5377 fn test_runner_config_in_full_config() {
5378 use super::Config;
5379
5380 let json = r#"{
5382 "productName": "Test App",
5383 "version": "1.0.0",
5384 "identifier": "com.test.app",
5385 "build": {
5386 "runner": {
5387 "cmd": "my_custom_cargo",
5388 "cwd": "/tmp/build",
5389 "args": ["--quiet", "--verbose"]
5390 }
5391 }
5392 }"#;
5393
5394 let config: Config = serde_json::from_str(json).unwrap();
5395 let runner = config.build.runner.unwrap();
5396
5397 assert_eq!(runner.cmd(), "my_custom_cargo");
5398 assert_eq!(runner.cwd(), Some("/tmp/build"));
5399 assert_eq!(
5400 runner.args(),
5401 Some(&["--quiet".to_string(), "--verbose".to_string()][..])
5402 );
5403 }
5404
5405 #[test]
5406 fn test_runner_config_equality() {
5407 use super::RunnerConfig;
5408
5409 let runner1 = RunnerConfig::String("cargo".to_string());
5410 let runner2 = RunnerConfig::String("cargo".to_string());
5411 let runner3 = RunnerConfig::String("cross".to_string());
5412
5413 assert_eq!(runner1, runner2);
5414 assert_ne!(runner1, runner3);
5415
5416 let runner4 = RunnerConfig::Object {
5417 cmd: "cargo".to_string(),
5418 cwd: Some("/tmp".to_string()),
5419 args: Some(vec!["--quiet".to_string()]),
5420 };
5421 let runner5 = RunnerConfig::Object {
5422 cmd: "cargo".to_string(),
5423 cwd: Some("/tmp".to_string()),
5424 args: Some(vec!["--quiet".to_string()]),
5425 };
5426
5427 assert_eq!(runner4, runner5);
5428 assert_ne!(runner1, runner4);
5429 }
5430
5431 #[test]
5432 fn test_runner_config_untagged_serialization() {
5433 use super::RunnerConfig;
5434
5435 let string_runner = RunnerConfig::String("cargo".to_string());
5437 let string_json = serde_json::to_string(&string_runner).unwrap();
5438 assert_eq!(string_json, r#""cargo""#);
5439
5440 let object_runner = RunnerConfig::Object {
5442 cmd: "cross".to_string(),
5443 cwd: None,
5444 args: None,
5445 };
5446 let object_json = serde_json::to_string(&object_runner).unwrap();
5447 assert!(object_json.contains("\"cmd\":\"cross\""));
5448 assert!(object_json.contains("\"cwd\":null") || !object_json.contains("cwd"));
5450 assert!(object_json.contains("\"args\":null") || !object_json.contains("args"));
5451 }
5452
5453 #[test]
5454 fn header_source_map_display_is_deterministic() {
5455 let map = HashMap::from([
5456 ("key3".to_string(), "'value3'".to_string()),
5457 ("key1".to_string(), "'value1' 'value2'".to_string()),
5458 ("key2".to_string(), "'value4'".to_string()),
5459 ]);
5460
5461 assert_eq!(
5463 HeaderSource::Map(map.clone()).to_string(),
5464 "key1 'value1' 'value2'; key2 'value4'; key3 'value3'"
5465 );
5466
5467 let expected = HeaderSource::Map(map).to_string();
5468 for _ in 0..10 {
5469 let map = HashMap::from([
5470 ("key2".to_string(), "'value4'".to_string()),
5471 ("key3".to_string(), "'value3'".to_string()),
5472 ("key1".to_string(), "'value1' 'value2'".to_string()),
5473 ]);
5474 assert_eq!(HeaderSource::Map(map).to_string(), expected);
5475 }
5476
5477 let map = HashMap::from([
5479 ("b".to_string(), "2".to_string()),
5480 ("a".to_string(), "1".to_string()),
5481 ]);
5482 assert_eq!(
5483 serde_json::to_string(&HeaderSource::Map(map)).unwrap(),
5484 r#"{"a":"1","b":"2"}"#
5485 );
5486 }
5487
5488 #[test]
5489 fn header_source_display() {
5490 assert_eq!(
5491 HeaderSource::Inline("same-origin".into()).to_string(),
5492 "same-origin"
5493 );
5494 assert_eq!(
5495 HeaderSource::List(vec!["https://a.example".into(), "https://b.example".into()]).to_string(),
5496 "https://a.example, https://b.example"
5497 );
5498 }
5499
5500 #[test]
5501 fn window_config_default_same_as_deserialize() {
5502 let config_from_deserialization: WindowConfig = serde_json::from_str("{}").unwrap();
5503 let config_from_default: WindowConfig = WindowConfig::default();
5504
5505 assert_eq!(config_from_deserialization, config_from_default);
5506 }
5507}