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")]
1065 pub allow_downgrades: bool,
1066 #[serde(alias = "minimum-webview2-version")]
1070 pub minimum_webview2_version: Option<String>,
1071 pub wix: Option<WixConfig>,
1073 pub nsis: Option<NsisConfig>,
1075 #[serde(alias = "sign-command")]
1083 pub sign_command: Option<CustomSignCommandConfig>,
1084 #[serde(
1091 default,
1092 rename = "bundleVCRuntime",
1093 alias = "bundle-vc-runtime",
1094 alias = "bundleVcRuntime"
1095 )]
1096 pub bundle_vc_runtime: bool,
1097}
1098
1099impl Default for WindowsConfig {
1100 fn default() -> Self {
1101 Self {
1102 digest_algorithm: None,
1103 certificate_thumbprint: None,
1104 timestamp_url: None,
1105 tsp: false,
1106 webview_install_mode: Default::default(),
1107 allow_downgrades: true,
1108 minimum_webview2_version: None,
1109 wix: None,
1110 nsis: None,
1111 sign_command: None,
1112 bundle_vc_runtime: false,
1113 }
1114 }
1115}
1116
1117#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1119#[cfg_attr(feature = "schema", derive(JsonSchema))]
1120pub enum BundleTypeRole {
1121 #[default]
1123 Editor,
1124 Viewer,
1126 Shell,
1128 QLGenerator,
1130 None,
1132}
1133
1134impl Display for BundleTypeRole {
1135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1136 match self {
1137 Self::Editor => write!(f, "Editor"),
1138 Self::Viewer => write!(f, "Viewer"),
1139 Self::Shell => write!(f, "Shell"),
1140 Self::QLGenerator => write!(f, "QLGenerator"),
1141 Self::None => write!(f, "None"),
1142 }
1143 }
1144}
1145
1146#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1150#[cfg_attr(feature = "schema", derive(JsonSchema))]
1151pub enum HandlerRank {
1152 #[default]
1154 Default,
1155 Owner,
1157 Alternate,
1159 None,
1161}
1162
1163impl Display for HandlerRank {
1164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1165 match self {
1166 Self::Default => write!(f, "Default"),
1167 Self::Owner => write!(f, "Owner"),
1168 Self::Alternate => write!(f, "Alternate"),
1169 Self::None => write!(f, "None"),
1170 }
1171 }
1172}
1173
1174#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
1178#[cfg_attr(feature = "schema", derive(JsonSchema))]
1179pub struct AssociationExt(pub String);
1180
1181impl fmt::Display for AssociationExt {
1182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1183 write!(f, "{}", self.0)
1184 }
1185}
1186
1187impl<'d> serde::Deserialize<'d> for AssociationExt {
1188 fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
1189 let ext = String::deserialize(deserializer)?;
1190 if let Some(ext) = ext.strip_prefix('.') {
1191 Ok(AssociationExt(ext.into()))
1192 } else {
1193 Ok(AssociationExt(ext))
1194 }
1195 }
1196}
1197
1198#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1200#[cfg_attr(feature = "schema", derive(JsonSchema))]
1201#[serde(rename_all = "camelCase", deny_unknown_fields)]
1202pub struct FileAssociation {
1203 pub ext: Vec<AssociationExt>,
1205 #[serde(alias = "content-types")]
1210 pub content_types: Option<Vec<String>>,
1211 pub name: Option<String>,
1213 pub description: Option<String>,
1215 #[serde(default)]
1217 pub role: BundleTypeRole,
1218 #[serde(alias = "mime-type")]
1226 pub mime_type: Option<String>,
1227 #[serde(default)]
1229 pub rank: HandlerRank,
1230 pub exported_type: Option<ExportedFileAssociation>,
1234 #[serde(alias = "android-intent-action-filters")]
1238 pub android_intent_action_filters: Option<Vec<AndroidIntentAction>>,
1239}
1240
1241#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Hash)]
1243#[cfg_attr(feature = "schema", derive(JsonSchema))]
1244#[serde(rename_all = "camelCase")]
1245#[non_exhaustive]
1246pub enum AndroidIntentAction {
1247 Send,
1251 SendMultiple,
1255 View,
1259}
1260
1261#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1263#[cfg_attr(feature = "schema", derive(JsonSchema))]
1264#[serde(rename_all = "camelCase", deny_unknown_fields)]
1265pub struct ExportedFileAssociation {
1266 pub identifier: String,
1268 #[serde(alias = "conforms-to")]
1272 pub conforms_to: Option<Vec<String>>,
1273}
1274
1275impl FileAssociation {
1276 pub fn infer_content_types(&self) -> HashSet<String> {
1283 let mut content_types = HashSet::new();
1284
1285 if let Some(exported_type) = &self.exported_type {
1287 content_types.insert(exported_type.identifier.clone());
1288 return content_types;
1289 }
1290
1291 if let Some(explicit_types) = &self.content_types {
1293 content_types.extend(explicit_types.iter().cloned());
1294 }
1295
1296 for ext in &self.ext {
1298 if let Some(uti) = extension_to_uti(&ext.0) {
1299 content_types.insert(uti.to_string());
1300 }
1301 }
1302
1303 if let Some(mime_type) = &self.mime_type {
1305 if let Some(uti) = mime_type_to_uti(mime_type) {
1306 content_types.insert(uti.to_string());
1307 }
1308 }
1309
1310 content_types
1311 }
1312}
1313
1314pub fn file_associations_plist(associations: &[FileAssociation]) -> Option<plist::Value> {
1320 use plist::{Dictionary, Value};
1321
1322 if associations.is_empty() {
1323 return None;
1324 }
1325
1326 let exported_associations = associations
1327 .iter()
1328 .filter_map(|association| {
1329 association.exported_type.as_ref().map(|exported_type| {
1330 let mut dict = Dictionary::new();
1331
1332 dict.insert(
1333 "UTTypeIdentifier".into(),
1334 exported_type.identifier.clone().into(),
1335 );
1336 if let Some(description) = &association.description {
1337 dict.insert("UTTypeDescription".into(), description.clone().into());
1338 }
1339 if let Some(conforms_to) = &exported_type.conforms_to {
1340 dict.insert(
1341 "UTTypeConformsTo".into(),
1342 Value::Array(conforms_to.iter().map(|s| s.clone().into()).collect()),
1343 );
1344 }
1345
1346 let mut specification = Dictionary::new();
1347 specification.insert(
1348 "public.filename-extension".into(),
1349 Value::Array(
1350 association
1351 .ext
1352 .iter()
1353 .map(|s| s.to_string().into())
1354 .collect(),
1355 ),
1356 );
1357 if let Some(mime_type) = &association.mime_type {
1358 specification.insert("public.mime-type".into(), mime_type.clone().into());
1359 }
1360
1361 dict.insert("UTTypeTagSpecification".into(), specification.into());
1362
1363 Value::Dictionary(dict)
1364 })
1365 })
1366 .collect::<Vec<_>>();
1367
1368 let document_types = associations
1369 .iter()
1370 .map(|association| {
1371 let mut dict = Dictionary::new();
1372
1373 if !association.ext.is_empty() {
1374 dict.insert(
1375 "CFBundleTypeExtensions".into(),
1376 Value::Array(
1377 association
1378 .ext
1379 .iter()
1380 .map(|ext| ext.to_string().into())
1381 .collect(),
1382 ),
1383 );
1384 }
1385
1386 let content_types = association.infer_content_types();
1388
1389 if !content_types.is_empty() {
1391 dict.insert(
1392 "LSItemContentTypes".into(),
1393 Value::Array(content_types.iter().map(|s| s.clone().into()).collect()),
1394 );
1395 }
1396
1397 let type_name = association
1398 .name
1399 .clone()
1400 .or_else(|| association.ext.first().map(|ext| ext.0.clone()))
1401 .unwrap_or_default();
1402 dict.insert("CFBundleTypeName".into(), type_name.into());
1403 dict.insert(
1404 "CFBundleTypeRole".into(),
1405 association.role.to_string().into(),
1406 );
1407 dict.insert("LSHandlerRank".into(), association.rank.to_string().into());
1408
1409 Value::Dictionary(dict)
1410 })
1411 .collect::<Vec<_>>();
1412
1413 if exported_associations.is_empty() && document_types.is_empty() {
1414 return None;
1415 }
1416
1417 let mut plist = Dictionary::new();
1418 if !exported_associations.is_empty() {
1419 plist.insert(
1420 "UTExportedTypeDeclarations".into(),
1421 Value::Array(exported_associations),
1422 );
1423 }
1424 if !document_types.is_empty() {
1425 plist.insert("CFBundleDocumentTypes".into(), Value::Array(document_types));
1426 }
1427
1428 Some(Value::Dictionary(plist))
1429}
1430
1431fn extension_to_uti(ext: &str) -> Option<&'static str> {
1433 match ext.to_lowercase().as_str() {
1434 "png" => Some("public.png"),
1436 "jpg" | "jpeg" => Some("public.jpeg"),
1437 "gif" => Some("com.compuserve.gif"),
1438 "bmp" => Some("com.microsoft.bmp"),
1439 "tiff" | "tif" => Some("public.tiff"),
1440 "ico" => Some("com.microsoft.ico"),
1441 "heic" | "heif" => Some("public.heif-standard-image"),
1442 "webp" => Some("org.webmproject.webp"),
1443 "svg" => Some("public.svg-image"),
1444 "mp4" => Some("public.mpeg-4"),
1446 "mov" => Some("com.apple.quicktime-movie"),
1447 "avi" => Some("public.avi"),
1448 "mkv" => Some("public.mpeg-4"),
1449 "mp3" => Some("public.mp3"),
1451 "wav" => Some("com.microsoft.waveform-audio"),
1452 "aac" => Some("public.aac-audio"),
1453 "m4a" => Some("public.mpeg-4-audio"),
1454 "pdf" => Some("com.adobe.pdf"),
1456 "txt" => Some("public.plain-text"),
1457 "rtf" => Some("public.rtf"),
1458 "html" | "htm" => Some("public.html"),
1459 "json" => Some("public.json"),
1460 "xml" => Some("public.xml"),
1461 _ => None,
1462 }
1463}
1464
1465fn mime_type_to_uti(mime_type: &str) -> Option<&'static str> {
1467 match mime_type {
1468 "image/png" => Some("public.png"),
1469 "image/jpeg" | "image/jpg" => Some("public.jpeg"),
1470 "image/gif" => Some("com.compuserve.gif"),
1471 "image/bmp" => Some("com.microsoft.bmp"),
1472 "image/tiff" => Some("public.tiff"),
1473 "image/heic" | "image/heif" => Some("public.heif-standard-image"),
1474 "image/webp" => Some("org.webmproject.webp"),
1475 "image/svg+xml" => Some("public.svg-image"),
1476 mime if mime.starts_with("image/") => Some("public.image"),
1477 "video/mp4" => Some("public.mpeg-4"),
1478 "video/quicktime" => Some("com.apple.quicktime-movie"),
1479 "video/x-msvideo" => Some("public.avi"),
1480 mime if mime.starts_with("video/") => Some("public.movie"),
1481 "audio/mpeg" | "audio/mp3" => Some("public.mp3"),
1482 "audio/wav" | "audio/wave" => Some("com.microsoft.waveform-audio"),
1483 "audio/aac" => Some("public.aac-audio"),
1484 "audio/mp4" => Some("public.mpeg-4-audio"),
1485 mime if mime.starts_with("audio/") => Some("public.audio"),
1486 "application/pdf" => Some("com.adobe.pdf"),
1487 "text/plain" => Some("public.plain-text"),
1488 "text/rtf" => Some("public.rtf"),
1489 "text/html" => Some("public.html"),
1490 "application/json" => Some("public.json"),
1491 "application/xml" | "text/xml" => Some("public.xml"),
1492 _ => None,
1493 }
1494}
1495
1496#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1498#[cfg_attr(feature = "schema", derive(JsonSchema))]
1499#[serde(rename_all = "camelCase", deny_unknown_fields)]
1500pub struct DeepLinkProtocol {
1501 #[serde(default)]
1503 pub schemes: Vec<String>,
1504 #[serde(default)]
1512 pub domains: Vec<String>,
1513 pub name: Option<String>,
1515 #[serde(default)]
1517 pub role: BundleTypeRole,
1518}
1519
1520#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1523#[cfg_attr(feature = "schema", derive(JsonSchema))]
1524#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1525pub enum BundleResources {
1526 List(Vec<String>),
1528 Map(HashMap<String, String>),
1530}
1531
1532impl BundleResources {
1533 pub fn push(&mut self, path: impl Into<String>) {
1535 match self {
1536 Self::List(l) => l.push(path.into()),
1537 Self::Map(l) => {
1538 let path = path.into();
1539 l.insert(path.clone(), path);
1540 }
1541 }
1542 }
1543}
1544
1545#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1547#[cfg_attr(feature = "schema", derive(JsonSchema))]
1548#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
1549pub enum Updater {
1550 String(V1Compatible),
1552 Bool(bool),
1555}
1556
1557impl Default for Updater {
1558 fn default() -> Self {
1559 Self::Bool(false)
1560 }
1561}
1562
1563#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1565#[cfg_attr(feature = "schema", derive(JsonSchema))]
1566#[serde(rename_all = "camelCase", deny_unknown_fields)]
1567pub enum V1Compatible {
1568 V1Compatible,
1570}
1571
1572#[skip_serializing_none]
1576#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
1577#[cfg_attr(feature = "schema", derive(JsonSchema))]
1578#[serde(rename_all = "camelCase", deny_unknown_fields)]
1579pub struct BundleConfig {
1580 #[serde(default)]
1582 pub active: bool,
1583 #[serde(default)]
1585 pub targets: BundleTarget,
1586 #[serde(default)]
1587 pub create_updater_artifacts: Updater,
1589 pub publisher: Option<String>,
1594 pub homepage: Option<String>,
1599 #[serde(default)]
1601 pub icon: Vec<String>,
1602 pub resources: Option<BundleResources>,
1647 pub copyright: Option<String>,
1649 pub license: Option<String>,
1652 #[serde(alias = "license-file")]
1654 pub license_file: Option<PathBuf>,
1655 pub category: Option<String>,
1660 pub file_associations: Option<Vec<FileAssociation>>,
1662 #[serde(alias = "short-description")]
1664 pub short_description: Option<String>,
1665 #[serde(alias = "long-description")]
1667 pub long_description: Option<String>,
1668 #[serde(default, alias = "use-local-tools-dir")]
1676 pub use_local_tools_dir: bool,
1677 #[serde(alias = "external-bin")]
1689 pub external_bin: Option<Vec<String>>,
1690 #[serde(default)]
1692 pub windows: WindowsConfig,
1693 #[serde(default)]
1695 pub linux: LinuxConfig,
1696 #[serde(rename = "macOS", alias = "macos", default)]
1698 pub macos: MacConfig,
1699 #[serde(rename = "iOS", alias = "ios", default)]
1701 pub ios: IosConfig,
1702 #[serde(default)]
1704 pub android: AndroidConfig,
1705}
1706
1707#[derive(Debug, PartialEq, Eq, Serialize, Default, Clone, Copy)]
1709#[serde(rename_all = "camelCase", deny_unknown_fields)]
1710pub struct Color(pub u8, pub u8, pub u8, pub u8);
1711
1712impl From<Color> for (u8, u8, u8, u8) {
1713 fn from(value: Color) -> Self {
1714 (value.0, value.1, value.2, value.3)
1715 }
1716}
1717
1718impl From<Color> for (u8, u8, u8) {
1719 fn from(value: Color) -> Self {
1720 (value.0, value.1, value.2)
1721 }
1722}
1723
1724impl From<(u8, u8, u8, u8)> for Color {
1725 fn from(value: (u8, u8, u8, u8)) -> Self {
1726 Color(value.0, value.1, value.2, value.3)
1727 }
1728}
1729
1730impl From<(u8, u8, u8)> for Color {
1731 fn from(value: (u8, u8, u8)) -> Self {
1732 Color(value.0, value.1, value.2, 255)
1733 }
1734}
1735
1736impl From<Color> for [u8; 4] {
1737 fn from(value: Color) -> Self {
1738 [value.0, value.1, value.2, value.3]
1739 }
1740}
1741
1742impl From<Color> for [u8; 3] {
1743 fn from(value: Color) -> Self {
1744 [value.0, value.1, value.2]
1745 }
1746}
1747
1748impl From<[u8; 4]> for Color {
1749 fn from(value: [u8; 4]) -> Self {
1750 Color(value[0], value[1], value[2], value[3])
1751 }
1752}
1753
1754impl From<[u8; 3]> for Color {
1755 fn from(value: [u8; 3]) -> Self {
1756 Color(value[0], value[1], value[2], 255)
1757 }
1758}
1759
1760impl FromStr for Color {
1761 type Err = String;
1762 fn from_str(mut color: &str) -> Result<Self, Self::Err> {
1763 color = color.trim().strip_prefix('#').unwrap_or(color);
1764 let color = match color.len() {
1765 3 => color.chars()
1766 .flat_map(|c| std::iter::repeat_n(c, 2))
1767 .chain(std::iter::repeat_n('f', 2))
1768 .collect(),
1769 6 => format!("{color}FF"),
1770 8 => color.to_string(),
1771 _ => return Err("Invalid hex color length, must be either 3, 6 or 8, for example: #fff, #ffffff, or #ffffffff".into()),
1772 };
1773
1774 let r = u8::from_str_radix(&color[0..2], 16).map_err(|e| e.to_string())?;
1775 let g = u8::from_str_radix(&color[2..4], 16).map_err(|e| e.to_string())?;
1776 let b = u8::from_str_radix(&color[4..6], 16).map_err(|e| e.to_string())?;
1777 let a = u8::from_str_radix(&color[6..8], 16).map_err(|e| e.to_string())?;
1778
1779 Ok(Color(r, g, b, a))
1780 }
1781}
1782
1783fn default_alpha() -> u8 {
1784 255
1785}
1786
1787#[derive(Deserialize)]
1788#[cfg_attr(feature = "schema", derive(JsonSchema))]
1789#[serde(untagged)]
1790enum InnerColor {
1791 String(String),
1793 Rgb((u8, u8, u8)),
1795 Rgba((u8, u8, u8, u8)),
1797 RgbaObject {
1799 red: u8,
1800 green: u8,
1801 blue: u8,
1802 #[serde(default = "default_alpha")]
1803 alpha: u8,
1804 },
1805}
1806
1807impl<'de> Deserialize<'de> for Color {
1808 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1809 where
1810 D: Deserializer<'de>,
1811 {
1812 let color = InnerColor::deserialize(deserializer)?;
1813 let color = match color {
1814 InnerColor::String(string) => string.parse().map_err(serde::de::Error::custom)?,
1815 InnerColor::Rgb(rgb) => Color(rgb.0, rgb.1, rgb.2, 255),
1816 InnerColor::Rgba(rgb) => rgb.into(),
1817 InnerColor::RgbaObject {
1818 red,
1819 green,
1820 blue,
1821 alpha,
1822 } => Color(red, green, blue, alpha),
1823 };
1824
1825 Ok(color)
1826 }
1827}
1828
1829#[cfg(feature = "schema")]
1830impl schemars::JsonSchema for Color {
1831 fn schema_name() -> String {
1832 "Color".to_string()
1833 }
1834
1835 fn json_schema(_generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
1836 let mut schema = schemars::schema_for!(InnerColor).schema;
1837 schema.metadata = None; let any_of = schema.subschemas().any_of.as_mut().unwrap();
1841 let schemars::schema::Schema::Object(str_schema) = any_of.first_mut().unwrap() else {
1842 unreachable!()
1843 };
1844 str_schema.string().pattern = Some("^#?([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$".into());
1845
1846 schema.into()
1847 }
1848}
1849
1850#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
1852#[cfg_attr(feature = "schema", derive(JsonSchema))]
1853#[serde(rename_all = "camelCase", deny_unknown_fields)]
1854pub enum BackgroundThrottlingPolicy {
1855 Disabled,
1857 Suspend,
1859 Throttle,
1861}
1862
1863#[skip_serializing_none]
1865#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
1866#[cfg_attr(feature = "schema", derive(JsonSchema))]
1867#[serde(rename_all = "camelCase", deny_unknown_fields)]
1868pub struct WindowEffectsConfig {
1869 pub effects: Vec<WindowEffect>,
1875 pub state: Option<WindowEffectState>,
1877 pub radius: Option<f64>,
1879 pub color: Option<Color>,
1887 #[serde(default)]
1891 pub interactive: bool,
1892}
1893
1894#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
1897#[cfg_attr(feature = "schema", derive(JsonSchema))]
1898#[serde(rename_all = "camelCase", deny_unknown_fields)]
1899pub struct PreventOverflowMargin {
1900 pub width: u32,
1902 pub height: u32,
1904}
1905
1906#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
1908#[cfg_attr(feature = "schema", derive(JsonSchema))]
1909#[serde(untagged)]
1910pub enum PreventOverflowConfig {
1911 Enable(bool),
1913 Margin(PreventOverflowMargin),
1916}
1917
1918#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
1924#[cfg_attr(feature = "schema", derive(JsonSchema))]
1925#[serde(rename_all = "camelCase", deny_unknown_fields)]
1926#[non_exhaustive]
1927pub enum ScrollBarStyle {
1928 #[default]
1929 Default,
1933
1934 FluentOverlay,
1939}
1940
1941#[skip_serializing_none]
1945#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
1946#[cfg_attr(feature = "schema", derive(JsonSchema))]
1947#[serde(rename_all = "camelCase", deny_unknown_fields)]
1948pub struct WindowConfig {
1949 #[serde(default = "default_window_label")]
1951 pub label: String,
1952 #[serde(default = "default_true")]
1967 pub create: bool,
1968 #[serde(default)]
1970 pub url: WebviewUrl,
1971 #[serde(alias = "user-agent")]
1973 pub user_agent: Option<String>,
1974 #[serde(default = "default_true", alias = "drag-drop-enabled")]
1984 pub drag_drop_enabled: bool,
1985 #[serde(default)]
1987 pub center: bool,
1988 pub x: Option<f64>,
1990 pub y: Option<f64>,
1992 #[serde(default = "default_width")]
1994 pub width: f64,
1995 #[serde(default = "default_height")]
1997 pub height: f64,
1998 #[serde(alias = "min-width")]
2000 pub min_width: Option<f64>,
2001 #[serde(alias = "min-height")]
2003 pub min_height: Option<f64>,
2004 #[serde(alias = "max-width")]
2006 pub max_width: Option<f64>,
2007 #[serde(alias = "max-height")]
2009 pub max_height: Option<f64>,
2010 #[serde(alias = "prevent-overflow")]
2016 pub prevent_overflow: Option<PreventOverflowConfig>,
2017 #[serde(default = "default_true")]
2019 pub resizable: bool,
2020 #[serde(default = "default_true")]
2028 pub maximizable: bool,
2029 #[serde(default = "default_true")]
2035 pub minimizable: bool,
2036 #[serde(default = "default_true")]
2044 pub closable: bool,
2045 #[serde(default = "default_title")]
2047 pub title: String,
2048 #[serde(default)]
2050 pub fullscreen: bool,
2051 #[serde(default = "default_true")]
2053 pub focus: bool,
2054 #[serde(default = "default_true")]
2056 pub focusable: bool,
2057 #[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)]
3377 pub macos_private_api: bool,
3378 #[serde(default, alias = "with-global-tauri")]
3380 pub with_global_tauri: bool,
3381 #[serde(rename = "enableGTKAppId", alias = "enable-gtk-app-id", default)]
3397 pub enable_gtk_app_id: bool,
3398 #[serde(alias = "app-directories-override")]
3509 pub app_directories_override: Option<AppDirectoriesOverride>,
3510}
3511
3512impl AppConfig {
3513 pub fn all_features() -> Vec<&'static str> {
3515 vec![
3516 "tray-icon",
3517 "macos-private-api",
3518 "protocol-asset",
3519 "isolation",
3520 ]
3521 }
3522
3523 pub fn features(&self) -> Vec<&str> {
3525 let mut features = Vec::new();
3526 if self.tray_icon.is_some() {
3527 features.push("tray-icon");
3528 }
3529 if self.macos_private_api {
3530 features.push("macos-private-api");
3531 }
3532 if self.security.asset_protocol.enable {
3533 features.push("protocol-asset");
3534 }
3535
3536 if let PatternKind::Isolation { .. } = self.security.pattern {
3537 features.push("isolation");
3538 }
3539
3540 features.sort_unstable();
3541 features
3542 }
3543}
3544
3545#[skip_serializing_none]
3549#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
3550#[cfg_attr(feature = "schema", derive(JsonSchema))]
3551#[serde(rename_all = "camelCase", deny_unknown_fields)]
3552pub struct TrayIconConfig {
3553 pub id: Option<String>,
3555 #[serde(alias = "icon-path")]
3561 pub icon_path: PathBuf,
3562 #[serde(default, alias = "icon-as-template")]
3564 pub icon_as_template: bool,
3565 #[serde(default = "default_true", alias = "menu-on-left-click")]
3573 #[deprecated(
3574 since = "2.2.0",
3575 note = "No longer works, use `show_menu_on_left_click` instead."
3576 )]
3577 pub menu_on_left_click: bool,
3578 #[serde(default = "default_true", alias = "show-menu-on-left-click")]
3584 pub show_menu_on_left_click: bool,
3585 pub title: Option<String>,
3587 pub tooltip: Option<String>,
3589}
3590
3591#[skip_serializing_none]
3593#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3594#[cfg_attr(feature = "schema", derive(JsonSchema))]
3595#[serde(rename_all = "camelCase", deny_unknown_fields)]
3596pub struct IosConfig {
3597 pub template: Option<PathBuf>,
3601 pub frameworks: Option<Vec<String>>,
3605 #[serde(alias = "development-team")]
3608 pub development_team: Option<String>,
3609 #[serde(alias = "bundle-version")]
3613 pub bundle_version: Option<String>,
3614 #[serde(
3618 alias = "minimum-system-version",
3619 default = "ios_minimum_system_version"
3620 )]
3621 pub minimum_system_version: String,
3622 #[serde(alias = "info-plist")]
3626 pub info_plist: Option<PathBuf>,
3627}
3628
3629impl Default for IosConfig {
3630 fn default() -> Self {
3631 Self {
3632 template: None,
3633 frameworks: None,
3634 development_team: None,
3635 bundle_version: None,
3636 minimum_system_version: ios_minimum_system_version(),
3637 info_plist: None,
3638 }
3639 }
3640}
3641
3642#[skip_serializing_none]
3644#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3645#[cfg_attr(feature = "schema", derive(JsonSchema))]
3646#[serde(rename_all = "camelCase", deny_unknown_fields)]
3647pub struct AndroidConfig {
3648 #[serde(alias = "min-sdk-version", default = "default_min_sdk_version")]
3651 pub min_sdk_version: u32,
3652
3653 #[serde(alias = "version-code")]
3659 #[cfg_attr(feature = "schema", validate(range(min = 1, max = 2_100_000_000)))]
3660 pub version_code: Option<u32>,
3661
3662 #[serde(alias = "auto-increment-version-code", default)]
3670 pub auto_increment_version_code: bool,
3671
3672 #[serde(alias = "debug-application-id-suffix")]
3676 pub debug_application_id_suffix: Option<String>,
3677}
3678
3679impl Default for AndroidConfig {
3680 fn default() -> Self {
3681 Self {
3682 min_sdk_version: default_min_sdk_version(),
3683 version_code: None,
3684 auto_increment_version_code: false,
3685 debug_application_id_suffix: None,
3686 }
3687 }
3688}
3689
3690fn default_min_sdk_version() -> u32 {
3691 24
3692}
3693
3694#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3696#[cfg_attr(feature = "schema", derive(JsonSchema))]
3697#[serde(untagged, deny_unknown_fields)]
3698#[non_exhaustive]
3699pub enum FrontendDist {
3700 Url(Url),
3702 Directory(PathBuf),
3704 Files(Vec<PathBuf>),
3706}
3707
3708impl std::fmt::Display for FrontendDist {
3709 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3710 match self {
3711 Self::Url(url) => write!(f, "{url}"),
3712 Self::Directory(p) => write!(f, "{}", p.display()),
3713 Self::Files(files) => write!(f, "{}", serde_json::to_string(files).unwrap()),
3714 }
3715 }
3716}
3717
3718#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3720#[cfg_attr(feature = "schema", derive(JsonSchema))]
3721#[serde(rename_all = "camelCase", untagged)]
3722pub enum BeforeDevCommand {
3723 Script(String),
3725 ScriptWithOptions {
3727 script: String,
3729 cwd: Option<String>,
3731 #[serde(default)]
3733 wait: bool,
3734 },
3735}
3736
3737#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3739#[cfg_attr(feature = "schema", derive(JsonSchema))]
3740#[serde(rename_all = "camelCase", untagged)]
3741pub enum HookCommand {
3742 Script(String),
3744 ScriptWithOptions {
3746 script: String,
3748 cwd: Option<String>,
3750 },
3751}
3752
3753#[skip_serializing_none]
3755#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
3756#[cfg_attr(feature = "schema", derive(JsonSchema))]
3757#[serde(untagged)]
3758pub enum RunnerConfig {
3759 String(String),
3761 Object {
3763 cmd: String,
3765 cwd: Option<String>,
3767 args: Option<Vec<String>>,
3769 },
3770}
3771
3772impl Default for RunnerConfig {
3773 fn default() -> Self {
3774 RunnerConfig::String("cargo".to_string())
3775 }
3776}
3777
3778impl RunnerConfig {
3779 pub fn cmd(&self) -> &str {
3781 match self {
3782 RunnerConfig::String(cmd) => cmd,
3783 RunnerConfig::Object { cmd, .. } => cmd,
3784 }
3785 }
3786
3787 pub fn cwd(&self) -> Option<&str> {
3789 match self {
3790 RunnerConfig::String(_) => None,
3791 RunnerConfig::Object { cwd, .. } => cwd.as_deref(),
3792 }
3793 }
3794
3795 pub fn args(&self) -> Option<&[String]> {
3797 match self {
3798 RunnerConfig::String(_) => None,
3799 RunnerConfig::Object { args, .. } => args.as_deref(),
3800 }
3801 }
3802}
3803
3804impl std::str::FromStr for RunnerConfig {
3805 type Err = std::convert::Infallible;
3806
3807 fn from_str(s: &str) -> Result<Self, Self::Err> {
3808 Ok(RunnerConfig::String(s.to_string()))
3809 }
3810}
3811
3812impl From<&str> for RunnerConfig {
3813 fn from(s: &str) -> Self {
3814 RunnerConfig::String(s.to_string())
3815 }
3816}
3817
3818impl From<String> for RunnerConfig {
3819 fn from(s: String) -> Self {
3820 RunnerConfig::String(s)
3821 }
3822}
3823
3824#[skip_serializing_none]
3828#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)]
3829#[cfg_attr(feature = "schema", derive(JsonSchema))]
3830#[serde(rename_all = "camelCase", deny_unknown_fields)]
3831pub struct BuildConfig {
3832 pub runner: Option<RunnerConfig>,
3834 #[serde(alias = "dev-url")]
3842 pub dev_url: Option<Url>,
3843 #[serde(alias = "frontend-dist")]
3857 pub frontend_dist: Option<FrontendDist>,
3858 #[serde(alias = "before-dev-command")]
3865 pub before_dev_command: Option<BeforeDevCommand>,
3866 #[serde(alias = "before-build-command")]
3873 pub before_build_command: Option<HookCommand>,
3874 #[serde(alias = "before-bundle-command")]
3881 pub before_bundle_command: Option<HookCommand>,
3882 pub features: Option<Vec<String>>,
3884 #[serde(alias = "remove-unused-commands", default)]
3892 pub remove_unused_commands: bool,
3893 #[serde(
3895 alias = "additional-watch-folders",
3896 alias = "additional-watch-directories",
3897 default
3898 )]
3899 pub additional_watch_folders: Vec<PathBuf>,
3900 #[serde(default)]
3902 pub windows: WindowsBuildConfig,
3903}
3904
3905#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
3907#[cfg_attr(feature = "schema", derive(JsonSchema))]
3908#[serde(rename_all = "camelCase", deny_unknown_fields)]
3909pub struct WindowsBuildConfig {
3910 #[serde(
3912 default = "default_true",
3913 rename = "staticVCRuntime",
3914 alias = "static-vc-runtime",
3915 alias = "staticVcRuntime"
3916 )]
3917 pub static_vc_runtime: bool,
3918}
3919
3920impl Default for WindowsBuildConfig {
3921 fn default() -> Self {
3922 Self {
3923 static_vc_runtime: true,
3924 }
3925 }
3926}
3927
3928#[derive(Debug, PartialEq, Eq)]
3929struct PackageVersion(String);
3930
3931impl<'d> serde::Deserialize<'d> for PackageVersion {
3932 fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
3933 struct PackageVersionVisitor;
3934
3935 impl Visitor<'_> for PackageVersionVisitor {
3936 type Value = PackageVersion;
3937
3938 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
3939 write!(
3940 formatter,
3941 "a semver string or a path to a package.json file"
3942 )
3943 }
3944
3945 fn visit_str<E: DeError>(self, value: &str) -> Result<PackageVersion, E> {
3946 let path = PathBuf::from(value);
3947 if path.exists() {
3948 let json_str = read_to_string(&path)
3949 .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
3950 let package_json: serde_json::Value = serde_json::from_str(&json_str)
3951 .map_err(|e| DeError::custom(format!("failed to read version JSON file: {e}")))?;
3952 if let Some(obj) = package_json.as_object() {
3953 let version = obj
3954 .get("version")
3955 .ok_or_else(|| DeError::custom("JSON must contain a `version` field"))?
3956 .as_str()
3957 .ok_or_else(|| {
3958 DeError::custom(format!("`{} > version` must be a string", path.display()))
3959 })?;
3960 Ok(PackageVersion(
3961 Version::from_str(version)
3962 .map_err(|_| {
3963 DeError::custom("`tauri.conf.json > version` must be a semver string")
3964 })?
3965 .to_string(),
3966 ))
3967 } else {
3968 Err(DeError::custom(
3969 "`tauri.conf.json > version` value is not a path to a JSON object",
3970 ))
3971 }
3972 } else {
3973 Ok(PackageVersion(
3974 Version::from_str(value)
3975 .map_err(|_| DeError::custom("`tauri.conf.json > version` must be a semver string"))?
3976 .to_string(),
3977 ))
3978 }
3979 }
3980 }
3981
3982 deserializer.deserialize_string(PackageVersionVisitor {})
3983 }
3984}
3985
3986fn version_deserializer<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
3987where
3988 D: Deserializer<'de>,
3989{
3990 Option::<PackageVersion>::deserialize(deserializer).map(|v| v.map(|v| v.0))
3991}
3992
3993#[skip_serializing_none]
4059#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
4060#[cfg_attr(feature = "schema", derive(JsonSchema))]
4061#[serde(rename_all = "camelCase", deny_unknown_fields)]
4062pub struct Config {
4063 #[serde(rename = "$schema")]
4065 pub schema: Option<String>,
4066 #[serde(alias = "product-name")]
4084 #[cfg_attr(feature = "schema", validate(regex(pattern = "^[^/\\:*?\"<>|]+$")))]
4085 pub product_name: Option<String>,
4086 #[serde(alias = "main-binary-name")]
4100 pub main_binary_name: Option<String>,
4101 #[serde(deserialize_with = "version_deserializer", default)]
4117 pub version: Option<String>,
4118 pub identifier: String,
4126 #[serde(default)]
4128 pub app: AppConfig,
4129 #[serde(default)]
4131 pub build: BuildConfig,
4132 #[serde(default)]
4134 pub bundle: BundleConfig,
4135 #[serde(default)]
4137 pub plugins: PluginConfig,
4138}
4139
4140#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
4144#[cfg_attr(feature = "schema", derive(JsonSchema))]
4145pub struct PluginConfig(pub HashMap<String, JsonValue>);
4146
4147impl Serialize for PluginConfig {
4148 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
4149 where
4150 S: Serializer,
4151 {
4152 let btree_map: BTreeMap<_, _> = self.0.iter().collect();
4156 btree_map.serialize(serializer)
4157 }
4158}
4159
4160#[cfg(any(feature = "build", feature = "build-2"))]
4166mod build {
4167 use super::*;
4168 use crate::{literal_struct, tokens::*};
4169 use proc_macro2::TokenStream;
4170 use quote::{ToTokens, TokenStreamExt, quote};
4171 use std::convert::identity;
4172
4173 impl ToTokens for WebviewUrl {
4174 fn to_tokens(&self, tokens: &mut TokenStream) {
4175 let prefix = quote! { ::tauri::utils::config::WebviewUrl };
4176
4177 tokens.append_all(match self {
4178 Self::App(path) => {
4179 let path = path_buf_lit(path);
4180 quote! { #prefix::App(#path) }
4181 }
4182 Self::External(url) => {
4183 let url = url_lit(url);
4184 quote! { #prefix::External(#url) }
4185 }
4186 Self::CustomProtocol(url) => {
4187 let url = url_lit(url);
4188 quote! { #prefix::CustomProtocol(#url) }
4189 }
4190 })
4191 }
4192 }
4193
4194 impl ToTokens for BackgroundThrottlingPolicy {
4195 fn to_tokens(&self, tokens: &mut TokenStream) {
4196 let prefix = quote! { ::tauri::utils::config::BackgroundThrottlingPolicy };
4197 tokens.append_all(match self {
4198 Self::Disabled => quote! { #prefix::Disabled },
4199 Self::Throttle => quote! { #prefix::Throttle },
4200 Self::Suspend => quote! { #prefix::Suspend },
4201 })
4202 }
4203 }
4204
4205 impl ToTokens for crate::Theme {
4206 fn to_tokens(&self, tokens: &mut TokenStream) {
4207 let prefix = quote! { ::tauri::utils::Theme };
4208
4209 tokens.append_all(match self {
4210 Self::Light => quote! { #prefix::Light },
4211 Self::Dark => quote! { #prefix::Dark },
4212 })
4213 }
4214 }
4215
4216 impl ToTokens for Color {
4217 fn to_tokens(&self, tokens: &mut TokenStream) {
4218 let Color(r, g, b, a) = self;
4219 tokens.append_all(quote! {::tauri::utils::config::Color(#r,#g,#b,#a)});
4220 }
4221 }
4222 impl ToTokens for WindowEffectsConfig {
4223 fn to_tokens(&self, tokens: &mut TokenStream) {
4224 let effects = vec_lit(self.effects.clone(), |d| d);
4225 let state = opt_lit(self.state.as_ref());
4226 let radius = opt_lit(self.radius.as_ref());
4227 let color = opt_lit(self.color.as_ref());
4228 let interactive = self.interactive;
4229
4230 literal_struct!(
4231 tokens,
4232 ::tauri::utils::config::WindowEffectsConfig,
4233 effects,
4234 state,
4235 radius,
4236 color,
4237 interactive
4238 )
4239 }
4240 }
4241
4242 impl ToTokens for crate::TitleBarStyle {
4243 fn to_tokens(&self, tokens: &mut TokenStream) {
4244 let prefix = quote! { ::tauri::utils::TitleBarStyle };
4245
4246 tokens.append_all(match self {
4247 Self::Visible => quote! { #prefix::Visible },
4248 Self::Transparent => quote! { #prefix::Transparent },
4249 Self::Overlay => quote! { #prefix::Overlay },
4250 })
4251 }
4252 }
4253
4254 impl ToTokens for LogicalPosition {
4255 fn to_tokens(&self, tokens: &mut TokenStream) {
4256 let LogicalPosition { x, y } = self;
4257 literal_struct!(tokens, ::tauri::utils::config::LogicalPosition, x, y)
4258 }
4259 }
4260
4261 impl ToTokens for crate::WindowEffect {
4262 fn to_tokens(&self, tokens: &mut TokenStream) {
4263 let prefix = quote! { ::tauri::utils::WindowEffect };
4264
4265 #[allow(deprecated)]
4266 tokens.append_all(match self {
4267 WindowEffect::AppearanceBased => quote! { #prefix::AppearanceBased},
4268 WindowEffect::Light => quote! { #prefix::Light},
4269 WindowEffect::Dark => quote! { #prefix::Dark},
4270 WindowEffect::MediumLight => quote! { #prefix::MediumLight},
4271 WindowEffect::UltraDark => quote! { #prefix::UltraDark},
4272 WindowEffect::Titlebar => quote! { #prefix::Titlebar},
4273 WindowEffect::Selection => quote! { #prefix::Selection},
4274 WindowEffect::Menu => quote! { #prefix::Menu},
4275 WindowEffect::Popover => quote! { #prefix::Popover},
4276 WindowEffect::Sidebar => quote! { #prefix::Sidebar},
4277 WindowEffect::HeaderView => quote! { #prefix::HeaderView},
4278 WindowEffect::Sheet => quote! { #prefix::Sheet},
4279 WindowEffect::WindowBackground => quote! { #prefix::WindowBackground},
4280 WindowEffect::HudWindow => quote! { #prefix::HudWindow},
4281 WindowEffect::FullScreenUI => quote! { #prefix::FullScreenUI},
4282 WindowEffect::Tooltip => quote! { #prefix::Tooltip},
4283 WindowEffect::ContentBackground => quote! { #prefix::ContentBackground},
4284 WindowEffect::UnderWindowBackground => quote! { #prefix::UnderWindowBackground},
4285 WindowEffect::UnderPageBackground => quote! { #prefix::UnderPageBackground},
4286 WindowEffect::LiquidGlassRegular => quote! { #prefix::LiquidGlassRegular },
4287 WindowEffect::LiquidGlassClear => quote! { #prefix::LiquidGlassClear },
4288 WindowEffect::Mica => quote! { #prefix::Mica},
4289 WindowEffect::MicaDark => quote! { #prefix::MicaDark},
4290 WindowEffect::MicaLight => quote! { #prefix::MicaLight},
4291 WindowEffect::Blur => quote! { #prefix::Blur},
4292 WindowEffect::Acrylic => quote! { #prefix::Acrylic},
4293 WindowEffect::Tabbed => quote! { #prefix::Tabbed },
4294 WindowEffect::TabbedDark => quote! { #prefix::TabbedDark },
4295 WindowEffect::TabbedLight => quote! { #prefix::TabbedLight },
4296 })
4297 }
4298 }
4299
4300 impl ToTokens for crate::WindowEffectState {
4301 fn to_tokens(&self, tokens: &mut TokenStream) {
4302 let prefix = quote! { ::tauri::utils::WindowEffectState };
4303
4304 #[allow(deprecated)]
4305 tokens.append_all(match self {
4306 WindowEffectState::Active => quote! { #prefix::Active},
4307 WindowEffectState::FollowsWindowActiveState => quote! { #prefix::FollowsWindowActiveState},
4308 WindowEffectState::Inactive => quote! { #prefix::Inactive},
4309 })
4310 }
4311 }
4312
4313 impl ToTokens for PreventOverflowMargin {
4314 fn to_tokens(&self, tokens: &mut TokenStream) {
4315 let width = self.width;
4316 let height = self.height;
4317
4318 literal_struct!(
4319 tokens,
4320 ::tauri::utils::config::PreventOverflowMargin,
4321 width,
4322 height
4323 )
4324 }
4325 }
4326
4327 impl ToTokens for PreventOverflowConfig {
4328 fn to_tokens(&self, tokens: &mut TokenStream) {
4329 let prefix = quote! { ::tauri::utils::config::PreventOverflowConfig };
4330
4331 #[allow(deprecated)]
4332 tokens.append_all(match self {
4333 Self::Enable(enable) => quote! { #prefix::Enable(#enable) },
4334 Self::Margin(margin) => quote! { #prefix::Margin(#margin) },
4335 })
4336 }
4337 }
4338
4339 impl ToTokens for ScrollBarStyle {
4340 fn to_tokens(&self, tokens: &mut TokenStream) {
4341 let prefix = quote! { ::tauri::utils::config::ScrollBarStyle };
4342
4343 tokens.append_all(match self {
4344 Self::Default => quote! { #prefix::Default },
4345 Self::FluentOverlay => quote! { #prefix::FluentOverlay },
4346 })
4347 }
4348 }
4349
4350 impl ToTokens for WindowConfig {
4351 fn to_tokens(&self, tokens: &mut TokenStream) {
4352 let label = str_lit(&self.label);
4353 let create = &self.create;
4354 let url = &self.url;
4355 let user_agent = opt_str_lit(self.user_agent.as_ref());
4356 let drag_drop_enabled = self.drag_drop_enabled;
4357 let center = self.center;
4358 let x = opt_lit(self.x.as_ref());
4359 let y = opt_lit(self.y.as_ref());
4360 let width = self.width;
4361 let height = self.height;
4362 let min_width = opt_lit(self.min_width.as_ref());
4363 let min_height = opt_lit(self.min_height.as_ref());
4364 let max_width = opt_lit(self.max_width.as_ref());
4365 let max_height = opt_lit(self.max_height.as_ref());
4366 let prevent_overflow = opt_lit(self.prevent_overflow.as_ref());
4367 let resizable = self.resizable;
4368 let maximizable = self.maximizable;
4369 let minimizable = self.minimizable;
4370 let closable = self.closable;
4371 let title = str_lit(&self.title);
4372 let proxy_url = opt_lit(self.proxy_url.as_ref().map(url_lit).as_ref());
4373 let fullscreen = self.fullscreen;
4374 let focus = self.focus;
4375 let focusable = self.focusable;
4376 let transparent = self.transparent;
4377 let maximized = self.maximized;
4378 let visible = self.visible;
4379 let decorations = self.decorations;
4380 let always_on_bottom = self.always_on_bottom;
4381 let always_on_top = self.always_on_top;
4382 let visible_on_all_workspaces = self.visible_on_all_workspaces;
4383 let content_protected = self.content_protected;
4384 let skip_taskbar = self.skip_taskbar;
4385 let window_classname = opt_str_lit(self.window_classname.as_ref());
4386 let no_redirection_bitmap = self.no_redirection_bitmap;
4387 let theme = opt_lit(self.theme.as_ref());
4388 let title_bar_style = &self.title_bar_style;
4389 let traffic_light_position = opt_lit(self.traffic_light_position.as_ref());
4390 let hidden_title = self.hidden_title;
4391 let accept_first_mouse = self.accept_first_mouse;
4392 let tabbing_identifier = opt_str_lit(self.tabbing_identifier.as_ref());
4393 let additional_browser_args = opt_str_lit(self.additional_browser_args.as_ref());
4394 let shadow = self.shadow;
4395 let window_effects = opt_lit(self.window_effects.as_ref());
4396 let incognito = self.incognito;
4397 let parent = opt_str_lit(self.parent.as_ref());
4398 let zoom_hotkeys_enabled = self.zoom_hotkeys_enabled;
4399 let browser_extensions_enabled = self.browser_extensions_enabled;
4400 let use_https_scheme = self.use_https_scheme;
4401 let devtools = opt_lit(self.devtools.as_ref());
4402 let background_color = opt_lit(self.background_color.as_ref());
4403 let background_throttling = opt_lit(self.background_throttling.as_ref());
4404 let javascript_disabled = self.javascript_disabled;
4405 let allow_link_preview = self.allow_link_preview;
4406 let disable_input_accessory_view = self.disable_input_accessory_view;
4407 let data_directory = opt_lit(self.data_directory.as_ref().map(path_buf_lit).as_ref());
4408 let data_store_identifier = opt_vec_lit(self.data_store_identifier, identity);
4409 let scroll_bar_style = &self.scroll_bar_style;
4410 let limit_navigations_to_app_bound_domains = self.limit_navigations_to_app_bound_domains;
4411 let activity_name = opt_lit(self.activity_name.as_ref());
4412 let created_by_activity_name = opt_lit(self.created_by_activity_name.as_ref());
4413 let requested_by_scene_identifier = opt_lit(self.requested_by_scene_identifier.as_ref());
4414 let general_autofill_enabled = self.general_autofill_enabled;
4415
4416 literal_struct!(
4417 tokens,
4418 ::tauri::utils::config::WindowConfig,
4419 label,
4420 url,
4421 create,
4422 user_agent,
4423 drag_drop_enabled,
4424 center,
4425 x,
4426 y,
4427 width,
4428 height,
4429 min_width,
4430 min_height,
4431 max_width,
4432 max_height,
4433 prevent_overflow,
4434 resizable,
4435 maximizable,
4436 minimizable,
4437 closable,
4438 title,
4439 proxy_url,
4440 fullscreen,
4441 focus,
4442 focusable,
4443 transparent,
4444 maximized,
4445 visible,
4446 decorations,
4447 always_on_bottom,
4448 always_on_top,
4449 visible_on_all_workspaces,
4450 content_protected,
4451 skip_taskbar,
4452 window_classname,
4453 no_redirection_bitmap,
4454 theme,
4455 title_bar_style,
4456 traffic_light_position,
4457 hidden_title,
4458 accept_first_mouse,
4459 tabbing_identifier,
4460 additional_browser_args,
4461 shadow,
4462 window_effects,
4463 incognito,
4464 parent,
4465 zoom_hotkeys_enabled,
4466 browser_extensions_enabled,
4467 use_https_scheme,
4468 devtools,
4469 background_color,
4470 background_throttling,
4471 javascript_disabled,
4472 allow_link_preview,
4473 disable_input_accessory_view,
4474 data_directory,
4475 data_store_identifier,
4476 scroll_bar_style,
4477 limit_navigations_to_app_bound_domains,
4478 activity_name,
4479 created_by_activity_name,
4480 requested_by_scene_identifier,
4481 general_autofill_enabled
4482 );
4483 }
4484 }
4485
4486 impl ToTokens for PatternKind {
4487 fn to_tokens(&self, tokens: &mut TokenStream) {
4488 let prefix = quote! { ::tauri::utils::config::PatternKind };
4489
4490 tokens.append_all(match self {
4491 Self::Brownfield => quote! { #prefix::Brownfield },
4492 #[cfg(not(feature = "isolation"))]
4493 Self::Isolation { dir: _ } => quote! { #prefix::Brownfield },
4494 #[cfg(feature = "isolation")]
4495 Self::Isolation { dir } => {
4496 let dir = path_buf_lit(dir);
4497 quote! { #prefix::Isolation { dir: #dir } }
4498 }
4499 })
4500 }
4501 }
4502
4503 impl ToTokens for WebviewInstallMode {
4504 fn to_tokens(&self, tokens: &mut TokenStream) {
4505 let prefix = quote! { ::tauri::utils::config::WebviewInstallMode };
4506
4507 tokens.append_all(match self {
4508 Self::Skip => quote! { #prefix::Skip },
4509 Self::DownloadBootstrapper { silent } => {
4510 quote! { #prefix::DownloadBootstrapper { silent: #silent } }
4511 }
4512 Self::EmbedBootstrapper { silent } => {
4513 quote! { #prefix::EmbedBootstrapper { silent: #silent } }
4514 }
4515 Self::OfflineInstaller { silent } => {
4516 quote! { #prefix::OfflineInstaller { silent: #silent } }
4517 }
4518 Self::FixedRuntime { path } => {
4519 let path = path_buf_lit(path);
4520 quote! { #prefix::FixedRuntime { path: #path } }
4521 }
4522 })
4523 }
4524 }
4525
4526 impl ToTokens for WindowsConfig {
4527 fn to_tokens(&self, tokens: &mut TokenStream) {
4528 let webview_install_mode = &self.webview_install_mode;
4529 tokens.append_all(quote! { ::tauri::utils::config::WindowsConfig {
4530 webview_install_mode: #webview_install_mode,
4531 ..Default::default()
4532 }})
4533 }
4534 }
4535
4536 impl ToTokens for BundleConfig {
4537 fn to_tokens(&self, tokens: &mut TokenStream) {
4538 let publisher = quote!(None);
4539 let homepage = quote!(None);
4540 let icon = vec_lit(&self.icon, str_lit);
4541 let active = self.active;
4542 let targets = quote!(Default::default());
4543 let create_updater_artifacts = quote!(Default::default());
4544 let resources = quote!(None);
4545 let copyright = quote!(None);
4546 let category = quote!(None);
4547 let file_associations = quote!(None);
4548 let short_description = quote!(None);
4549 let long_description = quote!(None);
4550 let use_local_tools_dir = self.use_local_tools_dir;
4551 let external_bin = opt_vec_lit(self.external_bin.as_ref(), str_lit);
4552 let windows = &self.windows;
4553 let license = opt_str_lit(self.license.as_ref());
4554 let license_file = opt_lit(self.license_file.as_ref().map(path_buf_lit).as_ref());
4555 let linux = quote!(Default::default());
4556 let macos = quote!(Default::default());
4557 let ios = quote!(Default::default());
4558 let android = quote!(Default::default());
4559
4560 literal_struct!(
4561 tokens,
4562 ::tauri::utils::config::BundleConfig,
4563 active,
4564 publisher,
4565 homepage,
4566 icon,
4567 targets,
4568 create_updater_artifacts,
4569 resources,
4570 copyright,
4571 category,
4572 license,
4573 license_file,
4574 file_associations,
4575 short_description,
4576 long_description,
4577 use_local_tools_dir,
4578 external_bin,
4579 windows,
4580 linux,
4581 macos,
4582 ios,
4583 android
4584 );
4585 }
4586 }
4587
4588 impl ToTokens for FrontendDist {
4589 fn to_tokens(&self, tokens: &mut TokenStream) {
4590 let prefix = quote! { ::tauri::utils::config::FrontendDist };
4591
4592 tokens.append_all(match self {
4593 Self::Url(url) => {
4594 let url = url_lit(url);
4595 quote! { #prefix::Url(#url) }
4596 }
4597 Self::Directory(path) => {
4598 let path = path_buf_lit(path);
4599 quote! { #prefix::Directory(#path) }
4600 }
4601 Self::Files(files) => {
4602 let files = vec_lit(files, path_buf_lit);
4603 quote! { #prefix::Files(#files) }
4604 }
4605 })
4606 }
4607 }
4608
4609 impl ToTokens for RunnerConfig {
4610 fn to_tokens(&self, tokens: &mut TokenStream) {
4611 let prefix = quote! { ::tauri::utils::config::RunnerConfig };
4612
4613 tokens.append_all(match self {
4614 Self::String(cmd) => {
4615 let cmd = cmd.as_str();
4616 quote!(#prefix::String(#cmd.into()))
4617 }
4618 Self::Object { cmd, cwd, args } => {
4619 let cmd = cmd.as_str();
4620 let cwd = opt_str_lit(cwd.as_ref());
4621 let args = opt_lit(args.as_ref().map(|v| vec_lit(v, str_lit)).as_ref());
4622 quote!(#prefix::Object {
4623 cmd: #cmd.into(),
4624 cwd: #cwd,
4625 args: #args,
4626 })
4627 }
4628 })
4629 }
4630 }
4631
4632 impl ToTokens for BuildConfig {
4633 fn to_tokens(&self, tokens: &mut TokenStream) {
4634 let dev_url = opt_lit(self.dev_url.as_ref().map(url_lit).as_ref());
4635 let frontend_dist = opt_lit(self.frontend_dist.as_ref());
4636 let runner = opt_lit(self.runner.as_ref());
4637 let before_dev_command = quote!(None);
4638 let before_build_command = quote!(None);
4639 let before_bundle_command = quote!(None);
4640 let features = quote!(None);
4641 let remove_unused_commands = quote!(false);
4642 let additional_watch_folders = quote!(Vec::new());
4643 let windows = &self.windows;
4644
4645 literal_struct!(
4646 tokens,
4647 ::tauri::utils::config::BuildConfig,
4648 runner,
4649 dev_url,
4650 frontend_dist,
4651 before_dev_command,
4652 before_build_command,
4653 before_bundle_command,
4654 features,
4655 remove_unused_commands,
4656 additional_watch_folders,
4657 windows
4658 );
4659 }
4660 }
4661
4662 impl ToTokens for WindowsBuildConfig {
4663 fn to_tokens(&self, tokens: &mut TokenStream) {
4664 let static_vc_runtime = self.static_vc_runtime;
4665
4666 literal_struct!(
4667 tokens,
4668 ::tauri::utils::config::WindowsBuildConfig,
4669 static_vc_runtime
4670 );
4671 }
4672 }
4673
4674 impl ToTokens for CspDirectiveSources {
4675 fn to_tokens(&self, tokens: &mut TokenStream) {
4676 let prefix = quote! { ::tauri::utils::config::CspDirectiveSources };
4677
4678 tokens.append_all(match self {
4679 Self::Inline(sources) => {
4680 let sources = sources.as_str();
4681 quote!(#prefix::Inline(#sources.into()))
4682 }
4683 Self::List(list) => {
4684 let list = vec_lit(list, str_lit);
4685 quote!(#prefix::List(#list))
4686 }
4687 })
4688 }
4689 }
4690
4691 impl ToTokens for Csp {
4692 fn to_tokens(&self, tokens: &mut TokenStream) {
4693 let prefix = quote! { ::tauri::utils::config::Csp };
4694
4695 tokens.append_all(match self {
4696 Self::Policy(policy) => {
4697 let policy = policy.as_str();
4698 quote!(#prefix::Policy(#policy.into()))
4699 }
4700 Self::DirectiveMap(list) => {
4701 let mut sorted: Vec<_> = list.iter().collect();
4705 sorted.sort_by_key(|(k, _)| *k);
4706 let map = map_lit(
4707 quote! { ::std::collections::HashMap },
4708 sorted,
4709 str_lit,
4710 identity,
4711 );
4712 quote!(#prefix::DirectiveMap(#map))
4713 }
4714 })
4715 }
4716 }
4717
4718 impl ToTokens for DisabledCspModificationKind {
4719 fn to_tokens(&self, tokens: &mut TokenStream) {
4720 let prefix = quote! { ::tauri::utils::config::DisabledCspModificationKind };
4721
4722 tokens.append_all(match self {
4723 Self::Flag(flag) => {
4724 quote! { #prefix::Flag(#flag) }
4725 }
4726 Self::List(directives) => {
4727 let directives = vec_lit(directives, str_lit);
4728 quote! { #prefix::List(#directives) }
4729 }
4730 });
4731 }
4732 }
4733
4734 impl ToTokens for CapabilityEntry {
4735 fn to_tokens(&self, tokens: &mut TokenStream) {
4736 let prefix = quote! { ::tauri::utils::config::CapabilityEntry };
4737
4738 tokens.append_all(match self {
4739 Self::Inlined(capability) => {
4740 quote! { #prefix::Inlined(#capability) }
4741 }
4742 Self::Reference(id) => {
4743 let id = str_lit(id);
4744 quote! { #prefix::Reference(#id) }
4745 }
4746 });
4747 }
4748 }
4749
4750 impl ToTokens for HeaderSource {
4751 fn to_tokens(&self, tokens: &mut TokenStream) {
4752 let prefix = quote! { ::tauri::utils::config::HeaderSource };
4753
4754 tokens.append_all(match self {
4755 Self::Inline(s) => {
4756 let line = s.as_str();
4757 quote!(#prefix::Inline(#line.into()))
4758 }
4759 Self::List(l) => {
4760 let list = vec_lit(l, str_lit);
4761 quote!(#prefix::List(#list))
4762 }
4763 Self::Map(m) => {
4764 let mut sorted: Vec<_> = m.iter().collect();
4768 sorted.sort_by_key(|(k, _)| *k);
4769 let map = map_lit(
4770 quote! { ::std::collections::HashMap },
4771 sorted,
4772 str_lit,
4773 str_lit,
4774 );
4775 quote!(#prefix::Map(#map))
4776 }
4777 })
4778 }
4779 }
4780
4781 impl ToTokens for HeaderConfig {
4782 fn to_tokens(&self, tokens: &mut TokenStream) {
4783 let access_control_allow_credentials =
4784 opt_lit(self.access_control_allow_credentials.as_ref());
4785 let access_control_allow_headers = opt_lit(self.access_control_allow_headers.as_ref());
4786 let access_control_allow_methods = opt_lit(self.access_control_allow_methods.as_ref());
4787 let access_control_expose_headers = opt_lit(self.access_control_expose_headers.as_ref());
4788 let access_control_max_age = opt_lit(self.access_control_max_age.as_ref());
4789 let cross_origin_embedder_policy = opt_lit(self.cross_origin_embedder_policy.as_ref());
4790 let cross_origin_opener_policy = opt_lit(self.cross_origin_opener_policy.as_ref());
4791 let cross_origin_resource_policy = opt_lit(self.cross_origin_resource_policy.as_ref());
4792 let permissions_policy = opt_lit(self.permissions_policy.as_ref());
4793 let service_worker_allowed = opt_lit(self.service_worker_allowed.as_ref());
4794 let timing_allow_origin = opt_lit(self.timing_allow_origin.as_ref());
4795 let x_content_type_options = opt_lit(self.x_content_type_options.as_ref());
4796 let tauri_custom_header = opt_lit(self.tauri_custom_header.as_ref());
4797
4798 literal_struct!(
4799 tokens,
4800 ::tauri::utils::config::HeaderConfig,
4801 access_control_allow_credentials,
4802 access_control_allow_headers,
4803 access_control_allow_methods,
4804 access_control_expose_headers,
4805 access_control_max_age,
4806 cross_origin_embedder_policy,
4807 cross_origin_opener_policy,
4808 cross_origin_resource_policy,
4809 permissions_policy,
4810 service_worker_allowed,
4811 timing_allow_origin,
4812 x_content_type_options,
4813 tauri_custom_header
4814 );
4815 }
4816 }
4817
4818 impl ToTokens for SecurityConfig {
4819 fn to_tokens(&self, tokens: &mut TokenStream) {
4820 let csp = opt_lit(self.csp.as_ref());
4821 let dev_csp = opt_lit(self.dev_csp.as_ref());
4822 let freeze_prototype = self.freeze_prototype;
4823 let dangerous_disable_asset_csp_modification = &self.dangerous_disable_asset_csp_modification;
4824 let asset_protocol = &self.asset_protocol;
4825 let pattern = &self.pattern;
4826 let capabilities = vec_lit(&self.capabilities, identity);
4827 let headers = opt_lit(self.headers.as_ref());
4828
4829 literal_struct!(
4830 tokens,
4831 ::tauri::utils::config::SecurityConfig,
4832 csp,
4833 dev_csp,
4834 freeze_prototype,
4835 dangerous_disable_asset_csp_modification,
4836 asset_protocol,
4837 pattern,
4838 capabilities,
4839 headers
4840 );
4841 }
4842 }
4843
4844 impl ToTokens for TrayIconConfig {
4845 fn to_tokens(&self, tokens: &mut TokenStream) {
4846 tokens.append_all(quote!(#[allow(deprecated)]));
4848
4849 let id = opt_str_lit(self.id.as_ref());
4850 let icon_as_template = self.icon_as_template;
4851 #[allow(deprecated)]
4852 let menu_on_left_click = self.menu_on_left_click;
4853 let show_menu_on_left_click = self.show_menu_on_left_click;
4854 let icon_path = path_buf_lit(&self.icon_path);
4855 let title = opt_str_lit(self.title.as_ref());
4856 let tooltip = opt_str_lit(self.tooltip.as_ref());
4857 literal_struct!(
4858 tokens,
4859 ::tauri::utils::config::TrayIconConfig,
4860 id,
4861 icon_path,
4862 icon_as_template,
4863 menu_on_left_click,
4864 show_menu_on_left_click,
4865 title,
4866 tooltip
4867 );
4868 }
4869 }
4870
4871 impl ToTokens for FsScope {
4872 fn to_tokens(&self, tokens: &mut TokenStream) {
4873 let prefix = quote! { ::tauri::utils::config::FsScope };
4874
4875 tokens.append_all(match self {
4876 Self::AllowedPaths(allow) => {
4877 let allowed_paths = vec_lit(allow, path_buf_lit);
4878 quote! { #prefix::AllowedPaths(#allowed_paths) }
4879 }
4880 Self::Scope { allow, deny , require_literal_leading_dot} => {
4881 let allow = vec_lit(allow, path_buf_lit);
4882 let deny = vec_lit(deny, path_buf_lit);
4883 let require_literal_leading_dot = opt_lit(require_literal_leading_dot.as_ref());
4884 quote! { #prefix::Scope { allow: #allow, deny: #deny, require_literal_leading_dot: #require_literal_leading_dot } }
4885 }
4886 });
4887 }
4888 }
4889
4890 impl ToTokens for AssetProtocolConfig {
4891 fn to_tokens(&self, tokens: &mut TokenStream) {
4892 let scope = &self.scope;
4893 tokens.append_all(quote! { ::tauri::utils::config::AssetProtocolConfig { scope: #scope, ..Default::default() } })
4894 }
4895 }
4896
4897 impl ToTokens for AppDirectoryOverrides {
4898 fn to_tokens(&self, tokens: &mut TokenStream) {
4899 let config = opt_lit_owned(self.config.as_ref().map(path_buf_lit));
4900 let data = opt_lit_owned(self.data.as_ref().map(path_buf_lit));
4901 let local_data = opt_lit_owned(self.local_data.as_ref().map(path_buf_lit));
4902 let cache = opt_lit_owned(self.cache.as_ref().map(path_buf_lit));
4903 let log = opt_lit_owned(self.log.as_ref().map(path_buf_lit));
4904
4905 literal_struct!(
4906 tokens,
4907 ::tauri::utils::config::AppDirectoryOverrides,
4908 config,
4909 data,
4910 local_data,
4911 cache,
4912 log
4913 );
4914 }
4915 }
4916
4917 impl ToTokens for AppDirectoriesOverride {
4918 fn to_tokens(&self, tokens: &mut TokenStream) {
4919 let prefix = quote! { ::tauri::utils::config::AppDirectoriesOverride };
4920
4921 tokens.append_all(match self {
4922 Self::Root(root) => {
4923 let root = path_buf_lit(root);
4924 quote! { #prefix::Root(#root) }
4925 }
4926 Self::Directories(directories) => quote! { #prefix::Directories(#directories) },
4927 })
4928 }
4929 }
4930
4931 impl ToTokens for AppConfig {
4932 fn to_tokens(&self, tokens: &mut TokenStream) {
4933 let windows = vec_lit(&self.windows, identity);
4934 let security = &self.security;
4935 let tray_icon = opt_lit(self.tray_icon.as_ref());
4936 let macos_private_api = self.macos_private_api;
4937 let with_global_tauri = self.with_global_tauri;
4938 let enable_gtk_app_id = self.enable_gtk_app_id;
4939 let app_directories_override = opt_lit(self.app_directories_override.as_ref());
4940
4941 literal_struct!(
4942 tokens,
4943 ::tauri::utils::config::AppConfig,
4944 windows,
4945 security,
4946 tray_icon,
4947 macos_private_api,
4948 with_global_tauri,
4949 enable_gtk_app_id,
4950 app_directories_override
4951 );
4952 }
4953 }
4954
4955 impl ToTokens for PluginConfig {
4956 fn to_tokens(&self, tokens: &mut TokenStream) {
4957 let mut sorted: Vec<_> = self.0.iter().collect();
4961 sorted.sort_by_key(|(k, _)| *k);
4962 let config = map_lit(
4963 quote! { ::std::collections::HashMap },
4964 sorted,
4965 str_lit,
4966 json_value_lit,
4967 );
4968 tokens.append_all(quote! { ::tauri::utils::config::PluginConfig(#config) })
4969 }
4970 }
4971
4972 impl ToTokens for Config {
4973 fn to_tokens(&self, tokens: &mut TokenStream) {
4974 let schema = quote!(None);
4975 let product_name = opt_str_lit(self.product_name.as_ref());
4976 let main_binary_name = opt_str_lit(self.main_binary_name.as_ref());
4977 let version = opt_str_lit(self.version.as_ref());
4978 let identifier = str_lit(&self.identifier);
4979 let app = &self.app;
4980 let build = &self.build;
4981 let bundle = &self.bundle;
4982 let plugins = &self.plugins;
4983
4984 literal_struct!(
4985 tokens,
4986 ::tauri::utils::config::Config,
4987 schema,
4988 product_name,
4989 main_binary_name,
4990 version,
4991 identifier,
4992 app,
4993 build,
4994 bundle,
4995 plugins
4996 );
4997 }
4998 }
4999}
5000
5001#[cfg(test)]
5002mod test {
5003 use super::*;
5004
5005 #[test]
5008 fn test_defaults() {
5010 let a_config = AppConfig::default();
5012 let b_config = BuildConfig::default();
5014 let d_windows: Vec<WindowConfig> = vec![];
5016 let d_bundle = BundleConfig::default();
5018
5019 let app = AppConfig {
5021 windows: vec![],
5022 security: SecurityConfig {
5023 csp: None,
5024 dev_csp: None,
5025 freeze_prototype: false,
5026 dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),
5027 asset_protocol: AssetProtocolConfig::default(),
5028 pattern: Default::default(),
5029 capabilities: Vec::new(),
5030 headers: None,
5031 },
5032 tray_icon: None,
5033 macos_private_api: false,
5034 with_global_tauri: false,
5035 enable_gtk_app_id: false,
5036 app_directories_override: None,
5037 };
5038
5039 let build = BuildConfig {
5041 runner: None,
5042 dev_url: None,
5043 frontend_dist: None,
5044 before_dev_command: None,
5045 before_build_command: None,
5046 before_bundle_command: None,
5047 features: None,
5048 remove_unused_commands: false,
5049 additional_watch_folders: Vec::new(),
5050 windows: WindowsBuildConfig::default(),
5051 };
5052
5053 let bundle = BundleConfig {
5055 active: false,
5056 targets: Default::default(),
5057 create_updater_artifacts: Default::default(),
5058 publisher: None,
5059 homepage: None,
5060 icon: Vec::new(),
5061 resources: None,
5062 copyright: None,
5063 category: None,
5064 file_associations: None,
5065 short_description: None,
5066 long_description: None,
5067 use_local_tools_dir: false,
5068 license: None,
5069 license_file: None,
5070 linux: Default::default(),
5071 macos: Default::default(),
5072 external_bin: None,
5073 windows: Default::default(),
5074 ios: Default::default(),
5075 android: Default::default(),
5076 };
5077
5078 assert_eq!(a_config, app);
5080 assert_eq!(b_config, build);
5081 assert_eq!(d_bundle, bundle);
5082 assert_eq!(d_windows, app.windows);
5083 }
5084
5085 #[test]
5086 fn app_directories_override_root() {
5087 let config: AppDirectoriesOverride = serde_json::from_str(r#""./""#).unwrap();
5088 assert_eq!(config, AppDirectoriesOverride::Root("./".into()));
5089
5090 let config: AppDirectoriesOverride = serde_json::from_str(r#""$DATA/my-app""#).unwrap();
5091 assert_eq!(config, AppDirectoriesOverride::Root("$DATA/my-app".into()));
5092 }
5093
5094 #[test]
5095 fn app_directories_override_directories() {
5096 let config: AppDirectoriesOverride = serde_json::from_str(
5097 r#"{ "log": "$DATA/logs", "cache": "$CACHE/my-app", "local-data": "data" }"#,
5098 )
5099 .unwrap();
5100 assert_eq!(
5101 config,
5102 AppDirectoriesOverride::Directories(AppDirectoryOverrides {
5103 config: None,
5104 data: None,
5105 local_data: Some("data".into()),
5106 cache: Some("$CACHE/my-app".into()),
5107 log: Some("$DATA/logs".into()),
5108 })
5109 );
5110
5111 let config: AppDirectoriesOverride =
5112 serde_json::from_str(r#"{ "config": "conf", "data": "data", "localData": "local" }"#)
5113 .unwrap();
5114 assert_eq!(
5115 config,
5116 AppDirectoriesOverride::Directories(AppDirectoryOverrides {
5117 config: Some("conf".into()),
5118 data: Some("data".into()),
5119 local_data: Some("local".into()),
5120 cache: None,
5121 log: None,
5122 })
5123 );
5124
5125 let config: AppDirectoriesOverride = serde_json::from_str("{}").unwrap();
5126 assert_eq!(
5127 config,
5128 AppDirectoriesOverride::Directories(AppDirectoryOverrides::default())
5129 );
5130 }
5131
5132 #[test]
5133 fn app_directories_override_rejects_unknown_directories() {
5134 let err = serde_json::from_str::<AppDirectoriesOverride>(r#"{ "logs": "x" }"#).unwrap_err();
5135 assert!(err.to_string().contains("unknown field `logs`"), "{err}");
5136 }
5137
5138 #[test]
5139 fn app_directories_override_accepts_supported_variables() {
5140 for variable in APP_DIRECTORIES_OVERRIDE_VARIABLES {
5141 for path in [
5142 variable.to_string(),
5143 format!("{variable}/my-app"),
5144 format!("{variable}/../my-app"),
5145 ] {
5146 let json = serde_json::to_string(&path).unwrap();
5147 let config: AppDirectoriesOverride = serde_json::from_str(&json).unwrap();
5148 assert_eq!(config, AppDirectoriesOverride::Root(path.into()));
5149 }
5150 }
5151 }
5152
5153 #[test]
5154 fn app_directories_override_rejects_unsupported_variables() {
5155 for variable in [
5156 "$APPCONFIG",
5157 "$APPDATA",
5158 "$APPLOCALDATA",
5159 "$APPCACHE",
5160 "$APPLOG",
5161 "$EXE",
5162 "$FONT",
5163 "$RESOURCE",
5164 "$RUNTIME",
5165 "$TEMPLATE",
5166 "$UNKNOWN",
5167 ] {
5168 let err = serde_json::from_str::<AppDirectoriesOverride>(&format!(r#""{variable}/my-app""#))
5169 .unwrap_err();
5170 assert!(
5171 err
5172 .to_string()
5173 .contains(&format!("unsupported base directory variable `{variable}`")),
5174 "{variable}: {err}"
5175 );
5176
5177 let err =
5178 serde_json::from_str::<AppDirectoriesOverride>(&format!(r#"{{ "log": "{variable}" }}"#))
5179 .unwrap_err();
5180 assert!(
5181 err
5182 .to_string()
5183 .contains("unsupported base directory variable"),
5184 "{variable}: {err}"
5185 );
5186 }
5187 }
5188
5189 #[cfg(windows)]
5190 #[test]
5191 fn app_directories_override_rejects_root_relative_paths() {
5192 for path in [r"\my-app", "C:my-app"] {
5193 let json = serde_json::to_string(path).unwrap();
5194 let err = serde_json::from_str::<AppDirectoriesOverride>(&json).unwrap_err();
5195 assert!(
5196 err.to_string().contains("must be an absolute path"),
5197 "{path}: {err}"
5198 );
5199 }
5200 }
5201
5202 #[cfg(feature = "build")]
5203 #[test]
5204 fn app_directories_override_to_tokens() {
5205 use quote::ToTokens;
5206
5207 let tokens = AppDirectoriesOverride::Root("./".into())
5208 .to_token_stream()
5209 .to_string()
5210 .replace(' ', "");
5211 assert_eq!(
5212 tokens,
5213 r#"::tauri::utils::config::AppDirectoriesOverride::Root(::std::path::PathBuf::from("./"))"#
5214 );
5215
5216 let tokens = AppDirectoriesOverride::Directories(AppDirectoryOverrides {
5217 log: Some("$DATA/logs".into()),
5218 ..Default::default()
5219 })
5220 .to_token_stream()
5221 .to_string()
5222 .replace(' ', "");
5223 assert_eq!(
5224 tokens,
5225 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"))})"#
5226 );
5227 }
5228
5229 #[test]
5230 fn parse_hex_color() {
5231 use super::Color;
5232
5233 assert_eq!(Color(255, 255, 255, 255), "fff".parse().unwrap());
5234 assert_eq!(Color(255, 255, 255, 255), "#fff".parse().unwrap());
5235 assert_eq!(Color(0, 0, 0, 255), "#000000".parse().unwrap());
5236 assert_eq!(Color(0, 0, 0, 255), "#000000ff".parse().unwrap());
5237 assert_eq!(Color(0, 255, 0, 255), "#00ff00ff".parse().unwrap());
5238 }
5239
5240 #[test]
5241 fn test_runner_config_string_format() {
5242 use super::RunnerConfig;
5243
5244 let json = r#""cargo""#;
5246 let runner: RunnerConfig = serde_json::from_str(json).unwrap();
5247
5248 assert_eq!(runner.cmd(), "cargo");
5249 assert_eq!(runner.cwd(), None);
5250 assert_eq!(runner.args(), None);
5251
5252 let serialized = serde_json::to_string(&runner).unwrap();
5254 assert_eq!(serialized, r#""cargo""#);
5255 }
5256
5257 #[test]
5258 fn test_runner_config_object_format_full() {
5259 use super::RunnerConfig;
5260
5261 let json = r#"{"cmd": "my_runner", "cwd": "/tmp/build", "args": ["--quiet", "--verbose"]}"#;
5263 let runner: RunnerConfig = serde_json::from_str(json).unwrap();
5264
5265 assert_eq!(runner.cmd(), "my_runner");
5266 assert_eq!(runner.cwd(), Some("/tmp/build"));
5267 assert_eq!(
5268 runner.args(),
5269 Some(&["--quiet".to_string(), "--verbose".to_string()][..])
5270 );
5271
5272 let serialized = serde_json::to_string(&runner).unwrap();
5274 let deserialized: RunnerConfig = serde_json::from_str(&serialized).unwrap();
5275 assert_eq!(runner, deserialized);
5276 }
5277
5278 #[test]
5279 fn test_runner_config_object_format_minimal() {
5280 use super::RunnerConfig;
5281
5282 let json = r#"{"cmd": "cross"}"#;
5284 let runner: RunnerConfig = serde_json::from_str(json).unwrap();
5285
5286 assert_eq!(runner.cmd(), "cross");
5287 assert_eq!(runner.cwd(), None);
5288 assert_eq!(runner.args(), None);
5289 }
5290
5291 #[test]
5292 fn test_runner_config_default() {
5293 use super::RunnerConfig;
5294
5295 let default_runner = RunnerConfig::default();
5296 assert_eq!(default_runner.cmd(), "cargo");
5297 assert_eq!(default_runner.cwd(), None);
5298 assert_eq!(default_runner.args(), None);
5299 }
5300
5301 #[test]
5302 fn test_runner_config_from_str() {
5303 use super::RunnerConfig;
5304
5305 let runner: RunnerConfig = "my_runner".into();
5307 assert_eq!(runner.cmd(), "my_runner");
5308 assert_eq!(runner.cwd(), None);
5309 assert_eq!(runner.args(), None);
5310 }
5311
5312 #[test]
5313 fn test_runner_config_from_string() {
5314 use super::RunnerConfig;
5315
5316 let runner: RunnerConfig = "another_runner".to_string().into();
5318 assert_eq!(runner.cmd(), "another_runner");
5319 assert_eq!(runner.cwd(), None);
5320 assert_eq!(runner.args(), None);
5321 }
5322
5323 #[test]
5324 fn test_runner_config_from_str_parse() {
5325 use super::RunnerConfig;
5326 use std::str::FromStr;
5327
5328 let runner = RunnerConfig::from_str("parsed_runner").unwrap();
5330 assert_eq!(runner.cmd(), "parsed_runner");
5331 assert_eq!(runner.cwd(), None);
5332 assert_eq!(runner.args(), None);
5333 }
5334
5335 #[test]
5336 fn test_runner_config_in_build_config() {
5337 use super::BuildConfig;
5338
5339 let json = r#"{"runner": "cargo"}"#;
5341 let build_config: BuildConfig = serde_json::from_str(json).unwrap();
5342
5343 let runner = build_config.runner.unwrap();
5344 assert_eq!(runner.cmd(), "cargo");
5345 assert_eq!(runner.cwd(), None);
5346 assert_eq!(runner.args(), None);
5347 }
5348
5349 #[test]
5350 fn test_runner_config_in_build_config_object() {
5351 use super::BuildConfig;
5352
5353 let json = r#"{"runner": {"cmd": "cross", "cwd": "/workspace", "args": ["--target", "x86_64-unknown-linux-gnu"]}}"#;
5355 let build_config: BuildConfig = serde_json::from_str(json).unwrap();
5356
5357 let runner = build_config.runner.unwrap();
5358 assert_eq!(runner.cmd(), "cross");
5359 assert_eq!(runner.cwd(), Some("/workspace"));
5360 assert_eq!(
5361 runner.args(),
5362 Some(
5363 &[
5364 "--target".to_string(),
5365 "x86_64-unknown-linux-gnu".to_string()
5366 ][..]
5367 )
5368 );
5369 }
5370
5371 #[test]
5372 fn test_runner_config_in_full_config() {
5373 use super::Config;
5374
5375 let json = r#"{
5377 "productName": "Test App",
5378 "version": "1.0.0",
5379 "identifier": "com.test.app",
5380 "build": {
5381 "runner": {
5382 "cmd": "my_custom_cargo",
5383 "cwd": "/tmp/build",
5384 "args": ["--quiet", "--verbose"]
5385 }
5386 }
5387 }"#;
5388
5389 let config: Config = serde_json::from_str(json).unwrap();
5390 let runner = config.build.runner.unwrap();
5391
5392 assert_eq!(runner.cmd(), "my_custom_cargo");
5393 assert_eq!(runner.cwd(), Some("/tmp/build"));
5394 assert_eq!(
5395 runner.args(),
5396 Some(&["--quiet".to_string(), "--verbose".to_string()][..])
5397 );
5398 }
5399
5400 #[test]
5401 fn test_runner_config_equality() {
5402 use super::RunnerConfig;
5403
5404 let runner1 = RunnerConfig::String("cargo".to_string());
5405 let runner2 = RunnerConfig::String("cargo".to_string());
5406 let runner3 = RunnerConfig::String("cross".to_string());
5407
5408 assert_eq!(runner1, runner2);
5409 assert_ne!(runner1, runner3);
5410
5411 let runner4 = RunnerConfig::Object {
5412 cmd: "cargo".to_string(),
5413 cwd: Some("/tmp".to_string()),
5414 args: Some(vec!["--quiet".to_string()]),
5415 };
5416 let runner5 = RunnerConfig::Object {
5417 cmd: "cargo".to_string(),
5418 cwd: Some("/tmp".to_string()),
5419 args: Some(vec!["--quiet".to_string()]),
5420 };
5421
5422 assert_eq!(runner4, runner5);
5423 assert_ne!(runner1, runner4);
5424 }
5425
5426 #[test]
5427 fn test_runner_config_untagged_serialization() {
5428 use super::RunnerConfig;
5429
5430 let string_runner = RunnerConfig::String("cargo".to_string());
5432 let string_json = serde_json::to_string(&string_runner).unwrap();
5433 assert_eq!(string_json, r#""cargo""#);
5434
5435 let object_runner = RunnerConfig::Object {
5437 cmd: "cross".to_string(),
5438 cwd: None,
5439 args: None,
5440 };
5441 let object_json = serde_json::to_string(&object_runner).unwrap();
5442 assert!(object_json.contains("\"cmd\":\"cross\""));
5443 assert!(object_json.contains("\"cwd\":null") || !object_json.contains("cwd"));
5445 assert!(object_json.contains("\"args\":null") || !object_json.contains("args"));
5446 }
5447
5448 #[test]
5449 fn header_source_map_display_is_deterministic() {
5450 let map = HashMap::from([
5451 ("key3".to_string(), "'value3'".to_string()),
5452 ("key1".to_string(), "'value1' 'value2'".to_string()),
5453 ("key2".to_string(), "'value4'".to_string()),
5454 ]);
5455
5456 assert_eq!(
5458 HeaderSource::Map(map.clone()).to_string(),
5459 "key1 'value1' 'value2'; key2 'value4'; key3 'value3'"
5460 );
5461
5462 let expected = HeaderSource::Map(map).to_string();
5463 for _ in 0..10 {
5464 let map = HashMap::from([
5465 ("key2".to_string(), "'value4'".to_string()),
5466 ("key3".to_string(), "'value3'".to_string()),
5467 ("key1".to_string(), "'value1' 'value2'".to_string()),
5468 ]);
5469 assert_eq!(HeaderSource::Map(map).to_string(), expected);
5470 }
5471
5472 let map = HashMap::from([
5474 ("b".to_string(), "2".to_string()),
5475 ("a".to_string(), "1".to_string()),
5476 ]);
5477 assert_eq!(
5478 serde_json::to_string(&HeaderSource::Map(map)).unwrap(),
5479 r#"{"a":"1","b":"2"}"#
5480 );
5481 }
5482
5483 #[test]
5484 fn header_source_display() {
5485 assert_eq!(
5486 HeaderSource::Inline("same-origin".into()).to_string(),
5487 "same-origin"
5488 );
5489 assert_eq!(
5490 HeaderSource::List(vec!["https://a.example".into(), "https://b.example".into()]).to_string(),
5491 "https://a.example, https://b.example"
5492 );
5493 }
5494
5495 #[test]
5496 fn window_config_default_same_as_deserialize() {
5497 let config_from_deserialization: WindowConfig = serde_json::from_str("{}").unwrap();
5498 let config_from_default: WindowConfig = WindowConfig::default();
5499
5500 assert_eq!(config_from_deserialization, config_from_default);
5501 }
5502}